const rateLimit = require("express-rate-limit"); const jwt = require('jsonwebtoken'); const prisma = require('../config/db'); // Protect routes - verify token const protect = async (req, res, next) => { let token; // Check if token exists in headers if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { try { // Get token from header token = req.headers.authorization.split(' ')[1]; // Verify token — pin the algorithm so a token signed with an // unexpected/attacker-chosen algorithm is never accepted. const decoded = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] }); // Get user from the token (exclude password) req.user = await prisma.user.findUnique({ where: { id: decoded.id }, select: { id: true, name: true, email: true, role: true, isActive: true, createdAt: true, updatedAt: true, phoneNumber: true, tokenVersion: true } }); if (!req.user) { res.status(401); return next(new Error('User not found')); } if (!req.user.isActive) { res.status(401); return next(new Error('User account is deactivated')); } // Revocation check — tokenVersion in JWT must match DB // Old tokens without tokenVersion are treated as version 0 const tokenVer = decoded.tokenVersion ?? 0; if (tokenVer !== req.user.tokenVersion) { res.status(401); return next(new Error('Session has been revoked. Please log in again.')); } next(); } catch (error) { console.error(error); res.status(401); return next(new Error('Not authorized, token failed')); } } else { res.status(401); return next(new Error('Not authorized, no token')); } }; // Admin only middleware const admin = (req, res, next) => { if (req.user && req.user.role === 'admin') { next(); } else { res.status(403); return next(new Error('Not authorized as an admin')); } }; // Staff or higher middleware const staff = (req, res, next) => { if (req.user && (req.user.role === 'admin' || req.user.role === 'supervisor' || req.user.role === 'staff')) { next(); } else { res.status(403); return next(new Error('Not authorized as staff')); } }; // Supervisor or higher middleware const supervisor = (req, res, next) => { if (req.user && (req.user.role === 'admin' || req.user.role === 'supervisor')) { next(); } else { res.status(403); return next(new Error('Not authorized as a supervisor')); } }; const loginLimiter = rateLimit({ windowMs: 60 * 1000, max: 15, message: "Too many login attempts. Try again later.", }); // Optional auth — populates req.user if a valid token is present, but never rejects the request const optionalAuth = async (req, res, next) => { if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer')) { return next(); } try { const token = req.headers.authorization.split(' ')[1]; const decoded = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] }); const user = await prisma.user.findUnique({ where: { id: decoded.id }, select: { id: true, name: true, email: true, role: true, isActive: true, createdAt: true, updatedAt: true, phoneNumber: true, tokenVersion: true } }); if (user && user.isActive && (decoded.tokenVersion ?? 0) === user.tokenVersion) { req.user = user; } } catch { // Token invalid or expired — proceed without user } next(); }; module.exports = { protect, admin, staff, supervisor, loginLimiter, optionalAuth };