/*********************************************************************************************************************************************************************** * File Name: rateLimiter.middleware.js * Type of Program: Middleware * Description: Express-rate-limit configurations for different route tiers. * - globalLimiter → applied to ALL routes (1000 req / 15 min) * - authLimiter → applied to login/register (20 req / 15 min) * - otpLimiter → applied to OTP send/verify (5 req / 15 min) * - sensitiveOpsLimiter → password change, account delete (10 req / hour) * - adminLimiter → admin routes (200 req / 15 min) * * Store selection is driven by CACHE_DRIVER: * redis → RedisStore (shared across processes; required in production) * memory → in-process MemoryStore (fine for local dev, single process) * Author: rgrgogu * Date Created: Oct. 6, 2025 * Date Modified: Jun. 19, 2026 *********************************************************************************************************************************************************************** * HOW TO USE: * const { authLimiter } = require('../middleware/rateLimiter.middleware'); * router.post('/login', authLimiter, loginHandler); ***********************************************************************************************************************************************************************/ const rateLimit = require('express-rate-limit'); const redisClient = require('../config/redis.config'); const windowMs15 = 15 * 60 * 1000; // 15 minutes const makeStore = redisClient ? (() => { const { RedisStore } = require('rate-limit-redis'); return (prefix) => new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args), prefix: `rl:${prefix}:`, }); })() : () => undefined; // undefined → express-rate-limit uses its default MemoryStore /** Applied globally in server.js */ const globalLimiter = rateLimit({ windowMs: windowMs15, max: 1000, standardHeaders: true, legacyHeaders: false, store: makeStore('global'), message: { status: 'error', message: 'Too many requests, please try again later.' }, }); /** Login & register routes */ const authLimiter = rateLimit({ windowMs: windowMs15, max: 20, standardHeaders: true, legacyHeaders: false, store: makeStore('auth'), message: { status: 'error', message: 'Too many auth attempts. Please wait 15 minutes.' }, }); /** OTP send / verify */ const otpLimiter = rateLimit({ windowMs: windowMs15, max: 5, standardHeaders: true, legacyHeaders: false, store: makeStore('otp'), message: { status: 'error', message: 'Too many OTP requests. Please wait 15 minutes.' }, }); /** * Sensitive write operations — bulk actions, uploads, financial transactions. * Keys by authenticated user ID when available, falls back to IP. * Keying by IP alone is unfair on shared networks (office NAT, university Wi-Fi) * where one user triggering the limit would block everyone behind the same IP. */ const sensitiveOpsLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 10, keyGenerator: (req) => req.user?.user_id?.toString() ?? req.ip, standardHeaders: true, legacyHeaders: false, store: makeStore('sensitive'), message: { status: 'error', message: 'Too many sensitive operations. Please wait 1 hour.' }, }); /** Admin routes — tighter than global */ const adminLimiter = rateLimit({ windowMs: windowMs15, max: 200, standardHeaders: true, legacyHeaders: false, store: makeStore('admin'), message: { status: 'error', message: 'Too many admin requests. Please wait 15 minutes.' }, }); module.exports = { globalLimiter, authLimiter, otpLimiter, sensitiveOpsLimiter, adminLimiter };