express-rate-limit 8.0.1 → 8.2.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
@@ -39,7 +39,8 @@ function ipKeyGenerator(ip, ipv6Subnet = 56) {
39
39
 
40
40
  // source/memory-store.ts
41
41
  var MemoryStore = class {
42
- constructor() {
42
+ constructor(validations2) {
43
+ this.validations = validations2;
43
44
  /**
44
45
  * These two maps store usage (requests) and reset time by key (for example, IP
45
46
  * addresses or API keys).
@@ -65,6 +66,7 @@ var MemoryStore = class {
65
66
  */
66
67
  init(options) {
67
68
  this.windowMs = options.windowMs;
69
+ this.validations?.windowMs(this.windowMs);
68
70
  if (this.interval) clearInterval(this.interval);
69
71
  this.interval = setInterval(() => {
70
72
  this.clearExpired();
@@ -237,7 +239,7 @@ var setDraft6Headers = (response, info, windowMs) => {
237
239
  response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
238
240
  response.setHeader("RateLimit-Limit", info.limit.toString());
239
241
  response.setHeader("RateLimit-Remaining", info.remaining.toString());
240
- if (resetSeconds)
242
+ if (typeof resetSeconds === "number")
241
243
  response.setHeader("RateLimit-Reset", resetSeconds.toString());
242
244
  };
243
245
  var setDraft7Headers = (response, info, windowMs) => {
@@ -367,6 +369,21 @@ var validations = {
367
369
  );
368
370
  }
369
371
  },
372
+ /**
373
+ * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
374
+ *
375
+ * @param request {Request} - The Express request object.
376
+ *
377
+ * @returns {void}
378
+ */
379
+ forwardedHeader(request) {
380
+ if (request.headers.forwarded && request.ip === request.socket?.remoteAddress) {
381
+ throw new ValidationError(
382
+ "ERR_ERL_FORWARDED_HEADER",
383
+ `The 'Forwarded' header (standardized X-Forwarded-For) is set but currently being ignored. Add a custom keyGenerator to use a value from this header.`
384
+ );
385
+ }
386
+ },
370
387
  /**
371
388
  * Ensures totalHits value from store is a positive integer.
372
389
  *
@@ -504,12 +521,50 @@ var validations = {
504
521
  );
505
522
  }
506
523
  },
524
+ knownOptions(passedOptions) {
525
+ if (!passedOptions) return;
526
+ const optionsMap = {
527
+ windowMs: true,
528
+ limit: true,
529
+ message: true,
530
+ statusCode: true,
531
+ legacyHeaders: true,
532
+ standardHeaders: true,
533
+ identifier: true,
534
+ requestPropertyName: true,
535
+ skipFailedRequests: true,
536
+ skipSuccessfulRequests: true,
537
+ keyGenerator: true,
538
+ ipv6Subnet: true,
539
+ handler: true,
540
+ skip: true,
541
+ requestWasSuccessful: true,
542
+ store: true,
543
+ validate: true,
544
+ headers: true,
545
+ max: true,
546
+ passOnStoreError: true
547
+ };
548
+ const validOptions = Object.keys(optionsMap).concat(
549
+ "draft_polli_ratelimit_headers"
550
+ // not a valid option anymore, but we have a more specific check for this one, so don't warn for it here
551
+ );
552
+ for (const key of Object.keys(passedOptions)) {
553
+ if (!validOptions.includes(key)) {
554
+ throw new ValidationError(
555
+ "ERR_ERL_UNKNOWN_OPTION",
556
+ `Unexpected configuration option: ${key}`
557
+ // todo: suggest a valid option with a short levenstein distance?
558
+ );
559
+ }
560
+ }
561
+ },
507
562
  /**
508
563
  * Checks the options.validate setting to ensure that only recognized
509
564
  * validations are enabled or disabled.
510
565
  *
511
566
  * If any unrecognized values are found, an error is logged that
512
- * includes the list of supported vaidations.
567
+ * includes the list of supported validations.
513
568
  */
514
569
  validationsConfig() {
515
570
  const supportedValidations = Object.keys(this).filter(
@@ -580,6 +635,21 @@ var validations = {
580
635
  "Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits."
581
636
  );
582
637
  }
638
+ },
639
+ /**
640
+ * Checks to see if the window duration is greater than 2^32 - 1. This is only
641
+ * called by the default MemoryStore, since it uses Node's setInterval method.
642
+ *
643
+ * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
644
+ */
645
+ windowMs(windowMs) {
646
+ const SET_TIMEOUT_MAX = 2 ** 31 - 1;
647
+ if (typeof windowMs !== "number" || Number.isNaN(windowMs) || windowMs < 1 || windowMs > SET_TIMEOUT_MAX) {
648
+ throw new ValidationError(
649
+ "ERR_ERL_WINDOW_MS",
650
+ `Invalid windowMs value: ${windowMs}${typeof windowMs !== "number" ? ` (${typeof windowMs})` : ""}, must be a number between 1 and ${SET_TIMEOUT_MAX} when using the default MemoryStore`
651
+ );
652
+ }
583
653
  }
584
654
  };
585
655
  var getValidations = (_enabled) => {
@@ -664,6 +734,7 @@ var parseOptions = (passedOptions) => {
664
734
  const notUndefinedOptions = omitUndefinedProperties(passedOptions);
665
735
  const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
666
736
  validations2.validationsConfig();
737
+ validations2.knownOptions(passedOptions);
667
738
  validations2.draftPolliHeaders(
668
739
  // @ts-expect-error see the note above.
669
740
  notUndefinedOptions.draft_polli_ratelimit_headers
@@ -706,6 +777,7 @@ var parseOptions = (passedOptions) => {
706
777
  validations2.ip(request.ip);
707
778
  validations2.trustProxy(request);
708
779
  validations2.xForwardedForHeader(request);
780
+ validations2.forwardedHeader(request);
709
781
  const ip = request.ip;
710
782
  let subnet = 56;
711
783
  if ((0, import_node_net3.isIPv6)(ip)) {
@@ -731,7 +803,9 @@ var parseOptions = (passedOptions) => {
731
803
  standardHeaders,
732
804
  // Note that this field is declared after the user's options are spread in,
733
805
  // so that this field doesn't get overridden with an un-promisified store!
734
- store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
806
+ store: promisifyStore(
807
+ notUndefinedOptions.store ?? new MemoryStore(validations2)
808
+ ),
735
809
  // Print an error to the console if a few known misconfigurations are detected.
736
810
  validations: validations2
737
811
  };
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by dts-bundle-generator v8.0.1
1
+ // Generated by dts-bundle-generator v8.1.2
2
2
 
3
3
  import { NextFunction, Request, RequestHandler, Response } from 'express';
4
4
 
@@ -60,6 +60,14 @@ declare const validations: {
60
60
  * @returns {void}
61
61
  */
62
62
  xForwardedForHeader(request: Request): void;
63
+ /**
64
+ * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
65
+ *
66
+ * @param request {Request} - The Express request object.
67
+ *
68
+ * @returns {void}
69
+ */
70
+ forwardedHeader(request: Request): void;
63
71
  /**
64
72
  * Ensures totalHits value from store is a positive integer.
65
73
  *
@@ -124,12 +132,13 @@ declare const validations: {
124
132
  * @returns {void}
125
133
  */
126
134
  headersResetTime(resetTime?: Date): void;
135
+ knownOptions(passedOptions?: Partial<Options>): void;
127
136
  /**
128
137
  * Checks the options.validate setting to ensure that only recognized
129
138
  * validations are enabled or disabled.
130
139
  *
131
140
  * If any unrecognized values are found, an error is logged that
132
- * includes the list of supported vaidations.
141
+ * includes the list of supported validations.
133
142
  */
134
143
  validationsConfig(): void;
135
144
  /**
@@ -141,6 +150,13 @@ declare const validations: {
141
150
  ipv6Subnet(ipv6Subnet?: any): void;
142
151
  ipv6SubnetOrKeyGenerator(options: Partial<Options>): void;
143
152
  keyGeneratorIpFallback(keyGenerator?: ValueDeterminingMiddleware<string>): void;
153
+ /**
154
+ * Checks to see if the window duration is greater than 2^32 - 1. This is only
155
+ * called by the default MemoryStore, since it uses Node's setInterval method.
156
+ *
157
+ * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
158
+ */
159
+ windowMs(windowMs: number): void;
144
160
  };
145
161
  export type Validations = typeof validations;
146
162
  /**
@@ -496,6 +512,7 @@ export type Client = {
496
512
  * @public
497
513
  */
498
514
  export declare class MemoryStore implements Store {
515
+ private validations?;
499
516
  /**
500
517
  * The duration of time before which all hit counts are reset (in milliseconds).
501
518
  */
@@ -521,6 +538,7 @@ export declare class MemoryStore implements Store {
521
538
  * cannot affect other instances.
522
539
  */
523
540
  localKeys: boolean;
541
+ constructor(validations?: Validations | undefined);
524
542
  /**
525
543
  * Method that initializes the store.
526
544
  *
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by dts-bundle-generator v8.0.1
1
+ // Generated by dts-bundle-generator v8.1.2
2
2
 
3
3
  import { NextFunction, Request, RequestHandler, Response } from 'express';
4
4
 
@@ -60,6 +60,14 @@ declare const validations: {
60
60
  * @returns {void}
61
61
  */
62
62
  xForwardedForHeader(request: Request): void;
63
+ /**
64
+ * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
65
+ *
66
+ * @param request {Request} - The Express request object.
67
+ *
68
+ * @returns {void}
69
+ */
70
+ forwardedHeader(request: Request): void;
63
71
  /**
64
72
  * Ensures totalHits value from store is a positive integer.
65
73
  *
@@ -124,12 +132,13 @@ declare const validations: {
124
132
  * @returns {void}
125
133
  */
126
134
  headersResetTime(resetTime?: Date): void;
135
+ knownOptions(passedOptions?: Partial<Options>): void;
127
136
  /**
128
137
  * Checks the options.validate setting to ensure that only recognized
129
138
  * validations are enabled or disabled.
130
139
  *
131
140
  * If any unrecognized values are found, an error is logged that
132
- * includes the list of supported vaidations.
141
+ * includes the list of supported validations.
133
142
  */
134
143
  validationsConfig(): void;
135
144
  /**
@@ -141,6 +150,13 @@ declare const validations: {
141
150
  ipv6Subnet(ipv6Subnet?: any): void;
142
151
  ipv6SubnetOrKeyGenerator(options: Partial<Options>): void;
143
152
  keyGeneratorIpFallback(keyGenerator?: ValueDeterminingMiddleware<string>): void;
153
+ /**
154
+ * Checks to see if the window duration is greater than 2^32 - 1. This is only
155
+ * called by the default MemoryStore, since it uses Node's setInterval method.
156
+ *
157
+ * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
158
+ */
159
+ windowMs(windowMs: number): void;
144
160
  };
145
161
  export type Validations = typeof validations;
146
162
  /**
@@ -496,6 +512,7 @@ export type Client = {
496
512
  * @public
497
513
  */
498
514
  export declare class MemoryStore implements Store {
515
+ private validations?;
499
516
  /**
500
517
  * The duration of time before which all hit counts are reset (in milliseconds).
501
518
  */
@@ -521,6 +538,7 @@ export declare class MemoryStore implements Store {
521
538
  * cannot affect other instances.
522
539
  */
523
540
  localKeys: boolean;
541
+ constructor(validations?: Validations | undefined);
524
542
  /**
525
543
  * Method that initializes the store.
526
544
  *
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by dts-bundle-generator v8.0.1
1
+ // Generated by dts-bundle-generator v8.1.2
2
2
 
3
3
  import { NextFunction, Request, RequestHandler, Response } from 'express';
4
4
 
@@ -60,6 +60,14 @@ declare const validations: {
60
60
  * @returns {void}
61
61
  */
62
62
  xForwardedForHeader(request: Request): void;
63
+ /**
64
+ * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
65
+ *
66
+ * @param request {Request} - The Express request object.
67
+ *
68
+ * @returns {void}
69
+ */
70
+ forwardedHeader(request: Request): void;
63
71
  /**
64
72
  * Ensures totalHits value from store is a positive integer.
65
73
  *
@@ -124,12 +132,13 @@ declare const validations: {
124
132
  * @returns {void}
125
133
  */
126
134
  headersResetTime(resetTime?: Date): void;
135
+ knownOptions(passedOptions?: Partial<Options>): void;
127
136
  /**
128
137
  * Checks the options.validate setting to ensure that only recognized
129
138
  * validations are enabled or disabled.
130
139
  *
131
140
  * If any unrecognized values are found, an error is logged that
132
- * includes the list of supported vaidations.
141
+ * includes the list of supported validations.
133
142
  */
134
143
  validationsConfig(): void;
135
144
  /**
@@ -141,6 +150,13 @@ declare const validations: {
141
150
  ipv6Subnet(ipv6Subnet?: any): void;
142
151
  ipv6SubnetOrKeyGenerator(options: Partial<Options>): void;
143
152
  keyGeneratorIpFallback(keyGenerator?: ValueDeterminingMiddleware<string>): void;
153
+ /**
154
+ * Checks to see if the window duration is greater than 2^32 - 1. This is only
155
+ * called by the default MemoryStore, since it uses Node's setInterval method.
156
+ *
157
+ * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
158
+ */
159
+ windowMs(windowMs: number): void;
144
160
  };
145
161
  export type Validations = typeof validations;
146
162
  /**
@@ -496,6 +512,7 @@ export type Client = {
496
512
  * @public
497
513
  */
498
514
  export declare class MemoryStore implements Store {
515
+ private validations?;
499
516
  /**
500
517
  * The duration of time before which all hit counts are reset (in milliseconds).
501
518
  */
@@ -521,6 +538,7 @@ export declare class MemoryStore implements Store {
521
538
  * cannot affect other instances.
522
539
  */
523
540
  localKeys: boolean;
541
+ constructor(validations?: Validations | undefined);
524
542
  /**
525
543
  * Method that initializes the store.
526
544
  *
package/dist/index.mjs CHANGED
@@ -10,7 +10,8 @@ function ipKeyGenerator(ip, ipv6Subnet = 56) {
10
10
 
11
11
  // source/memory-store.ts
12
12
  var MemoryStore = class {
13
- constructor() {
13
+ constructor(validations2) {
14
+ this.validations = validations2;
14
15
  /**
15
16
  * These two maps store usage (requests) and reset time by key (for example, IP
16
17
  * addresses or API keys).
@@ -36,6 +37,7 @@ var MemoryStore = class {
36
37
  */
37
38
  init(options) {
38
39
  this.windowMs = options.windowMs;
40
+ this.validations?.windowMs(this.windowMs);
39
41
  if (this.interval) clearInterval(this.interval);
40
42
  this.interval = setInterval(() => {
41
43
  this.clearExpired();
@@ -208,7 +210,7 @@ var setDraft6Headers = (response, info, windowMs) => {
208
210
  response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
209
211
  response.setHeader("RateLimit-Limit", info.limit.toString());
210
212
  response.setHeader("RateLimit-Remaining", info.remaining.toString());
211
- if (resetSeconds)
213
+ if (typeof resetSeconds === "number")
212
214
  response.setHeader("RateLimit-Reset", resetSeconds.toString());
213
215
  };
214
216
  var setDraft7Headers = (response, info, windowMs) => {
@@ -338,6 +340,21 @@ var validations = {
338
340
  );
339
341
  }
340
342
  },
343
+ /**
344
+ * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
345
+ *
346
+ * @param request {Request} - The Express request object.
347
+ *
348
+ * @returns {void}
349
+ */
350
+ forwardedHeader(request) {
351
+ if (request.headers.forwarded && request.ip === request.socket?.remoteAddress) {
352
+ throw new ValidationError(
353
+ "ERR_ERL_FORWARDED_HEADER",
354
+ `The 'Forwarded' header (standardized X-Forwarded-For) is set but currently being ignored. Add a custom keyGenerator to use a value from this header.`
355
+ );
356
+ }
357
+ },
341
358
  /**
342
359
  * Ensures totalHits value from store is a positive integer.
343
360
  *
@@ -475,12 +492,50 @@ var validations = {
475
492
  );
476
493
  }
477
494
  },
495
+ knownOptions(passedOptions) {
496
+ if (!passedOptions) return;
497
+ const optionsMap = {
498
+ windowMs: true,
499
+ limit: true,
500
+ message: true,
501
+ statusCode: true,
502
+ legacyHeaders: true,
503
+ standardHeaders: true,
504
+ identifier: true,
505
+ requestPropertyName: true,
506
+ skipFailedRequests: true,
507
+ skipSuccessfulRequests: true,
508
+ keyGenerator: true,
509
+ ipv6Subnet: true,
510
+ handler: true,
511
+ skip: true,
512
+ requestWasSuccessful: true,
513
+ store: true,
514
+ validate: true,
515
+ headers: true,
516
+ max: true,
517
+ passOnStoreError: true
518
+ };
519
+ const validOptions = Object.keys(optionsMap).concat(
520
+ "draft_polli_ratelimit_headers"
521
+ // not a valid option anymore, but we have a more specific check for this one, so don't warn for it here
522
+ );
523
+ for (const key of Object.keys(passedOptions)) {
524
+ if (!validOptions.includes(key)) {
525
+ throw new ValidationError(
526
+ "ERR_ERL_UNKNOWN_OPTION",
527
+ `Unexpected configuration option: ${key}`
528
+ // todo: suggest a valid option with a short levenstein distance?
529
+ );
530
+ }
531
+ }
532
+ },
478
533
  /**
479
534
  * Checks the options.validate setting to ensure that only recognized
480
535
  * validations are enabled or disabled.
481
536
  *
482
537
  * If any unrecognized values are found, an error is logged that
483
- * includes the list of supported vaidations.
538
+ * includes the list of supported validations.
484
539
  */
485
540
  validationsConfig() {
486
541
  const supportedValidations = Object.keys(this).filter(
@@ -551,6 +606,21 @@ var validations = {
551
606
  "Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits."
552
607
  );
553
608
  }
609
+ },
610
+ /**
611
+ * Checks to see if the window duration is greater than 2^32 - 1. This is only
612
+ * called by the default MemoryStore, since it uses Node's setInterval method.
613
+ *
614
+ * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
615
+ */
616
+ windowMs(windowMs) {
617
+ const SET_TIMEOUT_MAX = 2 ** 31 - 1;
618
+ if (typeof windowMs !== "number" || Number.isNaN(windowMs) || windowMs < 1 || windowMs > SET_TIMEOUT_MAX) {
619
+ throw new ValidationError(
620
+ "ERR_ERL_WINDOW_MS",
621
+ `Invalid windowMs value: ${windowMs}${typeof windowMs !== "number" ? ` (${typeof windowMs})` : ""}, must be a number between 1 and ${SET_TIMEOUT_MAX} when using the default MemoryStore`
622
+ );
623
+ }
554
624
  }
555
625
  };
556
626
  var getValidations = (_enabled) => {
@@ -635,6 +705,7 @@ var parseOptions = (passedOptions) => {
635
705
  const notUndefinedOptions = omitUndefinedProperties(passedOptions);
636
706
  const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
637
707
  validations2.validationsConfig();
708
+ validations2.knownOptions(passedOptions);
638
709
  validations2.draftPolliHeaders(
639
710
  // @ts-expect-error see the note above.
640
711
  notUndefinedOptions.draft_polli_ratelimit_headers
@@ -677,6 +748,7 @@ var parseOptions = (passedOptions) => {
677
748
  validations2.ip(request.ip);
678
749
  validations2.trustProxy(request);
679
750
  validations2.xForwardedForHeader(request);
751
+ validations2.forwardedHeader(request);
680
752
  const ip = request.ip;
681
753
  let subnet = 56;
682
754
  if (isIPv62(ip)) {
@@ -702,7 +774,9 @@ var parseOptions = (passedOptions) => {
702
774
  standardHeaders,
703
775
  // Note that this field is declared after the user's options are spread in,
704
776
  // so that this field doesn't get overridden with an un-promisified store!
705
- store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
777
+ store: promisifyStore(
778
+ notUndefinedOptions.store ?? new MemoryStore(validations2)
779
+ ),
706
780
  // Print an error to the console if a few known misconfigurations are detected.
707
781
  validations: validations2
708
782
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "8.0.1",
3
+ "version": "8.2.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",
@@ -77,29 +77,29 @@
77
77
  "express": ">= 4.11"
78
78
  },
79
79
  "devDependencies": {
80
- "@biomejs/biome": "2.1.1",
80
+ "@biomejs/biome": "2.3.1",
81
81
  "@express-rate-limit/prettier": "1.1.1",
82
82
  "@express-rate-limit/tsconfig": "1.0.2",
83
- "@jest/globals": "30.0.4",
84
- "@types/express": "5.0.3",
83
+ "@jest/globals": "30.2.0",
84
+ "@types/express": "5.0.4",
85
85
  "@types/jest": "30.0.0",
86
- "@types/node": "24.0.14",
86
+ "@types/node": "24.9.1",
87
87
  "@types/supertest": "6.0.3",
88
88
  "del-cli": "6.0.0",
89
- "dts-bundle-generator": "8.0.1",
90
- "esbuild": "0.25.6",
89
+ "dts-bundle-generator": "8.1.2",
90
+ "esbuild": "0.25.11",
91
91
  "express": "5.1.0",
92
92
  "husky": "9.1.7",
93
- "jest": "30.0.4",
94
- "lint-staged": "16.1.2",
95
- "mintlify": "4.2.15",
93
+ "jest": "30.2.0",
94
+ "lint-staged": "16.2.6",
95
+ "mintlify": "4.2.179",
96
96
  "npm-run-all": "4.1.5",
97
97
  "prettier": "3.6.2",
98
98
  "ratelimit-header-parser": "0.1.0",
99
- "supertest": "7.1.3",
100
- "ts-jest": "29.4.0",
99
+ "supertest": "7.1.4",
100
+ "ts-jest": "29.4.5",
101
101
  "ts-node": "10.9.2",
102
- "typescript": "5.8.3"
102
+ "typescript": "5.9.3"
103
103
  },
104
104
  "prettier": "@express-rate-limit/prettier",
105
105
  "lint-staged": {