express-rate-limit 7.3.0 → 7.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/dist/index.cjs CHANGED
@@ -227,8 +227,8 @@ var validations = {
227
227
  keys.push(prefixedKey);
228
228
  },
229
229
  /**
230
- * Warns the user that the behaviour for `max: 0` / `limit: 0` is changing in the next
231
- * major release.
230
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is
231
+ * changing in the next major release.
232
232
  *
233
233
  * @param limit {number} - The maximum number of hits per client.
234
234
  *
@@ -259,8 +259,8 @@ var validations = {
259
259
  }
260
260
  },
261
261
  /**
262
- * Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
263
- * major release.
262
+ * Warns the user that the `onLimitReached` option is deprecated and
263
+ * will be removed in the next major release.
264
264
  *
265
265
  * @param onLimitReached {any | undefined} - The maximum number of hits per client.
266
266
  *
@@ -291,9 +291,11 @@ var validations = {
291
291
  }
292
292
  },
293
293
  /**
294
- * Checks the options.validate setting to ensure that only recognized validations are enabled or disabled.
294
+ * Checks the options.validate setting to ensure that only recognized
295
+ * validations are enabled or disabled.
295
296
  *
296
- * If any unrecognized values are found, an error is logged that includes the list of supported vaidations.
297
+ * If any unrecognized values are found, an error is logged that
298
+ * includes the list of supported vaidations.
297
299
  */
