import { RateLimiterMemory } from 'rate-limiter-flexible'; import type { MiddlewareHandler } from 'astro'; // Rate limiter configuration const rateLimiter = new RateLimiterMemory({ points: 10, // Number of requests duration: 60, // Per minute }); // Security headers configuration const securityHeaders = { 'Content-Security-Policy': ` default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; block-all-mixed-content; upgrade-insecure-requests; `.replace(/\s+/g, ' ').trim(), 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'Permissions-Policy': 'camera=(), microphone=(), geolocation=()', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', }; // CORS configuration const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400', }; export const onRequest: MiddlewareHandler = async ({ request, next }, response) => { // Handle preflight requests if (request.method === 'OPTIONS') { return new Response(null, { status: 204, headers: corsHeaders, }); } try { // Rate limiting const ip = request.headers.get('x-forwarded-for') || 'unknown'; await rateLimiter.consume(ip); // Add security headers Object.entries(securityHeaders).forEach(([key, value]) => { response.headers.set(key, value); }); // Add CORS headers Object.entries(corsHeaders).forEach(([key, value]) => { response.headers.set(key, value); }); // Add cache headers for static assets const url = new URL(request.url); if (url.pathname.match(/\.(jpg|jpeg|png|gif|ico|css|js)$/)) { response.headers.set('Cache-Control', 'public, max-age=31536000'); } else { response.headers.set('Cache-Control', 'no-cache'); } return next(); } catch (error) { // Handle rate limit exceeded if (error.name === 'RateLimiterError') { return new Response('Too Many Requests', { status: 429, headers: { 'Retry-After': String(Math.ceil(error.msBeforeNext / 1000)), ...corsHeaders, }, }); } // Handle other errors console.error('Middleware error:', error); return new Response('Internal Server Error', { status: 500, headers: corsHeaders, }); } };