diff --git a/server/src/app.js b/server/src/app.js index d782d07eb..d5978a465 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -73,7 +73,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op }); // Main app routes - setupRoutes(app, controllers); + setupRoutes(app, controllers, services); // FE routes app.get("*", (req, res) => { diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts index 303dd4eb7..835480b56 100644 --- a/server/src/config/routes.ts +++ b/server/src/config/routes.ts @@ -1,4 +1,4 @@ -import { verifyJWT } from "../middleware/v1/verifyJWT.js"; +import { createVerifyJWT } from "../middleware/v1/verifyJWT.js"; import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; @@ -15,19 +15,20 @@ import NotificationRoutes from "../routes/v1/notificationRoute.js"; import IncidentRoutes from "../routes/v1/incidentRoute.js"; -export const setupRoutes = (app: any, controllers: Record) => { +export const setupRoutes = (app: any, controllers: Record, services: Record) => { + const verifyJWT = createVerifyJWT(services.settingsService); // V1 - const authRoutes = new AuthRoutes(controllers.authController); + const authRoutes = new AuthRoutes(controllers.authController, verifyJWT); const monitorRoutes = new MonitorRoutes(controllers.monitorController); const settingsRoutes = new SettingsRoutes(controllers.settingsController); const checkRoutes = new CheckRoutes(controllers.checkController); - const inviteRoutes = new InviteRoutes(controllers.inviteController); + const inviteRoutes = new InviteRoutes(controllers.inviteController, verifyJWT); const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController); const queueRoutes = new QueueRoutes(controllers.queueController); const logRoutes = new LogRoutes(controllers.logController); - const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController); + const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController, verifyJWT); const notificationRoutes = new NotificationRoutes(controllers.notificationController); - const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController); + const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController, verifyJWT); const incidentRoutes = new IncidentRoutes(controllers.incidentController); app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter()); diff --git a/server/src/config/services.ts b/server/src/config/services.ts index e3587c662..3f4aae9bc 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -1,4 +1,3 @@ -import ServiceRegistry from "../service/system/serviceRegistry.js"; import TranslationService from "../service/system/translationService.js"; import StringService from "../service/system/stringService.js"; import MongoDB from "../db/MongoDB.js"; @@ -46,7 +45,7 @@ import { games, GameDig } from "gamedig"; import jmespath from "jmespath"; // DB Modules -import { NormalizeData, NormalizeDataUptimeDetails } from "../utils/dataUtils.js"; +import { NormalizeData } from "../utils/dataUtils.js"; import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; @@ -140,9 +139,6 @@ export const initializeServices = async ({ envSettings: any; settingsService: any; }): Promise => { - const serviceRegistry = new ServiceRegistry({ logger }); - (ServiceRegistry as any).instance = serviceRegistry; - const translationService = new TranslationService(logger); await translationService.initialize(); @@ -335,9 +331,5 @@ export const initializeServices = async ({ incidentsRepository, }; - Object.values(services).forEach((service) => { - ServiceRegistry.register(service.serviceName, service); - }); - return services; }; diff --git a/server/src/db/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js index 152f9efc5..7c40877c7 100755 --- a/server/src/db/modules/statusPageModule.js +++ b/server/src/db/modules/statusPageModule.js @@ -1,7 +1,3 @@ -// import { NormalizeData } from "../../../utils/dataUtils.js"; -// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -// import StringService from "../../../service/system/stringService.js"; - const SERVICE_NAME = "statusPageModule"; class StatusPageModule { diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/v1/handleErrors.js index 0a30b0420..6be1b08ac 100755 --- a/server/src/middleware/v1/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,12 +1,9 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import StringService from "../../service/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const message = error.message || stringService.authIncorrectPassword; - const service = error.service || stringService.unknownService; + const message = error.message || "Server error"; + const service = error.service || "unknownService"; logger.error({ message: message, service: service, diff --git a/server/src/middleware/v1/isAllowed.js b/server/src/middleware/v1/isAllowed.js deleted file mode 100755 index 1d0de42b4..000000000 --- a/server/src/middleware/v1/isAllowed.js +++ /dev/null @@ -1,57 +0,0 @@ -import jwt from "jsonwebtoken"; -const TOKEN_PREFIX = "Bearer "; -const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import StringService from "../../service/system/stringService.js"; -import SettingsService from "../../service/system/settingsService.js"; - -const isAllowed = (allowedRoles) => { - return (req, res, next) => { - const token = req.headers["authorization"]; - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - // If no token is pressent, return an error - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - - // If the token is improperly formatted, return an error - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); - error.status = 400; - error.service = SERVICE_NAME; - next(error); - return; - } - // Parse the token - try { - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - var decoded = jwt.verify(parsedToken, jwtSecret); - const userRoles = decoded.role; - - // Check if the user has the required role - if (userRoles.some((role) => allowedRoles.includes(role))) { - next(); - return; - } else { - const error = new Error(stringService.insufficientPermissions); - error.status = 403; - error.service = SERVICE_NAME; - next(error); - return; - } - } catch (error) { - error.status = 401; - error.method = "isAllowed"; - error.service = SERVICE_NAME; - next(error); - return; - } - }; -}; - -export { isAllowed }; diff --git a/server/src/middleware/v1/isAllowed.ts b/server/src/middleware/v1/isAllowed.ts new file mode 100755 index 000000000..21690ee38 --- /dev/null +++ b/server/src/middleware/v1/isAllowed.ts @@ -0,0 +1,29 @@ +import type { Request, Response, NextFunction } from "express"; +const SERVICE_NAME = "allowedRoles"; +import { AppError } from "@/utils/AppError.js"; +import type { UserRole } from "@/types/index.js"; + +const isAllowed = (allowedRoles: UserRole[]) => { + return (req: Request, res: Response, next: NextFunction) => { + try { + const user = req.user; + if (!user) { + throw new AppError({ message: "Unauthorized", status: 403, service: SERVICE_NAME }); + } + const userRoles = req.user?.role || []; + + // Check if the user has the required role + if (userRoles.some((role) => allowedRoles.includes(role))) { + next(); + return; + } else { + throw new AppError({ message: "Unauthorized", status: 403, service: SERVICE_NAME }); + } + } catch (error) { + next(error); + return; + } + }; +}; + +export { isAllowed }; diff --git a/server/src/middleware/v1/verifyJWT.ts b/server/src/middleware/v1/verifyJWT.ts old mode 100755 new mode 100644 index b3211d7b6..6c2b0a229 --- a/server/src/middleware/v1/verifyJWT.ts +++ b/server/src/middleware/v1/verifyJWT.ts @@ -1,51 +1,52 @@ import { NextFunction, Request, Response } from "express"; import jwt from "jsonwebtoken"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import SettingsService from "../../service/system/settingsService.js"; -import StringService from "../../service/system/stringService.js"; +import { AppError } from "@/utils/AppError.js"; +import { Settings } from "@/types/settings.js"; + const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; -const verifyJWT = (req: Request, res: Response, next: NextFunction) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error: any = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error: any = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token - error.status = 401; - error.service = SERVICE_NAME; - error.method = "verifyJWT"; - next(error); - return; - } - - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // Verify the token's authenticity - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => { - if (err) { - const errorMessage = err.name === "TokenExpiredError" ? stringService.expiredAuthToken : stringService.invalidAuthToken; - err.details = { msg: errorMessage }; - err.status = 401; - err.service = SERVICE_NAME; - err.method = "verifyJWT"; - next(err); +export const createVerifyJWT = (settingsService: { getSettings: () => Settings }) => { + return (req: Request, res: Response, next: NextFunction) => { + const token = req.headers["authorization"]; + // Make sure a token is provided + if (!token) { + const error = new AppError({ message: "No token provided", status: 401, service: SERVICE_NAME }); + next(error); + return; + } + // Make sure it is properly formatted + if (!token.startsWith(TOKEN_PREFIX)) { + const error = new AppError({ message: "Invalid token format", status: 401, service: SERVICE_NAME, method: "verifyJWT" }); + next(error); return; - } else { - // Token is valid, carry on - req.user = decoded; - next(); } - }); -}; -export { verifyJWT }; + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + // Verify the token's authenticity + const { jwtSecret } = settingsService.getSettings(); + if (!jwtSecret) { + const error = new AppError({ message: "JWT secret not configured", status: 500, service: SERVICE_NAME }); + next(error); + return; + } + jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => { + if (err) { + const error = new AppError({ + message: "Failed to authenticate token", + details: err, + status: 401, + service: SERVICE_NAME, + method: "verifyJWT", + }); + next(error); + return; + } else { + // Token is valid, carry on + req.user = decoded; + next(); + } + }); + }; +}; diff --git a/server/src/middleware/v1/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js deleted file mode 100755 index 6fcc84f60..000000000 --- a/server/src/middleware/v1/verifyOwnership.js +++ /dev/null @@ -1,59 +0,0 @@ -import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/system/serviceRegistry.js"; -import StringService from "../../service/system/stringService.js"; -import { ObjectId } from "mongodb"; - -const SERVICE_NAME = "verifyOwnership"; - -const verifyOwnership = (Model, paramName) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - return async (req, res, next) => { - const userId = req.user._id; - let documentId = req.params[paramName]; - - try { - if (typeof documentId === "string") { - documentId = ObjectId.createFromHexString(documentId); - } - const doc = await Model.findById(documentId); - //If the document is not found, return a 404 error - if (!doc) { - logger.error({ - message: stringService.verifyOwnerNotFound, - service: SERVICE_NAME, - method: "verifyOwnership", - }); - const error = new Error(stringService.verifyOwnerNotFound); - error.status = 404; - throw error; - } - - // Special case for User model, as it will not have a `userId` field as other docs will - if (Model.modelName === "User") { - if (userId.toString() !== doc._id.toString()) { - const error = new Error(stringService.verifyOwnerUnauthorized); - error.status = 403; - throw error; - } - next(); - return; - } - - // If the userID does not match the document's userID, return a 403 error - if (userId.toString() !== doc.userId.toString()) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; - } - next(); - return; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "verifyOwnership"; - next(error); - return; - } - }; -}; - -export { verifyOwnership }; diff --git a/server/src/middleware/v1/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js deleted file mode 100755 index 41ac0a3e0..000000000 --- a/server/src/middleware/v1/verifySuperAdmin.js +++ /dev/null @@ -1,66 +0,0 @@ -const jwt = require("jsonwebtoken"); -const logger = require("../../utils/logger.js"); -const SERVICE_NAME = "verifyAdmin"; -const TOKEN_PREFIX = "Bearer "; -import ServiceRegistry from "../service/serviceRegistry.js"; -import SettingsService from "../service/settingsService.js"; -import StringService from "../service/stringService.js"; -/** - * Verifies the JWT token - * @function - * @param {express.Request} req - * @param {express.Response} res - * @param {express.NextFunction} next - * @returns {express.Response} - */ -const verifySuperAdmin = (req, res, next) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token - error.status = 400; - error.service = SERVICE_NAME; - error.method = "verifySuperAdmin"; - next(error); - return; - } - - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // verify admin role is present - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { - if (err) { - logger.error({ - message: err.message, - service: SERVICE_NAME, - method: "verifySuperAdmin", - stack: err.stack, - details: stringService.invalidAuthToken, - }); - return res.status(401).json({ success: false, msg: stringService.invalidAuthToken }); - } - - if (decoded.role.includes("superadmin") === false) { - logger.error({ - message: stringService.invalidAuthToken, - service: SERVICE_NAME, - method: "verifySuperAdmin", - stack: err.stack, - }); - return res.status(401).json({ success: false, msg: stringService.unauthorized }); - } - next(); - }); -}; - -module.exports = { verifySuperAdmin }; diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index 63b4c4201..0be1e4750 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,18 +1,17 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; const upload = multer(); class AuthRoutes { - constructor(authController) { + constructor(authController, verifyJWT) { this.router = Router(); this.authController = authController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + initRoutes(verifyJWT) { this.router.post("/register", upload.single("profileImage"), this.authController.registerUser); this.router.post("/login", this.authController.loginUser); diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 2cf481e08..d0d1be8a2 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,14 +1,14 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import { isAllowed } from "../../middleware/v1/isAllowed.js"; class DiagnosticRoutes { - constructor(diagnosticController) { + constructor(diagnosticController, verifyJWT) { this.router = Router(); this.diagnosticController = diagnosticController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + + initRoutes(verifyJWT) { this.router.get("/system", verifyJWT, isAllowed(["admin", "superadmin"]), this.diagnosticController.getSystemStats); } diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index a47550536..6ef9671a3 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,15 +1,14 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import { isAllowed } from "../../middleware/v1/isAllowed.js"; class InviteRoutes { - constructor(inviteController) { + constructor(inviteController, verifyJWT) { this.router = Router(); this.inviteController = inviteController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + initRoutes(verifyJWT) { this.router.post("/send", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.sendInviteEmail); this.router.post("/verify", this.inviteController.verifyInviteToken); this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.getInviteToken); diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 2c3b0b265..7d915cb86 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,16 +1,15 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import multer from "multer"; const upload = multer(); class StatusPageRoutes { - constructor(statusPageController) { + constructor(statusPageController, verifyJWT) { this.router = Router(); this.statusPageController = statusPageController; - this.initRoutes(); + this.initRoutes(verifyJWT); } - initRoutes() { + initRoutes(verifyJWT) { this.router.get("/", this.statusPageController.getStatusPage); this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId);