Files
hoerdle/lib/rateLimit.ts

77 lines
2.2 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
/**
* Rate limiting configuration
* Simple in-memory rate limiter for API endpoints
*/
interface RateLimitEntry {
count: number;
resetTime: number;
}
const rateLimitMap = new Map<string, RateLimitEntry>();
// Clean up old entries every 5 minutes
setInterval(() => {
const now = Date.now();
for (const [key, entry] of rateLimitMap.entries()) {
if (now > entry.resetTime) {
rateLimitMap.delete(key);
}
}
}, 5 * 60 * 1000);
export interface RateLimitConfig {
windowMs: number; // Time window in milliseconds
maxRequests: number; // Maximum requests per window
}
/**
* Rate limiting middleware
* @param request - The incoming request
* @param config - Rate limit configuration
* @returns NextResponse with 429 status if rate limit exceeded, null otherwise
*/
export function rateLimit(
request: NextRequest,
config: RateLimitConfig = { windowMs: 60000, maxRequests: 100 }
): NextResponse | null {
// Get client identifier (IP address or fallback)
const identifier =
request.headers.get('x-forwarded-for')?.split(',')[0] ||
request.headers.get('x-real-ip') ||
'unknown';
const now = Date.now();
const entry = rateLimitMap.get(identifier);
if (!entry || now > entry.resetTime) {
// Create new entry or reset expired entry
rateLimitMap.set(identifier, {
count: 1,
resetTime: now + config.windowMs
});
return null;
}
if (entry.count >= config.maxRequests) {
const retryAfter = Math.ceil((entry.resetTime - now) / 1000);
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
{
status: 429,
headers: {
'Retry-After': retryAfter.toString(),
'X-RateLimit-Limit': config.maxRequests.toString(),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': new Date(entry.resetTime).toISOString()
}
}
);
}
// Increment counter
entry.count++;
return null;
}