142 行
3.9 KiB
JavaScript
142 行
3.9 KiB
JavaScript
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" });
|