import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import fp from 'fastify-plugin'; import { eq } from 'drizzle-orm'; import { verifyToken, isAccessPayload } from '../utils/jwt.js'; import { unauthorized, forbidden } from '../utils/errors.js'; import { users } from '../db/schema/users.js'; declare module 'fastify' { interface FastifyInstance { authenticate: (req: FastifyRequest, reply: FastifyReply) => Promise; authenticateAdmin: (req: FastifyRequest, reply: FastifyReply) => Promise; } interface FastifyRequest { user?: { id: string; email: string }; } } export async function authenticate(req: FastifyRequest, _reply: FastifyReply): Promise { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { throw unauthorized('Missing or invalid authorization header'); } const token = authHeader.slice(7); try { const payload = await verifyToken(token); if (!isAccessPayload(payload)) { throw unauthorized('Invalid token type'); } req.user = { id: payload.sub, email: payload.email }; } catch { throw unauthorized('Invalid or expired token'); } } export async function authenticateAdmin(req: FastifyRequest, _reply: FastifyReply): Promise { if (!req.user) { throw unauthorized('Authentication required'); } const [user] = await req.server.db.select({ role: users.role }).from(users).where(eq(users.id, req.user.id)); if (!user || user.role !== 'admin') { throw forbidden('Admin access required'); } } const authPlugin = async (app: FastifyInstance) => { app.decorateRequest('user', undefined); app.decorate('authenticate', authenticate); app.decorate('authenticateAdmin', authenticateAdmin); }; export default fp(authPlugin, { name: 'auth', dependencies: ['database'] });