From 2eeb088b618a1c83f08d8abf7f1451789d71e001 Mon Sep 17 00:00:00 2001 From: Ali Shan Ahmad Date: Sun, 6 Sep 2026 08:35:43 +0000 Subject: [PATCH] [FIX]: rbac by adding the missing isAllowed(["admin", "superadmin"]) authorization middleware to mutating API endpoints for maintenance windows, notifications, and monitor check history. --- server/src/api/routes/checkRoutes.ts | 2 +- .../src/api/routes/maintenanceWindowRoutes.ts | 7 +- server/src/api/routes/notificationRoutes.ts | 11 +-- server/test/unit/middleware/isAllowed.test.ts | 79 +++++++++++++++++++ 4 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 server/test/unit/middleware/isAllowed.test.ts diff --git a/server/src/api/routes/checkRoutes.ts b/server/src/api/routes/checkRoutes.ts index 97974f9df..99c6f8a07 100644 --- a/server/src/api/routes/checkRoutes.ts +++ b/server/src/api/routes/checkRoutes.ts @@ -9,6 +9,6 @@ export const createCheckRoutes = (checkController: ICheckController): Router => router.get("/team", checkController.getChecksByTeam); router.delete("/team", isAllowed(["admin", "superadmin"]), checkController.deleteChecksByTeamId); router.get("/:monitorId", checkController.getChecksByMonitor); - router.delete("/:monitorId", checkController.deleteChecks); + router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), checkController.deleteChecks); return router; }; diff --git a/server/src/api/routes/maintenanceWindowRoutes.ts b/server/src/api/routes/maintenanceWindowRoutes.ts index 22c3fd94a..6796d1a72 100644 --- a/server/src/api/routes/maintenanceWindowRoutes.ts +++ b/server/src/api/routes/maintenanceWindowRoutes.ts @@ -1,15 +1,16 @@ import { IMaintenanceWindowController } from "@/api/controllers/maintenanceWindowController.js"; +import { isAllowed } from "@/api/middleware/isAllowed.js"; import { Router } from "express"; export const createMaintenanceWindowRoutes = (maintenanceWindowController: IMaintenanceWindowController): Router => { const router = Router(); - router.post("/", maintenanceWindowController.createMaintenanceWindows); + router.post("/", isAllowed(["admin", "superadmin"]), maintenanceWindowController.createMaintenanceWindows); router.get("/team/", maintenanceWindowController.getMaintenanceWindowsByTeamId); router.get("/monitor/:monitorId", maintenanceWindowController.getMaintenanceWindowsByMonitorId); router.get("/:id", maintenanceWindowController.getMaintenanceWindowById); - router.patch("/:id", maintenanceWindowController.editMaintenanceWindow); - router.delete("/:id", maintenanceWindowController.deleteMaintenanceWindow); + router.patch("/:id", isAllowed(["admin", "superadmin"]), maintenanceWindowController.editMaintenanceWindow); + router.delete("/:id", isAllowed(["admin", "superadmin"]), maintenanceWindowController.deleteMaintenanceWindow); return router; }; diff --git a/server/src/api/routes/notificationRoutes.ts b/server/src/api/routes/notificationRoutes.ts index 32ea1923c..e2d7bfc86 100644 --- a/server/src/api/routes/notificationRoutes.ts +++ b/server/src/api/routes/notificationRoutes.ts @@ -1,14 +1,15 @@ import { INotificationController } from "@/api/controllers/notificationController.js"; +import { isAllowed } from "@/api/middleware/isAllowed.js"; import { Router } from "express"; export const createNotificationRoutes = (notificationController: INotificationController): Router => { const router = Router(); - router.post("/", notificationController.createNotification); - router.post("/test/all", notificationController.testAllNotifications); - router.post("/test", notificationController.testNotification); + router.post("/", isAllowed(["admin", "superadmin"]), notificationController.createNotification); + router.post("/test/all", isAllowed(["admin", "superadmin"]), notificationController.testAllNotifications); + router.post("/test", isAllowed(["admin", "superadmin"]), notificationController.testNotification); router.get("/team", notificationController.getNotificationsByTeamId); router.get("/:id", notificationController.getNotificationById); - router.delete("/:id", notificationController.deleteNotification); - router.patch("/:id", notificationController.editNotification); + router.delete("/:id", isAllowed(["admin", "superadmin"]), notificationController.deleteNotification); + router.patch("/:id", isAllowed(["admin", "superadmin"]), notificationController.editNotification); return router; }; diff --git a/server/test/unit/middleware/isAllowed.test.ts b/server/test/unit/middleware/isAllowed.test.ts new file mode 100644 index 000000000..6354889b1 --- /dev/null +++ b/server/test/unit/middleware/isAllowed.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it, jest } from "@jest/globals"; +import type { NextFunction, Request, Response } from "express"; +import { isAllowed } from "../../../src/api/middleware/isAllowed.ts"; +import { AppError } from "../../../src/utils/AppError.ts"; + +describe("isAllowed middleware", () => { + it("calls next() without error when user has an allowed role", () => { + const middleware = isAllowed(["admin", "superadmin"]); + const req = { + user: { + userId: "user-1", + teamId: "team-1", + email: "admin@example.com", + role: ["admin"], + }, + } as unknown as Request; + const res = {} as Response; + const next = jest.fn() as unknown as NextFunction; + + middleware(req, res, next); + + expect(next).toHaveBeenCalledWith(); + }); + + it("calls next(error) with 403 when user does not have an allowed role", () => { + const middleware = isAllowed(["admin", "superadmin"]); + const req = { + user: { + userId: "user-2", + teamId: "team-1", + email: "user@example.com", + role: ["user"], + }, + } as unknown as Request; + const res = {} as Response; + const next = jest.fn() as unknown as NextFunction; + + middleware(req, res, next); + + expect(next).toHaveBeenCalledTimes(1); + const error = (next as unknown as jest.Mock).mock.calls[0][0] as AppError; + expect(error).toBeInstanceOf(AppError); + expect(error.status).toBe(403); + expect(error.message).toBe("Unauthorized"); + }); + + it("calls next(error) with 403 when req.user is undefined", () => { + const middleware = isAllowed(["admin", "superadmin"]); + const req = {} as Request; + const res = {} as Response; + const next = jest.fn() as unknown as NextFunction; + + middleware(req, res, next); + + expect(next).toHaveBeenCalledTimes(1); + const error = (next as unknown as jest.Mock).mock.calls[0][0] as AppError; + expect(error).toBeInstanceOf(AppError); + expect(error.status).toBe(403); + expect(error.message).toBe("Unauthorized"); + }); + + it("calls next() when user has multiple roles and at least one matches", () => { + const middleware = isAllowed(["admin", "superadmin"]); + const req = { + user: { + userId: "user-3", + teamId: "team-1", + email: "multi@example.com", + role: ["user", "admin"], + }, + } as unknown as Request; + const res = {} as Response; + const next = jest.fn() as unknown as NextFunction; + + middleware(req, res, next); + + expect(next).toHaveBeenCalledWith(); + }); +});