import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify" import fp from "fastify-plugin" import { env } from "../config.js" import { hasPermission, permissionForRequest, type AuthUser, } from "../lib/permissions.js" declare module "fastify" { interface FastifyRequest { authUser?: AuthUser } } declare module "@fastify/jwt" { interface FastifyJWT { payload: { sub: string email?: string name?: string apps?: string[] permissions?: string[] is_admin?: boolean iss?: string exp?: number } user: { sub: string email?: string name?: string apps?: string[] permissions?: string[] is_admin?: boolean iss?: string exp?: number } } } async function authPlugin(app: FastifyInstance) { if (env.authRequired && env.jwtSecret.length < 8) { throw new Error("AUTH_JWT_SECRET / JWT_SECRET required when AUTH_REQUIRED=true") } await app.register(import("@fastify/jwt"), { secret: env.jwtSecret, ...(env.authRequired ? { verify: { allowedIss: [env.authIssuer], }, } : {}), }) if (env.authRequired) { app.log.info( { issuer: env.authIssuer, portal: env.authPortalUrl }, "AUTH_REQUIRED=true — portal JWT middleware enabled", ) } else { app.log.info("AUTH_REQUIRED=false — /api/* open without JWT") } } /** * Protect /api/* when AUTH_REQUIRED=true. * Public: /health, /api/auth/config */ export async function requireAuth( request: FastifyRequest, reply: FastifyReply, ): Promise { if (!env.authRequired) return const pathname = (request.url.split("?")[0] ?? request.url) if (pathname === "/api/auth/config") return const authHeader = request.headers.authorization ?? "" const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "" if (!token) { return reply.code(401).send({ error: "Unauthorized" }) } try { await request.jwtVerify() } catch { return reply.code(401).send({ error: "Unauthorized" }) } const payload = request.user const apps = Array.isArray(payload.apps) ? payload.apps.map(String) : [] const permissions = Array.isArray(payload.permissions) ? payload.permissions.map(String) : [] if (!apps.includes("mm")) { return reply .code(403) .send({ error: "Нет доступа к приложению MikrotikManager" }) } request.authUser = { id: String(payload.sub), email: String(payload.email ?? ""), name: String(payload.name ?? ""), apps, permissions, isAdmin: Boolean(payload.is_admin), } const required = permissionForRequest(request.method, pathname) if (required && !hasPermission(permissions, required)) { return reply.code(403).send({ error: `Недостаточно прав: ${required}` }) } } export default fp(authPlugin, { name: "auth" })