import { NextResponse } from "next/server"; interface RateLimitStore { [key: string]: { count: number; resetTime: number; }; } const store: RateLimitStore = {}; interface RateLimitConfig { interval: number; // Time window in milliseconds maxRequests: number; // Maximum requests in time window } /** * Simple in-memory rate limiter * For production, use Redis or a dedicated rate limiting service * * @param identifier - Unique identifier (e.g., IP address, user ID) * @param config - Rate limit configuration * @returns NextResponse error if rate limited, undefined if allowed * * @example * const rateLimitError = rateLimit(userId, { interval: 60000, maxRequests: 5 }); * if (rateLimitError) return rateLimitError; */ export function rateLimit( identifier: string, config: RateLimitConfig, ): NextResponse | undefined { const now = Date.now(); const key = identifier; // Initialize or reset if window expired if (!store[key] || now > store[key].resetTime) { store[key] = { count: 1, resetTime: now + config.interval, }; return undefined; } // Increment count store[key].count++; // Check if limit exceeded if (store[key].count > config.maxRequests) { const retryAfter = Math.ceil((store[key].resetTime - now) / 1000); return NextResponse.json( { error: "Too many requests", message: `Rate limit exceeded. Try again in ${retryAfter} seconds.`, retryAfter, }, { status: 429, headers: { "Retry-After": retryAfter.toString(), "X-RateLimit-Limit": config.maxRequests.toString(), "X-RateLimit-Remaining": "0", "X-RateLimit-Reset": store[key].resetTime.toString(), }, }, ); } return undefined; } /** * Rate limit configurations for different endpoints */ export const RATE_LIMITS = { // Authentication endpoints - stricter limits login: { interval: 60000, maxRequests: 5 }, // 5 attempts per minute register: { interval: 3600000, maxRequests: 3 }, // 3 attempts per hour passwordReset: { interval: 3600000, maxRequests: 3 }, // 3 attempts per hour // API endpoints - more lenient api: { interval: 60000, maxRequests: 100 }, // 100 requests per minute apiStrict: { interval: 60000, maxRequests: 10 }, // 10 requests per minute } as const; /** * Get client identifier for rate limiting * Uses IP address as fallback if user is not authenticated * * @param request - NextRequest object * @param userId - Optional authenticated user ID * @returns Identifier for rate limiting */ export function getClientIdentifier(request: Request, userId?: string): string { if (userId) return `user:${userId}`; // Try to get IP from headers (works with most reverse proxies) const forwarded = request.headers.get("x-forwarded-for"); const ip = forwarded?.split(",")[0] ?? request.headers.get("x-real-ip") ?? "unknown"; return `ip:${ip}`; } /** * Cleanup expired rate limit entries (call periodically) */ export function cleanupRateLimitStore(): void { const now = Date.now(); for (const key in store) { if (store[key].resetTime < now) { delete store[key]; } } } // Cleanup every 10 minutes if (typeof setInterval !== "undefined") { setInterval(cleanupRateLimitStore, 10 * 60 * 1000); }