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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # Express Rate Limit
2
2
 
3
- ![Node.js CI](https://github.com/nfriedly/express-rate-limit/workflows/Node.js%20CI/badge.svg)
4
- [![NPM version](http://badge.fury.io/js/express-rate-limit.png)](https://npmjs.org/package/express-rate-limit "View this project on NPM")
3
+ [![Node.js CI](https://github.com/nfriedly/express-rate-limit/workflows/Node.js%20CI/badge.svg)](https://github.com/nfriedly/express-rate-limit/actions)
4
+ [![NPM version](https://img.shields.io/npm/v/express-rate-limit.svg)](https://npmjs.org/package/express-rate-limit "View this project on NPM")
5
5
  [![npm downloads](https://img.shields.io/npm/dm/express-rate-limit)](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
- skipFailedRequests: false, // Do not count failed requests (status >= 400)
14
- skipSuccessfulRequests: false, // Do not count successful requests (status < 400)
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.rateLimit = {
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("X-RateLimit-Remaining", req.rateLimit.remaining);
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().toGMTString());
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("RateLimit-Remaining", req.rateLimit.remaining);
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 (res.statusCode >= 400) {
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 (res.statusCode < 400) {
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.2.3",
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
- "eslint": "^6.8.0",
37
- "eslint-config-prettier": "^6.10.1",
38
- "eslint-plugin-prettier": "^3.1.2",
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": "^4.2.3",
41
- "mocha": "^7.1.1",
42
- "prettier": "^2.0.4",
43
- "pretty-quick": "^2.0.1",
44
- "supertest": "^4.0.2"
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 .",