diff --git a/server/eslint.config.js b/server/eslint.config.js index c854029a0..4e45d6157 100755 --- a/server/eslint.config.js +++ b/server/eslint.config.js @@ -37,6 +37,7 @@ export default [ }, }, rules: { + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], // Ignore unused variables that start with '_' "@typescript-eslint/no-explicit-any": "warn", // Warn on 'any' types "mocha/max-top-level-suites": "warn", // Warn if there are too many top-level suites instead of failing }, diff --git a/server/src/db/models/MaintenanceWindow.ts b/server/src/db/models/MaintenanceWindow.ts index ff309a117..311cf97bf 100644 --- a/server/src/db/models/MaintenanceWindow.ts +++ b/server/src/db/models/MaintenanceWindow.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { MaintenanceWindow, DurationUnit } from "@/types/maintenanceWindow.js"; +import type { MaintenanceWindow } from "@/types/maintenanceWindow.js"; type MaintenanceWindowDocumentBase = Omit & { monitorId: Types.ObjectId; diff --git a/server/src/middleware/handleErrors.ts b/server/src/middleware/handleErrors.ts index 1fffc9a8c..be9d9ea1e 100755 --- a/server/src/middleware/handleErrors.ts +++ b/server/src/middleware/handleErrors.ts @@ -1,15 +1,18 @@ import type { NextFunction, Request, Response } from "express"; import { logger } from "@/utils/logger.js"; +import { AppError } from "@/utils/AppError.js"; -const handleErrors = (error: any, req: Request, res: Response, next: NextFunction) => { - const status = error.status || 500; - const message = error.message || "Server error"; - const service = error.service || "unknownService"; +const handleErrors = (error: unknown, req: Request, res: Response, _next: NextFunction) => { + const status = error instanceof AppError ? error.status || 500 : 500; + const message = error instanceof AppError ? error.message : "Server error"; + const service = error instanceof AppError ? error.service : "unknownService"; + const method = error instanceof AppError ? error.method : "unknownMethod"; logger.error({ message: message, service: service, - method: error.method, - stack: error.stack, + method: method, + stack: error instanceof AppError ? error.stack : undefined, + details: error instanceof AppError ? error.details : undefined, }); res.status(status).json({ status, diff --git a/server/src/middleware/sanitization.ts b/server/src/middleware/sanitization.ts index be971a298..f191ca6ca 100644 --- a/server/src/middleware/sanitization.ts +++ b/server/src/middleware/sanitization.ts @@ -1,7 +1,9 @@ import type { Request, Response, NextFunction } from "express"; import DOMPurify from "isomorphic-dompurify"; -export const sanitizeInput = (input: any, options = {}) => { +export function sanitizeInput(input: string, options?: object): string; +export function sanitizeInput(input: unknown, options?: object): unknown; +export function sanitizeInput(input: unknown, options = {}): unknown { if (typeof input !== "string") { return input; } @@ -15,9 +17,9 @@ export const sanitizeInput = (input: any, options = {}) => { }; return DOMPurify.sanitize(input, defaultConfig); -}; +} -export const sanitizeObject = (obj: Record, options = {}): Record => { +export const sanitizeObject = (obj: unknown, options = {}): unknown => { if (typeof obj !== "object" || obj === null) { return obj; } @@ -26,7 +28,7 @@ export const sanitizeObject = (obj: Record, options = {}): Record sanitizeObject(item, options)); } - const sanitized: Record = {}; + const sanitized: Record = {}; for (const [key, value] of Object.entries(obj)) { if (typeof value === "string") { sanitized[key] = sanitizeInput(value, options); @@ -52,7 +54,12 @@ export const sanitizeBody = (options = {}): ((req: Request, res: Response, 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); + for (const key of Object.keys(req.query)) { + const value = req.query[key]; + if (typeof value === "string") { + req.query[key] = sanitizeInput(value, options); + } + } } next(); }; diff --git a/server/src/middleware/verifyJWT.ts b/server/src/middleware/verifyJWT.ts index 6c2b0a229..4bec4c077 100644 --- a/server/src/middleware/verifyJWT.ts +++ b/server/src/middleware/verifyJWT.ts @@ -3,10 +3,15 @@ import { NextFunction, Request, Response } from "express"; import jwt from "jsonwebtoken"; import { AppError } from "@/utils/AppError.js"; import { Settings } from "@/types/settings.js"; +import type { User } from "@/types/user.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; +const isUser = (payload: unknown): payload is User => { + return typeof payload === "object" && payload !== null && "id" in payload && "teamId" in payload && "role" in payload; +}; + export const createVerifyJWT = (settingsService: { getSettings: () => Settings }) => { return (req: Request, res: Response, next: NextFunction) => { const token = req.headers["authorization"]; @@ -31,21 +36,22 @@ export const createVerifyJWT = (settingsService: { getSettings: () => Settings } next(error); return; } - jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => { + jwt.verify(parsedToken, jwtSecret, (err: jwt.VerifyErrors | null, decoded: jwt.JwtPayload | string | undefined) => { if (err) { const error = new AppError({ - message: "Failed to authenticate token", - details: err, + message: err instanceof Error ? err.message : "Failed to authenticate token", status: 401, service: SERVICE_NAME, + details: err instanceof Error ? { error: err } : undefined, method: "verifyJWT", }); next(error); return; - } else { - // Token is valid, carry on + } else if (isUser(decoded)) { req.user = decoded; next(); + } else { + next(new AppError({ message: "Invalid token payload", status: 401, service: SERVICE_NAME, method: "verifyJWT" })); } }); }; diff --git a/server/src/service/infrastructure/statusService.ts b/server/src/service/infrastructure/statusService.ts index badcf78ec..ef725b80a 100755 --- a/server/src/service/infrastructure/statusService.ts +++ b/server/src/service/infrastructure/statusService.ts @@ -360,8 +360,7 @@ export class StatusService implements IStatusService { }; } catch (error: unknown) { throw new AppError({ - message: `Failed to update monitor status: ${error instanceof Error ? error.message : "Unknown error"}`, - details: `Error updating status for monitor with id ${check.metadata.monitorId}`, + message: `Failed to update monitor with id ${check.metadata.monitorId} with status: ${error instanceof Error ? error.message : "Unknown error"}`, service: SERVICE_NAME, method: "updateMonitorStatus", }); diff --git a/server/src/utils/AppError.ts b/server/src/utils/AppError.ts index c19f726f2..4c8887a9c 100644 --- a/server/src/utils/AppError.ts +++ b/server/src/utils/AppError.ts @@ -1,18 +1,18 @@ export interface AppErrorConfig { - message: string; + message?: string; status?: number; - service?: string | null; - method?: string | null; - details?: unknown; + service?: string; + method?: string; + details?: Record | undefined; } export class AppError extends Error { - private status: number; - private service: string | null; - private method: string | null; - private details: unknown; + readonly status: number; + readonly service: string; + readonly method: string; + readonly details: Record | undefined; - constructor({ message, status = 500, service = null, method = null, details = null }: AppErrorConfig) { + constructor({ message, status = 500, service = "unknownService", method = "unknownMethod", details = undefined }: AppErrorConfig) { super(message); this.status = status; this.service = service;