You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
3.9 KiB
170 lines
3.9 KiB
/** |
|
* 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, |
|
};
|
|
|