express-rate-limit 5.2.3 → 5.4.0
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/README.md +50 -6
- package/lib/express-rate-limit.js +23 -8
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Express Rate Limit
|
|
2
2
|
|
|
3
|
-

|
|
4
|
-
[](https://github.com/nfriedly/express-rate-limit/actions)
|
|
4
|
+
[](https://npmjs.org/package/express-rate-limit "View this project on NPM")
|
|
5
5
|
[](https://www.npmjs.com/package/express-rate-limit)
|
|
6
6
|
|
|
7
7
|
|
|
@@ -9,12 +9,11 @@ Basic rate-limiting middleware for Express. Use to limit repeated requests to pu
|
|
|
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)
|
|
@@ -103,6 +102,8 @@ app.post("/create-account", createAccountLimiter, function(req, res) {
|
|
|
103
102
|
|
|
104
103
|
A `req.rateLimit` property is added to all requests with the `limit`, `current`, and `remaining` number of requests and, if the store provides it, a `resetTime` Date object. These may be used in your application code to take additional actions or inform the user of their status.
|
|
105
104
|
|
|
105
|
+
The property name can be configured with the configuration option `requestPropertyName`
|
|
106
|
+
|
|
106
107
|
## Configuration options
|
|
107
108
|
|
|
108
109
|
### max
|
|
@@ -113,6 +114,31 @@ May be a number, or a function that returns a number or a promise. If `max` is a
|
|
|
113
114
|
|
|
114
115
|
Defaults to `5`. Set to `0` to disable.
|
|
115
116
|
|
|
117
|
+
Example of using a function:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const rateLimit = require("express-rate-limit");
|
|
121
|
+
|
|
122
|
+
function isPremium(req) {
|
|
123
|
+
//...
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const limiter = rateLimit({
|
|
127
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
128
|
+
|
|
129
|
+
// max could also be an async function or return a promise
|
|
130
|
+
max: function(req, res) {
|
|
131
|
+
if (isPremium(req)) {
|
|
132
|
+
return 10;
|
|
133
|
+
}
|
|
134
|
+
return 5;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// apply to all requests
|
|
139
|
+
app.use(limiter);
|
|
140
|
+
```
|
|
141
|
+
|
|
116
142
|
### windowMs
|
|
117
143
|
|
|
118
144
|
Timeframe for which requests are checked/remembered. Also used in the Retry-After header when the limit is reached.
|
|
@@ -187,6 +213,19 @@ function (req, res, options) {
|
|
|
187
213
|
}
|
|
188
214
|
```
|
|
189
215
|
|
|
216
|
+
### requestWasSuccessful
|
|
217
|
+
|
|
218
|
+
Function that is called when `skipFailedRequests` and/or `skipSuccessfulRequests` are set to `true`.
|
|
219
|
+
May be overridden if, for example, a service sends out a 200 status code on errors.
|
|
220
|
+
|
|
221
|
+
Defaults to
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
function (req, res) {
|
|
225
|
+
return res.statusCode < 400;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
190
229
|
### skipFailedRequests
|
|
191
230
|
|
|
192
231
|
When set to `true`, failed requests won't be counted. Request considered failed when:
|
|
@@ -218,6 +257,11 @@ function (/*req, res*/) {
|
|
|
218
257
|
}
|
|
219
258
|
```
|
|
220
259
|
|
|
260
|
+
### requestPropertyName
|
|
261
|
+
Parameter to add to `req`-Object.
|
|
262
|
+
|
|
263
|
+
Defaults to `rateLimit`.
|
|
264
|
+
|
|
221
265
|
### store
|
|
222
266
|
|
|
223
267
|
The storage to use when persisting rate limit attempts.
|
|
@@ -302,4 +346,4 @@ v2 uses a less precise but less resource intensive method of tracking hits from
|
|
|
302
346
|
|
|
303
347
|
## License
|
|
304
348
|
|
|
305
|
-
MIT © [Nathan Friedly](http://nfriedly.com/)
|
|
349
|
+
MIT © [Nathan Friedly](http://nfriedly.com/)
|
|
@@ -10,8 +10,12 @@ function RateLimit(options) {
|
|
|
10
10
|
statusCode: 429, // 429 status = Too Many Requests (RFC 6585)
|
|
11
11
|
headers: true, //Send custom rate limit header with limit and remaining
|
|
12
12
|
draft_polli_ratelimit_headers: false, //Support for the new RateLimit standardization headers
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// ability to manually decide if request was successful. Used when `skipSuccessfulRequests` and/or `skipFailedRequests` are set to `true`
|
|
14
|
+
requestWasSuccessful: function (req, res) {
|
|
15
|
+
return res.statusCode < 400;
|
|
16
|
+
},
|
|
17
|
+
skipFailedRequests: false, // Do not count failed requests
|
|
18
|
+
skipSuccessfulRequests: false, // Do not count successful requests
|
|
15
19
|
// allows to create custom keys (by default user IP is used)
|
|
16
20
|
keyGenerator: function (req /*, res*/) {
|
|
17
21
|
return req.ip;
|
|
@@ -23,6 +27,7 @@ function RateLimit(options) {
|
|
|
23
27
|
res.status(options.statusCode).send(options.message);
|
|
24
28
|
},
|
|
25
29
|
onLimitReached: function (/*req, res, optionsUsed*/) {},
|
|
30
|
+
requestPropertyName: "rateLimit", // Parameter name appended to req object
|
|
26
31
|
},
|
|
27
32
|
options
|
|
28
33
|
);
|
|
@@ -70,7 +75,7 @@ function RateLimit(options) {
|
|
|
70
75
|
|
|
71
76
|
Promise.resolve(maxResult)
|
|
72
77
|
.then((max) => {
|
|
73
|
-
req.
|
|
78
|
+
req[options.requestPropertyName] = {
|
|
74
79
|
limit: max,
|
|
75
80
|
current: current,
|
|
76
81
|
remaining: Math.max(max - current, 0),
|
|
@@ -79,10 +84,13 @@ function RateLimit(options) {
|
|
|
79
84
|
|
|
80
85
|
if (options.headers && !res.headersSent) {
|
|
81
86
|
res.setHeader("X-RateLimit-Limit", max);
|
|
82
|
-
res.setHeader(
|
|
87
|
+
res.setHeader(
|
|
88
|
+
"X-RateLimit-Remaining",
|
|
89
|
+
req[options.requestPropertyName].remaining
|
|
90
|
+
);
|
|
83
91
|
if (resetTime instanceof Date) {
|
|
84
92
|
// if we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
|
|
85
|
-
res.setHeader("Date", new Date().
|
|
93
|
+
res.setHeader("Date", new Date().toUTCString());
|
|
86
94
|
res.setHeader(
|
|
87
95
|
"X-RateLimit-Reset",
|
|
88
96
|
Math.ceil(resetTime.getTime() / 1000)
|
|
@@ -91,7 +99,10 @@ function RateLimit(options) {
|
|
|
91
99
|
}
|
|
92
100
|
if (options.draft_polli_ratelimit_headers && !res.headersSent) {
|
|
93
101
|
res.setHeader("RateLimit-Limit", max);
|
|
94
|
-
res.setHeader(
|
|
102
|
+
res.setHeader(
|
|
103
|
+
"RateLimit-Remaining",
|
|
104
|
+
req[options.requestPropertyName].remaining
|
|
105
|
+
);
|
|
95
106
|
if (resetTime) {
|
|
96
107
|
const deltaSeconds = Math.ceil(
|
|
97
108
|
(resetTime.getTime() - Date.now()) / 1000
|
|
@@ -114,7 +125,7 @@ function RateLimit(options) {
|
|
|
114
125
|
|
|
115
126
|
if (options.skipFailedRequests) {
|
|
116
127
|
res.on("finish", function () {
|
|
117
|
-
if (
|
|
128
|
+
if (!options.requestWasSuccessful(req, res)) {
|
|
118
129
|
decrementKey();
|
|
119
130
|
}
|
|
120
131
|
});
|
|
@@ -130,7 +141,7 @@ function RateLimit(options) {
|
|
|
130
141
|
|
|
131
142
|
if (options.skipSuccessfulRequests) {
|
|
132
143
|
res.on("finish", function () {
|
|
133
|
-
if (
|
|
144
|
+
if (options.requestWasSuccessful(req, res)) {
|
|
134
145
|
options.store.decrement(key);
|
|
135
146
|
}
|
|
136
147
|
});
|
|
@@ -152,9 +163,13 @@ function RateLimit(options) {
|
|
|
152
163
|
}
|
|
153
164
|
|
|
154
165
|
next();
|
|
166
|
+
|
|
167
|
+
return null;
|
|
155
168
|
})
|
|
156
169
|
.catch(next);
|
|
157
170
|
});
|
|
171
|
+
|
|
172
|
+
return null;
|
|
158
173
|
})
|
|
159
174
|
.catch(next);
|
|
160
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
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,18 @@
|
|
|
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.32.0",
|
|
37
|
+
"eslint-config-prettier": "^8.3.0",
|
|
38
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
39
39
|
"express": "^4.17.1",
|
|
40
|
-
"husky": "^
|
|
41
|
-
"mocha": "^
|
|
42
|
-
"prettier": "^2.
|
|
43
|
-
"pretty-quick": "^
|
|
44
|
-
"
|
|
40
|
+
"husky": "^7.0.2",
|
|
41
|
+
"mocha": "^9.1.2",
|
|
42
|
+
"prettier": "^2.4.1",
|
|
43
|
+
"pretty-quick": "^3.1.1",
|
|
44
|
+
"sinon": "^11.1.2",
|
|
45
|
+
"supertest": "^6.1.6"
|
|
45
46
|
},
|
|
46
47
|
"scripts": {
|
|
47
48
|
"lint": "eslint .",
|