api-v1/middleware/coarseAuthorize.js

172 lines
4.8 KiB
JavaScript

// 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;