// Lightweight in-memory rate limiter for sensitive endpoints such as login. // Keeps POSHManager dependency-free for a single-process deployment. For a // multi-replica deployment behind a load balancer, replace the Map with a // shared store (Redis) so limits are enforced across instances. const buckets = new Map(); export function rateLimit({ windowMs = 60_000, max = 10, keyPrefix = 'rl' } = {}) { return function rateLimiter(req, res, next) { const ip = req.ip || req.socket?.remoteAddress || 'unknown'; const key = `${keyPrefix}:${ip}`; const nowTs = Date.now(); const entry = buckets.get(key); if (!entry || nowTs > entry.resetAt) { buckets.set(key, { count: 1, resetAt: nowTs + windowMs }); return next(); } entry.count += 1; if (entry.count > max) { const retryAfter = Math.max(1, Math.ceil((entry.resetAt - nowTs) / 1000)); res.setHeader('Retry-After', String(retryAfter)); return res.status(429).json({ error: 'Too many requests. Please slow down and try again shortly.' }); } return next(); }; } // Periodically drop expired buckets so the Map cannot grow unbounded. const sweep = setInterval(() => { const nowTs = Date.now(); for (const [key, entry] of buckets) { if (nowTs > entry.resetAt) buckets.delete(key); } }, 5 * 60_000); sweep.unref?.();