express-rate-limit 7.5.1 → 8.0.1
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 +253 -195
- package/dist/index.d.cts +54 -18
- package/dist/index.d.mts +54 -18
- package/dist/index.d.ts +54 -18
- package/dist/index.mjs +249 -192
- package/package.json +31 -52
- package/readme.md +5 -1
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,179 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
MemoryStore: () => MemoryStore,
|
|
24
|
-
default: () =>
|
|
25
|
-
|
|
24
|
+
default: () => rate_limit_default,
|
|
25
|
+
ipKeyGenerator: () => ipKeyGenerator,
|
|
26
|
+
rateLimit: () => rate_limit_default
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
28
29
|
|
|
30
|
+
// source/ip-key-generator.ts
|
|
31
|
+
var import_node_net = require("node:net");
|
|
32
|
+
var import_ip_address = require("ip-address");
|
|
33
|
+
function ipKeyGenerator(ip, ipv6Subnet = 56) {
|
|
34
|
+
if (ipv6Subnet && (0, import_node_net.isIPv6)(ip)) {
|
|
35
|
+
return `${new import_ip_address.Address6(`${ip}/${ipv6Subnet}`).startAddress().correctForm()}/${ipv6Subnet}`;
|
|
36
|
+
}
|
|
37
|
+
return ip;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// source/memory-store.ts
|
|
41
|
+
var MemoryStore = class {
|
|
42
|
+
constructor() {
|
|
43
|
+
/**
|
|
44
|
+
* These two maps store usage (requests) and reset time by key (for example, IP
|
|
45
|
+
* addresses or API keys).
|
|
46
|
+
*
|
|
47
|
+
* They are split into two to avoid having to iterate through the entire set to
|
|
48
|
+
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
|
49
|
+
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
|
50
|
+
* left in `previous`, i.e., those that have not made any recent requests, are
|
|
51
|
+
* known to be expired and can be deleted in bulk.
|
|
52
|
+
*/
|
|
53
|
+
this.previous = /* @__PURE__ */ new Map();
|
|
54
|
+
this.current = /* @__PURE__ */ new Map();
|
|
55
|
+
/**
|
|
56
|
+
* Confirmation that the keys incremented in once instance of MemoryStore
|
|
57
|
+
* cannot affect other instances.
|
|
58
|
+
*/
|
|
59
|
+
this.localKeys = true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Method that initializes the store.
|
|
63
|
+
*
|
|
64
|
+
* @param options {Options} - The options used to setup the middleware.
|
|
65
|
+
*/
|
|
66
|
+
init(options) {
|
|
67
|
+
this.windowMs = options.windowMs;
|
|
68
|
+
if (this.interval) clearInterval(this.interval);
|
|
69
|
+
this.interval = setInterval(() => {
|
|
70
|
+
this.clearExpired();
|
|
71
|
+
}, this.windowMs);
|
|
72
|
+
this.interval.unref?.();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Method to fetch a client's hit count and reset time.
|
|
76
|
+
*
|
|
77
|
+
* @param key {string} - The identifier for a client.
|
|
78
|
+
*
|
|
79
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
80
|
+
*
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
async get(key) {
|
|
84
|
+
return this.current.get(key) ?? this.previous.get(key);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Method to increment a client's hit counter.
|
|
88
|
+
*
|
|
89
|
+
* @param key {string} - The identifier for a client.
|
|
90
|
+
*
|
|
91
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
92
|
+
*
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
async increment(key) {
|
|
96
|
+
const client = this.getClient(key);
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (client.resetTime.getTime() <= now) {
|
|
99
|
+
this.resetClient(client, now);
|
|
100
|
+
}
|
|
101
|
+
client.totalHits++;
|
|
102
|
+
return client;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Method to decrement a client's hit counter.
|
|
106
|
+
*
|
|
107
|
+
* @param key {string} - The identifier for a client.
|
|
108
|
+
*
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
111
|
+
async decrement(key) {
|
|
112
|
+
const client = this.getClient(key);
|
|
113
|
+
if (client.totalHits > 0) client.totalHits--;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Method to reset a client's hit counter.
|
|
117
|
+
*
|
|
118
|
+
* @param key {string} - The identifier for a client.
|
|
119
|
+
*
|
|
120
|
+
* @public
|
|
121
|
+
*/
|
|
122
|
+
async resetKey(key) {
|
|
123
|
+
this.current.delete(key);
|
|
124
|
+
this.previous.delete(key);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Method to reset everyone's hit counter.
|
|
128
|
+
*
|
|
129
|
+
* @public
|
|
130
|
+
*/
|
|
131
|
+
async resetAll() {
|
|
132
|
+
this.current.clear();
|
|
133
|
+
this.previous.clear();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Method to stop the timer (if currently running) and prevent any memory
|
|
137
|
+
* leaks.
|
|
138
|
+
*
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
shutdown() {
|
|
142
|
+
clearInterval(this.interval);
|
|
143
|
+
void this.resetAll();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Recycles a client by setting its hit count to zero, and reset time to
|
|
147
|
+
* `windowMs` milliseconds from now.
|
|
148
|
+
*
|
|
149
|
+
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
|
150
|
+
* `current` and `previous` maps.
|
|
151
|
+
*
|
|
152
|
+
* @param client {Client} - The client to recycle.
|
|
153
|
+
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
|
154
|
+
*
|
|
155
|
+
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
|
156
|
+
*/
|
|
157
|
+
resetClient(client, now = Date.now()) {
|
|
158
|
+
client.totalHits = 0;
|
|
159
|
+
client.resetTime.setTime(now + this.windowMs);
|
|
160
|
+
return client;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Retrieves or creates a client, given a key. Also ensures that the client being
|
|
164
|
+
* returned is in the `current` map.
|
|
165
|
+
*
|
|
166
|
+
* @param key {string} - The key under which the client is (or is to be) stored.
|
|
167
|
+
*
|
|
168
|
+
* @returns {Client} - The requested client.
|
|
169
|
+
*/
|
|
170
|
+
getClient(key) {
|
|
171
|
+
if (this.current.has(key)) return this.current.get(key);
|
|
172
|
+
let client;
|
|
173
|
+
if (this.previous.has(key)) {
|
|
174
|
+
client = this.previous.get(key);
|
|
175
|
+
this.previous.delete(key);
|
|
176
|
+
} else {
|
|
177
|
+
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
|
|
178
|
+
this.resetClient(client);
|
|
179
|
+
}
|
|
180
|
+
this.current.set(key, client);
|
|
181
|
+
return client;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Move current clients to previous, create a new map for current.
|
|
185
|
+
*
|
|
186
|
+
* This function is called every `windowMs`.
|
|
187
|
+
*/
|
|
188
|
+
clearExpired() {
|
|
189
|
+
this.previous = this.current;
|
|
190
|
+
this.current = /* @__PURE__ */ new Map();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// source/rate-limit.ts
|
|
195
|
+
var import_node_net3 = require("node:net");
|
|
196
|
+
|
|
29
197
|
// source/headers.ts
|
|
30
198
|
var import_node_buffer = require("node:buffer");
|
|
31
199
|
var import_node_crypto = require("node:crypto");
|
|
@@ -34,12 +202,12 @@ var SUPPORTED_DRAFT_VERSIONS = [
|
|
|
34
202
|
"draft-7",
|
|
35
203
|
"draft-8"
|
|
36
204
|
];
|
|
37
|
-
var getResetSeconds = (
|
|
38
|
-
let resetSeconds
|
|
205
|
+
var getResetSeconds = (windowMs, resetTime) => {
|
|
206
|
+
let resetSeconds;
|
|
39
207
|
if (resetTime) {
|
|
40
208
|
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
|
41
209
|
resetSeconds = Math.max(0, deltaSeconds);
|
|
42
|
-
} else
|
|
210
|
+
} else {
|
|
43
211
|
resetSeconds = Math.ceil(windowMs / 1e3);
|
|
44
212
|
}
|
|
45
213
|
return resetSeconds;
|
|
@@ -65,7 +233,7 @@ var setLegacyHeaders = (response, info) => {
|
|
|
65
233
|
var setDraft6Headers = (response, info, windowMs) => {
|
|
66
234
|
if (response.headersSent) return;
|
|
67
235
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
68
|
-
const resetSeconds = getResetSeconds(info.resetTime);
|
|
236
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
69
237
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
70
238
|
response.setHeader("RateLimit-Limit", info.limit.toString());
|
|
71
239
|
response.setHeader("RateLimit-Remaining", info.remaining.toString());
|
|
@@ -75,7 +243,7 @@ var setDraft6Headers = (response, info, windowMs) => {
|
|
|
75
243
|
var setDraft7Headers = (response, info, windowMs) => {
|
|
76
244
|
if (response.headersSent) return;
|
|
77
245
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
78
|
-
const resetSeconds = getResetSeconds(info.resetTime
|
|
246
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
79
247
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
80
248
|
response.setHeader(
|
|
81
249
|
"RateLimit",
|
|
@@ -85,21 +253,33 @@ var setDraft7Headers = (response, info, windowMs) => {
|
|
|
85
253
|
var setDraft8Headers = (response, info, windowMs, name, key) => {
|
|
86
254
|
if (response.headersSent) return;
|
|
87
255
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
88
|
-
const resetSeconds = getResetSeconds(info.resetTime
|
|
256
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
89
257
|
const partitionKey = getPartitionKey(key);
|
|
90
|
-
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
|
|
91
258
|
const header = `r=${info.remaining}; t=${resetSeconds}`;
|
|
92
|
-
|
|
259
|
+
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
|
|
93
260
|
response.append("RateLimit", `"${name}"; ${header}`);
|
|
261
|
+
response.append("RateLimit-Policy", `"${name}"; ${policy}`);
|
|
94
262
|
};
|
|
95
263
|
var setRetryAfterHeader = (response, info, windowMs) => {
|
|
96
264
|
if (response.headersSent) return;
|
|
97
|
-
const resetSeconds = getResetSeconds(info.resetTime
|
|
265
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
98
266
|
response.setHeader("Retry-After", resetSeconds.toString());
|
|
99
267
|
};
|
|
100
268
|
|
|
269
|
+
// source/utils.ts
|
|
270
|
+
var omitUndefinedProperties = (passedOptions) => {
|
|
271
|
+
const omittedOptions = {};
|
|
272
|
+
for (const k of Object.keys(passedOptions)) {
|
|
273
|
+
const key = k;
|
|
274
|
+
if (passedOptions[key] !== void 0) {
|
|
275
|
+
omittedOptions[key] = passedOptions[key];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return omittedOptions;
|
|
279
|
+
};
|
|
280
|
+
|
|
101
281
|
// source/validations.ts
|
|
102
|
-
var
|
|
282
|
+
var import_node_net2 = require("node:net");
|
|
103
283
|
var ValidationError = class extends Error {
|
|
104
284
|
/**
|
|
105
285
|
* The code must be a string, in snake case and all capital, that starts with
|
|
@@ -121,7 +301,6 @@ var ChangeWarning = class extends ValidationError {
|
|
|
121
301
|
var usedStores = /* @__PURE__ */ new Set();
|
|
122
302
|
var singleCountKeys = /* @__PURE__ */ new WeakMap();
|
|
123
303
|
var validations = {
|
|
124
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
125
304
|
enabled: {
|
|
126
305
|
default: true
|
|
127
306
|
},
|
|
@@ -146,7 +325,7 @@ var validations = {
|
|
|
146
325
|
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
|
|
147
326
|
);
|
|
148
327
|
}
|
|
149
|
-
if (!(0,
|
|
328
|
+
if (!(0, import_node_net2.isIP)(ip)) {
|
|
150
329
|
throw new ValidationError(
|
|
151
330
|
"ERR_ERL_INVALID_IP_ADDRESS",
|
|
152
331
|
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
|
|
@@ -256,7 +435,7 @@ var validations = {
|
|
|
256
435
|
if (limit === 0) {
|
|
257
436
|
throw new ChangeWarning(
|
|
258
437
|
"WRN_ERL_MAX_ZERO",
|
|
259
|
-
|
|
438
|
+
"Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7"
|
|
260
439
|
);
|
|
261
440
|
}
|
|
262
441
|
},
|
|
@@ -288,7 +467,7 @@ var validations = {
|
|
|
288
467
|
if (onLimitReached) {
|
|
289
468
|
throw new ChangeWarning(
|
|
290
469
|
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
|
|
291
|
-
|
|
470
|
+
"The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7."
|
|
292
471
|
);
|
|
293
472
|
}
|
|
294
473
|
},
|
|
@@ -357,7 +536,8 @@ var validations = {
|
|
|
357
536
|
const { stack } = new Error(
|
|
358
537
|
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
|
|
359
538
|
);
|
|
360
|
-
if (stack?.includes("Layer.handle [as handle_request]")
|
|
539
|
+
if (stack?.includes("Layer.handle [as handle_request]") || // express v4
|
|
540
|
+
stack?.includes("Layer.handleRequest")) {
|
|
361
541
|
if (!store.localKeys) {
|
|
362
542
|
throw new ValidationError(
|
|
363
543
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
|
@@ -366,7 +546,38 @@ var validations = {
|
|
|
366
546
|
}
|
|
367
547
|
throw new ValidationError(
|
|
368
548
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
|
369
|
-
|
|
549
|
+
"express-rate-limit instance should be created at app initialization, not when responding to a request."
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
ipv6Subnet(ipv6Subnet) {
|
|
554
|
+
if (ipv6Subnet === false) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (!Number.isInteger(ipv6Subnet) || ipv6Subnet < 32 || ipv6Subnet > 64) {
|
|
558
|
+
throw new ValidationError(
|
|
559
|
+
"ERR_ERL_IPV6_SUBNET",
|
|
560
|
+
`Unexpected ipv6Subnet value: ${ipv6Subnet}. Expected an integer between 32 and 64 (usually 48-64).`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
ipv6SubnetOrKeyGenerator(options) {
|
|
565
|
+
if (options.ipv6Subnet !== void 0 && options.keyGenerator) {
|
|
566
|
+
throw new ValidationError(
|
|
567
|
+
"ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR",
|
|
568
|
+
`Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
keyGeneratorIpFallback(keyGenerator) {
|
|
573
|
+
if (!keyGenerator) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const src = keyGenerator.toString();
|
|
577
|
+
if ((src.includes("req.ip") || src.includes("request.ip")) && !src.includes("ipKeyGenerator")) {
|
|
578
|
+
throw new ValidationError(
|
|
579
|
+
"ERR_ERL_KEY_GEN_IPV6",
|
|
580
|
+
"Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits."
|
|
370
581
|
);
|
|
371
582
|
}
|
|
372
583
|
}
|
|
@@ -383,9 +594,7 @@ var getValidations = (_enabled) => {
|
|
|
383
594
|
..._enabled
|
|
384
595
|
};
|
|
385
596
|
}
|
|
386
|
-
const wrappedValidations = {
|
|
387
|
-
enabled
|
|
388
|
-
};
|
|
597
|
+
const wrappedValidations = { enabled };
|
|
389
598
|
for (const [name, validation] of Object.entries(validations)) {
|
|
390
599
|
if (typeof validation === "function")
|
|
391
600
|
wrappedValidations[name] = (...args) => {
|
|
@@ -407,161 +616,7 @@ var getValidations = (_enabled) => {
|
|
|
407
616
|
return wrappedValidations;
|
|
408
617
|
};
|
|
409
618
|
|
|
410
|
-
// source/
|
|
411
|
-
var MemoryStore = class {
|
|
412
|
-
constructor() {
|
|
413
|
-
/**
|
|
414
|
-
* These two maps store usage (requests) and reset time by key (for example, IP
|
|
415
|
-
* addresses or API keys).
|
|
416
|
-
*
|
|
417
|
-
* They are split into two to avoid having to iterate through the entire set to
|
|
418
|
-
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
|
419
|
-
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
|
420
|
-
* left in `previous`, i.e., those that have not made any recent requests, are
|
|
421
|
-
* known to be expired and can be deleted in bulk.
|
|
422
|
-
*/
|
|
423
|
-
this.previous = /* @__PURE__ */ new Map();
|
|
424
|
-
this.current = /* @__PURE__ */ new Map();
|
|
425
|
-
/**
|
|
426
|
-
* Confirmation that the keys incremented in once instance of MemoryStore
|
|
427
|
-
* cannot affect other instances.
|
|
428
|
-
*/
|
|
429
|
-
this.localKeys = true;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Method that initializes the store.
|
|
433
|
-
*
|
|
434
|
-
* @param options {Options} - The options used to setup the middleware.
|
|
435
|
-
*/
|
|
436
|
-
init(options) {
|
|
437
|
-
this.windowMs = options.windowMs;
|
|
438
|
-
if (this.interval) clearInterval(this.interval);
|
|
439
|
-
this.interval = setInterval(() => {
|
|
440
|
-
this.clearExpired();
|
|
441
|
-
}, this.windowMs);
|
|
442
|
-
if (this.interval.unref) this.interval.unref();
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Method to fetch a client's hit count and reset time.
|
|
446
|
-
*
|
|
447
|
-
* @param key {string} - The identifier for a client.
|
|
448
|
-
*
|
|
449
|
-
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
450
|
-
*
|
|
451
|
-
* @public
|
|
452
|
-
*/
|
|
453
|
-
async get(key) {
|
|
454
|
-
return this.current.get(key) ?? this.previous.get(key);
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Method to increment a client's hit counter.
|
|
458
|
-
*
|
|
459
|
-
* @param key {string} - The identifier for a client.
|
|
460
|
-
*
|
|
461
|
-
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
462
|
-
*
|
|
463
|
-
* @public
|
|
464
|
-
*/
|
|
465
|
-
async increment(key) {
|
|
466
|
-
const client = this.getClient(key);
|
|
467
|
-
const now = Date.now();
|
|
468
|
-
if (client.resetTime.getTime() <= now) {
|
|
469
|
-
this.resetClient(client, now);
|
|
470
|
-
}
|
|
471
|
-
client.totalHits++;
|
|
472
|
-
return client;
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Method to decrement a client's hit counter.
|
|
476
|
-
*
|
|
477
|
-
* @param key {string} - The identifier for a client.
|
|
478
|
-
*
|
|
479
|
-
* @public
|
|
480
|
-
*/
|
|
481
|
-
async decrement(key) {
|
|
482
|
-
const client = this.getClient(key);
|
|
483
|
-
if (client.totalHits > 0) client.totalHits--;
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Method to reset a client's hit counter.
|
|
487
|
-
*
|
|
488
|
-
* @param key {string} - The identifier for a client.
|
|
489
|
-
*
|
|
490
|
-
* @public
|
|
491
|
-
*/
|
|
492
|
-
async resetKey(key) {
|
|
493
|
-
this.current.delete(key);
|
|
494
|
-
this.previous.delete(key);
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Method to reset everyone's hit counter.
|
|
498
|
-
*
|
|
499
|
-
* @public
|
|
500
|
-
*/
|
|
501
|
-
async resetAll() {
|
|
502
|
-
this.current.clear();
|
|
503
|
-
this.previous.clear();
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Method to stop the timer (if currently running) and prevent any memory
|
|
507
|
-
* leaks.
|
|
508
|
-
*
|
|
509
|
-
* @public
|
|
510
|
-
*/
|
|
511
|
-
shutdown() {
|
|
512
|
-
clearInterval(this.interval);
|
|
513
|
-
void this.resetAll();
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Recycles a client by setting its hit count to zero, and reset time to
|
|
517
|
-
* `windowMs` milliseconds from now.
|
|
518
|
-
*
|
|
519
|
-
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
|
520
|
-
* `current` and `previous` maps.
|
|
521
|
-
*
|
|
522
|
-
* @param client {Client} - The client to recycle.
|
|
523
|
-
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
|
524
|
-
*
|
|
525
|
-
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
|
526
|
-
*/
|
|
527
|
-
resetClient(client, now = Date.now()) {
|
|
528
|
-
client.totalHits = 0;
|
|
529
|
-
client.resetTime.setTime(now + this.windowMs);
|
|
530
|
-
return client;
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Retrieves or creates a client, given a key. Also ensures that the client being
|
|
534
|
-
* returned is in the `current` map.
|
|
535
|
-
*
|
|
536
|
-
* @param key {string} - The key under which the client is (or is to be) stored.
|
|
537
|
-
*
|
|
538
|
-
* @returns {Client} - The requested client.
|
|
539
|
-
*/
|
|
540
|
-
getClient(key) {
|
|
541
|
-
if (this.current.has(key)) return this.current.get(key);
|
|
542
|
-
let client;
|
|
543
|
-
if (this.previous.has(key)) {
|
|
544
|
-
client = this.previous.get(key);
|
|
545
|
-
this.previous.delete(key);
|
|
546
|
-
} else {
|
|
547
|
-
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
|
|
548
|
-
this.resetClient(client);
|
|
549
|
-
}
|
|
550
|
-
this.current.set(key, client);
|
|
551
|
-
return client;
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Move current clients to previous, create a new map for current.
|
|
555
|
-
*
|
|
556
|
-
* This function is called every `windowMs`.
|
|
557
|
-
*/
|
|
558
|
-
clearExpired() {
|
|
559
|
-
this.previous = this.current;
|
|
560
|
-
this.current = /* @__PURE__ */ new Map();
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
// source/lib.ts
|
|
619
|
+
// source/rate-limit.ts
|
|
565
620
|
var isLegacyStore = (store) => (
|
|
566
621
|
// Check that `incr` exists but `increment` does not - store authors might want
|
|
567
622
|
// to keep both around for backwards compatibility.
|
|
@@ -605,18 +660,8 @@ var getOptionsFromConfig = (config) => {
|
|
|
605
660
|
validate: validations2.enabled
|
|
606
661
|
};
|
|
607
662
|
};
|
|
608
|
-
var omitUndefinedOptions = (passedOptions) => {
|
|
609
|
-
const omittedOptions = {};
|
|
610
|
-
for (const k of Object.keys(passedOptions)) {
|
|
611
|
-
const key = k;
|
|
612
|
-
if (passedOptions[key] !== void 0) {
|
|
613
|
-
omittedOptions[key] = passedOptions[key];
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
return omittedOptions;
|
|
617
|
-
};
|
|
618
663
|
var parseOptions = (passedOptions) => {
|
|
619
|
-
const notUndefinedOptions =
|
|
664
|
+
const notUndefinedOptions = omitUndefinedProperties(passedOptions);
|
|
620
665
|
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
|
|
621
666
|
validations2.validationsConfig();
|
|
622
667
|
validations2.draftPolliHeaders(
|
|
@@ -624,6 +669,11 @@ var parseOptions = (passedOptions) => {
|
|
|
624
669
|
notUndefinedOptions.draft_polli_ratelimit_headers
|
|
625
670
|
);
|
|
626
671
|
validations2.onLimitReached(notUndefinedOptions.onLimitReached);
|
|
672
|
+
if (notUndefinedOptions.ipv6Subnet !== void 0 && typeof notUndefinedOptions.ipv6Subnet !== "function") {
|
|
673
|
+
validations2.ipv6Subnet(notUndefinedOptions.ipv6Subnet);
|
|
674
|
+
}
|
|
675
|
+
validations2.keyGeneratorIpFallback(notUndefinedOptions.keyGenerator);
|
|
676
|
+
validations2.ipv6SubnetOrKeyGenerator(notUndefinedOptions);
|
|
627
677
|
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
|
|
628
678
|
if (standardHeaders === true) standardHeaders = "draft-6";
|
|
629
679
|
const config = {
|
|
@@ -652,21 +702,27 @@ var parseOptions = (passedOptions) => {
|
|
|
652
702
|
skipSuccessfulRequests: false,
|
|
653
703
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
|
654
704
|
skip: (_request, _response) => false,
|
|
655
|
-
keyGenerator(request,
|
|
705
|
+
async keyGenerator(request, response) {
|
|
656
706
|
validations2.ip(request.ip);
|
|
657
707
|
validations2.trustProxy(request);
|
|
658
708
|
validations2.xForwardedForHeader(request);
|
|
659
|
-
|
|
709
|
+
const ip = request.ip;
|
|
710
|
+
let subnet = 56;
|
|
711
|
+
if ((0, import_node_net3.isIPv6)(ip)) {
|
|
712
|
+
subnet = typeof config.ipv6Subnet === "function" ? await config.ipv6Subnet(request, response) : config.ipv6Subnet;
|
|
713
|
+
if (typeof config.ipv6Subnet === "function")
|
|
714
|
+
validations2.ipv6Subnet(subnet);
|
|
715
|
+
}
|
|
716
|
+
return ipKeyGenerator(ip, subnet);
|
|
660
717
|
},
|
|
718
|
+
ipv6Subnet: 56,
|
|
661
719
|
async handler(request, response, _next, _optionsUsed) {
|
|
662
720
|
response.status(config.statusCode);
|
|
663
721
|
const message = typeof config.message === "function" ? await config.message(
|
|
664
722
|
request,
|
|
665
723
|
response
|
|
666
724
|
) : config.message;
|
|
667
|
-
if (!response.writableEnded)
|
|
668
|
-
response.send(message);
|
|
669
|
-
}
|
|
725
|
+
if (!response.writableEnded) response.send(message);
|
|
670
726
|
},
|
|
671
727
|
passOnStoreError: false,
|
|
672
728
|
// Allow the default options to be overridden by the passed options.
|
|
@@ -734,7 +790,8 @@ var rateLimit = (passedOptions) => {
|
|
|
734
790
|
limit,
|
|
735
791
|
used: totalHits,
|
|
736
792
|
remaining: Math.max(limit - totalHits, 0),
|
|
737
|
-
resetTime
|
|
793
|
+
resetTime,
|
|
794
|
+
key
|
|
738
795
|
};
|
|
739
796
|
Object.defineProperty(info, "current", {
|
|
740
797
|
configurable: false,
|
|
@@ -814,10 +871,11 @@ var rateLimit = (passedOptions) => {
|
|
|
814
871
|
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
|
|
815
872
|
return middleware;
|
|
816
873
|
};
|
|
817
|
-
var
|
|
874
|
+
var rate_limit_default = rateLimit;
|
|
818
875
|
// Annotate the CommonJS export names for ESM import in node:
|
|
819
876
|
0 && (module.exports = {
|
|
820
877
|
MemoryStore,
|
|
878
|
+
ipKeyGenerator,
|
|
821
879
|
rateLimit
|
|
822
880
|
});
|
|
823
|
-
module.exports =
|
|
881
|
+
module.exports = Object.assign(rateLimit, module.exports);
|