From 4ed76fe20df31099b1ae094a1bb1809298f629f0 Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Wed, 25 Mar 2026 09:07:31 +0100 Subject: [PATCH] fix permissions --- backend/src/middleware/rbac.middleware.ts | 46 +++++++++++++++++++ .../src/routes/ausruestungsanfrage.routes.ts | 10 ++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/backend/src/middleware/rbac.middleware.ts b/backend/src/middleware/rbac.middleware.ts index 451083f..2f62714 100644 --- a/backend/src/middleware/rbac.middleware.ts +++ b/backend/src/middleware/rbac.middleware.ts @@ -85,6 +85,52 @@ export function requirePermission(permission: string) { }; } +/** + * Middleware factory: passes if the user holds ANY of the listed permissions. + * Useful when multiple roles should access the same read endpoint. + * Maintenance mode is checked against the first permission's feature group. + */ +export function requireAnyPermission(...permissions: string[]) { + return async (req: Request, res: Response, next: NextFunction): Promise => { + if (!req.user) { + res.status(401).json({ success: false, message: 'Authentication required' }); + return; + } + + const groups: string[] = req.user?.groups ?? []; + (req as any).userRole = resolveRequestRole(req); + + if (groups.includes('dashboard_admin')) { + next(); + return; + } + + // Maintenance check on the feature group of the first permission + const featureGroup = permissions[0].split(':')[0]; + if (permissionService.isFeatureInMaintenance(featureGroup)) { + res.status(403).json({ success: false, message: 'Diese Funktion befindet sich im Wartungsmodus' }); + return; + } + + if (permissions.some(p => permissionService.hasPermission(groups, p))) { + next(); + return; + } + + logger.warn('Permission denied (any-of)', { + userId: req.user.id, + groups, + permissions, + path: req.path, + }); + auditPermissionDenied(req, AuditResourceType.SYSTEM, undefined, { + required_permissions: permissions, + user_groups: groups, + }); + res.status(403).json({ success: false, message: 'Keine Berechtigung' }); + }; +} + /** * Resolve the effective AppRole for a request. * Simplified: returns 'admin' for dashboard_admin, 'kommandant' for dashboard_kommando, diff --git a/backend/src/routes/ausruestungsanfrage.routes.ts b/backend/src/routes/ausruestungsanfrage.routes.ts index 54b17a6..cfd3cc7 100644 --- a/backend/src/routes/ausruestungsanfrage.routes.ts +++ b/backend/src/routes/ausruestungsanfrage.routes.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import ausruestungsanfrageController from '../controllers/ausruestungsanfrage.controller'; import { authenticate } from '../middleware/auth.middleware'; -import { requirePermission } from '../middleware/rbac.middleware'; +import { requirePermission, requireAnyPermission } from '../middleware/rbac.middleware'; const router = Router(); @@ -9,7 +9,7 @@ const router = Router(); // Categories (DB-backed CRUD) // --------------------------------------------------------------------------- -router.get('/kategorien', authenticate, requirePermission('ausruestungsanfrage:view'), ausruestungsanfrageController.getKategorien.bind(ausruestungsanfrageController)); +router.get('/kategorien', authenticate, requireAnyPermission('ausruestungsanfrage:view', 'ausruestungsanfrage:create_request', 'ausruestungsanfrage:approve'), ausruestungsanfrageController.getKategorien.bind(ausruestungsanfrageController)); router.post('/kategorien', authenticate, requirePermission('ausruestungsanfrage:manage_categories'), ausruestungsanfrageController.createKategorie.bind(ausruestungsanfrageController)); router.patch('/kategorien/:id', authenticate, requirePermission('ausruestungsanfrage:manage_categories'), ausruestungsanfrageController.updateKategorie.bind(ausruestungsanfrageController)); router.delete('/kategorien/:id', authenticate, requirePermission('ausruestungsanfrage:manage_categories'), ausruestungsanfrageController.deleteKategorie.bind(ausruestungsanfrageController)); @@ -18,14 +18,14 @@ router.delete('/kategorien/:id', authenticate, requirePermission('ausruestungsan // Catalog Items // --------------------------------------------------------------------------- -router.get('/items', authenticate, requirePermission('ausruestungsanfrage:view'), ausruestungsanfrageController.getItems.bind(ausruestungsanfrageController)); -router.get('/items/:id', authenticate, requirePermission('ausruestungsanfrage:view'), ausruestungsanfrageController.getItemById.bind(ausruestungsanfrageController)); +router.get('/items', authenticate, requireAnyPermission('ausruestungsanfrage:view', 'ausruestungsanfrage:create_request', 'ausruestungsanfrage:approve'), ausruestungsanfrageController.getItems.bind(ausruestungsanfrageController)); +router.get('/items/:id', authenticate, requireAnyPermission('ausruestungsanfrage:view', 'ausruestungsanfrage:create_request', 'ausruestungsanfrage:approve'), ausruestungsanfrageController.getItemById.bind(ausruestungsanfrageController)); router.post('/items', authenticate, requirePermission('ausruestungsanfrage:manage_catalog'), ausruestungsanfrageController.createItem.bind(ausruestungsanfrageController)); router.patch('/items/:id', authenticate, requirePermission('ausruestungsanfrage:manage_catalog'), ausruestungsanfrageController.updateItem.bind(ausruestungsanfrageController)); router.delete('/items/:id', authenticate, requirePermission('ausruestungsanfrage:manage_catalog'), ausruestungsanfrageController.deleteItem.bind(ausruestungsanfrageController)); // Item characteristics (Eigenschaften) -router.get('/items/:id/eigenschaften', authenticate, requirePermission('ausruestungsanfrage:view'), ausruestungsanfrageController.getArtikelEigenschaften.bind(ausruestungsanfrageController)); +router.get('/items/:id/eigenschaften', authenticate, requireAnyPermission('ausruestungsanfrage:view', 'ausruestungsanfrage:create_request', 'ausruestungsanfrage:approve'), ausruestungsanfrageController.getArtikelEigenschaften.bind(ausruestungsanfrageController)); router.post('/items/:id/eigenschaften', authenticate, requirePermission('ausruestungsanfrage:manage_catalog'), ausruestungsanfrageController.upsertArtikelEigenschaft.bind(ausruestungsanfrageController)); router.delete('/eigenschaften/:eigenschaftId', authenticate, requirePermission('ausruestungsanfrage:manage_catalog'), ausruestungsanfrageController.deleteArtikelEigenschaft.bind(ausruestungsanfrageController));