// RBAC Middleware for Express/Fastify API export enum Role { CLIENT = 'CLIENT', VENDOR = 'VENDOR', ADMIN = 'ADMIN', } export enum Permission { // Client permissions VIEW_OWN_BOOKINGS = 'view_own_bookings', CREATE_BOOKING = 'create_booking', SIGN_CONTRACT = 'sign_contract', VIEW_OWN_CONTRACTS = 'view_own_contracts', REQUEST_AMENDMENT = 'request_amendment', // Vendor permissions MANAGE_PROFILE = 'manage_profile', MANAGE_PACKAGES = 'manage_packages', VIEW_LEADS = 'view_leads', CREATE_CONTRACT = 'create_contract', SEND_CONTRACT = 'send_contract', MANAGE_DELIVERABLES = 'manage_deliverables', // Admin permissions MODERATE_VENDORS = 'moderate_vendors', MODERATE_CONTENT = 'moderate_content', OVERSEE_CONTRACTS = 'oversee_contracts', MEDIATE_DISPUTES = 'mediate_disputes', EXPORT_AUDIT_TRAIL = 'export_audit_trail', MANAGE_CATEGORIES = 'manage_categories', IMPERSONATE_USERS = 'impersonate_users', MANAGE_USERS = 'manage_users', } const rolePermissions: Record = { [Role.CLIENT]: [ Permission.VIEW_OWN_BOOKINGS, Permission.CREATE_BOOKING, Permission.SIGN_CONTRACT, Permission.VIEW_OWN_CONTRACTS, Permission.REQUEST_AMENDMENT, ], [Role.VENDOR]: [ Permission.MANAGE_PROFILE, Permission.MANAGE_PACKAGES, Permission.VIEW_LEADS, Permission.CREATE_CONTRACT, Permission.SEND_CONTRACT, Permission.MANAGE_DELIVERABLES, ], [Role.ADMIN]: Object.values(Permission), } export function hasPermission(role: Role, permission: Permission): boolean { return rolePermissions[role]?.includes(permission) ?? false } export function requirePermission(permission: Permission) { return function (req: any, res: any, next: Function) { const role = req.user?.role as Role if (!role || !hasPermission(role, permission)) { return res.status(403).json({ error: 'Forbidden', message: `Missing required permission: ${permission}`, }) } next() } } export function requireRole(...roles: Role[]) { return function (req: any, res: any, next: Function) { const role = req.user?.role as Role if (!role || !roles.includes(role)) { return res.status(403).json({ error: 'Forbidden', message: `Access restricted to: ${roles.join(', ')}`, }) } next() } } // Contract-specific RBAC: only the vendor who created it or the client who received it can access export function requireContractAccess(contractIdParam: string = 'contractId') { return async function (req: any, res: any, next: Function) { const contractId = req.params[contractIdParam] const userId = req.user?.id const role = req.user?.role as Role if (role === Role.ADMIN && hasPermission(role, Permission.OVERSEE_CONTRACTS)) { return next() } // Fetch contract and verify ownership try { const contract = await fetchContract(contractId) if (!contract) return res.status(404).json({ error: 'Contract not found' }) const hasAccess = (role === Role.VENDOR && contract.vendorId === userId) || (role === Role.CLIENT && contract.clientId === userId) if (!hasAccess) return res.status(403).json({ error: 'Access denied' }) next() } catch (err) { return res.status(500).json({ error: 'Internal error' }) } } } async function fetchContract(id: string) { // Replace with actual DB call return { id, vendorId: '', clientId: '' } } // ── Impersonation (admin-only) ── // Admins can impersonate with TTL-bound sessions and audit reason field export async function impersonateUser(adminId: string, targetUserId: string, reason: string, ttlMinutes: number = 30) { // 1. Verify admin has IMPERSONATE_USERS permission // 2. Create session with role=targetUser.role, token expires in ttlMinutes // 3. Log to AdminAuditLog: // { adminId, action: 'impersonate_start', target: targetUserId, detail: reason, ttl: ttlMinutes } // 4. Return impersonation session token } // ── Rate Limiting ── // Keyed on IP + method + pathname, backed by Redis // Implementation: use rate-limiter-flexible with Redis store // export const rateLimiter = new RateLimiterRedis({...})