0fe04d4d99
- Сервисы: testAttemptService, testAccess, document import/gen/extract, LLM, assignment, aiEditor - Конфиг: devAuthor, featureFlags; messages/ru; интеграция V.9 (skip без БД) - API/роуты: app, auth, server; Dockerfile и env example - Фронт: TestAttempt, TestAttemptReview, AttemptReviewBlock, стили, правки App/api/login/vite - compose и README; смоук-тесты расширены Закрывает отсутствие модулей в origin после клона. Made-with: Cursor
172 lines
3.9 KiB
JavaScript
172 lines
3.9 KiB
JavaScript
/**
|
|
* Authorization Middleware
|
|
* JWT authentication and role-based access control
|
|
*/
|
|
|
|
import { verifyToken } from '../utils/auth.js';
|
|
import { query } from '../db/db.js';
|
|
import { RU } from '../messages/ru.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: RU.authRequired });
|
|
}
|
|
|
|
const decoded = verifyToken(token);
|
|
|
|
if (!decoded) {
|
|
return res.status(401).json({ error: RU.tokenInvalid });
|
|
}
|
|
|
|
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: RU.userNotFound });
|
|
}
|
|
|
|
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: RU.authError });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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: RU.authRequired });
|
|
}
|
|
|
|
if (!allowedRoles.includes(req.user.role)) {
|
|
return res.status(403).json({ error: RU.insufficientPermissions });
|
|
}
|
|
|
|
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: RU.authRequired });
|
|
}
|
|
|
|
// 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: RU.departmentAccessDenied });
|
|
}
|
|
|
|
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,
|
|
};
|