express-rate-limit 7.5.0 → 8.0.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.mjs CHANGED
@@ -1,13 +1,187 @@
1
+ // source/ip-key-generator.ts
2
+ import { isIPv6 } from "node:net";
3
+ import iptools from "ip";
4
+ function ipKeyGenerator(ip, ipv6Subnet = 56) {
5
+ if (ipv6Subnet && isIPv6(ip)) {
6
+ return `${iptools.mask(
7
+ ip,
8
+ iptools.fromPrefixLen(ipv6Subnet)
9
+ )}/${ipv6Subnet}`;
10
+ }
11
+ return ip;
12
+ }
13
+
14
+ // source/memory-store.ts
15
+ var MemoryStore = class {
16
+ constructor() {
17
+ /**
18
+ * These two maps store usage (requests) and reset time by key (for example, IP
19
+ * addresses or API keys).
20
+ *
21
+ * They are split into two to avoid having to iterate through the entire set to
22
+ * determine which ones need reset. Instead, `Client`s are moved from `previous`
23
+ * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
24
+ * left in `previous`, i.e., those that have not made any recent requests, are
25
+ * known to be expired and can be deleted in bulk.
26
+ */
27
+ this.previous = /* @__PURE__ */ new Map();
28
+ this.current = /* @__PURE__ */ new Map();
29
+ /**
30
+ * Confirmation that the keys incremented in once instance of MemoryStore
31
+ * cannot affect other instances.
32
+ */
33
+ this.localKeys = true;
34
+ }
35
+ /**
36
+ * Method that initializes the store.
37
+ *
38
+ * @param options {Options} - The options used to setup the middleware.
39
+ */
40
+ init(options) {
41
+ this.windowMs = options.windowMs;
42
+ if (this.interval) clearInterval(this.interval);
43
+ this.interval = setInterval(() => {
44
+ this.clearExpired();
45
+ }, this.windowMs);
46
+ this.interval.unref?.();
47
+ }
48
+ /**
49
+ * Method to fetch a client's hit count and reset time.
50
+ *
51
+ * @param key {string} - The identifier for a client.
52
+ *
53
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
54
+ *
55
+ * @public
56
+ */
57
+ async get(key) {
58
+ return this.current.get(key) ?? this.previous.get(key);
59
+ }
60
+ /**
61
+ * Method to increment a client's hit counter.
62
+ *
63
+ * @param key {string} - The identifier for a client.
64
+ *
65
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
66
+ *
67
+ * @public
68
+ */
69
+ async increment(key) {
70
+ const client = this.getClient(key);
71
+ const now = Date.now();
72
+ if (client.resetTime.getTime() <= now) {
73
+ this.resetClient(client, now);
74
+ }
75
+ client.totalHits++;
76
+ return client;
77
+ }
78
+ /**
79
+ * Method to decrement a client's hit counter.
80
+ *
81
+ * @param key {string} - The identifier for a client.
82
+ *
83
+ * @public
84
+ */
85
+ async decrement(key) {
86
+ const client = this.getClient(key);
87
+ if (client.totalHits > 0) client.totalHits--;
88
+ }
89
+ /**
90
+ * Method to reset a client's hit counter.
91
+ *
92
+ * @param key {string} - The identifier for a client.
93
+ *
94
+ * @public
95
+ */
96
+ async resetKey(key) {
97
+ this.current.delete(key);
98
+ this.previous.delete(key);
99
+ }
100
+ /**
101
+ * Method to reset everyone's hit counter.
102
+ *
103
+ * @public
104
+ */
105
+ async resetAll() {
106
+ this.current.clear();
107
+ this.previous.clear();
108
+ }
109
+ /**
110
+ * Method to stop the timer (if currently running) and prevent any memory
111
+ * leaks.
112
+ *
113
+ * @public
114
+ */
115
+ shutdown() {
116
+ clearInterval(this.interval);
117
+ void this.resetAll();
118
+ }
119
+ /**
120
+ * Recycles a client by setting its hit count to zero, and reset time to
121
+ * `windowMs` milliseconds from now.
122
+ *
123
+ * NOT to be confused with `#resetKey()`, which removes a client from both the
124
+ * `current` and `previous` maps.
125
+ *
126
+ * @param client {Client} - The client to recycle.
127
+ * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
128
+ *
129
+ * @return {Client} - The modified client that was passed in, to allow for chaining.
130
+ */
131
+ resetClient(client, now = Date.now()) {
132
+ client.totalHits = 0;
133
+ client.resetTime.setTime(now + this.windowMs);
134
+ return client;
135
+ }
136
+ /**
137
+ * Retrieves or creates a client, given a key. Also ensures that the client being
138
+ * returned is in the `current` map.
139
+ *
140
+ * @param key {string} - The key under which the client is (or is to be) stored.
141
+ *
142
+ * @returns {Client} - The requested client.
143
+ */
144
+ getClient(key) {
145
+ if (this.current.has(key)) return this.current.get(key);
146
+ let client;
147
+ if (this.previous.has(key)) {
148
+ client = this.previous.get(key);
149
+ this.previous.delete(key);
150
+ } else {
151
+ client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
152
+ this.resetClient(client);
153
+ }
154
+ this.current.set(key, client);
155
+ return client;
156
+ }
157
+ /**
158
+ * Move current clients to previous, create a new map for current.
159
+ *
160
+ * This function is called every `windowMs`.
161
+ */
162
+ clearExpired() {
163
+ this.previous = this.current;
164
+ this.current = /* @__PURE__ */ new Map();
165
+ }
166
+ };
167
+
168
+ // source/rate-limit.ts
169
+ import { isIPv6 as isIPv62 } from "node:net";
170
+
1
171
  // source/headers.ts
