express-rate-limit 7.5.1 → 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 +262 -191
- package/dist/index.d.cts +56 -20
- package/dist/index.d.mts +56 -20
- package/dist/index.d.ts +56 -20
- package/dist/index.mjs +249 -189
- package/package.json +33 -53
- package/readme.md +5 -1
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,17 +17,196 @@ 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
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
MemoryStore: () => MemoryStore,
|
|
24
|
-
default: () =>
|
|
25
|
-
|
|
34
|
+
default: () => rate_limit_default,
|
|
35
|
+
ipKeyGenerator: () => ipKeyGenerator,
|
|
36
|
+
rateLimit: () => rate_limit_default
|
|
26
37
|
});
|
|
27
38
|
module.exports = __toCommonJS(index_exports);
|
|
28
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");
|
|
209
|
+
|
|
29
210
|
// source/headers.ts
|
|
30
211
|
var import_node_buffer = require("node:buffer");
|
|
31
212
|
var import_node_crypto = require("node:crypto");
|
|
@@ -34,12 +215,12 @@ var SUPPORTED_DRAFT_VERSIONS = [
|
|
|
34
215
|
"draft-7",
|
|
35
216
|
"draft-8"
|
|
36
217
|
];
|
|
37
|
-
var getResetSeconds = (
|
|
38
|
-
let resetSeconds
|
|
218
|
+
var getResetSeconds = (windowMs, resetTime) => {
|
|
219
|
+
let resetSeconds;
|
|
39
220
|
if (resetTime) {
|
|
40
221
|
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
|
41
222
|
resetSeconds = Math.max(0, deltaSeconds);
|
|
42
|
-
} else
|
|
223
|
+
} else {
|
|
43
224
|
resetSeconds = Math.ceil(windowMs / 1e3);
|
|
44
225
|
}
|
|
45
226
|
return resetSeconds;
|
|
@@ -65,7 +246,7 @@ var setLegacyHeaders = (response, info) => {
|
|
|
65
246
|
var setDraft6Headers = (response, info, windowMs) => {
|
|
66
247
|
if (response.headersSent) return;
|
|
67
248
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
68
|
-
const resetSeconds = getResetSeconds(info.resetTime);
|
|
249
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
69
250
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
70
251
|
response.setHeader("RateLimit-Limit", info.limit.toString());
|
|
71
252
|
response.setHeader("RateLimit-Remaining", info.remaining.toString());
|
|
@@ -75,7 +256,7 @@ var setDraft6Headers = (response, info, windowMs) => {
|
|
|
75
256
|
var setDraft7Headers = (response, info, windowMs) => {
|
|
76
257
|
if (response.headersSent) return;
|
|
77
258
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
78
|
-
const resetSeconds = getResetSeconds(info.resetTime
|
|
259
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
79
260
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
80
261
|
response.setHeader(
|
|
81
262
|
"RateLimit",
|
|
@@ -85,21 +266,33 @@ var setDraft7Headers = (response, info, windowMs) => {
|
|
|
85
266
|
var setDraft8Headers = (response, info, windowMs, name, key) => {
|
|
86
267
|
if (response.headersSent) return;
|
|
87
268
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
88
|
-
const resetSeconds = getResetSeconds(info.resetTime
|
|
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
|
-
|
|
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
277
|
if (response.headersSent) return;
|
|
97
|
-
const resetSeconds = getResetSeconds(info.resetTime
|
|
278
|
+
const resetSeconds = getResetSeconds(windowMs, info.resetTime);
|
|
98
279
|
response.setHeader("Retry-After", resetSeconds.toString());
|
|
99
280
|
};
|
|
100
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
|
+
|
|
101
294
|
// source/validations.ts
|
|
102
|
-
var
|
|
295
|
+
var import_node_net2 = require("node:net");
|
|
103
296
|
var ValidationError = class extends Error {
|
|
104
297
|
/**
|
|
105
298
|
* The code must be a string, in snake case and all capital, that starts with
|
|
@@ -121,7 +314,6 @@ var ChangeWarning = class extends ValidationError {
|
|
|
121
314
|
var usedStores = /* @__PURE__ */ new Set();
|
|
122
315
|
var singleCountKeys = /* @__PURE__ */ new WeakMap();
|
|
123
316
|
var validations = {
|
|
124
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
125
317
|
enabled: {
|
|
126
318
|
default: true
|
|
127
319
|
},
|
|
@@ -146,7 +338,7 @@ var validations = {
|
|
|
146
338
|
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
|
|
147
339
|
);
|
|
148
340
|
}
|
|
149
|
-
if (!(0,
|
|
341
|
+
if (!(0, import_node_net2.isIP)(ip)) {
|
|
150
342
|
throw new ValidationError(
|
|
151
343
|
"ERR_ERL_INVALID_IP_ADDRESS",
|
|
152
344
|
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
|
|
@@ -357,7 +549,8 @@ var validations = {
|
|
|
357
549
|
const { stack } = new Error(
|
|
358
550
|
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
|
|
359
551
|
);
|
|
360
|
-
if (stack?.includes("Layer.handle [as handle_request]")
|
|
552
|
+
if (stack?.includes("Layer.handle [as handle_request]") || // express v4
|
|
553
|
+
stack?.includes("Layer.handleRequest")) {
|
|
361
554
|
if (!store.localKeys) {
|
|
362
555
|
throw new ValidationError(
|
|
363
556
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
|
@@ -369,6 +562,37 @@ var validations = {
|
|
|
369
562
|
`express-rate-limit instance should be created at app initialization, not when responding to a request.`
|
|
370
563
|
);
|
|
371
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
|
+
}
|
|
372
596
|
}
|
|
373
597
|
};
|
|
374
598
|
var getValidations = (_enabled) => {
|
|
@@ -383,9 +607,7 @@ var getValidations = (_enabled) => {
|
|
|
383
607
|
..._enabled
|
|
384
608
|
};
|
|
385
609
|
}
|
|
386
|
-
const wrappedValidations = {
|
|
387
|
-
enabled
|
|
388
|
-
};
|
|
610
|
+
const wrappedValidations = { enabled };
|
|
389
611
|
for (const [name, validation] of Object.entries(validations)) {
|
|
390
612
|
if (typeof validation === "function")
|
|
391
613
|
wrappedValidations[name] = (...args) => {
|
|
@@ -407,161 +629,7 @@ var getValidations = (_enabled) => {
|
|
|
407
629
|
return wrappedValidations;
|
|
408
630
|
};
|
|
409
631
|
|
|
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
|
|
632
|
+
// source/rate-limit.ts
|
|
565
633
|
var isLegacyStore = (store) => (
|
|
566
634
|
// Check that `incr` exists but `increment` does not - store authors might want
|
|
567
635
|
// to keep both around for backwards compatibility.
|
|
@@ -605,18 +673,8 @@ var getOptionsFromConfig = (config) => {
|
|
|
605
673
|
validate: validations2.enabled
|
|
606
674
|
};
|
|
607
675
|
};
|
|
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
676
|
var parseOptions = (passedOptions) => {
|
|
619
|
-
const notUndefinedOptions =
|
|
677
|
+
const notUndefinedOptions = omitUndefinedProperties(passedOptions);
|
|
620
678
|
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
|
|
621
679
|
validations2.validationsConfig();
|
|
622
680
|
validations2.draftPolliHeaders(
|
|
@@ -624,6 +682,11 @@ var parseOptions = (passedOptions) => {
|
|
|
624
682
|
notUndefinedOptions.draft_polli_ratelimit_headers
|
|
625
683
|
);
|
|
626
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);
|
|
627
690
|
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
|
|
628
691
|
if (standardHeaders === true) standardHeaders = "draft-6";
|
|
629
692
|
const config = {
|
|
@@ -652,21 +715,27 @@ var parseOptions = (passedOptions) => {
|
|
|
652
715
|
skipSuccessfulRequests: false,
|
|
653
716
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
|
654
717
|
skip: (_request, _response) => false,
|
|
655
|
-
keyGenerator(request,
|
|
718
|
+
async keyGenerator(request, response) {
|
|
656
719
|
validations2.ip(request.ip);
|
|
657
720
|
validations2.trustProxy(request);
|
|
658
721
|
validations2.xForwardedForHeader(request);
|
|
659
|
-
|
|
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);
|
|
660
730
|
},
|
|
731
|
+
ipv6Subnet: 56,
|
|
661
732
|
async handler(request, response, _next, _optionsUsed) {
|
|
662
733
|
response.status(config.statusCode);
|
|
663
734
|
const message = typeof config.message === "function" ? await config.message(
|
|
664
735
|
request,
|
|
665
736
|
response
|
|
666
737
|
) : config.message;
|
|
667
|
-
if (!response.writableEnded)
|
|
668
|
-
response.send(message);
|
|
669
|
-
}
|
|
738
|
+
if (!response.writableEnded) response.send(message);
|
|
670
739
|
},
|
|
671
740
|
passOnStoreError: false,
|
|
672
741
|
// Allow the default options to be overridden by the passed options.
|
|
@@ -734,7 +803,8 @@ var rateLimit = (passedOptions) => {
|
|
|
734
803
|
limit,
|
|
735
804
|
used: totalHits,
|
|
736
805
|
remaining: Math.max(limit - totalHits, 0),
|
|
737
|
-
resetTime
|
|
806
|
+
resetTime,
|
|
807
|
+
key
|
|
738
808
|
};
|
|
739
809
|
Object.defineProperty(info, "current", {
|
|
740
810
|
configurable: false,
|
|
@@ -814,10 +884,11 @@ var rateLimit = (passedOptions) => {
|
|
|
814
884
|
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
|
|
815
885
|
return middleware;
|
|
816
886
|
};
|
|
817
|
-
var
|
|
887
|
+
var rate_limit_default = rateLimit;
|
|
818
888
|
// Annotate the CommonJS export names for ESM import in node:
|
|
819
889
|
0 && (module.exports = {
|
|
820
890
|
MemoryStore,
|
|
891
|
+
ipKeyGenerator,
|
|
821
892
|
rateLimit
|
|
822
893
|
});
|
|
823
894
|
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;
|