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.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,27 +17,210 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // source/index.ts
21
- var source_exports = {};
22
- __export(source_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
23
33
  MemoryStore: () => MemoryStore,
24
- default: () => lib_default,
25
- rateLimit: () => lib_default
34
+ default: () => rate_limit_default,
35
+ ipKeyGenerator: () => ipKeyGenerator,
36
+ rateLimit: () => rate_limit_default
26
37
  });
27
- module.exports = __toCommonJS(source_exports);
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // source/ip-key-generator.ts
41
+ var import_node_net = require("node:net");
42
+ var import_ip = __toESM(require("ip"), 1);
43
+ function ipKeyGenerator(ip, ipv6Subnet = 56) {
44
+ if (ipv6Subnet && (0, import_node_net.isIPv6)(ip)) {
45
+ return `${import_ip.default.mask(
46
+ ip,
47
+ import_ip.default.fromPrefixLen(ipv6Subnet)
48
+ )}/${ipv6Subnet}`;
49
+ }
50
+ return ip;
51
+ }
52
+
53
+ // source/memory-store.ts
54
+ var MemoryStore = class {
55
+ constructor() {
56
+ /**
57
+ * These two maps store usage (requests) and reset time by key (for example, IP
58
+ * addresses or API keys).
59
+ *
60
+ * They are split into two to avoid having to iterate through the entire set to
61
+ * determine which ones need reset. Instead, `Client`s are moved from `previous`
62
+ * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
63
+ * left in `previous`, i.e., those that have not made any recent requests, are
64
+ * known to be expired and can be deleted in bulk.
65
+ */
66
+ this.previous = /* @__PURE__ */ new Map();
67
+ this.current = /* @__PURE__ */ new Map();
68
+ /**
69
+ * Confirmation that the keys incremented in once instance of MemoryStore
70
+ * cannot affect other instances.
71
+ */
72
+ this.localKeys = true;
73
+ }
74
+ /**
75
+ * Method that initializes the store.
76
+ *
77
+ * @param options {Options} - The options used to setup the middleware.
78
+ */
79
+ init(options) {
80
+ this.windowMs = options.windowMs;
81
+ if (this.interval) clearInterval(this.interval);
82
+ this.interval = setInterval(() => {
83
+ this.clearExpired();
84
+ }, this.windowMs);
85
+ this.interval.unref?.();
86
+ }
87
+ /**
88
+ * Method to fetch a client's hit count and reset time.
89
+ *
90
+ * @param key {string} - The identifier for a client.
91
+ *
92
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
93
+ *
94
+ * @public
95
+ */
96
+ async get(key) {
97
+ return this.current.get(key) ?? this.previous.get(key);
98
+ }
99
+ /**
100
+ * Method to increment a client's hit counter.
101
+ *
102
+ * @param key {string} - The identifier for a client.
103
+ *
104
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
105
+ *
106
+ * @public
107
+ */
108
+ async increment(key) {
109
+ const client = this.getClient(key);
110
+ const now = Date.now();
111
+ if (client.resetTime.getTime() <= now) {
112
+ this.resetClient(client, now);
113
+ }
114
+ client.totalHits++;
115
+ return client;
116
+ }
117
+ /**
118
+ * Method to decrement a client's hit counter.
119
+ *
120
+ * @param key {string} - The identifier for a client.
121
+ *
122
+ * @public
123
+ */
124
+ async decrement(key) {
125
+ const client = this.getClient(key);
126
+ if (client.totalHits > 0) client.totalHits--;
127
+ }
128
+ /**
129
+ * Method to reset a client's hit counter.
130
+ *
131
+ * @param key {string} - The identifier for a client.
132
+ *
133
+ * @public
134
+ */
135
+ async resetKey(key) {
136
+ this.current.delete(key);
137
+ this.previous.delete(key);
138
+ }
139
+ /**
140
+ * Method to reset everyone's hit counter.
141
+ *
142
+ * @public
143
+ */
144
+ async resetAll() {
145
+ this.current.clear();
146
+ this.previous.clear();
147
+ }
148
+ /**
149
+ * Method to stop the timer (if currently running) and prevent any memory
150
+ * leaks.
151
+ *
152
+ * @public
153
+ */
154
+ shutdown() {
155
+ clearInterval(this.interval);
156
+ void this.resetAll();
157
+ }
158
+ /**
159
+ * Recycles a client by setting its hit count to zero, and reset time to
160
+ * `windowMs` milliseconds from now.
161
+ *
162
+ * NOT to be confused with `#resetKey()`, which removes a client from both the
163
+ * `current` and `previous` maps.
164
+ *
165
+ * @param client {Client} - The client to recycle.
166
+ * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
167
+ *
168
+ * @return {Client} - The modified client that was passed in, to allow for chaining.
169
+ */
170
+ resetClient(client, now = Date.now()) {
171
+ client.totalHits = 0;
172
+ client.resetTime.setTime(now + this.windowMs);
173
+ return client;
174
+ }
175
+ /**
176
+ * Retrieves or creates a client, given a key. Also ensures that the client being
177
+ * returned is in the `current` map.
178
+ *
179
+ * @param key {string} - The key under which the client is (or is to be) stored.
180
+ *
181
+ * @returns {Client} - The requested client.
182
+ */
183
+ getClient(key) {
184
+ if (this.current.has(key)) return this.current.get(key);
185
+ let client;
186
+ if (this.previous.has(key)) {
187
+ client = this.previous.get(key);
188
+ this.previous.delete(key);
189
+ } else {
190
+ client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
191
+ this.resetClient(client);
192
+ }
193
+ this.current.set(key, client);
194
+ return client;
195
+ }
196
+ /**
197
+ * Move current clients to previous, create a new map for current.
198
+ *
199
+ * This function is called every `windowMs`.
200
+ */
201
+ clearExpired() {
202
+ this.previous = this.current;
203
+ this.current = /* @__PURE__ */ new Map();
204
+ }
205
+ };
206
+
207
+ // source/rate-limit.ts
208
+ var import_node_net3 = require("node:net");
28
209
 
