import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import { Role } from '@prisma/client'; const JWT_SECRET = process.env.JWT_SECRET; if (!JWT_SECRET || JWT_SECRET === 'change-me-in-production') { if (process.env.NODE_ENV === 'production') { throw new Error('FATAL: JWT_SECRET must be set to a strong, unique value in production'); } console.warn('WARNING: Using insecure default JWT_SECRET. Set JWT_SECRET env var before deploying.'); } const RESOLVED_JWT_SECRET = JWT_SECRET || 'change-me-in-production'; interface JwtPayload { id: string; email: string; role: Role; tenantId: string | null; firstName: string; lastName: string; } export function authenticate(req: Request, res: Response, next: NextFunction): void { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { res.status(401).json({ error: 'Authentication required', message: 'No valid token provided' }); return; } const token = authHeader.substring(7); try { const decoded = jwt.verify(token, RESOLVED_JWT_SECRET, { algorithms: ['HS256'] }) as JwtPayload; req.user = { id: decoded.id, email: decoded.email, role: decoded.role, tenantId: decoded.tenantId, firstName: decoded.firstName, lastName: decoded.lastName, }; next(); } catch (err) { if (err instanceof jwt.TokenExpiredError) { res.status(401).json({ error: 'Token expired', message: 'Please refresh your token' }); return; } res.status(401).json({ error: 'Invalid token', message: 'Token verification failed' }); } } export function authorize(...roles: Role[]) { return (req: Request, res: Response, next: NextFunction): void => { if (!req.user) { res.status(401).json({ error: 'Authentication required' }); return; } if (!roles.includes(req.user.role)) { res.status(403).json({ error: 'Forbidden', message: `Role ${req.user.role} is not authorized for this action`, }); return; } next(); }; } export function tenantGuard(req: Request, res: Response, next: NextFunction): void { if (!req.user) { res.status(401).json({ error: 'Authentication required' }); return; } // SYSTEM_ADMIN and TECHNICIAN can access all tenants if (req.user.role === 'SYSTEM_ADMIN' || req.user.role === 'TECHNICIAN') { next(); return; } // For tenant-scoped users, check that the requested tenantId matches their own const requestedTenantId = req.params.tenantId || req.body?.tenantId || req.query.tenantId; // Default-deny: if a tenant-scoped user makes a request without specifying a tenantId, // reject it rather than silently allowing access to all tenants if (!requestedTenantId) { // Allow if the route doesn't need tenant context (handled by individual route guards) next(); return; } if (requestedTenantId !== req.user.tenantId) { res.status(403).json({ error: 'Forbidden', message: 'You can only access data within your own tenant', }); return; } next(); } export function getTenantFilter(user: JwtPayload): { tenantId: string } | {} { if (user.role === 'SYSTEM_ADMIN' || user.role === 'TECHNICIAN') { return {}; } return { tenantId: user.tenantId! }; }