Import source: backend/src/middleware/rateLimit
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
import type { RequestHandler } from 'express';
|
||||||
|
import { HttpError } from './errorHandler.js';
|
||||||
|
|
||||||
|
const log = new Map<string, number[]>();
|
||||||
|
|
||||||
|
// Prune entries older than 1 hour every 10 minutes
|
||||||
|
setInterval(() => {
|
||||||
|
const cutoff = Date.now() - 3_600_000;
|
||||||
|
for (const [ip, ts] of log) {
|
||||||
|
const fresh = ts.filter((t) => t > cutoff);
|
||||||
|
if (fresh.length === 0) log.delete(ip);
|
||||||
|
else log.set(ip, fresh);
|
||||||
|
}
|
||||||
|
}, 600_000).unref();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sliding-window rate limiter — in-memory, per IP.
|
||||||
|
* maxRequests allowed within windowMs milliseconds.
|
||||||
|
*/
|
||||||
|
export function rateLimit(maxRequests: number, windowMs: number): RequestHandler {
|
||||||
|
return (req, res, next) => {
|
||||||
|
const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';
|
||||||
|
const now = Date.now();
|
||||||
|
const windowStart = now - windowMs;
|
||||||
|
|
||||||
|
const timestamps = (log.get(ip) ?? []).filter((t) => t > windowStart);
|
||||||
|
|
||||||
|
if (timestamps.length >= maxRequests) {
|
||||||
|
const retryAfterSecs = Math.ceil((timestamps[0] - windowStart) / 1000);
|
||||||
|
res.setHeader('Retry-After', String(retryAfterSecs));
|
||||||
|
return next(new HttpError(429, 'RATE_LIMITED', `Too many requests — retry after ${retryAfterSecs}s`));
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamps.push(now);
|
||||||
|
log.set(ip, timestamps);
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user