298
300
  validationsConfig() {
299
301
  const supportedValidations = Object.keys(this).filter(
@@ -312,13 +314,21 @@ var validations = {
312
314
  }
313
315
  },
314
316
  /**
315
- * Checks to see if the instance was created inside of a request handler, which would prevent it from working correctly.
317
+ * Checks to see if the instance was created inside of a request handler,
318
+ * which would prevent it from working correctly, with the default memory
319
+ * store (or any other store with localKeys.)
316
320
  */
317
- creationStack() {
321
+ creationStack(store) {
318
322
  const { stack } = new Error(
319
323
  "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
320
324
  );
321
325
  if (stack?.includes("Layer.handle [as handle_request]")) {
326
+ if (!store.localKeys) {
327
+ throw new ValidationError(
328
+ "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
329
+ "express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
330
+ );
331
+ }
322
332
  throw new ValidationError(
323
333
  "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
324
334
  `express-rate-limit instance should be created at app initialization, not when responding to a request.`
@@ -617,6 +627,7 @@ var parseOptions = (passedOptions) => {
617
627
  response.send(message);
618
628
  }
619
629
  },
630
+ passOnStoreError: false,
620
631
  // Allow the default options to be overriden by the options passed to the middleware.
621
632
  ...notUndefinedOptions,
622
633
  // `standardHeaders` is resolved into a draft version above, use that.
@@ -644,7 +655,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
644
655
  var rateLimit = (passedOptions) => {
645
656
  const config = parseOptions(passedOptions ?? {});
646
657
  const options = getOptionsFromConfig(config);
647
- config.validations.creationStack();
658
+ config.validations.creationStack(config.store);
648
659
  config.validations.unsharedStore(config.store);
649
660
  if (typeof config.store.init === "function")
650
661
  config.store.init(options);
@@ -657,7 +668,23 @@ var rateLimit = (passedOptions) => {
657
668
  }
658
669
  const augmentedRequest = request;
659
670
  const key = await config.keyGenerator(request, response);
660
- const { totalHits, resetTime } = await config.store.increment(key);
671
+ let totalHits = 0;
672
+ let resetTime;
673
+ try {
674
+ const incrementResult = await config.store.increment(key);
675
+ totalHits = incrementResult.totalHits;
676
+ resetTime = incrementResult.resetTime;
677
+ } catch (error) {
678
+ if (config.passOnStoreError) {
679
+ console.error(
680
+ "express-rate-limit: error from store, allowing request without rate-limiting.",
681
+ error
682
+ );
683
+ next();
684
+ } else {
685
+ throw error;
686
+ }
687
+ }
661
688
  config.validations.positiveHits(totalHits);
662
689
  config.validations.singleCount(request, config.store, key);
663
690
  const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
package/dist/index.d.cts CHANGED
@@ -60,8 +60,8 @@ declare const validations: {
60
60
  */
61
61
  singleCount(request: Request, store: Store, key: string): void;
62
62
  /**
63
- * Warns the user that the behaviour for `max: 0` / `limit: 0` is changing in the next
64
- * major release.
63
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is
64
+ * changing in the next major release.
65
65
  *
66
66
  * @param limit {number} - The maximum number of hits per client.
67
67
  *
@@ -78,8 +78,8 @@ declare const validations: {
78
78
  */
79
79
  draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
80
80
  /**
81
- * Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
82
- * major release.
81
+ * Warns the user that the `onLimitReached` option is deprecated and
82
+ * will be removed in the next major release.
83
83
  *
84
84
  * @param onLimitReached {any | undefined} - The maximum number of hits per client.
85
85
  *
@@ -96,15 +96,19 @@ declare const validations: {
96
96
  */
97
97
  headersResetTime(resetTime?: Date): void;
98
98
  /**
99
- * Checks the options.validate setting to ensure that only recognized validations are enabled or disabled.
99
+ * Checks the options.validate setting to ensure that only recognized
100
+ * validations are enabled or disabled.
100
101
  *
101
- * If any unrecognized values are found, an error is logged that includes the list of supported vaidations.
102
+ * If any unrecognized values are found, an error is logged that
103
+ * includes the list of supported vaidations.
102
104
  */
103
105
  validationsConfig(): void;
104
106
  /**
105
- * Checks to see if the instance was created inside of a request handler, which would prevent it from working correctly.
107
+ * Checks to see if the instance was created inside of a request handler,
108
+ * which would prevent it from working correctly, with the default memory
109
+ * store (or any other store with localKeys.)
106
110
  */
107
- creationStack(): void;
111
+ creationStack(store: Store): void;
108
112
  };
109
113
  export type Validations = typeof validations;
110
114
  /**
@@ -398,6 +402,10 @@ export type Options = {
398
402
  * be removed from the library in the foreseeable future.
399
403
  */
400
404
  max?: number | ValueDeterminingMiddleware<number>;
405
+ /**
406
+ * If the Store generates an error, allow the request to pass.
407
+ */
408
+ passOnStoreError: boolean;
401
409
  };
402
410
  /**
403
411
  * The extended request object that includes information about the client's
package/dist/index.d.mts CHANGED
@@ -60,8 +60,8 @@ declare const validations: {
60
60
  */
61
61
  singleCount(request: Request, store: Store, key: string): void;
62
62
  /**
63
- * Warns the user that the behaviour for `max: 0` / `limit: 0` is changing in the next
64
- * major release.
63
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is
64
+ * changing in the next major release.
65
65
  *
66
66
  * @param limit {number} - The maximum number of hits per client.
67
67
  *
@@ -78,8 +78,8 @@ declare const validations: {
78
78
  */
79
79
  draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
80
80
  /**
81
- * Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
82
- * major release.
81
+ * Warns the user that the `onLimitReached` option is deprecated and
82
+ * will be removed in the next major release.
83
83
  *
84
84
  * @param onLimitReached {any | undefined} - The maximum number of hits per client.
85
85
  *
@@ -96,15 +96,19 @@ declare const validations: {
96
96
  */
97
97
  headersResetTime(resetTime?: Date): void;
98
98
  /**
99
- * Checks the options.validate setting to ensure that only recognized validations are enabled or disabled.
99
+ * Checks the options.validate setting to ensure that only recognized
100
+ * validations are enabled or disabled.
100
101
  *
101
- * If any unrecognized values are found, an error is logged that includes the list of supported vaidations.
102
+ * If any unrecognized values are found, an error is logged that
103
+ * includes the list of supported vaidations.
102
104
  */
103
105
  validationsConfig(): void;
104
106
  /**
105
- * Checks to see if the instance was created inside of a request handler, which would prevent it from working correctly.
107
+ * Checks to see if the instance was created inside of a request handler,
108
+ * which would prevent it from working correctly, with the default memory
109
+ * store (or any other store with localKeys.)
106
110
  */
107
- creationStack(): void;
111
+ creationStack(store: Store): void;
108
112
  };
109
113
  export type Validations = typeof validations;
110
114
  /**
@@ -398,6 +402,10 @@ export type Options = {
398
402
  * be removed from the library in the foreseeable future.
399
403
  */
400
404
  max?: number | ValueDeterminingMiddleware<number>;
405
+ /**
406
+ * If the Store generates an error, allow the request to pass.
407
+ */
408
+ passOnStoreError: boolean;
401
409
  };
402
410
  /**
403
411
  * The extended request object that includes information about the client's
package/dist/index.d.ts CHANGED
@@ -60,8 +60,8 @@ declare const validations: {
60
60
  */
61
61
  singleCount(request: Request, store: Store, key: string): void;
62
62
  /**
63
- * Warns the user that the behaviour for `max: 0` / `limit: 0` is changing in the next
64
- * major release.
63
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is
64
+ * changing in the next major release.
65
65
  *
66
66
  * @param limit {number} - The maximum number of hits per client.
67
67
  *
@@ -78,8 +78,8 @@ declare const validations: {
78
78
  */
79
79
  draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
80
80
  /**
81
- * Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
82
- * major release.
81
+ * Warns the user that the `onLimitReached` option is deprecated and
82
+ * will be removed in the next major release.
83
83
  *
84
84
  * @param onLimitReached {any | undefined} - The maximum number of hits per client.
85
85
  *
@@ -96,15 +96,19 @@ declare const validations: {
96
96
  */
97
97
  headersResetTime(resetTime?: Date): void;
98
98
  /**
99
- * Checks the options.validate setting to ensure that only recognized validations are enabled or disabled.
99
+ * Checks the options.validate setting to ensure that only recognized
100
+ * validations are enabled or disabled.
100
101
  *
101
- * If any unrecognized values are found, an error is logged that includes the list of supported vaidations.
102
+ * If any unrecognized values are found, an error is logged that
103
+ * includes the list of supported vaidations.
102
104
  */
103
105
  validationsConfig(): void;
104
106
  /**
105
- * Checks to see if the instance was created inside of a request handler, which would prevent it from working correctly.
107
+ * Checks to see if the instance was created inside of a request handler,
108
+ * which would prevent it from working correctly, with the default memory
109
+ * store (or any other store with localKeys.)
106
110
  */
107
- creationStack(): void;
111
+ creationStack(store: Store): void;
108
112
  };
109
113
  export type Validations = typeof validations;
110
114
  /**
@@ -398,6 +402,10 @@ export type Options = {
398
402
  * be removed from the library in the foreseeable future.
399
403
  */
400
404
  max?: number | ValueDeterminingMiddleware<number>;
405
+ /**
406
+ * If the Store generates an error, allow the request to pass.
407
+ */
408
+ passOnStoreError: boolean;
401
409
  };
402
410
  /**
403
411
  * The extended request object that includes information about the client's
package/dist/index.mjs CHANGED
@@ -199,8 +199,8 @@ var validations = {
199
199
  keys.push(prefixedKey);
200
200
  },
201
201
  /**
202
- * Warns the user that the behaviour for `max: 0` / `limit: 0` is changing in the next
203
- * major release.
202
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is
203
+ * changing in the next major release.
204
204
  *
205
205
  * @param limit {number} - The maximum number of hits per client.
206
206
  *
@@ -231,8 +231,8 @@ var validations = {
231
231
  }
232
232
  },
233
233
  /**
234
- * Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
235
- * major release.
234
+ * Warns the user that the `onLimitReached` option is deprecated and
235
+ * will be removed in the next major release.
236
236
  *
237
237
  * @param onLimitReached {any | undefined} - The maximum number of hits per client.
238
238
  *
@@ -263,9 +263,11 @@ var validations = {
263
263
  }
264
264
  },
265
265
  /**
266
- * Checks the options.validate setting to ensure that only recognized validations are enabled or disabled.
266
+ * Checks the options.validate setting to ensure that only recognized
267
+ * validations are enabled or disabled.
267
268
  *
268
- * If any unrecognized values are found, an error is logged that includes the list of supported vaidations.
269
+ * If any unrecognized values are found, an error is logged that
270
+ * includes the list of supported vaidations.
269
271
  */
