express-rate-limit 6.7.1 → 6.8.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/changelog.md +7 -0
- package/dist/index.cjs +167 -42
- package/dist/index.d.cts +22 -18
- package/dist/index.d.mts +22 -18
- package/dist/index.d.ts +22 -18
- package/dist/index.mjs +162 -42
- package/package.json +15 -13
- package/readme.md +15 -0
package/changelog.md
CHANGED
|
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to
|
|
7
7
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [6.8.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.8.0)
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Added a set of validation checks to execute on the first request. (See
|
|
14
|
+
[#358](https://github.com/express-rate-limit/express-rate-limit/issues/358))
|
|
15
|
+
|
|
9
16
|
## [6.7.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.1)
|
|
10
17
|
|
|
11
18
|
### Fixed
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,116 @@ __export(source_exports, {
|
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(source_exports);
|
|
33
33
|
|
|
34
|
+
// source/validations.ts
|
|
35
|
+
var import_node_net = require("net");
|
|
36
|
+
var ValidationError = class extends Error {
|
|
37
|
+
/**
|
|
38
|
+
* The code must be a string, in snake case and all capital, that starts with
|
|
39
|
+
* the substring `ERR_ERL_`.
|
|
40
|
+
*
|
|
41
|
+
* The message must be a string, starting with a lowercase character,
|
|
42
|
+
* describing the issue in detail.
|
|
43
|
+
*/
|
|
44
|
+
constructor(code, message) {
|
|
45
|
+
super(
|
|
46
|
+
`express-rate-limit: ${code} - ${message} See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#${code.toLowerCase()} for more information on this error.`
|
|
47
|
+
);
|
|
48
|
+
__publicField(this, "name");
|
|
49
|
+
__publicField(this, "code");
|
|
50
|
+
this.name = this.constructor.name;
|
|
51
|
+
this.code = code;
|
|
52
|
+
this.message = message;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var Validations = class {
|
|
56
|
+
constructor(enabled) {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/parameter-properties
|
|
58
|
+
__publicField(this, "enabled");
|
|
59
|
+
this.enabled = enabled;
|
|
60
|
+
}
|
|
61
|
+
enable() {
|
|
62
|
+
this.enabled = true;
|
|
63
|
+
}
|
|
64
|
+
disable() {
|
|
65
|
+
this.enabled = false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Checks whether the IP address is valid, and that it does not have a port
|
|
69
|
+
* number in it.
|
|
70
|
+
*
|
|
71
|
+
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
|
72
|
+
*
|
|
73
|
+
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
|
74
|
+
*
|
|
75
|
+
* @returns {void}
|
|
76
|
+
*/
|
|
77
|
+
ip(ip) {
|
|
78
|
+
this.wrap(() => {
|
|
79
|
+
if (ip === void 0) {
|
|
80
|
+
throw new ValidationError(
|
|
81
|
+
"ERR_ERL_UNDEFINED_IP_ADDRESS",
|
|
82
|
+
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (!(0, import_node_net.isIP)(ip)) {
|
|
86
|
+
throw new ValidationError(
|
|
87
|
+
"ERR_ERL_INVALID_IP_ADDRESS",
|
|
88
|
+
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Makes sure the trust proxy setting is not set to `true`.
|
|
95
|
+
*
|
|
96
|
+
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
|
97
|
+
*
|
|
98
|
+
* @param request {Request} - The Express request object.
|
|
99
|
+
*
|
|
100
|
+
* @returns {void}
|
|
101
|
+
*/
|
|
102
|
+
trustProxy(request) {
|
|
103
|
+
this.wrap(() => {
|
|
104
|
+
if (request.app.get("trust proxy") === true) {
|
|
105
|
+
throw new ValidationError(
|
|
106
|
+
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
|
|
107
|
+
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
|
114
|
+
* header is present.
|
|
115
|
+
*
|
|
116
|
+
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
|
117
|
+
*
|
|
118
|
+
* @param request {Request} - The Express request object.
|
|
119
|
+
*
|
|
120
|
+
* @returns {void}
|
|
121
|
+
*/
|
|
122
|
+
xForwardedForHeader(request) {
|
|
123
|
+
this.wrap(() => {
|
|
124
|
+
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
|
|
125
|
+
throw new ValidationError(
|
|
126
|
+
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
|
|
127
|
+
`The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
wrap(validation) {
|
|
133
|
+
if (!this.enabled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
validation.call(this);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
34
144
|
// source/memory-store.ts
|
|
35
145
|
var calculateNextResetTime = (windowMs) => {
|
|
36
146
|
const resetTime = /* @__PURE__ */ new Date();
|
|
@@ -168,27 +278,43 @@ var promisifyStore = (passedStore) => {
|
|
|
168
278
|
}
|
|
169
279
|
return new PromisifiedStore();
|
|
170
280
|
};
|
|
281
|
+
var getOptionsFromConfig = (config) => {
|
|
282
|
+
const { validations, ...directlyPassableEntries } = config;
|
|
283
|
+
return {
|
|
284
|
+
...directlyPassableEntries,
|
|
285
|
+
validate: validations.enabled
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
var omitUndefinedOptions = (passedOptions) => {
|
|
289
|
+
const omittedOptions = {};
|
|
290
|
+
for (const k of Object.keys(passedOptions)) {
|
|
291
|
+
const key = k;
|
|
292
|
+
if (passedOptions[key] !== void 0) {
|
|
293
|
+
omittedOptions[key] = passedOptions[key];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return omittedOptions;
|
|
297
|
+
};
|
|
171
298
|
var parseOptions = (passedOptions) => {
|
|
172
|
-
var _a, _b, _c;
|
|
299
|
+
var _a, _b, _c, _d;
|
|
173
300
|
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
|
301
|
+
const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
|
|
174
302
|
const config = {
|
|
175
303
|
windowMs: 60 * 1e3,
|
|
176
304
|
max: 5,
|
|
177
305
|
message: "Too many requests, please try again later.",
|
|
178
306
|
statusCode: 429,
|
|
179
|
-
legacyHeaders: (
|
|
180
|
-
standardHeaders: (
|
|
307
|
+
legacyHeaders: (_b = passedOptions.headers) != null ? _b : true,
|
|
308
|
+
standardHeaders: (_c = passedOptions.draft_polli_ratelimit_headers) != null ? _c : false,
|
|
181
309
|
requestPropertyName: "rateLimit",
|
|
182
310
|
skipFailedRequests: false,
|
|
183
311
|
skipSuccessfulRequests: false,
|
|
184
312
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
|
185
313
|
skip: (_request, _response) => false,
|
|
186
314
|
keyGenerator(request, _response) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
-
}
|
|
315
|
+
validations.ip(request.ip);
|
|
316
|
+
validations.trustProxy(request);
|
|
317
|
+
validations.xForwardedForHeader(request);
|
|
192
318
|
return request.ip;
|
|
193
319
|
},
|
|
194
320
|
async handler(request, response, _next, _optionsUsed) {
|
|
@@ -207,7 +333,9 @@ var parseOptions = (passedOptions) => {
|
|
|
207
333
|
...notUndefinedOptions,
|
|
208
334
|
// Note that this field is declared after the user's options are spread in,
|
|
209
335
|
// so that this field doesn't get overriden with an un-promisified store!
|
|
210
|
-
store: promisifyStore((
|
|
336
|
+
store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
|
|
337
|
+
// Print an error to the console if a few known misconfigurations are detected.
|
|
338
|
+
validations
|
|
211
339
|
};
|
|
212
340
|
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
|
|
213
341
|
throw new TypeError(
|
|
@@ -224,32 +352,33 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
|
|
|
224
352
|
}
|
|
225
353
|
};
|
|
226
354
|
var rateLimit = (passedOptions) => {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
355
|
+
const config = parseOptions(passedOptions != null ? passedOptions : {});
|
|
356
|
+
const options = getOptionsFromConfig(config);
|
|
357
|
+
if (typeof config.store.init === "function")
|
|
358
|
+
config.store.init(options);
|
|
230
359
|
const middleware = handleAsyncErrors(
|
|
231
360
|
async (request, response, next) => {
|
|
232
|
-
const skip = await
|
|
361
|
+
const skip = await config.skip(request, response);
|
|
233
362
|
if (skip) {
|
|
234
363
|
next();
|
|
235
364
|
return;
|
|
236
365
|
}
|
|
237
366
|
const augmentedRequest = request;
|
|
238
|
-
const key = await
|
|
239
|
-
const { totalHits, resetTime } = await
|
|
240
|
-
const retrieveQuota = typeof
|
|
367
|
+
const key = await config.keyGenerator(request, response);
|
|
368
|
+
const { totalHits, resetTime } = await config.store.increment(key);
|
|
369
|
+
const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
|
|
241
370
|
const maxHits = await retrieveQuota;
|
|
242
|
-
augmentedRequest[
|
|
371
|
+
augmentedRequest[config.requestPropertyName] = {
|
|
243
372
|
limit: maxHits,
|
|
244
373
|
current: totalHits,
|
|
245
374
|
remaining: Math.max(maxHits - totalHits, 0),
|
|
246
375
|
resetTime
|
|
247
376
|
};
|
|
248
|
-
if (
|
|
377
|
+
if (config.legacyHeaders && !response.headersSent) {
|
|
249
378
|
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
250
379
|
response.setHeader(
|
|
251
380
|
"X-RateLimit-Remaining",
|
|
252
|
-
augmentedRequest[
|
|
381
|
+
augmentedRequest[config.requestPropertyName].remaining
|
|
253
382
|
);
|
|
254
383
|
if (resetTime instanceof Date) {
|
|
255
384
|
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
@@ -259,11 +388,11 @@ var rateLimit = (passedOptions) => {
|
|
|
259
388
|
);
|
|
260
389
|
}
|
|
261
390
|
}
|
|
262
|
-
if (
|
|
391
|
+
if (config.standardHeaders && !response.headersSent) {
|
|
263
392
|
response.setHeader("RateLimit-Limit", maxHits);
|
|
264
393
|
response.setHeader(
|
|
265
394
|
"RateLimit-Remaining",
|
|
266
|
-
augmentedRequest[
|
|
395
|
+
augmentedRequest[config.requestPropertyName].remaining
|
|
267
396
|
);
|
|
268
397
|
if (resetTime) {
|
|
269
398
|
const deltaSeconds = Math.ceil(
|
|
@@ -272,17 +401,17 @@ var rateLimit = (passedOptions) => {
|
|
|
272
401
|
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
273
402
|
}
|
|
274
403
|
}
|
|
275
|
-
if (
|
|
404
|
+
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
|
|
276
405
|
let decremented = false;
|
|
277
406
|
const decrementKey = async () => {
|
|
278
407
|
if (!decremented) {
|
|
279
|
-
await
|
|
408
|
+
await config.store.decrement(key);
|
|
280
409
|
decremented = true;
|
|
281
410
|
}
|
|
282
411
|
};
|
|
283
|
-
if (
|
|
412
|
+
if (config.skipFailedRequests) {
|
|
284
413
|
response.on("finish", async () => {
|
|
285
|
-
if (!
|
|
414
|
+
if (!config.requestWasSuccessful(request, response))
|
|
286
415
|
await decrementKey();
|
|
287
416
|
});
|
|
288
417
|
response.on("close", async () => {
|
|
@@ -293,38 +422,34 @@ var rateLimit = (passedOptions) => {
|
|
|
293
422
|
await decrementKey();
|
|
294
423
|
});
|
|
295
424
|
}
|
|
296
|
-
if (
|
|
425
|
+
if (config.skipSuccessfulRequests) {
|
|
297
426
|
response.on("finish", async () => {
|
|
298
|
-
if (
|
|
427
|
+
if (config.requestWasSuccessful(request, response))
|
|
299
428
|
await decrementKey();
|
|
300
429
|
});
|
|
301
430
|
}
|
|
302
431
|
}
|
|
303
432
|
if (maxHits && totalHits === maxHits + 1) {
|
|
304
|
-
|
|
433
|
+
config.onLimitReached(request, response, options);
|
|
305
434
|
}
|
|
435
|
+
config.validations.disable();
|
|
306
436
|
if (maxHits && totalHits > maxHits) {
|
|
307
|
-
if ((
|
|
308
|
-
response.setHeader("Retry-After", Math.ceil(
|
|
437
|
+
if ((config.legacyHeaders || config.standardHeaders) && !response.headersSent) {
|
|
438
|
+
response.setHeader("Retry-After", Math.ceil(config.windowMs / 1e3));
|
|
309
439
|
}
|
|
310
|
-
|
|
440
|
+
config.handler(request, response, next, options);
|
|
311
441
|
return;
|
|
312
442
|
}
|
|
313
443
|
next();
|
|
314
444
|
}
|
|
315
445
|
);
|
|
316
|
-
middleware.resetKey =
|
|
446
|
+
middleware.resetKey = config.store.resetKey.bind(config.store);
|
|
317
447
|
return middleware;
|
|
318
448
|
};
|
|
319
|
-
var omitUndefinedOptions = (passedOptions) => {
|
|
320
|
-
const omittedOptions = {};
|
|
321
|
-
for (const k of Object.keys(passedOptions)) {
|
|
322
|
-
const key = k;
|
|
323
|
-
if (passedOptions[key] !== void 0) {
|
|
324
|
-
omittedOptions[key] = passedOptions[key];
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return omittedOptions;
|
|
328
|
-
};
|
|
329
449
|
var lib_default = rateLimit;
|
|
450
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
451
|
+
0 && (module.exports = {
|
|
452
|
+
MemoryStore,
|
|
453
|
+
rateLimit
|
|
454
|
+
});
|
|
330
455
|
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;
|
package/dist/index.d.cts
CHANGED
|
@@ -140,7 +140,7 @@ export type Options = {
|
|
|
140
140
|
*
|
|
141
141
|
* Defaults to `60000` ms (= 1 minute).
|
|
142
142
|
*/
|
|
143
|
-
|
|
143
|
+
windowMs: number;
|
|
144
144
|
/**
|
|
145
145
|
* The maximum number of connections to allow during the `window` before
|
|
146
146
|
* rate limiting the client.
|
|
@@ -150,65 +150,65 @@ export type Options = {
|
|
|
150
150
|
*
|
|
151
151
|
* Defaults to `5`.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
max: number | ValueDeterminingMiddleware<number>;
|
|
154
154
|
/**
|
|
155
155
|
* The response body to send back when a client is rate limited.
|
|
156
156
|
*
|
|
157
157
|
* Defaults to `'Too many requests, please try again later.'`
|
|
158
158
|
*/
|
|
159
|
-
|
|
159
|
+
message: any | ValueDeterminingMiddleware<any>;
|
|
160
160
|
/**
|
|
161
161
|
* The HTTP status code to send back when a client is rate limited.
|
|
162
162
|
*
|
|
163
163
|
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
|
|
164
164
|
*/
|
|
165
|
-
|
|
165
|
+
statusCode: number;
|
|
166
166
|
/**
|
|
167
167
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
|
168
168
|
* of requests.
|
|
169
169
|
*
|
|
170
170
|
* Defaults to `true` (for backward compatibility).
|
|
171
171
|
*/
|
|
172
|
-
|
|
172
|
+
legacyHeaders: boolean;
|
|
173
173
|
/**
|
|
174
174
|
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
|
|
175
175
|
*
|
|
176
176
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
|
177
177
|
*/
|
|
178
|
-
|
|
178
|
+
standardHeaders: boolean;
|
|
179
179
|
/**
|
|
180
180
|
* The name of the property on the request object to store the rate limit info.
|
|
181
181
|
*
|
|
182
182
|
* Defaults to `rateLimit`.
|
|
183
183
|
*/
|
|
184
|
-
|
|
184
|
+
requestPropertyName: string;
|
|
185
185
|
/**
|
|
186
186
|
* If `true`, the library will (by default) skip all requests that have a 4XX
|
|
187
187
|
* or 5XX status.
|
|
188
188
|
*
|
|
189
189
|
* Defaults to `false`.
|
|
190
190
|
*/
|
|
191
|
-
|
|
191
|
+
skipFailedRequests: boolean;
|
|
192
192
|
/**
|
|
193
193
|
* If `true`, the library will (by default) skip all requests that have a
|
|
194
194
|
* status code less than 400.
|
|
195
195
|
*
|
|
196
196
|
* Defaults to `false`.
|
|
197
197
|
*/
|
|
198
|
-
|
|
198
|
+
skipSuccessfulRequests: boolean;
|
|
199
199
|
/**
|
|
200
200
|
* Method to generate custom identifiers for clients.
|
|
201
201
|
*
|
|
202
202
|
* By default, the client's IP address is used.
|
|
203
203
|
*/
|
|
204
|
-
|
|
204
|
+
keyGenerator: ValueDeterminingMiddleware<string>;
|
|
205
205
|
/**
|
|
206
206
|
* Express request handler that sends back a response when a client is
|
|
207
207
|
* rate-limited.
|
|
208
208
|
*
|
|
209
209
|
* By default, sends back the `statusCode` and `message` set via the options.
|
|
210
210
|
*/
|
|
211
|
-
|
|
211
|
+
handler: RateLimitExceededEventHandler;
|
|
212
212
|
/**
|
|
213
213
|
* Express request handler that sends back a response when a client has
|
|
214
214
|
* reached their rate limit, and will be rate limited on their next request.
|
|
@@ -216,14 +216,14 @@ export type Options = {
|
|
|
216
216
|
* @deprecated 6.x - Please use a custom `handler` that checks the number of
|
|
217
217
|
* hits instead.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
onLimitReached: RateLimitReachedEventHandler;
|
|
220
220
|
/**
|
|
221
221
|
* Method (in the form of middleware) to determine whether or not this request
|
|
222
222
|
* counts towards a client's quota.
|
|
223
223
|
*
|
|
224
224
|
* By default, skips no requests.
|
|
225
225
|
*/
|
|
226
|
-
|
|
226
|
+
skip: ValueDeterminingMiddleware<boolean>;
|
|
227
227
|
/**
|
|
228
228
|
* Method to determine whether or not the request counts as 'succesful'. Used
|
|
229
229
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
|
@@ -231,13 +231,17 @@ export type Options = {
|
|
|
231
231
|
* By default, requests with a response status code less than 400 are considered
|
|
232
232
|
* successful.
|
|
233
233
|
*/
|
|
234
|
-
|
|
234
|
+
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
|
|
235
235
|
/**
|
|
236
236
|
* The `Store` to use to store the hit count for each client.
|
|
237
237
|
*
|
|
238
238
|
* By default, the built-in `MemoryStore` will be used.
|
|
239
239
|
*/
|
|
240
240
|
store: Store | LegacyStore;
|
|
241
|
+
/**
|
|
242
|
+
* Whether or not the validation checks should run.
|
|
243
|
+
*/
|
|
244
|
+
validate: boolean;
|
|
241
245
|
/**
|
|
242
246
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
|
243
247
|
* of requests.
|
|
@@ -265,10 +269,10 @@ export type AugmentedRequest = Request & {
|
|
|
265
269
|
* Express request object.
|
|
266
270
|
*/
|
|
267
271
|
export type RateLimitInfo = {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
limit: number;
|
|
273
|
+
current: number;
|
|
274
|
+
remaining: number;
|
|
275
|
+
resetTime: Date | undefined;
|
|
272
276
|
};
|
|
273
277
|
/**
|
|
274
278
|
*
|
package/dist/index.d.mts
CHANGED
|
@@ -140,7 +140,7 @@ export type Options = {
|
|
|
140
140
|
*
|
|
141
141
|
* Defaults to `60000` ms (= 1 minute).
|
|
142
142
|
*/
|
|
143
|
-
|
|
143
|
+
windowMs: number;
|
|
144
144
|
/**
|
|
145
145
|
* The maximum number of connections to allow during the `window` before
|
|
146
146
|
* rate limiting the client.
|
|
@@ -150,65 +150,65 @@ export type Options = {
|
|
|
150
150
|
*
|
|
151
151
|
* Defaults to `5`.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
max: number | ValueDeterminingMiddleware<number>;
|
|
154
154
|
/**
|
|
155
155
|
* The response body to send back when a client is rate limited.
|
|
156
156
|
*
|
|
157
157
|
* Defaults to `'Too many requests, please try again later.'`
|
|
158
158
|
*/
|
|
159
|
-
|
|
159
|
+
message: any | ValueDeterminingMiddleware<any>;
|
|
160
160
|
/**
|
|
161
161
|
* The HTTP status code to send back when a client is rate limited.
|
|
162
162
|
*
|
|
163
163
|
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
|
|
164
164
|
*/
|
|
165
|
-
|
|
165
|
+
statusCode: number;
|
|
166
166
|
/**
|
|
167
167
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
|
168
168
|
* of requests.
|
|
169
169
|
*
|
|
170
170
|
* Defaults to `true` (for backward compatibility).
|
|
171
171
|
*/
|
|
172
|
-
|
|
172
|
+
legacyHeaders: boolean;
|
|
173
173
|
/**
|
|
174
174
|
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
|
|
175
175
|
*
|
|
176
176
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
|
177
177
|
*/
|
|
178
|
-
|
|
178
|
+
standardHeaders: boolean;
|
|
179
179
|
/**
|
|
180
180
|
* The name of the property on the request object to store the rate limit info.
|
|
181
181
|
*
|
|
182
182
|
* Defaults to `rateLimit`.
|
|
183
183
|
*/
|
|
184
|
-
|
|
184
|
+
requestPropertyName: string;
|
|
185
185
|
/**
|
|
186
186
|
* If `true`, the library will (by default) skip all requests that have a 4XX
|
|
187
187
|
* or 5XX status.
|
|
188
188
|
*
|
|
189
189
|
* Defaults to `false`.
|
|
190
190
|
*/
|
|
191
|
-
|
|
191
|
+
skipFailedRequests: boolean;
|
|
192
192
|
/**
|
|
193
193
|
* If `true`, the library will (by default) skip all requests that have a
|
|
194
194
|
* status code less than 400.
|
|
195
195
|
*
|
|
196
196
|
* Defaults to `false`.
|
|
197
197
|
*/
|
|
198
|
-
|
|
198
|
+
skipSuccessfulRequests: boolean;
|
|
199
199
|
/**
|
|
200
200
|
* Method to generate custom identifiers for clients.
|
|
201
201
|
*
|
|
202
202
|
* By default, the client's IP address is used.
|
|
203
203
|
*/
|
|
204
|
-
|
|
204
|
+
keyGenerator: ValueDeterminingMiddleware<string>;
|
|
205
205
|
/**
|
|
206
206
|
* Express request handler that sends back a response when a client is
|
|
207
207
|
* rate-limited.
|
|
208
208
|
*
|
|
209
209
|
* By default, sends back the `statusCode` and `message` set via the options.
|
|
210
210
|
*/
|
|
211
|
-
|
|
211
|
+
handler: RateLimitExceededEventHandler;
|
|
212
212
|
/**
|
|
213
213
|
* Express request handler that sends back a response when a client has
|
|
214
214
|
* reached their rate limit, and will be rate limited on their next request.
|
|
@@ -216,14 +216,14 @@ export type Options = {
|
|
|
216
216
|
* @deprecated 6.x - Please use a custom `handler` that checks the number of
|
|
217
217
|
* hits instead.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
onLimitReached: RateLimitReachedEventHandler;
|
|
220
220
|
/**
|
|
221
221
|
* Method (in the form of middleware) to determine whether or not this request
|
|
222
222
|
* counts towards a client's quota.
|
|
223
223
|
*
|
|
224
224
|
* By default, skips no requests.
|
|
225
225
|
*/
|
|
226
|
-
|
|
226
|
+
skip: ValueDeterminingMiddleware<boolean>;
|
|
227
227
|
/**
|
|
228
228
|
* Method to determine whether or not the request counts as 'succesful'. Used
|
|
229
229
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
|
@@ -231,13 +231,17 @@ export type Options = {
|
|
|
231
231
|
* By default, requests with a response status code less than 400 are considered
|
|
232
232
|
* successful.
|
|
233
233
|
*/
|
|
234
|
-
|
|
234
|
+
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
|
|
235
235
|
/**
|
|
236
236
|
* The `Store` to use to store the hit count for each client.
|
|
237
237
|
*
|
|
238
238
|
* By default, the built-in `MemoryStore` will be used.
|
|
239
239
|
*/
|
|
240
240
|
store: Store | LegacyStore;
|
|
241
|
+
/**
|
|
242
|
+
* Whether or not the validation checks should run.
|
|
243
|
+
*/
|
|
244
|
+
validate: boolean;
|
|
241
245
|
/**
|
|
242
246
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
|
243
247
|
* of requests.
|
|
@@ -265,10 +269,10 @@ export type AugmentedRequest = Request & {
|
|
|
265
269
|
* Express request object.
|
|
266
270
|
*/
|
|
267
271
|
export type RateLimitInfo = {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
limit: number;
|
|
273
|
+
current: number;
|
|
274
|
+
remaining: number;
|
|
275
|
+
resetTime: Date | undefined;
|
|
272
276
|
};
|
|
273
277
|
/**
|
|
274
278
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -140,7 +140,7 @@ export type Options = {
|
|
|
140
140
|
*
|
|
141
141
|
* Defaults to `60000` ms (= 1 minute).
|
|
142
142
|
*/
|
|
143
|
-
|
|
143
|
+
windowMs: number;
|
|
144
144
|
/**
|
|
145
145
|
* The maximum number of connections to allow during the `window` before
|
|
146
146
|
* rate limiting the client.
|
|
@@ -150,65 +150,65 @@ export type Options = {
|
|
|
150
150
|
*
|
|
151
151
|
* Defaults to `5`.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
max: number | ValueDeterminingMiddleware<number>;
|
|
154
154
|
/**
|
|
155
155
|
* The response body to send back when a client is rate limited.
|
|
156
156
|
*
|
|
157
157
|
* Defaults to `'Too many requests, please try again later.'`
|
|
158
158
|
*/
|
|
159
|
-
|
|
159
|
+
message: any | ValueDeterminingMiddleware<any>;
|
|
160
160
|
/**
|
|
161
161
|
* The HTTP status code to send back when a client is rate limited.
|
|
162
162
|
*
|
|
163
163
|
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
|
|
164
164
|
*/
|
|
165
|
-
|
|
165
|
+
statusCode: number;
|
|
166
166
|
/**
|
|
167
167
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
|
168
168
|
* of requests.
|
|
169
169
|
*
|
|
170
170
|
* Defaults to `true` (for backward compatibility).
|
|
171
171
|
*/
|
|
172
|
-
|
|
172
|
+
legacyHeaders: boolean;
|
|
173
173
|
/**
|
|
174
174
|
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
|
|
175
175
|
*
|
|
176
176
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
|
177
177
|
*/
|
|
178
|
-
|
|
178
|
+
standardHeaders: boolean;
|
|
179
179
|
/**
|
|
180
180
|
* The name of the property on the request object to store the rate limit info.
|
|
181
181
|
*
|
|
182
182
|
* Defaults to `rateLimit`.
|
|
183
183
|
*/
|
|
184
|
-
|
|
184
|
+
requestPropertyName: string;
|
|
185
185
|
/**
|
|
186
186
|
* If `true`, the library will (by default) skip all requests that have a 4XX
|
|
187
187
|
* or 5XX status.
|
|
188
188
|
*
|
|
189
189
|
* Defaults to `false`.
|
|
190
190
|
*/
|
|
191
|
-
|
|
191
|
+
skipFailedRequests: boolean;
|
|
192
192
|
/**
|
|
193
193
|
* If `true`, the library will (by default) skip all requests that have a
|
|
194
194
|
* status code less than 400.
|
|
195
195
|
*
|
|
196
196
|
* Defaults to `false`.
|
|
197
197
|
*/
|
|
198
|
-
|
|
198
|
+
skipSuccessfulRequests: boolean;
|
|
199
199
|
/**
|
|
200
200
|
* Method to generate custom identifiers for clients.
|
|
201
201
|
*
|
|
202
202
|
* By default, the client's IP address is used.
|
|
203
203
|
*/
|
|
204
|
-
|
|
204
|
+
keyGenerator: ValueDeterminingMiddleware<string>;
|
|
205
205
|
/**
|
|
206
206
|
* Express request handler that sends back a response when a client is
|
|
207
207
|
* rate-limited.
|
|
208
208
|
*
|
|
209
209
|
* By default, sends back the `statusCode` and `message` set via the options.
|
|
210
210
|
*/
|
|
211
|
-
|
|
211
|
+
handler: RateLimitExceededEventHandler;
|
|
212
212
|
/**
|
|
213
213
|
* Express request handler that sends back a response when a client has
|
|
214
214
|
* reached their rate limit, and will be rate limited on their next request.
|
|
@@ -216,14 +216,14 @@ export type Options = {
|
|
|
216
216
|
* @deprecated 6.x - Please use a custom `handler` that checks the number of
|
|
217
217
|
* hits instead.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
onLimitReached: RateLimitReachedEventHandler;
|
|
220
220
|
/**
|
|
221
221
|
* Method (in the form of middleware) to determine whether or not this request
|
|
222
222
|
* counts towards a client's quota.
|
|
223
223
|
*
|
|
224
224
|
* By default, skips no requests.
|
|
225
225
|
*/
|
|
226
|
-
|
|
226
|
+
skip: ValueDeterminingMiddleware<boolean>;
|
|
227
227
|
/**
|
|
228
228
|
* Method to determine whether or not the request counts as 'succesful'. Used
|
|
229
229
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
|
@@ -231,13 +231,17 @@ export type Options = {
|
|
|
231
231
|
* By default, requests with a response status code less than 400 are considered
|
|
232
232
|
* successful.
|
|
233
233
|
*/
|
|
234
|
-
|
|
234
|
+
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
|
|
235
235
|
/**
|
|
236
236
|
* The `Store` to use to store the hit count for each client.
|
|
237
237
|
*
|
|
238
238
|
* By default, the built-in `MemoryStore` will be used.
|
|
239
239
|
*/
|
|
240
240
|
store: Store | LegacyStore;
|
|
241
|
+
/**
|
|
242
|
+
* Whether or not the validation checks should run.
|
|
243
|
+
*/
|
|
244
|
+
validate: boolean;
|
|
241
245
|
/**
|
|
242
246
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
|
243
247
|
* of requests.
|
|
@@ -265,10 +269,10 @@ export type AugmentedRequest = Request & {
|
|
|
265
269
|
* Express request object.
|
|
266
270
|
*/
|
|
267
271
|
export type RateLimitInfo = {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
limit: number;
|
|
273
|
+
current: number;
|
|
274
|
+
remaining: number;
|
|
275
|
+
resetTime: Date | undefined;
|
|
272
276
|
};
|
|
273
277
|
/**
|
|
274
278
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,116 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// source/validations.ts
|
|
9
|
+
import { isIP } from "net";
|
|
10
|
+
var ValidationError = class extends Error {
|
|
11
|
+
/**
|
|
12
|
+
* The code must be a string, in snake case and all capital, that starts with
|
|
13
|
+
* the substring `ERR_ERL_`.
|
|
14
|
+
*
|
|
15
|
+
* The message must be a string, starting with a lowercase character,
|
|
16
|
+
* describing the issue in detail.
|
|
17
|
+
*/
|
|
18
|
+
constructor(code, message) {
|
|
19
|
+
super(
|
|
20
|
+
`express-rate-limit: ${code} - ${message} See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#${code.toLowerCase()} for more information on this error.`
|
|
21
|
+
);
|
|
22
|
+
__publicField(this, "name");
|
|
23
|
+
__publicField(this, "code");
|
|
24
|
+
this.name = this.constructor.name;
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.message = message;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var Validations = class {
|
|
30
|
+
constructor(enabled) {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/parameter-properties
|
|
32
|
+
__publicField(this, "enabled");
|
|
33
|
+
this.enabled = enabled;
|
|
34
|
+
}
|
|
35
|
+
enable() {
|
|
36
|
+
this.enabled = true;
|
|
37
|
+
}
|
|
38
|
+
disable() {
|
|
39
|
+
this.enabled = false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether the IP address is valid, and that it does not have a port
|
|
43
|
+
* number in it.
|
|
44
|
+
*
|
|
45
|
+
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
|
46
|
+
*
|
|
47
|
+
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
|
48
|
+
*
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
ip(ip) {
|
|
52
|
+
this.wrap(() => {
|
|
53
|
+
if (ip === void 0) {
|
|
54
|
+
throw new ValidationError(
|
|
55
|
+
"ERR_ERL_UNDEFINED_IP_ADDRESS",
|
|
56
|
+
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (!isIP(ip)) {
|
|
60
|
+
throw new ValidationError(
|
|
61
|
+
"ERR_ERL_INVALID_IP_ADDRESS",
|
|
62
|
+
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Makes sure the trust proxy setting is not set to `true`.
|
|
69
|
+
*
|
|
70
|
+
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
|
71
|
+
*
|
|
72
|
+
* @param request {Request} - The Express request object.
|
|
73
|
+
*
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
trustProxy(request) {
|
|
77
|
+
this.wrap(() => {
|
|
78
|
+
if (request.app.get("trust proxy") === true) {
|
|
79
|
+
throw new ValidationError(
|
|
80
|
+
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
|
|
81
|
+
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
|
88
|
+
* header is present.
|
|
89
|
+
*
|
|
90
|
+
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
|
91
|
+
*
|
|
92
|
+
* @param request {Request} - The Express request object.
|
|
93
|
+
*
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
xForwardedForHeader(request) {
|
|
97
|
+
this.wrap(() => {
|
|
98
|
+
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
|
|
99
|
+
throw new ValidationError(
|
|
100
|
+
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
|
|
101
|
+
`The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
wrap(validation) {
|
|
107
|
+
if (!this.enabled) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
validation.call(this);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
8
118
|
// source/memory-store.ts
|
|
9
119
|
var calculateNextResetTime = (windowMs) => {
|
|
10
120
|
const resetTime = /* @__PURE__ */ new Date();
|
|
@@ -142,27 +252,43 @@ var promisifyStore = (passedStore) => {
|
|
|
142
252
|
}
|
|
143
253
|
return new PromisifiedStore();
|
|
144
254
|
};
|
|
255
|
+
var getOptionsFromConfig = (config) => {
|
|
256
|
+
const { validations, ...directlyPassableEntries } = config;
|
|
257
|
+
return {
|
|
258
|
+
...directlyPassableEntries,
|
|
259
|
+
validate: validations.enabled
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
var omitUndefinedOptions = (passedOptions) => {
|
|
263
|
+
const omittedOptions = {};
|
|
264
|
+
for (const k of Object.keys(passedOptions)) {
|
|
265
|
+
const key = k;
|
|
266
|
+
if (passedOptions[key] !== void 0) {
|
|
267
|
+
omittedOptions[key] = passedOptions[key];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return omittedOptions;
|
|
271
|
+
};
|
|
145
272
|
var parseOptions = (passedOptions) => {
|
|
146
|
-
var _a, _b, _c;
|
|
273
|
+
var _a, _b, _c, _d;
|
|
147
274
|
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
|
275
|
+
const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
|
|
148
276
|
const config = {
|
|
149
277
|
windowMs: 60 * 1e3,
|
|
150
278
|
max: 5,
|
|
151
279
|
message: "Too many requests, please try again later.",
|
|
152
280
|
statusCode: 429,
|
|
153
|
-
legacyHeaders: (
|
|
154
|
-
standardHeaders: (
|
|
281
|
+
legacyHeaders: (_b = passedOptions.headers) != null ? _b : true,
|
|
282
|
+
standardHeaders: (_c = passedOptions.draft_polli_ratelimit_headers) != null ? _c : false,
|
|
155
283
|
requestPropertyName: "rateLimit",
|
|
156
284
|
skipFailedRequests: false,
|
|
157
285
|
skipSuccessfulRequests: false,
|
|
158
286
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
|
159
287
|
skip: (_request, _response) => false,
|
|
160
288
|
keyGenerator(request, _response) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
);
|
|
165
|
-
}
|
|
289
|
+
validations.ip(request.ip);
|
|
290
|
+
validations.trustProxy(request);
|
|
291
|
+
validations.xForwardedForHeader(request);
|
|
166
292
|
return request.ip;
|
|
167
293
|
},
|
|
168
294
|
async handler(request, response, _next, _optionsUsed) {
|
|
@@ -181,7 +307,9 @@ var parseOptions = (passedOptions) => {
|
|
|
181
307
|
...notUndefinedOptions,
|
|
182
308
|
// Note that this field is declared after the user's options are spread in,
|
|
183
309
|
// so that this field doesn't get overriden with an un-promisified store!
|
|
184
|
-
store: promisifyStore((
|
|
310
|
+
store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
|
|
311
|
+
// Print an error to the console if a few known misconfigurations are detected.
|
|
312
|
+
validations
|
|
185
313
|
};
|
|
186
314
|
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
|
|
187
315
|
throw new TypeError(
|
|
@@ -198,32 +326,33 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
|
|
|
198
326
|
}
|
|
199
327
|
};
|
|
200
328
|
var rateLimit = (passedOptions) => {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
329
|
+
const config = parseOptions(passedOptions != null ? passedOptions : {});
|
|
330
|
+
const options = getOptionsFromConfig(config);
|
|
331
|
+
if (typeof config.store.init === "function")
|
|
332
|
+
config.store.init(options);
|
|
204
333
|
const middleware = handleAsyncErrors(
|
|
205
334
|
async (request, response, next) => {
|
|
206
|
-
const skip = await
|
|
335
|
+
const skip = await config.skip(request, response);
|
|
207
336
|
if (skip) {
|
|
208
337
|
next();
|
|
209
338
|
return;
|
|
210
339
|
}
|
|
211
340
|
const augmentedRequest = request;
|
|
212
|
-
const key = await
|
|
213
|
-
const { totalHits, resetTime } = await
|
|
214
|
-
const retrieveQuota = typeof
|
|
341
|
+
const key = await config.keyGenerator(request, response);
|
|
342
|
+
const { totalHits, resetTime } = await config.store.increment(key);
|
|
343
|
+
const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
|
|
215
344
|
const maxHits = await retrieveQuota;
|
|
216
|
-
augmentedRequest[
|
|
345
|
+
augmentedRequest[config.requestPropertyName] = {
|
|
217
346
|
limit: maxHits,
|
|
218
347
|
current: totalHits,
|
|
219
348
|
remaining: Math.max(maxHits - totalHits, 0),
|
|
220
349
|
resetTime
|
|
221
350
|
};
|
|
222
|
-
if (
|
|
351
|
+
if (config.legacyHeaders && !response.headersSent) {
|
|
223
352
|
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
224
353
|
response.setHeader(
|
|
225
354
|
"X-RateLimit-Remaining",
|
|
226
|
-
augmentedRequest[
|
|
355
|
+
augmentedRequest[config.requestPropertyName].remaining
|
|
227
356
|
);
|
|
228
357
|
if (resetTime instanceof Date) {
|
|
229
358
|
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
@@ -233,11 +362,11 @@ var rateLimit = (passedOptions) => {
|
|
|
233
362
|
);
|
|
234
363
|
}
|
|
235
364
|
}
|
|
236
|
-
if (
|
|
365
|
+
if (config.standardHeaders && !response.headersSent) {
|
|
237
366
|
response.setHeader("RateLimit-Limit", maxHits);
|
|
238
367
|
response.setHeader(
|
|
239
368
|
"RateLimit-Remaining",
|
|
240
|
-
augmentedRequest[
|
|
369
|
+
augmentedRequest[config.requestPropertyName].remaining
|
|
241
370
|
);
|
|
242
371
|
if (resetTime) {
|
|
243
372
|
const deltaSeconds = Math.ceil(
|
|
@@ -246,17 +375,17 @@ var rateLimit = (passedOptions) => {
|
|
|
246
375
|
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
247
376
|
}
|
|
248
377
|
}
|
|
249
|
-
if (
|
|
378
|
+
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
|
|
250
379
|
let decremented = false;
|
|
251
380
|
const decrementKey = async () => {
|
|
252
381
|
if (!decremented) {
|
|
253
|
-
await
|
|
382
|
+
await config.store.decrement(key);
|
|
254
383
|
decremented = true;
|
|
255
384
|
}
|
|
256
385
|
};
|
|
257
|
-
if (
|
|
386
|
+
if (config.skipFailedRequests) {
|
|
258
387
|
response.on("finish", async () => {
|
|
259
|
-
if (!
|
|
388
|
+
if (!config.requestWasSuccessful(request, response))
|
|
260
389
|
await decrementKey();
|
|
261
390
|
});
|
|
262
391
|
response.on("close", async () => {
|
|
@@ -267,39 +396,30 @@ var rateLimit = (passedOptions) => {
|
|
|
267
396
|
await decrementKey();
|
|
268
397
|
});
|
|
269
398
|
}
|
|
270
|
-
if (
|
|
399
|
+
if (config.skipSuccessfulRequests) {
|
|
271
400
|
response.on("finish", async () => {
|
|
272
|
-
if (
|
|
401
|
+
if (config.requestWasSuccessful(request, response))
|
|
273
402
|
await decrementKey();
|
|
274
403
|
});
|
|
275
404
|
}
|
|
276
405
|
}
|
|
277
406
|
if (maxHits && totalHits === maxHits + 1) {
|
|
278
|
-
|
|
407
|
+
config.onLimitReached(request, response, options);
|
|
279
408
|
}
|
|
409
|
+
config.validations.disable();
|
|
280
410
|
if (maxHits && totalHits > maxHits) {
|
|
281
|
-
if ((
|
|
282
|
-
response.setHeader("Retry-After", Math.ceil(
|
|
411
|
+
if ((config.legacyHeaders || config.standardHeaders) && !response.headersSent) {
|
|
412
|
+
response.setHeader("Retry-After", Math.ceil(config.windowMs / 1e3));
|
|
283
413
|
}
|
|
284
|
-
|
|
414
|
+
config.handler(request, response, next, options);
|
|
285
415
|
return;
|
|
286
416
|
}
|
|
287
417
|
next();
|
|
288
418
|
}
|
|
289
419
|
);
|
|
290
|
-
middleware.resetKey =
|
|
420
|
+
middleware.resetKey = config.store.resetKey.bind(config.store);
|
|
291
421
|
return middleware;
|
|
292
422
|
};
|
|
293
|
-
var omitUndefinedOptions = (passedOptions) => {
|
|
294
|
-
const omittedOptions = {};
|
|
295
|
-
for (const k of Object.keys(passedOptions)) {
|
|
296
|
-
const key = k;
|
|
297
|
-
if (passedOptions[key] !== void 0) {
|
|
298
|
-
omittedOptions[key] = passedOptions[key];
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return omittedOptions;
|
|
302
|
-
};
|
|
303
423
|
var lib_default = rateLimit;
|
|
304
424
|
export {
|
|
305
425
|
MemoryStore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.0",
|
|
4
4
|
"description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Nathan Friedly",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
|
|
59
|
-
"build:cjs": "esbuild --bundle --target=es2019 --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;\" source/index.ts",
|
|
60
|
-
"build:esm": "esbuild --bundle --target=es2019 --format=esm --outfile=dist/index.mjs source/index.ts",
|
|
59
|
+
"build:cjs": "esbuild --platform=node --bundle --target=es2019 --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;\" source/index.ts",
|
|
60
|
+
"build:esm": "esbuild --platform=node --bundle --target=es2019 --format=esm --outfile=dist/index.mjs source/index.ts",
|
|
61
61
|
"build:types": "dts-bundle-generator --out-file=dist/index.d.ts source/index.ts && cp dist/index.d.ts dist/index.d.cts && cp dist/index.d.ts dist/index.d.mts",
|
|
62
62
|
"compile": "run-s clean build:*",
|
|
63
63
|
"lint:code": "xo --ignore test/external/",
|
|
64
64
|
"lint:rest": "prettier --ignore-path .gitignore --ignore-unknown --check .",
|
|
65
65
|
"lint": "run-s lint:*",
|
|
66
|
-
"autofix:code": "run
|
|
67
|
-
"autofix:rest": "run
|
|
66
|
+
"autofix:code": "npm run lint:code -- --fix",
|
|
67
|
+
"autofix:rest": "npm run lint:rest -- --write .",
|
|
68
68
|
"autofix": "run-s autofix:*",
|
|
69
|
-
"test:lib": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
|
|
69
|
+
"test:lib": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-vm-modules jest",
|
|
70
70
|
"test:ext": "cd test/external/ && bash run-all-tests",
|
|
71
71
|
"test": "run-s lint test:*",
|
|
72
72
|
"pre-commit": "lint-staged",
|
|
@@ -107,7 +107,15 @@
|
|
|
107
107
|
"index-signature"
|
|
108
108
|
],
|
|
109
109
|
"n/no-unsupported-features/es-syntax": 0
|
|
110
|
-
}
|
|
110
|
+
},
|
|
111
|
+
"overrides": [
|
|
112
|
+
{
|
|
113
|
+
"files": "test/library/*.ts",
|
|
114
|
+
"rules": {
|
|
115
|
+
"@typescript-eslint/no-unsafe-argument": 0
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
111
119
|
},
|
|
112
120
|
"prettier": {
|
|
113
121
|
"semi": false,
|
|
@@ -119,12 +127,6 @@
|
|
|
119
127
|
},
|
|
120
128
|
"jest": {
|
|
121
129
|
"preset": "ts-jest/presets/default-esm",
|
|
122
|
-
"globals": {
|
|
123
|
-
"ts-jest": {
|
|
124
|
-
"useESM": true
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
"verbose": true,
|
|
128
130
|
"collectCoverage": true,
|
|
129
131
|
"collectCoverageFrom": [
|
|
130
132
|
"source/**/*.ts"
|
package/readme.md
CHANGED
|
@@ -451,6 +451,21 @@ const limiter = rateLimit({
|
|
|
451
451
|
})
|
|
452
452
|
```
|
|
453
453
|
|
|
454
|
+
### `validate`
|
|
455
|
+
|
|
456
|
+
> `boolean`
|
|
457
|
+
|
|
458
|
+
When enabled, a set of validation checks are run on the first request to detect
|
|
459
|
+
common misconfigurations with proxies, etc. Prints an error to the console if
|
|
460
|
+
any issue is detected.
|
|
461
|
+
|
|
462
|
+
Automatically disables after the first request is processed.
|
|
463
|
+
|
|
464
|
+
See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes
|
|
465
|
+
for more info.
|
|
466
|
+
|
|
467
|
+
Defaults to true.
|
|
468
|
+
|
|
454
469
|
### `store`
|
|
455
470
|
|
|
456
471
|
> `Store`
|