feat: Implement rate limiting, request timeouts, and security headers for improved server protection
This commit is contained in:
@@ -5,16 +5,25 @@ import { apiError } from "./errors.js";
|
||||
* conversion and seconds of CPU, so one client must not be able to occupy the server.
|
||||
*/
|
||||
|
||||
/** At most `max` requests per client address in each `windowMs`; the rest get 429. */
|
||||
export function rateLimit({ max, windowMs, message }) {
|
||||
/**
|
||||
* At most `max` requests per client address in each `windowMs`; the rest get 429.
|
||||
* `skip(req)` exempts a request, such as a health check, from counting.
|
||||
*/
|
||||
export function rateLimit({ max, windowMs, message, skip = () => false }) {
|
||||
const windows = new Map();
|
||||
let swept = 0;
|
||||
return (req, res, next) => {
|
||||
if (skip(req)) return next();
|
||||
const now = Date.now();
|
||||
// Finished windows are dropped on the way, so the map holds only active clients.
|
||||
for (const [key, w] of windows) if (now - w.start >= windowMs) windows.delete(key);
|
||||
const w = windows.get(req.ip) ?? { start: now, count: 0 };
|
||||
// Finished windows are dropped, at most once a second, so the map holds only active clients
|
||||
// and a crowd of addresses does not make every request walk the whole map.
|
||||
if (now - swept >= 1000) {
|
||||
swept = now;
|
||||
for (const [key, w] of windows) if (now - w.start >= windowMs) windows.delete(key);
|
||||
}
|
||||
let w = windows.get(req.ip);
|
||||
if (!w || now - w.start >= windowMs) windows.set(req.ip, (w = { start: now, count: 0 }));
|
||||
w.count++;
|
||||
windows.set(req.ip, w);
|
||||
if (w.count <= max) return next();
|
||||
res.set("Retry-After", String(Math.ceil((w.start + windowMs - now) / 1000)));
|
||||
res.status(429).json({ error: message, code: "too_many_requests" });
|
||||
|
||||
Reference in New Issue
Block a user