43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import type { NextRequest } from 'next/server';
|
|
|
|
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
|
|
const MAX_REQUESTS = 100; // 100 requests per window
|
|
const CLEANUP_THRESHOLD = 500;
|
|
|
|
const ipMap = new Map<string, { count: number; expires: number }>();
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const forwarded = request.headers.get('x-forwarded-for');
|
|
const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
|
|
const now = Date.now();
|
|
|
|
console.log(`[${new Date().toISOString()}] Request to ${request.nextUrl.pathname} from ${ip}`);
|
|
|
|
const record = ipMap.get(ip);
|
|
|
|
if (!record || now > record.expires) {
|
|
ipMap.set(ip, { count: 1, expires: now + RATE_LIMIT_WINDOW });
|
|
} else {
|
|
record.count++;
|
|
if (record.count > MAX_REQUESTS) {
|
|
return new NextResponse('Too Many Requests', { status: 429 });
|
|
}
|
|
}
|
|
|
|
// Cleanup old entries probabilistically (1-in-10 requests)
|
|
if (ipMap.size > CLEANUP_THRESHOLD && Math.random() < 0.1) {
|
|
for (const [key, val] of ipMap.entries()) {
|
|
if (now > val.expires) {
|
|
ipMap.delete(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)'],
|
|
};
|