/* Simple in-memory rate limiter — no external dependency needed */ // Clean stale entries every 5 minutes across all stores const _allStores = new Set(); setInterval(() => { const now = Date.now(); for (const store of _allStores) { for (const [key, entry] of store) { if (now > entry.resetAt) store.delete(key); } } }, 5 * 60 * 1000).unref(); module.exports = function rateLimit({ windowMs = 60_000, max = 10, message = 'Too many requests, please try again later', byUser = false } = {}) { // Skip rate limiting in test environment if (process.env.NODE_ENV === 'test') return (_req, _res, next) => next(); // Each rateLimit() call gets its own isolated store — counters don't bleed between limiters const store = new Map(); _allStores.add(store); return (req, res, next) => { // Per-user key (when authenticated and byUser=true) avoids penalising users who share a NAT; // otherwise fall back to IP. trust proxy is set in server.js so req.ip honors X-Forwarded-For. const key = (byUser && req.user?.id != null) ? `u:${req.user.id}` : (req.ip || req.socket?.remoteAddress || 'unknown'); const now = Date.now(); let entry = store.get(key); if (!entry || now > entry.resetAt) { entry = { count: 0, resetAt: now + windowMs }; } entry.count++; store.set(key, entry); if (entry.count > max) { const retryAfter = Math.ceil((entry.resetAt - now) / 1000); res.set('Retry-After', retryAfter); return res.status(429).json({ error: message }); } next(); }; };