Initial
This commit is contained in:
37
server/middleware/rateLimit.js
Normal file
37
server/middleware/rateLimit.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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?.();
|
||||
Reference in New Issue
Block a user