import { Request, Response, NextFunction } from 'express'; import { Participant } from '@models/Participant'; import { WorkflowRequest } from '@models/WorkflowRequest'; import { Op } from 'sequelize'; type AllowedType = 'INITIATOR' | 'APPROVER' | 'SPECTATOR'; // Helper to check if identifier is UUID or requestNumber function isUuid(identifier: string): boolean { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(identifier); } // Helper to find workflow by either requestId or requestNumber async function findWorkflowByIdentifier(identifier: string): Promise { if (isUuid(identifier)) { return await WorkflowRequest.findByPk(identifier); } else { return await WorkflowRequest.findOne({ where: { requestNumber: identifier } }); } } export function requireParticipantTypes(allowed: AllowedType[]) { return async (req: Request, res: Response, next: NextFunction) => { try { const userId: string | undefined = (req as any).user?.userId || (req as any).user?.id; const requestIdentifier: string | undefined = (req.params as any)?.id; if (!userId || !requestIdentifier) { return res.status(403).json({ success: false, error: 'Forbidden' }); } // Resolve requestIdentifier to actual requestId (UUID) const workflow = await findWorkflowByIdentifier(requestIdentifier); if (!workflow) { return res.status(404).json({ success: false, error: 'Workflow not found' }); } const actualRequestId = (workflow as any).requestId; // Check initiator if (allowed.includes('INITIATOR')) { if ((workflow as any).initiatorId === userId) { return next(); } } // Check participants table for SPECTATOR if (allowed.includes('SPECTATOR')) { const participant = await Participant.findOne({ where: { requestId: actualRequestId, userId, participantType: 'SPECTATOR', isActive: true }, }); if (participant) { return next(); } } // For APPROVER role, check ApprovalLevel table // This is the primary source of truth for approvers if (allowed.includes('APPROVER')) { const { ApprovalLevel } = await import('@models/ApprovalLevel'); const approvalLevel = await ApprovalLevel.findOne({ where: { requestId: actualRequestId, approverId: userId, status: { [Op.in]: ['PENDING', 'IN_PROGRESS'] as any } } }); if (approvalLevel) { return next(); } // Fallback: also check Participants table (some approvers might be added there) const participant = await Participant.findOne({ where: { requestId: actualRequestId, userId, participantType: 'APPROVER', isActive: true }, }); if (participant) { return next(); } } return res.status(403).json({ success: false, error: 'Insufficient permissions' }); } catch (err) { console.error('❌ Authorization check failed:', err); return res.status(500).json({ success: false, error: 'Authorization check failed' }); } }; } /** * Role-Based Access Control (RBAC) Middleware * * Roles: * - USER: Default role - can create/view own requests, participate in assigned workflows * - MANAGEMENT: Read access to all requests, enhanced dashboard visibility * - ADMIN: Full system access - configuration, user management, all workflows */ /** * Middleware: requireAdmin * * Purpose: Restrict access to ADMIN role only * * Use Cases: * - System configuration management * - User role assignment * - Holiday calendar management * - Email/notification settings * - Audit log access * * Returns: 403 Forbidden if user is not ADMIN */ export function requireAdmin(req: Request, res: Response, next: NextFunction): void { try { const userRole = req.user?.role; if (userRole !== 'ADMIN') { res.status(403).json({ success: false, error: 'Admin access required. Only administrators can perform this action.' }); return; } next(); } catch (error) { console.error('❌ Admin authorization failed:', error); res.status(500).json({ success: false, error: 'Authorization check failed' }); } } /** * Middleware: requireManagement * * Purpose: Restrict access to MANAGEMENT and ADMIN roles * * Use Cases: * - View all requests (read-only) * - Access comprehensive dashboards * - Export reports * - View analytics across all departments * * Permissions: * - MANAGEMENT: Read access to all data * - ADMIN: Read + Write access * * Returns: 403 Forbidden if user is only USER role */ export function requireManagement(req: Request, res: Response, next: NextFunction): void { try { const userRole = req.user?.role; if (userRole !== 'MANAGEMENT' && userRole !== 'ADMIN') { res.status(403).json({ success: false, error: 'Management access required. This feature is available to management and admin users only.' }); return; } next(); } catch (error) { console.error('❌ Management authorization failed:', error); res.status(500).json({ success: false, error: 'Authorization check failed' }); } } /** * Middleware: requireRole * * Purpose: Flexible role checking - accepts multiple allowed roles * * Example Usage: * - requireRole(['ADMIN']) - Admin only * - requireRole(['MANAGEMENT', 'ADMIN']) - Management or Admin * - requireRole(['USER', 'MANAGEMENT', 'ADMIN']) - Any authenticated user * * @param allowedRoles - Array of allowed role strings * @returns Express middleware function */ export function requireRole(allowedRoles: ('USER' | 'MANAGEMENT' | 'ADMIN')[]) { return (req: Request, res: Response, next: NextFunction): void => { try { const userRole = req.user?.role; if (!userRole || !allowedRoles.includes(userRole as any)) { res.status(403).json({ success: false, error: `Access denied. Required roles: ${allowedRoles.join(' or ')}` }); return; } next(); } catch (error) { console.error('❌ Role authorization failed:', error); res.status(500).json({ success: false, error: 'Authorization check failed' }); } }; } /** * Helper: Check if user has specific role * * Purpose: Programmatic role checking within controllers * * @param user - Express req.user object * @param role - Role to check * @returns boolean */ export function hasRole(user: any, role: 'USER' | 'MANAGEMENT' | 'ADMIN'): boolean { return user?.role === role; } /** * Helper: Check if user has management or admin access * * Purpose: Quick check for enhanced permissions * * @param user - Express req.user object * @returns boolean */ export function hasManagementAccess(user: any): boolean { return user?.role === 'MANAGEMENT' || user?.role === 'ADMIN'; } /** * Helper: Check if user has admin access * * Purpose: Quick check for admin-only permissions * * @param user - Express req.user object * @returns boolean */ export function hasAdminAccess(user: any): boolean { return user?.role === 'ADMIN'; }