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(); // 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; }