express-rate-limit 4.0.3 → 5.1.3
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 +7 -0
- package/README.md +17 -4
- package/lib/express-rate-limit.js +25 -14
- package/lib/memory-store.js +4 -5
- package/package.json +13 -13
- package/index.d.ts +0 -39
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2019 Nathan Friedly
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -109,13 +109,15 @@ A `req.rateLimit` property is added to all requests with the `limit`, `current`,
|
|
|
109
109
|
|
|
110
110
|
Max number of connections during `windowMs` milliseconds before sending a 429 response.
|
|
111
111
|
|
|
112
|
-
May be a number, or a function that returns a number or a promise.
|
|
112
|
+
May be a number, or a function that returns a number or a promise. If `max` is a function, it will be called with `req` and `res` params.
|
|
113
113
|
|
|
114
114
|
Defaults to `5`. Set to `0` to disable.
|
|
115
115
|
|
|
116
116
|
### windowMs
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
Timeframe for which requests are checked/remembered. Also used in the Retry-After header when the limit is reached.
|
|
119
|
+
|
|
120
|
+
Note: with non-default stores, you may need to configure this value twice, once here and once on the store. In some cases the units also differ (e.g. seconds vs miliseconds)
|
|
119
121
|
|
|
120
122
|
Defaults to `60000` (1 minute).
|
|
121
123
|
|
|
@@ -137,7 +139,13 @@ Defaults to `429`.
|
|
|
137
139
|
|
|
138
140
|
Enable headers for request limit (`X-RateLimit-Limit`) and current usage (`X-RateLimit-Remaining`) on all responses and time to wait before retrying (`Retry-After`) when `max` is exceeded.
|
|
139
141
|
|
|
140
|
-
Defaults to `true`.
|
|
142
|
+
Defaults to `true`. Behavior may change in the next major release.
|
|
143
|
+
|
|
144
|
+
### draft_polli_ratelimit_headers
|
|
145
|
+
|
|
146
|
+
Enable headers conforming to the [ratelimit standardization proposal](https://tools.ietf.org/id/draft-polli-ratelimit-headers-01.html): `RateLimit-Limit`, `RateLimit-Remaining`, and, if the store supports it, `RateLimit-Reset`. May be used in conjunction with, or instead of the `headers` option.
|
|
147
|
+
|
|
148
|
+
Defaults to `false`. Behavior and name will likely change in future releases.
|
|
141
149
|
|
|
142
150
|
### keyGenerator
|
|
143
151
|
|
|
@@ -200,7 +208,7 @@ Defaults to `false`.
|
|
|
200
208
|
|
|
201
209
|
### skip
|
|
202
210
|
|
|
203
|
-
Function used to skip requests. Returning `true` from the function will skip limiting for that request.
|
|
211
|
+
Function used to skip (whitelist) requests. Returning `true` from the function will skip limiting for that request.
|
|
204
212
|
|
|
205
213
|
Defaults to always `false` (count all requests):
|
|
206
214
|
|
|
@@ -221,6 +229,7 @@ Available data stores are:
|
|
|
221
229
|
- MemoryStore: _(default)_ Simple in-memory option. Does not share state when app has multiple processes or servers.
|
|
222
230
|
- [rate-limit-redis](https://npmjs.com/package/rate-limit-redis): A [Redis](http://redis.io/)-backed store, more suitable for large or demanding deployments.
|
|
223
231
|
- [rate-limit-memcached](https://npmjs.org/package/rate-limit-memcached): A [Memcached](https://memcached.org/)-backed store.
|
|
232
|
+
- [rate-limit-mongo](https://www.npmjs.com/package/rate-limit-mongo): A [MongoDB](https://www.mongodb.com/)-backed store.
|
|
224
233
|
|
|
225
234
|
You may also create your own store. It must implement the following in order to function:
|
|
226
235
|
|
|
@@ -273,6 +282,10 @@ Resets the rate limiting for a given key. (Allow users to complete a captcha or
|
|
|
273
282
|
|
|
274
283
|
## Summary of breaking changes:
|
|
275
284
|
|
|
285
|
+
### v5 changes
|
|
286
|
+
|
|
287
|
+
- Removed index.d.ts. (See [#138](https://github.com/nfriedly/express-rate-limit/issues/138))
|
|
288
|
+
|
|
276
289
|
### v4 Changes
|
|
277
290
|
|
|
278
291
|
- Express Rate Limit no longer modifies the passed-in options object, it instead makes a clone of it.
|
|
@@ -9,19 +9,20 @@ function RateLimit(options) {
|
|
|
9
9
|
message: "Too many requests, please try again later.",
|
|
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
|
+
draft_polli_ratelimit_headers: false, //Support for the new RateLimit standardization headers
|
|
12
13
|
skipFailedRequests: false, // Do not count failed requests (status >= 400)
|
|
13
14
|
skipSuccessfulRequests: false, // Do not count successful requests (status < 400)
|
|
14
15
|
// allows to create custom keys (by default user IP is used)
|
|
15
|
-
keyGenerator: function(req /*, res*/) {
|
|
16
|
+
keyGenerator: function (req /*, res*/) {
|
|
16
17
|
return req.ip;
|
|
17
18
|
},
|
|
18
|
-
skip: function(/*req, res*/) {
|
|
19
|
+
skip: function (/*req, res*/) {
|
|
19
20
|
return false;
|
|
20
21
|
},
|
|
21
|
-
handler: function(req, res /*, next*/) {
|
|
22
|
+
handler: function (req, res /*, next*/) {
|
|
22
23
|
res.status(options.statusCode).send(options.message);
|
|
23
24
|
},
|
|
24
|
-
onLimitReached: function(/*req, res, optionsUsed*/) {}
|
|
25
|
+
onLimitReached: function (/*req, res, optionsUsed*/) {},
|
|
25
26
|
},
|
|
26
27
|
options
|
|
27
28
|
);
|
|
@@ -39,7 +40,7 @@ function RateLimit(options) {
|
|
|
39
40
|
throw new Error("The store is not valid.");
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
["global", "delayMs", "delayAfter"].forEach(key => {
|
|
43
|
+
["global", "delayMs", "delayAfter"].forEach((key) => {
|
|
43
44
|
// note: this doesn't trigger if delayMs or delayAfter are set to 0, because that essentially disables them
|
|
44
45
|
if (options[key]) {
|
|
45
46
|
throw new Error(
|
|
@@ -55,7 +56,7 @@ function RateLimit(options) {
|
|
|
55
56
|
|
|
56
57
|
const key = options.keyGenerator(req, res);
|
|
57
58
|
|
|
58
|
-
options.store.incr(key, function(err, current, resetTime) {
|
|
59
|
+
options.store.incr(key, function (err, current, resetTime) {
|
|
59
60
|
if (err) {
|
|
60
61
|
return next(err);
|
|
61
62
|
}
|
|
@@ -64,16 +65,15 @@ function RateLimit(options) {
|
|
|
64
65
|
typeof options.max === "function" ? options.max(req, res) : options.max;
|
|
65
66
|
|
|
66
67
|
Promise.resolve(maxResult)
|
|
67
|
-
.
|
|
68
|
-
.then(max => {
|
|
68
|
+
.then((max) => {
|
|
69
69
|
req.rateLimit = {
|
|
70
70
|
limit: max,
|
|
71
71
|
current: current,
|
|
72
72
|
remaining: Math.max(max - current, 0),
|
|
73
|
-
resetTime: resetTime
|
|
73
|
+
resetTime: resetTime,
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
if (options.headers) {
|
|
76
|
+
if (options.headers && !res.headersSent) {
|
|
77
77
|
res.setHeader("X-RateLimit-Limit", max);
|
|
78
78
|
res.setHeader("X-RateLimit-Remaining", req.rateLimit.remaining);
|
|
79
79
|
if (resetTime instanceof Date) {
|
|
@@ -85,6 +85,16 @@ function RateLimit(options) {
|
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
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
|
+
}
|
|
97
|
+
}
|
|
88
98
|
|
|
89
99
|
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
90
100
|
let decremented = false;
|
|
@@ -96,7 +106,7 @@ function RateLimit(options) {
|
|
|
96
106
|
};
|
|
97
107
|
|
|
98
108
|
if (options.skipFailedRequests) {
|
|
99
|
-
res.on("finish", function() {
|
|
109
|
+
res.on("finish", function () {
|
|
100
110
|
if (res.statusCode >= 400) {
|
|
101
111
|
decrementKey();
|
|
102
112
|
}
|
|
@@ -112,7 +122,7 @@ function RateLimit(options) {
|
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
if (options.skipSuccessfulRequests) {
|
|
115
|
-
res.on("finish", function() {
|
|
125
|
+
res.on("finish", function () {
|
|
116
126
|
if (res.statusCode < 400) {
|
|
117
127
|
options.store.decrement(key);
|
|
118
128
|
}
|
|
@@ -125,14 +135,15 @@ function RateLimit(options) {
|
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
if (max && current > max) {
|
|
128
|
-
if (options.headers) {
|
|
138
|
+
if (options.headers && !res.headersSent) {
|
|
129
139
|
res.setHeader("Retry-After", Math.ceil(options.windowMs / 1000));
|
|
130
140
|
}
|
|
131
141
|
return options.handler(req, res, next);
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
next();
|
|
135
|
-
})
|
|
145
|
+
})
|
|
146
|
+
.catch(next);
|
|
136
147
|
});
|
|
137
148
|
}
|
|
138
149
|
|
package/lib/memory-store.js
CHANGED
|
@@ -10,7 +10,7 @@ function MemoryStore(windowMs) {
|
|
|
10
10
|
let hits = {};
|
|
11
11
|
let resetTime = calculateNextResetTime(windowMs);
|
|
12
12
|
|
|
13
|
-
this.incr = function(key, cb) {
|
|
13
|
+
this.incr = function (key, cb) {
|
|
14
14
|
if (hits[key]) {
|
|
15
15
|
hits[key]++;
|
|
16
16
|
} else {
|
|
@@ -20,22 +20,21 @@ function MemoryStore(windowMs) {
|
|
|
20
20
|
cb(null, hits[key], resetTime);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
this.decrement = function(key) {
|
|
23
|
+
this.decrement = function (key) {
|
|
24
24
|
if (hits[key]) {
|
|
25
25
|
hits[key]--;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// export an API to allow hits all IPs to be reset
|
|
30
|
-
this.resetAll = function() {
|
|
30
|
+
this.resetAll = function () {
|
|
31
31
|
hits = {};
|
|
32
32
|
resetTime = calculateNextResetTime(windowMs);
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
// export an API to allow hits from one IP to be reset
|
|
36
|
-
this.resetKey = function(key) {
|
|
36
|
+
this.resetKey = function (key) {
|
|
37
37
|
delete hits[key];
|
|
38
|
-
delete resetTime[key];
|
|
39
38
|
};
|
|
40
39
|
|
|
41
40
|
// simply reset ALL hits every windowMs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.3",
|
|
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": {
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
"repository": "nfriedly/express-rate-limit",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"main": "lib/express-rate-limit.js",
|
|
13
|
-
"types": "index.d.ts",
|
|
14
13
|
"files": [
|
|
15
|
-
"lib/"
|
|
16
|
-
"index.d.ts"
|
|
14
|
+
"lib/"
|
|
17
15
|
],
|
|
18
16
|
"keywords": [
|
|
19
17
|
"express-rate-limit",
|
|
@@ -35,18 +33,20 @@
|
|
|
35
33
|
],
|
|
36
34
|
"dependencies": {},
|
|
37
35
|
"devDependencies": {
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint-config-prettier": "^
|
|
40
|
-
"eslint-plugin-prettier": "^3.
|
|
41
|
-
"express": "^4.
|
|
42
|
-
"husky": "^
|
|
43
|
-
"mocha": "^
|
|
44
|
-
"prettier": "^
|
|
45
|
-
"pretty-quick": "^
|
|
36
|
+
"eslint": "^6.8.0",
|
|
37
|
+
"eslint-config-prettier": "^6.10.1",
|
|
38
|
+
"eslint-plugin-prettier": "^3.1.2",
|
|
39
|
+
"express": "^4.17.1",
|
|
40
|
+
"husky": "^4.2.3",
|
|
41
|
+
"mocha": "^7.1.1",
|
|
42
|
+
"prettier": "^2.0.4",
|
|
43
|
+
"pretty-quick": "^2.0.1",
|
|
46
44
|
"supertest": "^4.0.2"
|
|
47
45
|
},
|
|
48
46
|
"scripts": {
|
|
49
|
-
"
|
|
47
|
+
"lint": "eslint .",
|
|
48
|
+
"autofix": "npm run lint -- --fix",
|
|
49
|
+
"test": "npm run lint && mocha",
|
|
50
50
|
"precommit": "pretty-quick --staged"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/index.d.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import {RequestHandler, Request, Response, NextFunction} from 'express';
|
|
2
|
-
|
|
3
|
-
export interface Options {
|
|
4
|
-
max?: number;
|
|
5
|
-
message?: any;
|
|
6
|
-
headers?: boolean;
|
|
7
|
-
windowMs?: number;
|
|
8
|
-
store?: Store | any;
|
|
9
|
-
statusCode?: number;
|
|
10
|
-
skipFailedRequests?: boolean;
|
|
11
|
-
skipSuccessfulRequests?: boolean;
|
|
12
|
-
|
|
13
|
-
skip?(req?: Request, res?: Response): boolean;
|
|
14
|
-
|
|
15
|
-
onLimitReached?(req?: Request, res?: Response): void;
|
|
16
|
-
|
|
17
|
-
handler?(req: Request, res: Response, next?: NextFunction): void;
|
|
18
|
-
|
|
19
|
-
keyGenerator?(req: Request, res?: Response): string | Request['ip'];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface Store {
|
|
23
|
-
hits: {
|
|
24
|
-
[key: string]: number;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
resetAll(): void;
|
|
28
|
-
|
|
29
|
-
resetTime: number;
|
|
30
|
-
setInterval: NodeJS.Timeout;
|
|
31
|
-
|
|
32
|
-
resetKey(key: string | any): void;
|
|
33
|
-
|
|
34
|
-
decrement(key: string | any): void;
|
|
35
|
-
|
|
36
|
-
incr(key: string | any, cb: (err?: Error, hits?: number) => void): void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export declare function RateLimit(options?: Options): (req: Request, res: Response, next: NextFunction) => void;
|