/** * Rate Limiting Middleware * Agent 4: Production Deployment * * Provides rate limiting for API routes to prevent abuse. */ import type { NextApiRequest, NextApiResponse, NextApiHandler } from 'next'; import { env } from '../config'; import { logger } from '../logging'; interface RateLimitConfig { windowMs: number; // Time window in milliseconds maxRequests: number; // Max requests per window keyGenerator?: (req: NextApiRequest) => string; skipSuccessfulRequests?: boolean; skipFailedRequests?: boolean; message?: string; } interface RateLimitEntry { count: number; resetTime: number; } // In-memory store for rate limiting // In production, use Redis for distributed rate limiting const store = new Map(); // Cleanup old entries periodically setInterval(() => { const now = Date.now(); for (const [key, entry] of store.entries()) { if (entry.resetTime < now) { store.delete(key); } } }, 60000); // Cleanup every minute const DEFAULT_CONFIG: RateLimitConfig = { windowMs: env.rateLimitWindowMs, maxRequests: env.rateLimitMaxRequests, message: 'Too many requests, please try again later.', }; /** * Default key generator - uses IP address */ function defaultKeyGenerator(req: NextApiRequest): string { const forwarded = req.headers['x-forwarded-for']; if (typeof forwarded === 'string') { return forwarded.split(',')[0].trim(); } if (Array.isArray(forwarded)) { return forwarded[0]; } return req.socket?.remoteAddress || 'unknown'; } /** * Check if request is rate limited */ function checkRateLimit( key: string, config: RateLimitConfig ): { limited: boolean; remaining: number; resetTime: number } { const now = Date.now(); let entry = store.get(key); // Create new entry if doesn't exist or expired if (!entry || entry.resetTime < now) { entry = { count: 0, resetTime: now + config.windowMs, }; store.set(key, entry); } // Increment count entry.count += 1; const remaining = Math.max(0, config.maxRequests - entry.count); const limited = entry.count > config.maxRequests; return { limited, remaining, resetTime: entry.resetTime }; } /** * Rate limiting middleware */ export function withRateLimit( handler: NextApiHandler, config: Partial = {} ): NextApiHandler { const mergedConfig: RateLimitConfig = { ...DEFAULT_CONFIG, ...config }; const keyGenerator = mergedConfig.keyGenerator || defaultKeyGenerator; return async (req: NextApiRequest, res: NextApiResponse) => { const key = keyGenerator(req); const { limited, remaining, resetTime } = checkRateLimit(key, mergedConfig); // Set rate limit headers res.setHeader('X-RateLimit-Limit', mergedConfig.maxRequests); res.setHeader('X-RateLimit-Remaining', remaining); res.setHeader('X-RateLimit-Reset', Math.ceil(resetTime / 1000)); if (limited) { logger.warn('Rate limit exceeded', { ip: key, path: req.url, method: req.method, }); res.setHeader('Retry-After', Math.ceil((resetTime - Date.now()) / 1000)); return res.status(429).json({ error: 'Too Many Requests', message: mergedConfig.message, retryAfter: Math.ceil((resetTime - Date.now()) / 1000), }); } return handler(req, res); }; } /** * Create a rate limiter with custom settings */ export function createRateLimiter(config: Partial) { return (handler: NextApiHandler) => withRateLimit(handler, config); } /** * Stricter rate limiter for authentication endpoints */ export const authRateLimiter = createRateLimiter({ windowMs: 15 * 60 * 1000, // 15 minutes maxRequests: 5, // 5 attempts per window message: 'Too many authentication attempts. Please try again in 15 minutes.', }); /** * API rate limiter for general endpoints */ export const apiRateLimiter = createRateLimiter({ windowMs: 60 * 1000, // 1 minute maxRequests: 60, // 60 requests per minute }); // Export types export type { RateLimitConfig, RateLimitEntry };