93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
import { Router, Request, Response, NextFunction } from 'express';
|
|
import memberController from '../controllers/member.controller';
|
|
import { authenticate } from '../middleware/auth.middleware';
|
|
import { requirePermission } from '../middleware/rbac.middleware';
|
|
|
|
const router = Router();
|
|
|
|
// ----------------------------------------------------------------
|
|
// Apply authentication to every route in this router.
|
|
// requirePermission is applied per-route because PATCH allows the
|
|
// owner to update their own limited fields even without 'members:write'.
|
|
// ----------------------------------------------------------------
|
|
router.use(authenticate);
|
|
|
|
// IMPORTANT: The static /stats route must be registered BEFORE
|
|
// the dynamic /:userId route, otherwise Express would match
|
|
// "stats" as a userId parameter.
|
|
router.get(
|
|
'/stats',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getMemberStats.bind(memberController)
|
|
);
|
|
|
|
router.get(
|
|
'/',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getMembers.bind(memberController)
|
|
);
|
|
|
|
router.get(
|
|
'/:userId',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getMemberById.bind(memberController)
|
|
);
|
|
|
|
router.post(
|
|
'/:userId/profile',
|
|
requirePermission('mitglieder:edit'),
|
|
memberController.createMemberProfile.bind(memberController)
|
|
);
|
|
|
|
router.get(
|
|
'/:userId/befoerderungen',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getBefoerderungen.bind(memberController)
|
|
);
|
|
|
|
router.get(
|
|
'/:userId/untersuchungen',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getUntersuchungen.bind(memberController)
|
|
);
|
|
|
|
router.get(
|
|
'/:userId/fahrgenehmigungen',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getFahrgenehmigungen.bind(memberController)
|
|
);
|
|
|
|
router.get(
|
|
'/:userId/ausbildungen',
|
|
requirePermission('mitglieder:view_all'),
|
|
memberController.getAusbildungen.bind(memberController)
|
|
);
|
|
|
|
/**
|
|
* Inline middleware for PATCH /:userId.
|
|
* Enforces that the caller is either the profile owner OR holds members:write.
|
|
* This is the route-level IDOR guard; the controller still applies the
|
|
* correct Zod schema (full vs. limited fields) based on role.
|
|
*/
|
|
const requireOwnerOrWrite = (req: Request, res: Response, next: NextFunction): void => {
|
|
const isOwner = req.user?.id === req.params.userId;
|
|
if (isOwner) {
|
|
next();
|
|
return;
|
|
}
|
|
// Not the owner — must have members:write permission
|
|
requirePermission('mitglieder:edit')(req, res, next);
|
|
};
|
|
|
|
/**
|
|
* PATCH /:userId — open to both privileged users AND the profile owner.
|
|
* Route-level guard rejects all other callers before the controller runs.
|
|
*/
|
|
router.patch(
|
|
'/:userId',
|
|
requireOwnerOrWrite,
|
|
memberController.updateMember.bind(memberController)
|
|
);
|
|
|
|
export default router;
|