270
272
  validationsConfig() {
271
273
  const supportedValidations = Object.keys(this).filter(
@@ -284,13 +286,21 @@ var validations = {
284
286
  }
285
287
  },
286
288
  /**
287
- * Checks to see if the instance was created inside of a request handler, which would prevent it from working correctly.
289
+ * Checks to see if the instance was created inside of a request handler,
290
+ * which would prevent it from working correctly, with the default memory
291
+ * store (or any other store with localKeys.)
288
292
  */
289
- creationStack() {
293
+ creationStack(store) {
290
294
  const { stack } = new Error(
291
295
  "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
292
296
  );
293
297
  if (stack?.includes("Layer.handle [as handle_request]")) {
298
+ if (!store.localKeys) {
299
+ throw new ValidationError(
300
+ "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
301
+ "express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
302
+ );
303
+ }
294
304
  throw new ValidationError(
295
305
  "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
296
306
  `express-rate-limit instance should be created at app initialization, not when responding to a request.`
@@ -589,6 +599,7 @@ var parseOptions = (passedOptions) => {
589
599
  response.send(message);
590
600
  }
591
601
  },
602
+ passOnStoreError: false,
592
603
  // Allow the default options to be overriden by the options passed to the middleware.
593
604
  ...notUndefinedOptions,
594
605
  // `standardHeaders` is resolved into a draft version above, use that.
@@ -616,7 +627,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
616
627
  var rateLimit = (passedOptions) => {
617
628
  const config = parseOptions(passedOptions ?? {});
618
629
  const options = getOptionsFromConfig(config);
619
- config.validations.creationStack();
630
+ config.validations.creationStack(config.store);
620
631
  config.validations.unsharedStore(config.store);
621
632
  if (typeof config.store.init === "function")
622
633
  config.store.init(options);
@@ -629,7 +640,23 @@ var rateLimit = (passedOptions) => {
629
640
  }
630
641
  const augmentedRequest = request;
631
642
  const key = await config.keyGenerator(request, response);
632
- const { totalHits, resetTime } = await config.store.increment(key);
643
+ let totalHits = 0;
644
+ let resetTime;
645
+ try {
646
+ const incrementResult = await config.store.increment(key);
647
+ totalHits = incrementResult.totalHits;
648
+ resetTime = incrementResult.resetTime;
649
+ } catch (error) {
650
+ if (config.passOnStoreError) {
651
+ console.error(
652
+ "express-rate-limit: error from store, allowing request without rate-limiting.",
653
+ error
654
+ );
655
+ next();
656
+ } else {
657
+ throw error;
658
+ }
659
+ }
633
660
  config.validations.positiveHits(totalHits);
634
661
  config.validations.singleCount(request, config.store, key);
635
662
  const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "7.3.0",
3
+ "version": "7.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
  "author": {
6
6
  "name": "Nathan Friedly",
package/readme.md CHANGED
@@ -45,23 +45,24 @@ The rate limiter comes with a built-in memory store, and supports a variety of
45
45
  All function options may be async. Click the name for additional info and
46
46
  default values.
47
47
 
48
- | Option | Type | Remarks |
49
- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------- | ----------------------------------------------------------------------------------------------- |
50
- | [`windowMs`](https://express-rate-limit.mintlify.app/reference/configuration#windowms) | `number` | How long to remember requests for, in milliseconds. |
51
- | [`limit`](https://express-rate-limit.mintlify.app/reference/configuration#limit) | `number` \| `function` | How many requests to allow. |
52
- | [`message`](https://express-rate-limit.mintlify.app/reference/configuration#message) | `string` \| `json` \| `function` | Response to return after limit is reached. |
53
- | [`statusCode`](https://express-rate-limit.mintlify.app/reference/configuration#statuscode) | `number` | HTTP status code after limit is reached (default is 429). |
54
- | [`legacyHeaders`](https://express-rate-limit.mintlify.app/reference/configuration#legacyheaders) | `boolean` | Enable the `X-Rate-Limit` header. |
55
- | [`standardHeaders`](https://express-rate-limit.mintlify.app/reference/configuration#standardheaders) | `'draft-6'` \| `'draft-7'` | Enable the `Ratelimit` header. |
56
- | [`requestPropertyName`](https://express-rate-limit.mintlify.app/reference/configuration#requestpropertyname) | `string` | Add rate limit info to the `req` object. |
57
- | [`skipFailedRequests`](https://express-rate-limit.mintlify.app/reference/configuration#skipfailedrequests) | `boolean` | Uncount 4xx/5xx responses. |
58
- | [`skipSuccessfulRequests`](https://express-rate-limit.mintlify.app/reference/configuration#skipsuccessfulrequests) | `boolean` | Uncount 1xx/2xx/3xx responses. |
59
- | [`keyGenerator`](https://express-rate-limit.mintlify.app/reference/configuration#keygenerator) | `function` | Identify users (defaults to IP address). |
60
- | [`handler`](https://express-rate-limit.mintlify.app/reference/configuration#handler) | `function` | Function to run after limit is reached (overrides `message` and `statusCode` settings, if set). |
61
- | [`skip`](https://express-rate-limit.mintlify.app/reference/configuration#skip) | `function` | Return `true` to bypass the limiter for the given request. |
62
- | [`requestWasSuccessful`](https://express-rate-limit.mintlify.app/reference/configuration#requestwassuccessful) | `function` | Used by `skipFailedRequests` and `skipSuccessfulRequests`. |
63
- | [`validate`](https://express-rate-limit.mintlify.app/reference/configuration#validate) | `boolean` \| `object` | Enable or disable built-in validation checks. |
64
- | [`store`](https://express-rate-limit.mintlify.app/reference/configuration#store) | `Store` | Use a custom store to share hit counts across multiple nodes. |
48
+ | Option | Type | Remarks |
49
+ | -------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------- |
50
+ | [`windowMs`] | `number` | How long to remember requests for, in milliseconds. |
51
+ | [`limit`] | `number` \| `function` | How many requests to allow. |
52
+ | [`message`] | `string` \| `json` \| `function` | Response to return after limit is reached. |
53
+ | [`statusCode`] | `number` | HTTP status code after limit is reached (default is 429). |
54
+ | [`handler`] | `function` | Function to run after limit is reached (overrides `message` and `statusCode` settings, if set). |
55
+ | [`legacyHeaders`] | `boolean` | Enable the `X-Rate-Limit` header. |
56
+ | [`standardHeaders`] | `'draft-6'` \| `'draft-7'` | Enable the `Ratelimit` header. |
57
+ | [`store`] | `Store` | Use a custom store to share hit counts across multiple nodes. |
58
+ | [`passOnStoreError`] | `boolean` | Allow (`true`) or block (`false`, default) traffic if the store becomes unavailable. |
59
+ | [`keyGenerator`] | `function` | Identify users (defaults to IP address). |
60
+ | [`requestPropertyName`] | `string` | Add rate limit info to the `req` object. |
61
+ | [`skip`] | `function` | Return `true` to bypass the limiter for the given request. |
62
+ | [`skipSuccessfulRequests`] | `boolean` | Uncount 1xx/2xx/3xx responses. |
63
+ | [`skipFailedRequests`] | `boolean` | Uncount 4xx/5xx responses. |
64
+ | [`requestWasSuccessful`] | `function` | Used by `skipSuccessfulRequests` and `skipFailedRequests`. |
65
+ | [`validate`] | `boolean` \| `object` | Enable or disable built-in validation checks. |
65
66
 
66
67
  ## Thank You
67
68
 
@@ -111,3 +112,33 @@ Then you can pick up any issue and fix/implement it!
111
112
 
112
113
  MIT © [Nathan Friedly](http://nfriedly.com/),
113
114
  [Vedant K](https://github.com/gamemaker1)
115
+
116
+ [`windowMs`]:
117
+ https://express-rate-limit.mintlify.app/reference/configuration#windowms
118
+ [`limit`]: https://express-rate-limit.mintlify.app/reference/configuration#limit
119
+ [`message`]:
120
+ https://express-rate-limit.mintlify.app/reference/configuration#message
121
+ [`statusCode`]:
122
+ https://express-rate-limit.mintlify.app/reference/configuration#statuscode
123
+ [`handler`]:
124
+ https://express-rate-limit.mintlify.app/reference/configuration#handler
125
+ [`legacyHeaders`]:
126
+ https://express-rate-limit.mintlify.app/reference/configuration#legacyheaders
127
+ [`standardHeaders`]:
128
+ https://express-rate-limit.mintlify.app/reference/configuration#standardheaders
129
+ [`store`]: https://express-rate-limit.mintlify.app/reference/configuration#store
130
+ [`passOnStoreError`]:
131
+ https://express-rate-limit.mintlify.app/reference/configuration#passOnStoreError
132
+ [`keyGenerator`]:
133
+ https://express-rate-limit.mintlify.app/reference/configuration#keygenerator
134
+ [`requestPropertyName`]:
135
+ https://express-rate-limit.mintlify.app/reference/configuration#requestpropertyname
136
+ [`skip`]: https://express-rate-limit.mintlify.app/reference/configuration#skip
137
+ [`skipSuccessfulRequests`]:
138
+ https://express-rate-limit.mintlify.app/reference/configuration#skipsuccessfulrequests
139
+ [`skipFailedRequests`]:
140
+ https://express-rate-limit.mintlify.app/reference/configuration#skipfailedrequests
141
+ [`requestWasSuccessful`]:
142
+ https://express-rate-limit.mintlify.app/reference/configuration#requestwassuccessful
143
+ [`validate`]:
144
+ https://express-rate-limit.mintlify.app/reference/configuration#validate