29
210
  // source/headers.ts
30
- var import_node_buffer = require("buffer");
31
- var import_node_crypto = require("crypto");
32
- var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
33
- var getResetSeconds = (resetTime, windowMs) => {
34
- let resetSeconds = void 0;
211
+ var import_node_buffer = require("node:buffer");
212
+ var import_node_crypto = require("node:crypto");
213
+ var SUPPORTED_DRAFT_VERSIONS = [
214
+ "draft-6",
215
+ "draft-7",
216
+ "draft-8"
217
+ ];
218
+ var getResetSeconds = (windowMs, resetTime) => {
219
+ let resetSeconds;
35
220
  if (resetTime) {
36
221
  const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
37
222
  resetSeconds = Math.max(0, deltaSeconds);
38
- } else if (windowMs) {
223
+ } else {
39
224
  resetSeconds = Math.ceil(windowMs / 1e3);
40
225
  }
41
226
  return resetSeconds;
@@ -47,8 +232,7 @@ var getPartitionKey = (key) => {
47
232
  return import_node_buffer.Buffer.from(partitionKey).toString("base64");
48
233
  };
49
234
  var setLegacyHeaders = (response, info) => {
50
- if (response.headersSent)
51
- return;
235
+ if (response.headersSent) return;
52
236
  response.setHeader("X-RateLimit-Limit", info.limit.toString());
53
237
  response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
54
238
  if (info.resetTime instanceof Date) {
@@ -60,10 +244,9 @@ var setLegacyHeaders = (response, info) => {
60
244
  }
61
245
  };
62
246
  var setDraft6Headers = (response, info, windowMs) => {
63
- if (response.headersSent)
64
- return;
247
+ if (response.headersSent) return;
65
248
  const windowSeconds = Math.ceil(windowMs / 1e3);
66
- const resetSeconds = getResetSeconds(info.resetTime);
249
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
67
250
  response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
68
251
  response.setHeader("RateLimit-Limit", info.limit.toString());
69
252
  response.setHeader("RateLimit-Remaining", info.remaining.toString());
@@ -71,10 +254,9 @@ var setDraft6Headers = (response, info, windowMs) => {
71
254
  response.setHeader("RateLimit-Reset", resetSeconds.toString());
72
255
  };
73
256
  var setDraft7Headers = (response, info, windowMs) => {
74
- if (response.headersSent)
75
- return;
257
+ if (response.headersSent) return;
76
258
  const windowSeconds = Math.ceil(windowMs / 1e3);
77
- const resetSeconds = getResetSeconds(info.resetTime, windowMs);
259
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
78
260
  response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
79
261
  response.setHeader(
80
262
  "RateLimit",
@@ -82,25 +264,35 @@ var setDraft7Headers = (response, info, windowMs) => {
82
264
  );
83
265
  };
84
266
  var setDraft8Headers = (response, info, windowMs, name, key) => {
85
- if (response.headersSent)
86
- return;
267
+ if (response.headersSent) return;
87
268
  const windowSeconds = Math.ceil(windowMs / 1e3);
88
- const resetSeconds = getResetSeconds(info.resetTime, windowMs);
269
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
89
270
  const partitionKey = getPartitionKey(key);
90
- const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
91
271
  const header = `r=${info.remaining}; t=${resetSeconds}`;
92
- response.append("RateLimit-Policy", `"${name}"; ${policy}`);
272
+ const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
93
273
  response.append("RateLimit", `"${name}"; ${header}`);
274
+ response.append("RateLimit-Policy", `"${name}"; ${policy}`);
94
275
  };
95
276
  var setRetryAfterHeader = (response, info, windowMs) => {
96
- if (response.headersSent)
97
- return;
98
- const resetSeconds = getResetSeconds(info.resetTime, windowMs);
277
+ if (response.headersSent) return;
278
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
99
279
  response.setHeader("Retry-After", resetSeconds.toString());
100
280
  };
101
281
 
282
+ // source/utils.ts
283
+ var omitUndefinedProperties = (passedOptions) => {
284
+ const omittedOptions = {};
285
+ for (const k of Object.keys(passedOptions)) {
286
+ const key = k;
287
+ if (passedOptions[key] !== void 0) {
288
+ omittedOptions[key] = passedOptions[key];
289
+ }
290
+ }
291
+ return omittedOptions;
292
+ };
293
+
102
294
  // source/validations.ts
103
- var import_node_net = require("net");
295
+ var import_node_net2 = require("node:net");
104
296
  var ValidationError = class extends Error {
105
297
  /**
106
298
  * The code must be a string, in snake case and all capital, that starts with
@@ -122,14 +314,12 @@ var ChangeWarning = class extends ValidationError {
122
314
  var usedStores = /* @__PURE__ */ new Set();
123
315
  var singleCountKeys = /* @__PURE__ */ new WeakMap();
124
316
  var validations = {
125
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
126
317
  enabled: {
127
318
  default: true
128
319
  },
129
320
  // Should be EnabledValidations type, but that's a circular reference
130
321
  disable() {
131
- for (const k of Object.keys(this.enabled))
132
- this.enabled[k] = false;
322
+ for (const k of Object.keys(this.enabled)) this.enabled[k] = false;
133
323
  },
134
324
  /**
135
325
  * Checks whether the IP address is valid, and that it does not have a port
@@ -148,7 +338,7 @@ var validations = {
148
338
  `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
149
339
  );
150
340
  }
151
- if (!(0, import_node_net.isIP)(ip)) {
341
+ if (!(0, import_node_net2.isIP)(ip)) {
152
342
  throw new ValidationError(
153
343
  "ERR_ERL_INVALID_IP_ADDRESS",
154
344
  `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
@@ -302,7 +492,8 @@ var validations = {
302
492
  * @returns {void}
303
493
  */
304
494
  headersDraftVersion(version) {
305
- if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
495
+ if (typeof version !== "string" || // @ts-expect-error This is fine. If version is not in the array, it will just return false.
496
+ !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
306
497
  const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
307
498
  throw new ValidationError(
308
499
  "ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
@@ -358,7 +549,8 @@ var validations = {
358
549
  const { stack } = new Error(
359
550
  "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
360
551
  );
361
- if (stack?.includes("Layer.handle [as handle_request]")) {
552
+ if (stack?.includes("Layer.handle [as handle_request]") || // express v4
553
+ stack?.includes("Layer.handleRequest")) {
362
554
  if (!store.localKeys) {
363
555
  throw new ValidationError(
364
556
  "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
@@ -370,6 +562,37 @@ var validations = {
370
562
  `express-rate-limit instance should be created at app initialization, not when responding to a request.`
371
563
  );
372
564
  }
565
+ },
566
+ ipv6Subnet(ipv6Subnet) {
567
+ if (ipv6Subnet === false) {
568
+ return;
569
+ }
570
+ if (!Number.isInteger(ipv6Subnet) || ipv6Subnet < 32 || ipv6Subnet > 64) {
571
+ throw new ValidationError(
572
+ "ERR_ERL_IPV6_SUBNET",
573
+ `Unexpected ipv6Subnet value: ${ipv6Subnet}. Expected an integer between 32 and 64 (usually 48-64).`
574
+ );
575
+ }
576
+ },
577
+ ipv6SubnetOrKeyGenerator(options) {
578
+ if (options.ipv6Subnet !== void 0 && options.keyGenerator) {
579
+ throw new ValidationError(
580
+ "ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR",
581
+ `Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.`
582
+ );
583
+ }
584
+ },
585
+ keyGeneratorIpFallback(keyGenerator) {
586
+ if (!keyGenerator) {
587
+ return;
588
+ }
589
+ const src = keyGenerator.toString();
590
+ if ((src.includes("req.ip") || src.includes("request.ip")) && !src.includes("ipKeyGenerator")) {
591
+ throw new ValidationError(
592
+ "ERR_ERL_KEY_GEN_IPV6",
593
+ `Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits.`
594
+ );
595
+ }
373
596
  }
374
597
  };
375
598
  var getValidations = (_enabled) => {
@@ -384,9 +607,7 @@ var getValidations = (_enabled) => {
384
607
  ..._enabled
385
608
  };
386
609
  }
387
- const wrappedValidations = {
388
- enabled
389
- };
610
+ const wrappedValidations = { enabled };
390
611
  for (const [name, validation] of Object.entries(validations)) {
391
612
  if (typeof validation === "function")
392
613
  wrappedValidations[name] = (...args) => {
@@ -400,175 +621,15 @@ var getValidations = (_enabled) => {
400
621
  args
401
622
  );
402
623
  } catch (error) {
403
- if (error instanceof ChangeWarning)
404
- console.warn(error);
405
- else
406
- console.error(error);
624
+ if (error instanceof ChangeWarning) console.warn(error);
625
+ else console.error(error);
407
626
  }
408
627
  };
409
628
  }
410
629
  return wrappedValidations;
411
630
  };
412
631
 
413
- // source/memory-store.ts
414
- var MemoryStore = class {
415
- constructor() {
416
- /**
417
- * These two maps store usage (requests) and reset time by key (for example, IP
418
- * addresses or API keys).
419
- *
420
- * They are split into two to avoid having to iterate through the entire set to
421
- * determine which ones need reset. Instead, `Client`s are moved from `previous`
422
- * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
423
- * left in `previous`, i.e., those that have not made any recent requests, are
424
- * known to be expired and can be deleted in bulk.
425
- */
426
- this.previous = /* @__PURE__ */ new Map();
427
- this.current = /* @__PURE__ */ new Map();
428
- /**
429
- * Confirmation that the keys incremented in once instance of MemoryStore
430
- * cannot affect other instances.
431
- */
432
- this.localKeys = true;
433
- }
434
- /**
435
- * Method that initializes the store.
436
- *
437
- * @param options {Options} - The options used to setup the middleware.
438
- */
439
- init(options) {
440
- this.windowMs = options.windowMs;
441
- if (this.interval)
442
- clearInterval(this.interval);
443
- this.interval = setInterval(() => {
444
- this.clearExpired();
445
- }, this.windowMs);
446
- if (this.interval.unref)
447
- this.interval.unref();
448
- }
449
- /**
450
- * Method to fetch a client's hit count and reset time.
451
- *
452
- * @param key {string} - The identifier for a client.
453
- *
454
- * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
455
- *
456
- * @public
457
- */
458
- async get(key) {
459
- return this.current.get(key) ?? this.previous.get(key);
460
- }
461
- /**
462
- * Method to increment a client's hit counter.
463
- *
464
- * @param key {string} - The identifier for a client.
465
- *
466
- * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
467
- *
468
- * @public
469
- */
470
- async increment(key) {
471
- const client = this.getClient(key);
472
- const now = Date.now();
473
- if (client.resetTime.getTime() <= now) {
474
- this.resetClient(client, now);
475
- }
476
- client.totalHits++;
477
- return client;
478
- }
479
- /**
480
- * Method to decrement a client's hit counter.
481
- *
482
- * @param key {string} - The identifier for a client.
483
- *
484
- * @public
485
- */
486
- async decrement(key) {
487
- const client = this.getClient(key);
488
- if (client.totalHits > 0)
489
- client.totalHits--;
490
- }
491
- /**
492
- * Method to reset a client's hit counter.
493
- *
494
- * @param key {string} - The identifier for a client.
495
- *
496
- * @public
497
- */
498
- async resetKey(key) {
499
- this.current.delete(key);
500
- this.previous.delete(key);
501
- }
502
- /**
503
- * Method to reset everyone's hit counter.
504
- *
505
- * @public
506
- */
507
- async resetAll() {
508
- this.current.clear();
509
- this.previous.clear();
510
- }
511
- /**
512
- * Method to stop the timer (if currently running) and prevent any memory
513
- * leaks.
514
- *
515
- * @public
516
- */
517
- shutdown() {
518
- clearInterval(this.interval);
519
- void this.resetAll();
520
- }
521
- /**
522
- * Recycles a client by setting its hit count to zero, and reset time to
523
- * `windowMs` milliseconds from now.
524
- *
525
- * NOT to be confused with `#resetKey()`, which removes a client from both the
526
- * `current` and `previous` maps.
527
- *
528
- * @param client {Client} - The client to recycle.
529
- * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
530
- *
531
- * @return {Client} - The modified client that was passed in, to allow for chaining.
532
- */
533
- resetClient(client, now = Date.now()) {
534
- client.totalHits = 0;
535
- client.resetTime.setTime(now + this.windowMs);
536
- return client;
537
- }
538
- /**
539
- * Retrieves or creates a client, given a key. Also ensures that the client being
540
- * returned is in the `current` map.
541
- *
542
- * @param key {string} - The key under which the client is (or is to be) stored.
543
- *
544
- * @returns {Client} - The requested client.
545
- */
546
- getClient(key) {
547
- if (this.current.has(key))
548
- return this.current.get(key);
549
- let client;
550
- if (this.previous.has(key)) {
551
- client = this.previous.get(key);
552
- this.previous.delete(key);
553
- } else {
554
- client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
555
- this.resetClient(client);
556
- }
557
- this.current.set(key, client);
558
- return client;
559
- }
560
- /**
561
- * Move current clients to previous, create a new map for current.
562
- *
563
- * This function is called every `windowMs`.
564
- */
565
- clearExpired() {
566
- this.previous = this.current;
567
- this.current = /* @__PURE__ */ new Map();
568
- }
569
- };
570
-
571
- // source/lib.ts
632
+ // source/rate-limit.ts
572
633
  var isLegacyStore = (store) => (
573
634
  // Check that `incr` exists but `increment` does not - store authors might want
574
635
  // to keep both around for backwards compatibility.
@@ -585,8 +646,7 @@ var promisifyStore = (passedStore) => {
585
646
  legacyStore.incr(
586
647
  key,
587
648
  (error, totalHits, resetTime) => {
588
- if (error)
589
- reject(error);
649
+ if (error) reject(error);
590
650
  resolve({ totalHits, resetTime });
591
651
  }
592
652
  );
@@ -613,18 +673,8 @@ var getOptionsFromConfig = (config) => {
613
673
  validate: validations2.enabled
614
674
  };
615
675
  };
616
- var omitUndefinedOptions = (passedOptions) => {
617
- const omittedOptions = {};
618
- for (const k of Object.keys(passedOptions)) {
619
- const key = k;
620
- if (passedOptions[key] !== void 0) {
621
- omittedOptions[key] = passedOptions[key];
622
- }
623
- }
624
- return omittedOptions;
625
- };
626
676
  var parseOptions = (passedOptions) => {
627
- const notUndefinedOptions = omitUndefinedOptions(passedOptions);
677
+ const notUndefinedOptions = omitUndefinedProperties(passedOptions);
628
678
  const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
629
679
  validations2.validationsConfig();
630
680
  validations2.draftPolliHeaders(
@@ -632,9 +682,13 @@ var parseOptions = (passedOptions) => {
632
682
  notUndefinedOptions.draft_polli_ratelimit_headers
633
683
  );
634
684
  validations2.onLimitReached(notUndefinedOptions.onLimitReached);
685
+ if (notUndefinedOptions.ipv6Subnet !== void 0 && typeof notUndefinedOptions.ipv6Subnet !== "function") {
686
+ validations2.ipv6Subnet(notUndefinedOptions.ipv6Subnet);
687
+ }
688
+ validations2.keyGeneratorIpFallback(notUndefinedOptions.keyGenerator);
689
+ validations2.ipv6SubnetOrKeyGenerator(notUndefinedOptions);
635
690
  let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
636
- if (standardHeaders === true)
637
- standardHeaders = "draft-6";
691
+ if (standardHeaders === true) standardHeaders = "draft-6";
638
692
  const config = {
639
693
  windowMs: 60 * 1e3,
640
694
  limit: passedOptions.max ?? 5,
@@ -650,14 +704,10 @@ var parseOptions = (passedOptions) => {
650
704
  const minutes = config.windowMs / (1e3 * 60);
651
705
  const hours = config.windowMs / (1e3 * 60 * 60);
652
706
  const days = config.windowMs / (1e3 * 60 * 60 * 24);
653
- if (seconds < 60)
654
- duration = `${seconds}sec`;
655
- else if (minutes < 60)
656
- duration = `${minutes}min`;
657
- else if (hours < 24)
658
- duration = `${hours}hr${hours > 1 ? "s" : ""}`;
659
- else
660
- duration = `${days}day${days > 1 ? "s" : ""}`;
707
+ if (seconds < 60) duration = `${seconds}sec`;
708
+ else if (minutes < 60) duration = `${minutes}min`;
709
+ else if (hours < 24) duration = `${hours}hr${hours > 1 ? "s" : ""}`;
710
+ else duration = `${days}day${days > 1 ? "s" : ""}`;
661
711
  return `${limit}-in-${duration}`;
662
712
  },
663
713
  requestPropertyName: "rateLimit",
@@ -665,29 +715,35 @@ var parseOptions = (passedOptions) => {
665
715
  skipSuccessfulRequests: false,
666
716
  requestWasSuccessful: (_request, response) => response.statusCode < 400,
667
717
  skip: (_request, _response) => false,
668
- keyGenerator(request, _response) {
718
+ async keyGenerator(request, response) {
669
719
  validations2.ip(request.ip);
670
720
  validations2.trustProxy(request);
671
721
  validations2.xForwardedForHeader(request);
672
- return request.ip;
722
+ const ip = request.ip;
723
+ let subnet = 56;
724
+ if ((0, import_node_net3.isIPv6)(ip)) {
725
+ subnet = typeof config.ipv6Subnet === "function" ? await config.ipv6Subnet(request, response) : config.ipv6Subnet;
726
+ if (typeof config.ipv6Subnet === "function")
727
+ validations2.ipv6Subnet(subnet);
728
+ }
729
+ return ipKeyGenerator(ip, subnet);
673
730
  },
731
+ ipv6Subnet: 56,
674
732
  async handler(request, response, _next, _optionsUsed) {
675
733
  response.status(config.statusCode);
676
734
  const message = typeof config.message === "function" ? await config.message(
677
735
  request,
678
736
  response
679
737
  ) : config.message;
680
- if (!response.writableEnded) {
681
- response.send(message);
682
- }
738
+ if (!response.writableEnded) response.send(message);
683
739
  },
684
740
  passOnStoreError: false,
685
- // Allow the default options to be overriden by the passed options.
741
+ // Allow the default options to be overridden by the passed options.
686
742
  ...notUndefinedOptions,
687
743
  // `standardHeaders` is resolved into a draft version above, use that.
688
744
  standardHeaders,
689
745
  // Note that this field is declared after the user's options are spread in,
690
- // so that this field doesn't get overriden with an un-promisified store!
746
+ // so that this field doesn't get overridden with an un-promisified store!
691
747
  store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
692
748
  // Print an error to the console if a few known misconfigurations are detected.
693
749
  validations: validations2
@@ -711,8 +767,7 @@ var rateLimit = (passedOptions) => {
711
767
  const options = getOptionsFromConfig(config);
712
768
  config.validations.creationStack(config.store);
713
769
  config.validations.unsharedStore(config.store);
714
- if (typeof config.store.init === "function")
715
- config.store.init(options);
770
+ if (typeof config.store.init === "function") config.store.init(options);
716
771
  const middleware = handleAsyncErrors(
717
772
  async (request, response, next) => {
718
773
  const skip = await config.skip(request, response);
@@ -748,7 +803,8 @@ var rateLimit = (passedOptions) => {
748
803
  limit,
749
804
  used: totalHits,
750
805
  remaining: Math.max(limit - totalHits, 0),
751
- resetTime
806
+ resetTime,
807
+ key
752
808
  };
753
809
  Object.defineProperty(info, "current", {
754
810
  configurable: false,
@@ -797,8 +853,7 @@ var rateLimit = (passedOptions) => {
797
853
  await decrementKey();
798
854
  });
799
855
  response.on("close", async () => {
800
- if (!response.writableEnded)
801
- await decrementKey();
856
+ if (!response.writableEnded) await decrementKey();
802
857
  });
803
858
  response.on("error", async () => {
804
859
  await decrementKey();
@@ -829,10 +884,11 @@ var rateLimit = (passedOptions) => {
829
884
  middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
830
885
  return middleware;
831
886
  };
832
- var lib_default = rateLimit;
887
+ var rate_limit_default = rateLimit;
833
888
  // Annotate the CommonJS export names for ESM import in node:
834
889
  0 && (module.exports = {
835
890
  MemoryStore,
891
+ ipKeyGenerator,
836
892
  rateLimit
837
893
  });
838
894
  module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;