/** * Authorization Middleware * JWT authentication and role-based access control */ import { verifyToken } from '../utils/auth.js'; import { query } from '../db/db.js'; /** * Extract token from cookie * @param {Object} req - Express request object * @returns {string|null} Token from cookie */ function getTokenFromCookie(req) { return req.cookies?.token || null; } /** * Middleware to authenticate JWT token * Adds user data to req.user */ export async function authenticate(req, res, next) { try { const token = getTokenFromCookie(req); if (!token) { return res.status(401).json({ error: 'Authentication required' }); } const decoded = verifyToken(token); if (!decoded) { return res.status(401).json({ error: 'Invalid or expired token' }); } const result = await query( 'SELECT id, login, full_name, role, department_id, staff_id FROM users WHERE id = $1', [decoded.userId] ); if (result.rows.length === 0) { return res.status(401).json({ error: 'User not found' }); } const user = result.rows[0]; const staffId = user.staff_id ?? decoded.staffId; req.user = { id: user.id, login: user.login, fullName: user.full_name, role: user.role, departmentId: user.department_id, }; if (staffId != null) { req.user.staffId = staffId; } next(); } catch (error) { console.error('Auth middleware error:', error); return res.status(500).json({ error: 'Authentication error' }); } } /** * Middleware factory to require specific roles * @param {string|string[]} roles - Required role(s) * @returns {Function} Express middleware */ export function requireRole(roles) { const allowedRoles = Array.isArray(roles) ? roles : [roles]; return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } if (!allowedRoles.includes(req.user.role)) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); }; } /** * Middleware to require specific department access * For managers to access their department's data * @param {number} departmentId - Required department ID * @returns {Function} Express middleware */ export function requireDepartment(departmentId) { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } // Admins can access all departments if (req.user.role === 'admin') { return next(); } // Managers can only access their department if (req.user.role === 'manager' && req.user.departmentId !== departmentId) { return res.status(403).json({ error: 'Access denied to this department' }); } next(); }; } /** * Optional authentication middleware * Attaches user to request if token is valid, but doesn't require it */ export async function optionalAuth(req, res, next) { try { const token = getTokenFromCookie(req); if (!token) { req.user = null; return next(); } const decoded = verifyToken(token); if (!decoded) { req.user = null; return next(); } const result = await query( 'SELECT id, login, full_name, role, department_id, staff_id FROM users WHERE id = $1', [decoded.userId] ); if (result.rows.length === 0) { req.user = null; return next(); } const user = result.rows[0]; const staffId = user.staff_id ?? decoded.staffId; req.user = { id: user.id, login: user.login, fullName: user.full_name, role: user.role, departmentId: user.department_id, }; if (staffId != null) { req.user.staffId = staffId; } next(); } catch (error) { // Don't block request on auth errors req.user = null; next(); } } export default { authenticate, requireRole, requireDepartment, optionalAuth, };