express-rate-limit 5.1.3 → 5.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +6 -7
- package/lib/express-rate-limit.js +97 -81
- package/package.json +10 -10
package/LICENSE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright
|
|
1
|
+
Copyright 2020 Nathan Friedly
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
4
|
|
package/README.md
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
# Express Rate Limit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
[](https://npmjs.org/package/express-rate-limit "View this project on NPM")
|
|
5
|
-
[](https://www.npmjs.com/package/express-rate-limit)
|
|
6
|
+
|
|
7
7
|
|
|
8
8
|
Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.
|
|
9
9
|
|
|
10
10
|
Plays nice with [express-slow-down](https://www.npmjs.com/package/express-slow-down).
|
|
11
11
|
|
|
12
|
-
Note: this module does not share state with other processes/servers by default.
|
|
13
|
-
If you need a more robust solution, I recommend using an external store:
|
|
12
|
+
Note: this module does not share state with other processes/servers by default. It also buckets all requests to an internal clock rather than starting a new timer for each end-user. It's fine for abuse-prevention but might not produce the desired effect when attempting to strictly enforce API rate-limits or similar. If you need a more robust solution, I recommend using an external store:
|
|
14
13
|
|
|
15
14
|
### Stores
|
|
16
15
|
|
|
17
|
-
- Memory Store _(default, built-in)_ - stores hits in-memory in the Node.js process. Does not share state with other servers or processes.
|
|
16
|
+
- Memory Store _(default, built-in)_ - stores hits in-memory in the Node.js process. Does not share state with other servers or processes, and does not start a separate timer for each end user.
|
|
18
17
|
- [Redis Store](https://npmjs.com/package/rate-limit-redis)
|
|
19
18
|
- [Memcached Store](https://npmjs.org/package/rate-limit-memcached)
|
|
20
19
|
- [Mongo Store](https://www.npmjs.com/package/rate-limit-mongo)
|
|
@@ -208,7 +207,7 @@ Defaults to `false`.
|
|
|
208
207
|
|
|
209
208
|
### skip
|
|
210
209
|
|
|
211
|
-
Function used to skip (whitelist) requests. Returning `true` from the function will skip limiting for that request.
|
|
210
|
+
Function used to skip (whitelist) requests. Returning `true`, or a promise that resolves with `true`, from the function will skip limiting for that request.
|
|
212
211
|
|
|
213
212
|
Defaults to always `false` (count all requests):
|
|
214
213
|
|
|
@@ -50,101 +50,117 @@ function RateLimit(options) {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
function rateLimit(req, res, next) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
Promise.resolve(options.skip(req, res))
|
|
54
|
+
.then((skip) => {
|
|
55
|
+
if (skip) {
|
|
56
|
+
return next();
|
|
57
|
+
}
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const maxResult =
|
|
65
|
-
typeof options.max === "function" ? options.max(req, res) : options.max;
|
|
66
|
-
|
|
67
|
-
Promise.resolve(maxResult)
|
|
68
|
-
.then((max) => {
|
|
69
|
-
req.rateLimit = {
|
|
70
|
-
limit: max,
|
|
71
|
-
current: current,
|
|
72
|
-
remaining: Math.max(max - current, 0),
|
|
73
|
-
resetTime: resetTime,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
if (options.headers && !res.headersSent) {
|
|
77
|
-
res.setHeader("X-RateLimit-Limit", max);
|
|
78
|
-
res.setHeader("X-RateLimit-Remaining", req.rateLimit.remaining);
|
|
79
|
-
if (resetTime instanceof Date) {
|
|
80
|
-
// if we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
|
|
81
|
-
res.setHeader("Date", new Date().toGMTString());
|
|
82
|
-
res.setHeader(
|
|
83
|
-
"X-RateLimit-Reset",
|
|
84
|
-
Math.ceil(resetTime.getTime() / 1000)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (options.draft_polli_ratelimit_headers && !res.headersSent) {
|
|
89
|
-
res.setHeader("RateLimit-Limit", max);
|
|
90
|
-
res.setHeader("RateLimit-Remaining", req.rateLimit.remaining);
|
|
91
|
-
if (resetTime) {
|
|
92
|
-
const deltaSeconds = Math.ceil(
|
|
93
|
-
(resetTime.getTime() - Date.now()) / 1000
|
|
94
|
-
);
|
|
95
|
-
res.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
96
|
-
}
|
|
59
|
+
const key = options.keyGenerator(req, res);
|
|
60
|
+
|
|
61
|
+
options.store.incr(key, function (err, current, resetTime) {
|
|
62
|
+
if (err) {
|
|
63
|
+
return next(err);
|
|
97
64
|
}
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
66
|
+
const maxResult =
|
|
67
|
+
typeof options.max === "function"
|
|
68
|
+
? options.max(req, res)
|
|
69
|
+
: options.max;
|
|
70
|
+
|
|
71
|
+
Promise.resolve(maxResult)
|
|
72
|
+
.then((max) => {
|
|
73
|
+
req.rateLimit = {
|
|
74
|
+
limit: max,
|
|
75
|
+
current: current,
|
|
76
|
+
remaining: Math.max(max - current, 0),
|
|
77
|
+
resetTime: resetTime,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (options.headers && !res.headersSent) {
|
|
81
|
+
res.setHeader("X-RateLimit-Limit", max);
|
|
82
|
+
res.setHeader("X-RateLimit-Remaining", req.rateLimit.remaining);
|
|
83
|
+
if (resetTime instanceof Date) {
|
|
84
|
+
// if we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
|
|
85
|
+
res.setHeader("Date", new Date().toGMTString());
|
|
86
|
+
res.setHeader(
|
|
87
|
+
"X-RateLimit-Reset",
|
|
88
|
+
Math.ceil(resetTime.getTime() / 1000)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (options.draft_polli_ratelimit_headers && !res.headersSent) {
|
|
93
|
+
res.setHeader("RateLimit-Limit", max);
|
|
94
|
+
res.setHeader("RateLimit-Remaining", req.rateLimit.remaining);
|
|
95
|
+
if (resetTime) {
|
|
96
|
+
const deltaSeconds = Math.ceil(
|
|
97
|
+
(resetTime.getTime() - Date.now()) / 1000
|
|
98
|
+
);
|
|
99
|
+
res.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
100
|
+
}
|
|
105
101
|
}
|
|
106
|
-
};
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
if (
|
|
104
|
+
options.skipFailedRequests ||
|
|
105
|
+
options.skipSuccessfulRequests
|
|
106
|
+
) {
|
|
107
|
+
let decremented = false;
|
|
108
|
+
const decrementKey = () => {
|
|
109
|
+
if (!decremented) {
|
|
110
|
+
options.store.decrement(key);
|
|
111
|
+
decremented = true;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (options.skipFailedRequests) {
|
|
116
|
+
res.on("finish", function () {
|
|
117
|
+
if (res.statusCode >= 400) {
|
|
118
|
+
decrementKey();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
res.on("close", () => {
|
|
123
|
+
if (!res.finished) {
|
|
124
|
+
decrementKey();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
res.on("error", () => decrementKey());
|
|
112
129
|
}
|
|
113
|
-
});
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
if (options.skipSuccessfulRequests) {
|
|
132
|
+
res.on("finish", function () {
|
|
133
|
+
if (res.statusCode < 400) {
|
|
134
|
+
options.store.decrement(key);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
118
137
|
}
|
|
119
|
-
}
|
|
138
|
+
}
|
|
120
139
|
|
|
121
|
-
|
|
122
|
-
|
|
140
|
+
if (max && current === max + 1) {
|
|
141
|
+
options.onLimitReached(req, res, options);
|
|
142
|
+
}
|
|
123
143
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
if (max && current > max) {
|
|
145
|
+
if (options.headers && !res.headersSent) {
|
|
146
|
+
res.setHeader(
|
|
147
|
+
"Retry-After",
|
|
148
|
+
Math.ceil(options.windowMs / 1000)
|
|
149
|
+
);
|
|
128
150
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
151
|
+
return options.handler(req, res, next);
|
|
152
|
+
}
|
|
132
153
|
|
|
133
|
-
|
|
134
|
-
options.onLimitReached(req, res, options);
|
|
135
|
-
}
|
|
154
|
+
next();
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return options.handler(req, res, next);
|
|
142
|
-
}
|
|
156
|
+
return null;
|
|
157
|
+
})
|
|
158
|
+
.catch(next);
|
|
159
|
+
});
|
|
143
160
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
161
|
+
return null;
|
|
162
|
+
})
|
|
163
|
+
.catch(next);
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
rateLimit.resetKey = options.store.resetKey.bind(options.store);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.5",
|
|
4
4
|
"description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
|
|
5
5
|
"homepage": "https://github.com/nfriedly/express-rate-limit",
|
|
6
6
|
"author": {
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
"brute-force",
|
|
32
32
|
"attack"
|
|
33
33
|
],
|
|
34
|
-
"dependencies": {},
|
|
35
34
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"eslint
|
|
38
|
-
"eslint-
|
|
35
|
+
"bluebird": "^3.7.2",
|
|
36
|
+
"eslint": "^7.19.0",
|
|
37
|
+
"eslint-config-prettier": "^7.2.0",
|
|
38
|
+
"eslint-plugin-prettier": "^3.3.1",
|
|
39
39
|
"express": "^4.17.1",
|
|
40
|
-
"husky": "^4.
|
|
41
|
-
"mocha": "^
|
|
42
|
-
"prettier": "^2.
|
|
43
|
-
"pretty-quick": "^
|
|
44
|
-
"supertest": "^
|
|
40
|
+
"husky": "^4.3.8",
|
|
41
|
+
"mocha": "^8.2.1",
|
|
42
|
+
"prettier": "^2.2.1",
|
|
43
|
+
"pretty-quick": "^3.1.0",
|
|
44
|
+
"supertest": "^6.1.3"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"lint": "eslint .",
|