2
- import { Buffer } from "buffer";
3
- import { createHash } from "crypto";
4
- var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
5
- var getResetSeconds = (resetTime, windowMs) => {
6
- let resetSeconds = void 0;
172
+ import { Buffer } from "node:buffer";
173
+ import { createHash } from "node:crypto";
174
+ var SUPPORTED_DRAFT_VERSIONS = [
175
+ "draft-6",
176
+ "draft-7",
177
+ "draft-8"
178
+ ];
179
+ var getResetSeconds = (windowMs, resetTime) => {
180
+ let resetSeconds;
7
181
  if (resetTime) {
8
182
  const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
9
183
  resetSeconds = Math.max(0, deltaSeconds);
10
- } else if (windowMs) {
184
+ } else {
11
185
  resetSeconds = Math.ceil(windowMs / 1e3);
12
186
  }
13
187
  return resetSeconds;
@@ -19,8 +193,7 @@ var getPartitionKey = (key) => {
19
193
  return Buffer.from(partitionKey).toString("base64");
20
194
  };
21
195
  var setLegacyHeaders = (response, info) => {
22
- if (response.headersSent)
23
- return;
196
+ if (response.headersSent) return;
24
197
  response.setHeader("X-RateLimit-Limit", info.limit.toString());
25
198
  response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
26
199
  if (info.resetTime instanceof Date) {
@@ -32,10 +205,9 @@ var setLegacyHeaders = (response, info) => {
32
205
  }
33
206
  };
34
207
  var setDraft6Headers = (response, info, windowMs) => {
35
- if (response.headersSent)
36
- return;
208
+ if (response.headersSent) return;
37
209
  const windowSeconds = Math.ceil(windowMs / 1e3);
38
- const resetSeconds = getResetSeconds(info.resetTime);
210
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
39
211
  response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
40
212
  response.setHeader("RateLimit-Limit", info.limit.toString());
41
213
  response.setHeader("RateLimit-Remaining", info.remaining.toString());
@@ -43,10 +215,9 @@ var setDraft6Headers = (response, info, windowMs) => {
43
215
  response.setHeader("RateLimit-Reset", resetSeconds.toString());
44
216
  };
45
217
  var setDraft7Headers = (response, info, windowMs) => {
46
- if (response.headersSent)
47
- return;
218
+ if (response.headersSent) return;
48
219
  const windowSeconds = Math.ceil(windowMs / 1e3);
49
- const resetSeconds = getResetSeconds(info.resetTime, windowMs);
220
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
50
221
  response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
51
222
  response.setHeader(
52
223
  "RateLimit",
@@ -54,25 +225,35 @@ var setDraft7Headers = (response, info, windowMs) => {
54
225
  );
55
226
  };
56
227
  var setDraft8Headers = (response, info, windowMs, name, key) => {
57
- if (response.headersSent)
58
- return;
228
+ if (response.headersSent) return;
59
229
  const windowSeconds = Math.ceil(windowMs / 1e3);
60
- const resetSeconds = getResetSeconds(info.resetTime, windowMs);
230
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
61
231
  const partitionKey = getPartitionKey(key);
62
- const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
63
232
  const header = `r=${info.remaining}; t=${resetSeconds}`;
64
- response.append("RateLimit-Policy", `"${name}"; ${policy}`);
233
+ const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
65
234
  response.append("RateLimit", `"${name}"; ${header}`);
235
+ response.append("RateLimit-Policy", `"${name}"; ${policy}`);
66
236
  };
67
237
  var setRetryAfterHeader = (response, info, windowMs) => {
68
- if (response.headersSent)
69
- return;
70
- const resetSeconds = getResetSeconds(info.resetTime, windowMs);
238
+ if (response.headersSent) return;
239
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
71
240
  response.setHeader("Retry-After", resetSeconds.toString());
72
241
  };
73
242
 
243
+ // source/utils.ts
244
+ var omitUndefinedProperties = (passedOptions) => {
245
+ const omittedOptions = {};
246
+ for (const k of Object.keys(passedOptions)) {
247
+ const key = k;
248
+ if (passedOptions[key] !== void 0) {
249
+ omittedOptions[key] = passedOptions[key];
250
+ }
251
+ }
252
+ return omittedOptions;
253
+ };
254
+
74
255
  // source/validations.ts
75
- import { isIP } from "net";
256
+ import { isIP } from "node:net";
76
257
  var ValidationError = class extends Error {
77
258
  /**
78
259
  * The code must be a string, in snake case and all capital, that starts with
@@ -94,14 +275,12 @@ var ChangeWarning = class extends ValidationError {
94
275
  var usedStores = /* @__PURE__ */ new Set();
95
276
  var singleCountKeys = /* @__PURE__ */ new WeakMap();
96
277
  var validations = {
97
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
98
278
  enabled: {
99
279
  default: true
100
280
  },
101
281
  // Should be EnabledValidations type, but that's a circular reference
102
282
  disable() {
103
- for (const k of Object.keys(this.enabled))
104
- this.enabled[k] = false;
283
+ for (const k of Object.keys(this.enabled)) this.enabled[k] = false;
105
284
  },
106
285
  /**
107
286
  * Checks whether the IP address is valid, and that it does not have a port
@@ -274,7 +453,8 @@ var validations = {
274
453
  * @returns {void}
275
454
  */
276
455
  headersDraftVersion(version) {
277
- if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
456
+ if (typeof version !== "string" || // @ts-expect-error This is fine. If version is not in the array, it will just return false.
457
+ !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
278
458
  const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
279
459
  throw new ValidationError(
280
460
  "ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
@@ -330,7 +510,8 @@ var validations = {
330
510
  const { stack } = new Error(
331
511
  "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
332
512
  );
333
- if (stack?.includes("Layer.handle [as handle_request]")) {
513
+ if (stack?.includes("Layer.handle [as handle_request]") || // express v4
514
+ stack?.includes("Layer.handleRequest")) {
334
515
  if (!store.localKeys) {
335
516
  throw new ValidationError(
336
517
  "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
@@ -342,6 +523,37 @@ var validations = {
342
523
  `express-rate-limit instance should be created at app initialization, not when responding to a request.`
343
524
  );
344
525
  }
526
+ },
527
+ ipv6Subnet(ipv6Subnet) {
528
+ if (ipv6Subnet === false) {
529
+ return;
530
+ }
531
+ if (!Number.isInteger(ipv6Subnet) || ipv6Subnet < 32 || ipv6Subnet > 64) {
532
+ throw new ValidationError(
533
+ "ERR_ERL_IPV6_SUBNET",
534
+ `Unexpected ipv6Subnet value: ${ipv6Subnet}. Expected an integer between 32 and 64 (usually 48-64).`
535
+ );
536
+ }
537
+ },
538
+ ipv6SubnetOrKeyGenerator(options) {
539
+ if (options.ipv6Subnet !== void 0 && options.keyGenerator) {
540
+ throw new ValidationError(
541
+ "ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR",
542
+ `Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.`
543
+ );
544
+ }
545
+ },
546
+ keyGeneratorIpFallback(keyGenerator) {
547
+ if (!keyGenerator) {
548
+ return;
549
+ }
550
+ const src = keyGenerator.toString();
551
+ if ((src.includes("req.ip") || src.includes("request.ip")) && !src.includes("ipKeyGenerator")) {
552
+ throw new ValidationError(
553
+ "ERR_ERL_KEY_GEN_IPV6",
554
+ `Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits.`
555
+ );
556
+ }
345
557
  }
346
558
  };
347
559
  var getValidations = (_enabled) => {
@@ -356,9 +568,7 @@ var getValidations = (_enabled) => {
356
568
  ..._enabled
357
569
  };
358
570
  }
359
- const wrappedValidations = {
360
- enabled
361
- };
571
+ const wrappedValidations = { enabled };
362
572
  for (const [name, validation] of Object.entries(validations)) {
363
573
  if (typeof validation === "function")
364
574
  wrappedValidations[name] = (...args) => {
@@ -372,175 +582,15 @@ var getValidations = (_enabled) => {
372
582
  args
373
583
  );
374
584
  } catch (error) {
375
- if (error instanceof ChangeWarning)
376
- console.warn(error);
377
- else
378
- console.error(error);
585
+ if (error instanceof ChangeWarning) console.warn(error);
586
+ else console.error(error);
379
587
  }
380
588
  };
381
589
  }
382
590
  return wrappedValidations;
383
591
  };
384
592
 
385
- // source/memory-store.ts
386
- var MemoryStore = class {
387
- constructor() {
388
- /**
389
- * These two maps store usage (requests) and reset time by key (for example, IP
390
- * addresses or API keys).
391
- *
392
- * They are split into two to avoid having to iterate through the entire set to
393
- * determine which ones need reset. Instead, `Client`s are moved from `previous`
394
- * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
395
- * left in `previous`, i.e., those that have not made any recent requests, are
396
- * known to be expired and can be deleted in bulk.
397
- */
398
- this.previous = /* @__PURE__ */ new Map();
399
- this.current = /* @__PURE__ */ new Map();
400
- /**
401
- * Confirmation that the keys incremented in once instance of MemoryStore
402
- * cannot affect other instances.
403
- */
404
- this.localKeys = true;
405
- }
406
- /**
407
- * Method that initializes the store.
408
- *
409
- * @param options {Options} - The options used to setup the middleware.
410
- */
411
- init(options) {
412
- this.windowMs = options.windowMs;
413
- if (this.interval)
414
- clearInterval(this.interval);
415
- this.interval = setInterval(() => {
416
- this.clearExpired();
417
- }, this.windowMs);
418
- if (this.interval.unref)
419
- this.interval.unref();
420
- }
421
- /**
422
- * Method to fetch a client's hit count and reset time.
423
- *
424
- * @param key {string} - The identifier for a client.
425
- *
426
- * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
427
- *
428
- * @public
429
- */
430
- async get(key) {
431
- return this.current.get(key) ?? this.previous.get(key);
432
- }
433
- /**
434
- * Method to increment a client's hit counter.
435
- *
436
- * @param key {string} - The identifier for a client.
437
- *
438
- * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
439
- *
440
- * @public
441
- */
442
- async increment(key) {
443
- const client = this.getClient(key);
444
- const now = Date.now();
445
- if (client.resetTime.getTime() <= now) {
446
- this.resetClient(client, now);
447
- }
448
- client.totalHits++;
449
- return client;
450
- }
451
- /**
452
- * Method to decrement a client's hit counter.
453
- *
454
- * @param key {string} - The identifier for a client.
455
- *
456
- * @public
457
- */
458
- async decrement(key) {
459
- const client = this.getClient(key);
460
- if (client.totalHits > 0)
461
- client.totalHits--;
462
- }
463
- /**
464
- * Method to reset a client's hit counter.
465
- *
466
- * @param key {string} - The identifier for a client.
467
- *
468
- * @public
469
- */
470
- async resetKey(key) {
471
- this.current.delete(key);
472
- this.previous.delete(key);
473
- }
474
- /**
475
- * Method to reset everyone's hit counter.
476
- *
477
- * @public
478
- */
479
- async resetAll() {
480
- this.current.clear();
481
- this.previous.clear();
482
- }
483
- /**
484
- * Method to stop the timer (if currently running) and prevent any memory
485
- * leaks.
486
- *
487
- * @public
488
- */
489
- shutdown() {
490
- clearInterval(this.interval);
491
- void this.resetAll();
492
- }
493
- /**
494
- * Recycles a client by setting its hit count to zero, and reset time to
495
- * `windowMs` milliseconds from now.
496
- *
497
- * NOT to be confused with `#resetKey()`, which removes a client from both the
498
- * `current` and `previous` maps.
499
- *
500
- * @param client {Client} - The client to recycle.
501
- * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
502
- *
503
- * @return {Client} - The modified client that was passed in, to allow for chaining.
504
- */
505
- resetClient(client, now = Date.now()) {
506
- client.totalHits = 0;
507
- client.resetTime.setTime(now + this.windowMs);
508
- return client;
509
- }
510
- /**
511
- * Retrieves or creates a client, given a key. Also ensures that the client being
512
- * returned is in the `current` map.
513
- *
514
- * @param key {string} - The key under which the client is (or is to be) stored.
515
- *
516
- * @returns {Client} - The requested client.
517
- */
518
- getClient(key) {
519
- if (this.current.has(key))
520
- return this.current.get(key);
521
- let client;
522
- if (this.previous.has(key)) {
523
- client = this.previous.get(key);
524
- this.previous.delete(key);
525
- } else {
526
- client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
527
- this.resetClient(client);
528
- }
529
- this.current.set(key, client);
530
- return client;
531
- }
532
- /**
533
- * Move current clients to previous, create a new map for current.
534
- *
535
- * This function is called every `windowMs`.
536
- */
537
- clearExpired() {
538
- this.previous = this.current;
539
- this.current = /* @__PURE__ */ new Map();
540
- }
541
- };
542
-
543
- // source/lib.ts
593
+ // source/rate-limit.ts
544
594
  var isLegacyStore = (store) => (
545
595
  // Check that `incr` exists but `increment` does not - store authors might want
546
596
  // to keep both around for backwards compatibility.
@@ -557,8 +607,7 @@ var promisifyStore = (passedStore) => {
557
607
  legacyStore.incr(
558
608
  key,
559
609
  (error, totalHits, resetTime) => {
560
- if (error)
561
- reject(error);
610
+ if (error) reject(error);
562
611
  resolve({ totalHits, resetTime });
563
612
  }
564
613
  );
@@ -585,18 +634,8 @@ var getOptionsFromConfig = (config) => {
585
634
  validate: validations2.enabled
586
635
  };
587
636
  };
588
- var omitUndefinedOptions = (passedOptions) => {
589
- const omittedOptions = {};
590
- for (const k of Object.keys(passedOptions)) {
591
- const key = k;
592
- if (passedOptions[key] !== void 0) {
593
- omittedOptions[key] = passedOptions[key];
594
- }
595
- }
596
- return omittedOptions;
597
- };
598
637
  var parseOptions = (passedOptions) => {
599
- const notUndefinedOptions = omitUndefinedOptions(passedOptions);
638
+ const notUndefinedOptions = omitUndefinedProperties(passedOptions);
600
639
  const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
601
640
  validations2.validationsConfig();
602
641
  validations2.draftPolliHeaders(
@@ -604,9 +643,13 @@ var parseOptions = (passedOptions) => {
604
643
  notUndefinedOptions.draft_polli_ratelimit_headers
605
644
  );
606
645
  validations2.onLimitReached(notUndefinedOptions.onLimitReached);
646
+ if (notUndefinedOptions.ipv6Subnet !== void 0 && typeof notUndefinedOptions.ipv6Subnet !== "function") {
647
+ validations2.ipv6Subnet(notUndefinedOptions.ipv6Subnet);
648
+ }
649
+ validations2.keyGeneratorIpFallback(notUndefinedOptions.keyGenerator);
650
+ validations2.ipv6SubnetOrKeyGenerator(notUndefinedOptions);
607
651
  let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
608
- if (standardHeaders === true)
609
- standardHeaders = "draft-6";
652
+ if (standardHeaders === true) standardHeaders = "draft-6";
610
653
  const config = {
611
654
  windowMs: 60 * 1e3,
612
655
  limit: passedOptions.max ?? 5,
@@ -622,14 +665,10 @@ var parseOptions = (passedOptions) => {
622
665
  const minutes = config.windowMs / (1e3 * 60);
623
666
  const hours = config.windowMs / (1e3 * 60 * 60);
624
667
  const days = config.windowMs / (1e3 * 60 * 60 * 24);
625
- if (seconds < 60)
626
- duration = `${seconds}sec`;
627
- else if (minutes < 60)
628
- duration = `${minutes}min`;
629
- else if (hours < 24)
630
- duration = `${hours}hr${hours > 1 ? "s" : ""}`;
631
- else
632
- duration = `${days}day${days > 1 ? "s" : ""}`;
668
+ if (seconds < 60) duration = `${seconds}sec`;
669
+ else if (minutes < 60) duration = `${minutes}min`;
670
+ else if (hours < 24) duration = `${hours}hr${hours > 1 ? "s" : ""}`;
671
+ else duration = `${days}day${days > 1 ? "s" : ""}`;
633
672
  return `${limit}-in-${duration}`;
634
673
  },
635
674
  requestPropertyName: "rateLimit",
@@ -637,29 +676,35 @@ var parseOptions = (passedOptions) => {
637
676
  skipSuccessfulRequests: false,
638
677
  requestWasSuccessful: (_request, response) => response.statusCode < 400,
639
678
  skip: (_request, _response) => false,
640
- keyGenerator(request, _response) {
679
+ async keyGenerator(request, response) {
641
680
  validations2.ip(request.ip);
642
681
  validations2.trustProxy(request);
643
682
  validations2.xForwardedForHeader(request);
644
- return request.ip;
683
+ const ip = request.ip;
684
+ let subnet = 56;
685
+ if (isIPv62(ip)) {
686
+ subnet = typeof config.ipv6Subnet === "function" ? await config.ipv6Subnet(request, response) : config.ipv6Subnet;
687
+ if (typeof config.ipv6Subnet === "function")
688
+ validations2.ipv6Subnet(subnet);
689
+ }
690
+ return ipKeyGenerator(ip, subnet);
645
691
  },
692
+ ipv6Subnet: 56,
646
693
  async handler(request, response, _next, _optionsUsed) {
647
694
  response.status(config.statusCode);
648
695
  const message = typeof config.message === "function" ? await config.message(
649
696
  request,
650
697
  response
651
698
  ) : config.message;
652
- if (!response.writableEnded) {
653
- response.send(message);
654
- }
699
+ if (!response.writableEnded) response.send(message);
655
700
  },
656
701
  passOnStoreError: false,
657
- // Allow the default options to be overriden by the passed options.
702
+ // Allow the default options to be overridden by the passed options.
658
703
  ...notUndefinedOptions,
659
704
  // `standardHeaders` is resolved into a draft version above, use that.
660
705
  standardHeaders,
661
706
  // Note that this field is declared after the user's options are spread in,
662
- // so that this field doesn't get overriden with an un-promisified store!
707
+ // so that this field doesn't get overridden with an un-promisified store!
663
708
  store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
664
709
  // Print an error to the console if a few known misconfigurations are detected.
665
710
  validations: validations2
@@ -683,8 +728,7 @@ var rateLimit = (passedOptions) => {
683
728
  const options = getOptionsFromConfig(config);
684
729
  config.validations.creationStack(config.store);
685
730
  config.validations.unsharedStore(config.store);
686
- if (typeof config.store.init === "function")
687
- config.store.init(options);
731
+ if (typeof config.store.init === "function") config.store.init(options);
688
732
  const middleware = handleAsyncErrors(
689
733
  async (request, response, next) => {
690
734
  const skip = await config.skip(request, response);
@@ -720,7 +764,8 @@ var rateLimit = (passedOptions) => {
720
764
  limit,
721
765
  used: totalHits,
722
766
  remaining: Math.max(limit - totalHits, 0),
723
- resetTime
767
+ resetTime,
768
+ key
724
769
  };
725
770
  Object.defineProperty(info, "current", {
726
771
  configurable: false,
@@ -769,8 +814,7 @@ var rateLimit = (passedOptions) => {
769
814
  await decrementKey();
770
815
  });
771
816
  response.on("close", async () => {
772
- if (!response.writableEnded)
773
- await decrementKey();
817
+ if (!response.writableEnded) await decrementKey();
774
818
  });
775
819
  response.on("error", async () => {
776
820
  await decrementKey();
@@ -801,9 +845,10 @@ var rateLimit = (passedOptions) => {
801
845
  middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
802
846
  return middleware;
803
847
  };
804
- var lib_default = rateLimit;
848
+ var rate_limit_default = rateLimit;
805
849
  export {
806
850
  MemoryStore,
807
- lib_default as default,
808
- lib_default as rateLimit
851
+ rate_limit_default as default,
852
+ ipKeyGenerator,
853
+ rate_limit_default as rateLimit
809
854
  };