/** * Security middleware for Fastify */ import { FastifyInstance } from 'fastify'; import fastifyHelmet from '@fastify/helmet'; import fastifyRateLimit from '@fastify/rate-limit'; import fastifyCors from '@fastify/cors'; import { getEnv } from './env'; /** * Register security plugins on a Fastify instance */ export async function registerSecurityPlugins(server: FastifyInstance): Promise { const env = getEnv(); // Helmet for security headers await server.register(fastifyHelmet, { contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'"], fontSrc: ["'self'"], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, crossOriginEmbedderPolicy: false, }); // CORS const corsOrigins = env.CORS_ORIGIN ? env.CORS_ORIGIN.split(',').map((origin) => origin.trim()) : env.NODE_ENV === 'development' ? ['http://localhost:3000'] : []; await server.register(fastifyCors, { origin: corsOrigins, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], }); // Rate limiting await server.register(fastifyRateLimit, { max: 100, timeWindow: '1 minute', errorResponseBuilder: (_request, context) => { return { error: { code: 'RATE_LIMIT_EXCEEDED', message: `Rate limit exceeded, retry in ${Math.ceil(context.ttl / 1000)} seconds`, }, }; }, }); }