|
| 1 | +// middleware/auth.js |
| 2 | +// Authentication & authorization middleware |
| 3 | + |
| 4 | +const jwt = require('jsonwebtoken'); |
| 5 | +const { promisify } = require('util'); |
| 6 | +const config = require('../config/config'); |
| 7 | +const { can } = require('../config/rbacConfig'); |
| 8 | + |
| 9 | +/** |
| 10 | + * Verify JWT, attach decoded payload to req.user |
| 11 | + */ |
| 12 | +exports.authenticate = async (req, res, next) => { |
| 13 | + try { |
| 14 | + const token = |
| 15 | + req.cookies.jid || |
| 16 | + req.headers.authorization?.split(' ')[1]; |
| 17 | + if (!token) { |
| 18 | + return res.status(401).json({ error: 'Authentication required' }); |
| 19 | + } |
| 20 | + const decoded = await promisify(jwt.verify)(token, config.jwt.secret); |
| 21 | + req.user = decoded; |
| 22 | + next(); |
| 23 | + } catch (err) { |
| 24 | + res.status(401).json({ error: 'Invalid or expired token' }); |
| 25 | + } |
| 26 | +}; |
| 27 | + |
| 28 | +/** |
| 29 | + * Check that user has at least one of the allowed roles *and* |
| 30 | + * is permitted to perform this HTTP method on the current resource. |
| 31 | + */ |
| 32 | +exports.authorizeRoles = (...allowedRoles) => (req, res, next) => { |
| 33 | + if (!req.user) { |
| 34 | + return res.status(401).json({ error: 'Authentication required' }); |
| 35 | + } |
| 36 | + |
| 37 | + const method = req.method.toLowerCase(); // e.g., 'get', 'post' |
| 38 | + const resource = req.baseUrl.split('/').pop(); // e.g., 'users', 'auditLogs' |
| 39 | + |
| 40 | + const permitted = req.user.roles.some(role => |
| 41 | + allowedRoles.includes(role) && can(role, resource, method) |
| 42 | + ); |
| 43 | + |
| 44 | + if (!permitted) { |
| 45 | + return res.status(403).json({ error: 'Forbidden' }); |
| 46 | + } |
| 47 | + next(); |
| 48 | +}; |
0 commit comments