-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Expand file tree
/
Copy pathrate-limit.js
More file actions
94 lines (79 loc) · 2.07 KB
/
Copy pathrate-limit.js
File metadata and controls
94 lines (79 loc) · 2.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { isCI } from "../config.js";
/**
* In-memory sliding window rate limiter for authentication endpoints
*/
const ipRequests = new Map();
// Clean up old entries every 2 minutes
const cleanupTimer = setInterval(() => {
const now = Date.now();
for (const [ip, record] of ipRequests.entries()) {
if (now - record.startTime > record.windowMs * 2) {
ipRequests.delete(ip);
}
}
}, 120000);
cleanupTimer.unref();
export default function createRateLimiter(options = {}) {
const windowMs = options.windowMs || 60 * 1000; // 1 minute default
const max = options.max || 10; // 10 requests default
const message = options.message || "Too many attempts from this IP, please try again later.";
const skipSuccessfulRequests = options.skipSuccessfulRequests !== false;
return (req, res, next) => {
// Bypass rate limiting in CI, test environments, or when explicitly disabled
if (
isCI() ||
process.env.CI === "true" ||
process.env.NODE_ENV === "test" ||
process.env.DISABLE_RATE_LIMIT === "true"
) {
return next();
}
const clientIp =
req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
req.socket?.remoteAddress ||
req.ip ||
"unknown";
const now = Date.now();
let record = ipRequests.get(clientIp);
if (!record || now - record.startTime > windowMs) {
record = {
count: 1,
startTime: now,
windowMs,
};
ipRequests.set(clientIp, record);
if (skipSuccessfulRequests) {
res.on("finish", () => {
if (res.statusCode < 400) {
const cur = ipRequests.get(clientIp);
if (cur && cur.count > 0) {
cur.count--;
}
}
});
}
return next();
}
record.count++;
if (skipSuccessfulRequests) {
res.on("finish", () => {
if (res.statusCode < 400) {
const cur = ipRequests.get(clientIp);
if (cur && cur.count > 0) {
cur.count--;
}
}
});
}
if (record.count > max) {
res.setHeader("Retry-After", Math.ceil((record.startTime + windowMs - now) / 1000));
return res.status(429).json({
error: {
code: 429,
message,
},
});
}
next();
};
}