/** * Rate limiting for Next.js API routes * Uses in-memory store (for production, use Redis) */ interface RateLimitEntry { count: number; resetTime: number; } // In-memory store for rate limiting const rateLimitStore = new Map(); // Cleanup old entries every 5 minutes setInterval(() => { const now = Date.now(); for (const [key, entry] of rateLimitStore.entries()) { if (entry.resetTime < now) { rateLimitStore.delete(key); } } }, 5 * 60 * 1000); interface RateLimitConfig { windowMs: number; // Time window in milliseconds max: number; // Max requests per window message: { error: string; message: string; retryAfter: number; // seconds }; keyGenerator?: (request: Request) => string; skipSuccessfulRequests?: boolean; } /** * Creates a rate limiter middleware */ export function createRateLimiter(config: RateLimitConfig) { return async (request: Request): Promise => { const now = Date.now(); const key = config.keyGenerator ? config.keyGenerator(request) : request.headers.get('x-forwarded-for') || 'unknown'; const identifier = `${key}:${request.url}`; const entry = rateLimitStore.get(identifier); if (!entry || entry.resetTime < now) { // Create new entry rateLimitStore.set(identifier, { count: 1, resetTime: now + config.windowMs, }); return null; // Allow request } if (entry.count >= config.max) { // Rate limit exceeded const retryAfter = Math.ceil((entry.resetTime - now) / 1000); return new Response( JSON.stringify({ ...config.message, retryAfter, }), { status: 429, headers: { 'Content-Type': 'application/json', 'RateLimit-Limit': config.max.toString(), 'RateLimit-Remaining': '0', 'RateLimit-Reset': entry.resetTime.toString(), 'Retry-After': retryAfter.toString(), }, } ); } // Increment count entry.count += 1; rateLimitStore.set(identifier, entry); return null; // Allow request }; } /** * Strict rate limit for authentication endpoints (login, register, password reset) */ export const authLimiter = createRateLimiter({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts per 15 minutes message: { error: 'AUTH_RATE_LIMIT_EXCEEDED', message: 'Too many authentication attempts. Please try again later.', retryAfter: 15 * 60, }, }); /** * Moderate rate limit for AI assistant endpoints */ export const aiLimiter = createRateLimiter({ windowMs: 60 * 60 * 1000, // 1 hour max: 10, // 10 queries per hour message: { error: 'AI_RATE_LIMIT_EXCEEDED', message: 'You have reached your AI assistant query limit. Please try again later or upgrade to premium.', retryAfter: 60 * 60, }, keyGenerator: (req) => { // TODO: Use user ID from session/token when available return req.headers.get('x-forwarded-for') || 'unknown'; }, }); /** * General rate limit for tracking endpoints (feeding, sleep, etc.) */ export const trackingLimiter = createRateLimiter({ windowMs: 1 * 60 * 1000, // 1 minute max: 30, // 30 requests per minute message: { error: 'TRACKING_RATE_LIMIT_EXCEEDED', message: 'Too many tracking requests. Please slow down.', retryAfter: 60, }, }); /** * Lenient rate limit for read-only endpoints (GET requests) */ export const readLimiter = createRateLimiter({ windowMs: 1 * 60 * 1000, // 1 minute max: 100, // 100 requests per minute message: { error: 'READ_RATE_LIMIT_EXCEEDED', message: 'Too many requests. Please slow down.', retryAfter: 60, }, }); /** * Very strict rate limit for sensitive operations (delete account, etc.) */ export const sensitiveLimiter = createRateLimiter({ windowMs: 60 * 60 * 1000, // 1 hour max: 3, // Only 3 sensitive operations per hour message: { error: 'SENSITIVE_RATE_LIMIT_EXCEEDED', message: 'Too many sensitive operations. Please try again later.', retryAfter: 60 * 60, }, }); /** * Helper to apply rate limiter to Next.js API routes * * Usage: * ```typescript * import { authLimiter } from '@/lib/middleware/rateLimiter'; * * export async function POST(request: Request) { * const rateLimitResult = await authLimiter(request); * if (rateLimitResult) return rateLimitResult; // Return 429 response * * // Continue with normal request handling * } * ``` */