/** * auth.plugin.ts * * Registra o @fastify/jwt e expõe dois decoradores: * fastify.authenticate → rejeita requests sem token válido (401) * fastify.optionalAuthenticate → injeta o usuário se houver token, mas não falha * * Uso nas rotas: * { preHandler: [fastify.authenticate] } */ import fp from 'fastify-plugin'; import jwt from '@fastify/jwt'; import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; // ─── Payload do token ───────────────────────────────────────────────────────── export interface JwtPayload { id: number; email: string; role: 'requester' | 'agent' | 'admin'; } // ─── Augmentação de tipos Fastify ───────────────────────────────────────────── declare module 'fastify' { interface FastifyInstance { authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise; optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply) => Promise; } interface FastifyRequest { user: JwtPayload; } } declare module '@fastify/jwt' { interface FastifyJWT { payload: JwtPayload; user: JwtPayload; } } // ─── Plugin ─────────────────────────────────────────────────────────────────── async function authPlugin(fastify: FastifyInstance): Promise { const secret = process.env['JWT_SECRET']; if (!secret) { throw new Error('JWT_SECRET não definido. Verifique o arquivo .env'); } await fastify.register(jwt, { secret, sign: { expiresIn: process.env['JWT_EXPIRES_IN'] ?? '7d', }, }); // ── authenticate ──────────────────────────────────────────────────────────── // Exige token válido — retorna 401 se ausente ou expirado fastify.decorate( 'authenticate', async (request: FastifyRequest, reply: FastifyReply) => { try { // Suporte para SSE (EventSource não envia headers, envia via query string) const query = request.query as { token?: string }; if (query.token && !request.headers.authorization) { request.headers.authorization = `Bearer ${query.token}`; } await request.jwtVerify(); } catch { await reply.unauthorized('Token inválido ou expirado. Faça login novamente.'); } }, ); // ── optionalAuthenticate ──────────────────────────────────────────────────── // Tenta verificar o token mas não falha se ausente fastify.decorate( 'optionalAuthenticate', async (request: FastifyRequest, _reply: FastifyReply) => { try { await request.jwtVerify(); } catch { // Sem token → request.user fica undefined — trate no controller } }, ); } export default fp(authPlugin, { name: 'auth-plugin', fastify: '4.x', });