From ffd67150ebae1fff86bf68efbf859fa6cb22b5ea Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 20 Jan 2026 22:47:06 +0000 Subject: [PATCH] sanitization ts --- server/package-lock.json | 20 +++++ server/package.json | 1 + server/src/controllers/monitorController.ts | 7 -- server/src/middleware/v1/sanitization.js | 85 -------------------- server/src/middleware/v1/sanitization.ts | 59 ++++++++++++++ server/src/middleware/v1/verifyTeamAccess.js | 38 --------- server/src/service/system/serviceRegistry.js | 63 --------------- 7 files changed, 80 insertions(+), 193 deletions(-) delete mode 100644 server/src/middleware/v1/sanitization.js create mode 100644 server/src/middleware/v1/sanitization.ts delete mode 100644 server/src/middleware/v1/verifyTeamAccess.js delete mode 100755 server/src/service/system/serviceRegistry.js diff --git a/server/package-lock.json b/server/package-lock.json index 11e1829b4..a1a392b18 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -51,6 +51,7 @@ "@types/gamedig": "^5.0.3", "@types/jest": "^30.0.0", "@types/jmespath": "^0.15.2", + "@types/jsdom": "^27.0.0", "@types/jsonwebtoken": "9.0.10", "@types/mjml": "^4.7.4", "@types/multer": "^2.0.0", @@ -4453,6 +4454,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4637,6 +4650,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 91ba82988..2bf898c94 100755 --- a/server/package.json +++ b/server/package.json @@ -63,6 +63,7 @@ "@types/gamedig": "^5.0.3", "@types/jest": "^30.0.0", "@types/jmespath": "^0.15.2", + "@types/jsdom": "^27.0.0", "@types/jsonwebtoken": "9.0.10", "@types/mjml": "^4.7.4", "@types/multer": "^2.0.0", diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index fcb731283..2ebb113ae 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -41,13 +41,6 @@ class MonitorController { return MonitorController.SERVICE_NAME; } - async verifyTeamAccess(teamId: string, monitorId: string) { - const monitor = await this.monitorService.getMonitorById({ teamId, monitorId }); - if (monitor.teamId !== teamId) { - throw new AppError({ message: "Access denied", status: 403 }); - } - } - getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => { try { await getCertificateParamValidation.validateAsync(req.params); diff --git a/server/src/middleware/v1/sanitization.js b/server/src/middleware/v1/sanitization.js deleted file mode 100644 index a89325de1..000000000 --- a/server/src/middleware/v1/sanitization.js +++ /dev/null @@ -1,85 +0,0 @@ -import { JSDOM } from "jsdom"; -import DOMPurify from "isomorphic-dompurify"; - -// Initialize DOMPurify with jsdom -const window = new JSDOM("").window; -const purify = DOMPurify(window); - -/** - * Sanitizes user input to prevent XSS attacks - * @param {string} input - The input string to sanitize - * @param {Object} options - Sanitization options - * @returns {string} The sanitized string - */ -export const sanitizeInput = (input, options = {}) => { - if (typeof input !== "string") { - return input; - } - - // Default configuration - remove all HTML tags and attributes - const defaultConfig = { - ALLOWED_TAGS: [], - ALLOWED_ATTR: [], - KEEP_CONTENT: true, - ...options, - }; - - return purify.sanitize(input, defaultConfig); -}; - -/** - * Sanitizes an object recursively - * @param {Object} obj - The object to sanitize - * @param {Object} options - Sanitization options - * @returns {Object} The sanitized object - */ -export const sanitizeObject = (obj, options = {}) => { - if (typeof obj !== "object" || obj === null) { - return obj; - } - - if (Array.isArray(obj)) { - return obj.map((item) => sanitizeObject(item, options)); - } - - const sanitized = {}; - for (const [key, value] of Object.entries(obj)) { - if (typeof value === "string") { - sanitized[key] = sanitizeInput(value, options); - } else if (typeof value === "object" && value !== null) { - sanitized[key] = sanitizeObject(value, options); - } else { - sanitized[key] = value; - } - } - - return sanitized; -}; - -/** - * Express middleware for sanitizing request body - * @param {Object} options - Sanitization options - * @returns {Function} Express middleware function - */ -export const sanitizeBody = (options = {}) => { - return (req, res, next) => { - if (req.body && typeof req.body === "object") { - req.body = sanitizeObject(req.body, options); - } - next(); - }; -}; - -/** - * Express middleware for sanitizing query parameters - * @param {Object} options - Sanitization options - * @returns {Function} Express middleware function - */ -export const sanitizeQuery = (options = {}) => { - return (req, res, next) => { - if (req.query && typeof req.query === "object") { - req.query = sanitizeObject(req.query, options); - } - next(); - }; -}; diff --git a/server/src/middleware/v1/sanitization.ts b/server/src/middleware/v1/sanitization.ts new file mode 100644 index 000000000..be971a298 --- /dev/null +++ b/server/src/middleware/v1/sanitization.ts @@ -0,0 +1,59 @@ +import type { Request, Response, NextFunction } from "express"; +import DOMPurify from "isomorphic-dompurify"; + +export const sanitizeInput = (input: any, options = {}) => { + if (typeof input !== "string") { + return input; + } + + // Default configuration - remove all HTML tags and attributes + const defaultConfig = { + ALLOWED_TAGS: [] as string[], + ALLOWED_ATTR: [] as string[], + KEEP_CONTENT: true, + ...options, + }; + + return DOMPurify.sanitize(input, defaultConfig); +}; + +export const sanitizeObject = (obj: Record, options = {}): Record => { + if (typeof obj !== "object" || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => sanitizeObject(item, options)); + } + + const sanitized: Record = {}; + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string") { + sanitized[key] = sanitizeInput(value, options); + } else if (typeof value === "object" && value !== null) { + sanitized[key] = sanitizeObject(value, options); + } else { + sanitized[key] = value; + } + } + + return sanitized; +}; + +export const sanitizeBody = (options = {}): ((req: Request, res: Response, next: NextFunction) => void) => { + return (req: Request, res: Response, next: NextFunction) => { + if (req.body && typeof req.body === "object") { + req.body = sanitizeObject(req.body, options); + } + next(); + }; +}; + +export const sanitizeQuery = (options = {}): ((req: Request, res: Response, next: NextFunction) => void) => { + return (req: Request, res: Response, next: NextFunction) => { + if (req.query && typeof req.query === "object") { + req.query = sanitizeObject(req.query, options); + } + next(); + }; +}; diff --git a/server/src/middleware/v1/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js deleted file mode 100644 index 60647bdc2..000000000 --- a/server/src/middleware/v1/verifyTeamAccess.js +++ /dev/null @@ -1,38 +0,0 @@ -const SERVICE_NAME = "verifyTeamAccess"; - -const verifyTeamAccess = (Model, paramName) => { - return async (req, res, next) => { - try { - const documentId = req.params[paramName]; - const doc = await Model.findById(documentId); - - if (!doc) { - const error = new Error("Document not found"); - error.status = 404; - throw error; - } - - if (!req?.user?.teamId || !doc.teamId) { - const error = new Error("Missing team information"); - error.status = 400; - throw error; - } - - if (req.user.teamId.toString() === doc.teamId.toString()) { - next(); - return; - } - - const error = new Error("Unauthorized"); - error.status = 403; - throw error; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "verifyTeamAccess"; - next(error); - return; - } - }; -}; - -export { verifyTeamAccess }; diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/system/serviceRegistry.js deleted file mode 100755 index 4e3e28464..000000000 --- a/server/src/service/system/serviceRegistry.js +++ /dev/null @@ -1,63 +0,0 @@ -const SERVICE_NAME = "ServiceRegistry"; - -class ServiceRegistry { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ logger }) { - this.services = {}; - this.logger = logger; - } - - get serviceName() { - return ServiceRegistry.SERVICE_NAME; - } - - // Instance methods - register(name, service) { - this.logger.info({ - message: `Registering service ${name}`, - service: SERVICE_NAME, - method: "register", - }); - this.services[name] = service; - } - - get(name) { - if (!this.services[name]) { - this.logger.error({ - message: `Service ${name} is not registered`, - service: SERVICE_NAME, - method: "get", - }); - throw new Error(`Service ${name} is not registered`); - } - return this.services[name]; - } - - listServices() { - return Object.keys(this.services); - } - - static get(name) { - if (!ServiceRegistry.instance) { - throw new Error("ServiceRegistry not initialized"); - } - return ServiceRegistry.instance.get(name); - } - - static register(name, service) { - if (!ServiceRegistry.instance) { - throw new Error("ServiceRegistry not initialized"); - } - return ServiceRegistry.instance.register(name, service); - } - - static listServices() { - if (!ServiceRegistry.instance) { - throw new Error("ServiceRegistry not initialized"); - } - return ServiceRegistry.instance.listServices(); - } -} - -export default ServiceRegistry;