const fastifyJwt = require("@fastify/jwt"); const fp = require("fastify-plugin"); const { AppError } = require("../utils/AppError"); const { resolvePermissionsForRoles, hasPermission, resolveEntityPermission, } = require("../constants/permissions"); async function authPlugin(fastify) { const { jwtSecret, accessTokenTtl } = fastify.config; await fastify.register(fastifyJwt, { secret: jwtSecret, }); fastify.decorateRequest("employee", null); fastify.decorate("issueAccessToken", (employee) => { return fastify.jwt.sign( { sub: employee.id, tokenVersion: employee.token_version, }, { expiresIn: accessTokenTtl }, ); }); fastify.decorate("hasPermission", (employee, permissionCode) => { return hasPermission(employee?.permissionCodes ?? [], permissionCode); }); fastify.decorate("assertPermission", (employee, permissionCode, details = {}) => { if (!permissionCode || !fastify.hasPermission(employee, permissionCode)) { throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: permissionCode, ...details }, }); } return permissionCode; }); fastify.decorate("assertEntityPermission", (employee, entityType, action) => { const permissionCode = resolveEntityPermission(entityType, action); if (!permissionCode) { throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 409, details: { entityType, action, permission: "unsupported_entity_action" }, }); } return fastify.assertPermission(employee, permissionCode, { entityType, action }); }); fastify.decorate("requirePermission", (permissionCode) => { return async (request) => { await fastify.authenticate(request); fastify.assertPermission(request.employee, permissionCode); }; }); fastify.decorate("requireEntityPermission", (entityType, action) => { return async (request) => { await fastify.authenticate(request); fastify.assertEntityPermission(request.employee, entityType, action); }; }); fastify.decorate("authenticate", async (request) => { try { await request.jwtVerify(); } catch { throw new AppError({ code: "AUTH_004", message: "Access Token 已过期/无效", statusCode: 401, }); } const employeeId = request.user?.sub; const tokenVersion = request.user?.tokenVersion; if (typeof employeeId !== "string" || typeof tokenVersion !== "number") { throw new AppError({ code: "AUTH_004", message: "Access Token 已过期/无效", statusCode: 401, }); } const employee = await fastify.prisma.employee.findUnique({ where: { id: employeeId }, select: { id: true, status: true, token_version: true, department_id: true, legal_entity_id: true, roles: { select: { role_code: true } }, }, }); if (!employee || employee.status !== "active") { throw new AppError({ code: "AUTH_003", message: "账号已离职/禁用", statusCode: 403, }); } if (tokenVersion < employee.token_version) { throw new AppError({ code: "AUTH_004", message: "Access Token 已过期/无效", statusCode: 401, }); } const roleCodes = employee.roles.map((item) => item.role_code); const permissionCodes = resolvePermissionsForRoles(roleCodes); request.employee = { id: employee.id, status: employee.status, token_version: employee.token_version, department_id: employee.department_id, legal_entity_id: employee.legal_entity_id ?? null, roleCodes, permissionCodes, }; if (request.log && typeof request.log.child === "function") { request.log = request.log.child({ actorId: employee.id }); } }); } module.exports = fp(authPlugin, { name: "auth" });