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 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
- How long in milliseconds to keep records of requests in memory.
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
- .catch(next)
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
 
@@ -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": "4.0.3",
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": "^5.15.3",
39
- "eslint-config-prettier": "^4.2.0",
40
- "eslint-plugin-prettier": "^3.0.1",
41
- "express": "^4.16.3",
42
- "husky": "^2.2.0",
43
- "mocha": "^6.1.4",
44
- "prettier": "^1.17.0",
45
- "pretty-quick": "^1.6.0",
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
- "test": "eslint . && mocha",
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;