/** * Rate Limiter Middleware * Provides request rate limiting using Redis */ import { Request, Response, NextFunction } from 'express'; import { RedisCache } from '../cache/redis-cache'; export interface RateLimitOptions { maxRequests: number; windowMs: number; message: string; statusCode: number; keyGenerator: (req: Request) => string; } /** * Create rate limiter middleware * * @param cache - Redis cache instance * @param options - Rate limit options * @returns Express middleware */ export function rateLimiter( cache: RedisCache, options?: Partial ) { const { maxRequests = 100, windowMs = 60000, // 1 minute message = 'Too many requests, please try again later', statusCode = 429, keyGenerator = (req: Request) => req.ip || 'unknown' } = options || {}; return async (req: Request, res: Response, next: NextFunction) => { try { const key = `rate-limit:${keyGenerator(req)}`; const count = await cache.get(key) || 0; if (count >= maxRequests) { const ttl = await cache.getTTL(key); res.set('X-RateLimit-Limit', maxRequests.toString()); res.set('X-RateLimit-Remaining', '0'); res.set('X-RateLimit-Reset', Math.ceil(Date.now() + ttl).toString()); return res.status(statusCode).json({ error: message, retryAfter: Math.ceil(ttl / 1000) }); } // Increment counter await cache.set(key, count + 1, windowMs); // Set rate limit headers res.set('X-RateLimit-Limit', maxRequests.toString()); res.set('X-RateLimit-Remaining', (maxRequests - count - 1).toString()); res.set('X-RateLimit-Reset', Math.ceil(Date.now() + windowMs).toString()); next(); } catch (error) { console.error('Rate limiter error:', error); // Fail open - allow request if cache fails next(); } }; } /** * Create API-specific rate limiter * Different limits for different endpoints */ export function apiRateLimiter( cache: RedisCache, limits: { default: { maxRequests: number; windowMs: number }; strict?: { maxRequests: number; windowMs: number }; relaxed?: { maxRequests: number; windowMs: number }; } ) { return (tier: 'default' | 'strict' | 'relaxed' = 'default') => { const limit = limits[tier] || limits.default; return rateLimiter(cache, { maxRequests: limit.maxRequests, windowMs: limit.windowMs, keyGenerator: (req: Request) => `${req.ip}:${req.path}` }); }; } /** * Create user-based rate limiter * Uses user ID instead of IP */ export function userRateLimiter(cache: RedisCache, maxRequests: number = 1000, windowMs: number = 60000) { return rateLimiter(cache, { maxRequests, windowMs, keyGenerator: (req: Request) => { // Try to get user ID from various sources const userId = (req as any).user?.id || req.headers['x-user-id'] || req.query.userId || 'anonymous'; return `user:${userId}`; } }); } export default { rateLimiter, apiRateLimiter, userRateLimiter };