// middleware/coarseAuthorize.js /** * Coarse-Grained Authorization Middleware (Route Level) * * Blocks access if user's role doesn't match required roles for the route * Returns 403 Forbidden if unauthorized * * Route-to-role mapping: * /admin/* → ADMIN * /users/* → USER, ADMIN * /orders/* → USER, ADMIN * etc. */ /** * Route-to-Role Mapping Configuration * Maps route patterns to allowed roles */ const ROUTE_ROLE_MAP = { // Admin routes require ADMIN role '/admin': ['ADMIN'], '/admin/*': ['ADMIN'], // User routes allow USER and ADMIN '/users': ['USER', 'ADMIN'], '/users/*': ['USER', 'ADMIN'], // Order routes allow USER and ADMIN '/orders': ['USER', 'ADMIN'], '/orders/*': ['USER', 'ADMIN'], // Listing routes allow USER and ADMIN '/listings': ['USER', 'ADMIN'], '/listings/*': ['USER', 'ADMIN'], // Location routes allow USER and ADMIN '/locations': ['USER', 'ADMIN'], '/locations/*': ['USER', 'ADMIN'], // Chat routes allow USER and ADMIN '/chat': ['USER', 'ADMIN'], '/chat/*': ['USER', 'ADMIN'], // Default: Allow all authenticated users '*': ['USER', 'ADMIN'], }; /** * Get required roles for a route */ function getRequiredRoles(path) { // Check exact matches first if (ROUTE_ROLE_MAP[path]) { return ROUTE_ROLE_MAP[path]; } // Check wildcard patterns for (const [pattern, roles] of Object.entries(ROUTE_ROLE_MAP)) { if (pattern.includes('*')) { const regexPattern = pattern.replace(/\*/g, '.*'); const regex = new RegExp(`^${regexPattern}$`); if (regex.test(path)) { return roles; } } } // Default: allow all authenticated users return ROUTE_ROLE_MAP['*'] || ['USER', 'ADMIN']; } /** * Normalize role to standard format */ function normalizeRole(role) { if (!role) return null; // Map common role variations to standard roles const roleMap = { 'user': 'USER', 'admin': 'ADMIN', 'USER': 'USER', 'ADMIN': 'ADMIN', 'security_admin': 'ADMIN', 'moderator': 'ADMIN', }; return roleMap[role.toUpperCase()] || role.toUpperCase(); } /** * Coarse-Grained Authorization Middleware Factory * * @param {string[]} allowedRoles - Roles allowed to access this route (optional, uses route mapping if not provided) * @returns {Function} Express middleware */ function createCoarseAuthorize(allowedRoles = null) { return function coarseAuthorize(req, res, next) { try { console.log(`[Coarse Auth] Starting authorization check for ${req.method} ${req.path}`); // User should be set by jwtAuthenticate middleware if (!req.user || !req.user.userId) { console.log(`[Coarse Auth] ❌ FAILED: No user found in request. req.user:`, req.user); return res.status(401).json({ error: 'Unauthorized', message: 'Authentication required', }); } console.log(`[Coarse Auth] User found:`, { userId: req.user.userId, role: req.user.role, userType: req.user.userType, }); const userRole = normalizeRole(req.user.role); // Determine required roles for this route const requiredRoles = allowedRoles || getRequiredRoles(req.path); const normalizedRequiredRoles = requiredRoles.map(normalizeRole); console.log(`[Coarse Auth] Role check:`, { userRole, requiredRoles: normalizedRequiredRoles, routePath: req.path, }); // Check if user's role is in the allowed roles list if (!normalizedRequiredRoles.includes(userRole)) { console.log(`[Coarse Auth] ❌ FAILED: User role "${userRole}" not in required roles:`, normalizedRequiredRoles); // Log authorization failure if (req.auditLogger) { req.auditLogger.log({ userId: req.user.userId, action: 'authorization_failed', route: req.path, status: 'forbidden', meta: { userRole, requiredRoles: normalizedRequiredRoles, }, }); } return res.status(403).json({ error: 'Forbidden', message: `Access denied. Required roles: ${normalizedRequiredRoles.join(', ')}. Your role: ${userRole}`, }); } console.log(`[Coarse Auth] ✅ Authorization passed: User role "${userRole}" is allowed`); next(); } catch (err) { console.error('[Coarse Auth] ❌ Authorization error:', err); return res.status(500).json({ error: 'Internal server error', message: 'Authorization check failed', }); } }; } /** * Pre-configured authorization middlewares for common cases */ export const requireUser = createCoarseAuthorize(['USER', 'ADMIN']); export const requireAdmin = createCoarseAuthorize(['ADMIN']); export default createCoarseAuthorize;