import fastifyJwt from '@fastify/jwt'; import type { FastifyPluginAsync, preHandlerHookHandler } from 'fastify'; import fastifyPlugin from 'fastify-plugin'; import { config } from '../config.js'; import { pool } from '../db/pool.js'; export type UserRole = 'student' | 'instructor' | 'admin'; export interface AuthUser { id: string; email: string; role: UserRole; name: string; } declare module '@fastify/jwt' { interface FastifyJWT { payload: AuthUser; user: AuthUser; } } declare module 'fastify' { interface FastifyInstance { authenticate: preHandlerHookHandler; requireRoles: (roles: UserRole[]) => preHandlerHookHandler; } } const registerAuth: FastifyPluginAsync = async (app) => { await app.register(fastifyJwt, { secret: config.JWT_SECRET }); const verifyActiveUser = async (request: { jwtVerify: () => Promise; user: AuthUser }, reply: { code: (status: number) => { send: (payload: object) => unknown } }) => { try { await request.jwtVerify(); } catch { reply.code(401).send({ error: 'Autenticação necessária.' }); return false; } const result = await pool.query<{ role: UserRole; display_name: string }>( 'select role, display_name from users where id = $1 and is_active = true', [request.user.id], ); const account = result.rows[0]; if (!account) { reply.code(401).send({ error: 'Esta conta não está mais ativa.' }); return false; } request.user.role = account.role; request.user.name = account.display_name; return true; }; app.decorate('authenticate', async (request, reply) => { await verifyActiveUser(request, reply); }); app.decorate('requireRoles', (roles: UserRole[]) => async (request, reply) => { if (!(await verifyActiveUser(request, reply))) return; if (!roles.includes(request.user.role)) { return reply.code(403).send({ error: 'Você não tem permissão para esta ação.' }); } }); }; export const authPlugin = fastifyPlugin(registerAuth, { name: 'auth' });