93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
};
|