Spaces:
Paused
Paused
| const { AppError } = require("../utils/appError"); | |
| function extractBearerToken(authorizationHeader) { | |
| if (!authorizationHeader || typeof authorizationHeader !== "string") { | |
| return null; | |
| } | |
| const [scheme, token] = authorizationHeader.trim().split(/\s+/, 2); | |
| if (!scheme || !token) { | |
| return null; | |
| } | |
| if (scheme.toLowerCase() !== "bearer") { | |
| return null; | |
| } | |
| return token; | |
| } | |
| function createRequireAuthMiddleware(authService) { | |
| if (!authService) { | |
| throw new Error("createRequireAuthMiddleware requires authService."); | |
| } | |
| return async function requireAuth(req, _res, next) { | |
| try { | |
| const token = extractBearerToken(req.headers.authorization); | |
| if (!token) { | |
| throw new AppError("Authorization bearer token is required.", 401, { | |
| code: "TOKEN_MISSING", | |
| }); | |
| } | |
| const decoded = authService.verifyAccessToken(token); | |
| const userId = String(decoded.sub || "").trim(); | |
| if (!userId) { | |
| throw new AppError("Invalid token subject.", 401, { | |
| code: "TOKEN_SUBJECT_INVALID", | |
| }); | |
| } | |
| const authenticatedUser = await authService.getAuthenticatedUserById(userId); | |
| req.auth = { | |
| id: authenticatedUser.id, | |
| email: authenticatedUser.email, | |
| fullName: authenticatedUser.fullName, | |
| roles: authenticatedUser.roles, | |
| isSuperAdmin: authenticatedUser.isSuperAdmin, | |
| tenant: authenticatedUser.tenant, | |
| }; | |
| if (!req.context) { | |
| req.context = {}; | |
| } | |
| req.context.actorUserId = authenticatedUser.id; | |
| return next(); | |
| } catch (error) { | |
| return next(error); | |
| } | |
| }; | |
| } | |
| function createRequireRolesMiddleware(allowedRoles) { | |
| const normalizedAllowed = (Array.isArray(allowedRoles) ? allowedRoles : []) | |
| .filter((role) => typeof role === "string") | |
| .map((role) => role.trim().toLowerCase()) | |
| .filter(Boolean); | |
| return function requireRoles(req, _res, next) { | |
| if (!req.auth) { | |
| return next(new AppError("Unauthorized.", 401)); | |
| } | |
| if (req.auth.isSuperAdmin) { | |
| return next(); | |
| } | |
| if (normalizedAllowed.length === 0) { | |
| return next(); | |
| } | |
| const roleSet = new Set((req.auth.roles || []).map((role) => String(role).toLowerCase())); | |
| const hasAllowedRole = normalizedAllowed.some((role) => roleSet.has(role)); | |
| if (!hasAllowedRole) { | |
| return next( | |
| new AppError("Insufficient permissions for this action.", 403, { | |
| code: "RBAC_FORBIDDEN", | |
| requiredRoles: normalizedAllowed, | |
| }) | |
| ); | |
| } | |
| return next(); | |
| }; | |
| } | |
| function createTenantScopeMiddleware() { | |
| return function enforceTenantScope(req, _res, next) { | |
| if (!req.auth) { | |
| return next(); | |
| } | |
| if (!req.context) { | |
| req.context = {}; | |
| } | |
| const requestedTenantCodeRaw = req.query?.tenantCode; | |
| const requestedTenantCode = | |
| typeof requestedTenantCodeRaw === "string" && requestedTenantCodeRaw.trim() | |
| ? requestedTenantCodeRaw.trim() | |
| : null; | |
| if (req.auth.isSuperAdmin) { | |
| req.context.tenantCode = requestedTenantCode; | |
| if (requestedTenantCode) { | |
| req.query.tenantCode = requestedTenantCode; | |
| } | |
| return next(); | |
| } | |
| const userTenantCode = req.auth.tenant?.code || null; | |
| if (!userTenantCode) { | |
| return next( | |
| new AppError("Authenticated user has no tenant scope.", 403, { | |
| code: "TENANT_SCOPE_MISSING", | |
| }) | |
| ); | |
| } | |
| if (requestedTenantCode && requestedTenantCode !== userTenantCode) { | |
| return next( | |
| new AppError("Cross-tenant access is not allowed.", 403, { | |
| code: "TENANT_SCOPE_VIOLATION", | |
| requestedTenantCode, | |
| tenantCode: userTenantCode, | |
| }) | |
| ); | |
| } | |
| req.context.tenantCode = userTenantCode; | |
| req.query.tenantCode = userTenantCode; | |
| return next(); | |
| }; | |
| } | |
| module.exports = { | |
| createRequireAuthMiddleware, | |
| createRequireRolesMiddleware, | |
| createTenantScopeMiddleware, | |
| }; | |