express-rate-limit 6.9.0 → 6.11.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 +24 -0
- package/dist/index.cjs +201 -33
- package/dist/index.d.cts +33 -6
- package/dist/index.d.mts +33 -6
- package/dist/index.d.ts +33 -6
- package/dist/index.mjs +201 -33
- package/package.json +21 -42
- package/readme.md +48 -25
- package/tsconfig.json +1 -9
package/changelog.md
CHANGED
|
@@ -6,6 +6,30 @@ 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.11.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.11.0)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Support for retrieving the current hit count and reset time for a given key
|
|
14
|
+
from a store (See
|
|
15
|
+
[#390](https://github.com/express-rate-limit/express-rate-limit/issues/389)).
|
|
16
|
+
|
|
17
|
+
## [6.10.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.10.0)
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Support for combined `RateLimit` header from the
|
|
22
|
+
[RateLimit header fields for HTTP standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers)
|
|
23
|
+
adopted by the IETF. Enable by setting `standardHeaders: 'draft-7'`
|
|
24
|
+
- New `standardHeaders: 'draft-6'` option, treated equivalent to
|
|
25
|
+
`standardHeaders: true` from previous releases. (`true` and `false` are still
|
|
26
|
+
supported.)
|
|
27
|
+
- New `RateLimit-Policy` header added when `standardHeaders` is set to
|
|
28
|
+
`'draft-6'`, `'draft-7'`, or `true`
|
|
29
|
+
- Warning when using deprecated `draft_polli_ratelimit_headers` option
|
|
30
|
+
- Warning when using deprecated `onLimitReached` option
|
|
31
|
+
- Warning when `totalHits` value returned from Store is invalid
|
|
32
|
+
|
|
9
33
|
## [6.9.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.9.0)
|
|
10
34
|
|
|
11
35
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,59 @@ __export(source_exports, {
|
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(source_exports);
|
|
33
33
|
|
|
34
|
+
// source/headers.ts
|
|
35
|
+
var getResetSeconds = (resetTime, windowMs) => {
|
|
36
|
+
let resetSeconds = void 0;
|
|
37
|
+
if (resetTime) {
|
|
38
|
+
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
|
39
|
+
resetSeconds = Math.max(0, deltaSeconds);
|
|
40
|
+
} else if (windowMs) {
|
|
41
|
+
resetSeconds = Math.ceil(windowMs / 1e3);
|
|
42
|
+
}
|
|
43
|
+
return resetSeconds;
|
|
44
|
+
};
|
|
45
|
+
var setLegacyHeaders = (response, info) => {
|
|
46
|
+
if (response.headersSent)
|
|
47
|
+
return;
|
|
48
|
+
response.setHeader("X-RateLimit-Limit", info.limit);
|
|
49
|
+
response.setHeader("X-RateLimit-Remaining", info.remaining);
|
|
50
|
+
if (info.resetTime instanceof Date) {
|
|
51
|
+
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
52
|
+
response.setHeader(
|
|
53
|
+
"X-RateLimit-Reset",
|
|
54
|
+
Math.ceil(info.resetTime.getTime() / 1e3)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var setDraft6Headers = (response, info, windowMs) => {
|
|
59
|
+
if (response.headersSent)
|
|
60
|
+
return;
|
|
61
|
+
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
62
|
+
const resetSeconds = getResetSeconds(info.resetTime);
|
|
63
|
+
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
64
|
+
response.setHeader("RateLimit-Limit", info.limit);
|
|
65
|
+
response.setHeader("RateLimit-Remaining", info.remaining);
|
|
66
|
+
if (resetSeconds)
|
|
67
|
+
response.setHeader("RateLimit-Reset", resetSeconds);
|
|
68
|
+
};
|
|
69
|
+
var setDraft7Headers = (response, info, windowMs) => {
|
|
70
|
+
if (response.headersSent)
|
|
71
|
+
return;
|
|
72
|
+
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
73
|
+
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
|
74
|
+
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
75
|
+
response.setHeader(
|
|
76
|
+
"RateLimit",
|
|
77
|
+
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
var setRetryAfterHeader = (response, info, windowMs) => {
|
|
81
|
+
if (response.headersSent)
|
|
82
|
+
return;
|
|
83
|
+
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
|
84
|
+
response.setHeader("Retry-After", resetSeconds);
|
|
85
|
+
};
|
|
86
|
+
|
|
34
87
|
// source/validations.ts
|
|
35
88
|
var import_node_net = require("net");
|
|
36
89
|
var ValidationError = class extends Error {
|
|
@@ -38,12 +91,12 @@ var ValidationError = class extends Error {
|
|
|
38
91
|
* The code must be a string, in snake case and all capital, that starts with
|
|
39
92
|
* the substring `ERR_ERL_`.
|
|
40
93
|
*
|
|
41
|
-
* The message must be a string, starting with
|
|
94
|
+
* The message must be a string, starting with an uppercase character,
|
|
42
95
|
* describing the issue in detail.
|
|
43
96
|
*/
|
|
44
97
|
constructor(code, message) {
|
|
45
98
|
const url = `https://express-rate-limit.github.io/${code}/`;
|
|
46
|
-
super(`${message} See ${url} for more information
|
|
99
|
+
super(`${message} See ${url} for more information.`);
|
|
47
100
|
__publicField(this, "name");
|
|
48
101
|
__publicField(this, "code");
|
|
49
102
|
__publicField(this, "help");
|
|
@@ -52,6 +105,8 @@ var ValidationError = class extends Error {
|
|
|
52
105
|
this.help = url;
|
|
53
106
|
}
|
|
54
107
|
};
|
|
108
|
+
var ChangeWarning = class extends ValidationError {
|
|
109
|
+
};
|
|
55
110
|
var _Validations = class _Validations {
|
|
56
111
|
constructor(enabled) {
|
|
57
112
|
// eslint-disable-next-line @typescript-eslint/parameter-properties
|
|
@@ -129,6 +184,22 @@ var _Validations = class _Validations {
|
|
|
129
184
|
}
|
|
130
185
|
});
|
|
131
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Ensures totalHits value from store is a positive integer.
|
|
189
|
+
*
|
|
190
|
+
* @param hits {any} - The `totalHits` returned by the store.
|
|
191
|
+
*/
|
|
192
|
+
positiveHits(hits) {
|
|
193
|
+
this.wrap(() => {
|
|
194
|
+
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
|
|
195
|
+
throw new ValidationError(
|
|
196
|
+
"ERR_ERL_INVALID_HITS",
|
|
197
|
+
`The totalHits value returned from the store must be a positive integer, got ${hits}`
|
|
198
|
+
// eslint-disable-line @typescript-eslint/restrict-template-expressions
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
132
203
|
/**
|
|
133
204
|
* Ensures a given key is incremented only once per request.
|
|
134
205
|
*
|
|
@@ -160,6 +231,78 @@ var _Validations = class _Validations {
|
|
|
160
231
|
keys.push(key);
|
|
161
232
|
});
|
|
162
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Warns the user that the behaviour for `max: 0` is changing in the next
|
|
236
|
+
* major release.
|
|
237
|
+
*
|
|
238
|
+
* @param max {number} - The maximum number of hits per client.
|
|
239
|
+
*
|
|
240
|
+
* @returns {void}
|
|
241
|
+
*/
|
|
242
|
+
max(max) {
|
|
243
|
+
this.wrap(() => {
|
|
244
|
+
if (max === 0) {
|
|
245
|
+
throw new ChangeWarning(
|
|
246
|
+
"WRN_ERL_MAX_ZERO",
|
|
247
|
+
`Setting max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
|
254
|
+
* and will be removed in the next major release.
|
|
255
|
+
*
|
|
256
|
+
* @param draft_polli_ratelimit_headers {boolean|undefined} - The now-deprecated setting that was used to enable standard headers.
|
|
257
|
+
*
|
|
258
|
+
* @returns {void}
|
|
259
|
+
*/
|
|
260
|
+
draftPolliHeaders(draft_polli_ratelimit_headers) {
|
|
261
|
+
this.wrap(() => {
|
|
262
|
+
if (draft_polli_ratelimit_headers) {
|
|
263
|
+
throw new ChangeWarning(
|
|
264
|
+
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
|
|
265
|
+
`The draft_polli_ratelimit_headers configuration option is deprecated and will be removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
|
|
272
|
+
* major release.
|
|
273
|
+
*
|
|
274
|
+
* @param onLimitReached {function|undefined} - The maximum number of hits per client.
|
|
275
|
+
*
|
|
276
|
+
* @returns {void}
|
|
277
|
+
*/
|
|
278
|
+
onLimitReached(onLimitReached) {
|
|
279
|
+
this.wrap(() => {
|
|
280
|
+
if (onLimitReached) {
|
|
281
|
+
throw new ChangeWarning(
|
|
282
|
+
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
|
|
283
|
+
`The onLimitReached configuration option is deprecated and will be removed in express-rate-limit v7.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Warns the user when the selected headers option requires a reset time but
|
|
290
|
+
* the store does not provide one.
|
|
291
|
+
*
|
|
292
|
+
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
|
293
|
+
*
|
|
294
|
+
* @returns {void}
|
|
295
|
+
*/
|
|
296
|
+
headersResetTime(resetTime) {
|
|
297
|
+
this.wrap(() => {
|
|
298
|
+
if (!resetTime) {
|
|
299
|
+
throw new ValidationError(
|
|
300
|
+
"ERR_ERL_HEADERS_NO_RESET",
|
|
301
|
+
`standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
163
306
|
wrap(validation) {
|
|
164
307
|
if (!this.enabled) {
|
|
165
308
|
return;
|
|
@@ -167,7 +310,10 @@ var _Validations = class _Validations {
|
|
|
167
310
|
try {
|
|
168
311
|
validation.call(this);
|
|
169
312
|
} catch (error) {
|
|
170
|
-
|
|
313
|
+
if (error instanceof ChangeWarning)
|
|
314
|
+
console.warn(error);
|
|
315
|
+
else
|
|
316
|
+
console.error(error);
|
|
171
317
|
}
|
|
172
318
|
}
|
|
173
319
|
};
|
|
@@ -229,12 +375,29 @@ var MemoryStore = class {
|
|
|
229
375
|
if (this.interval.unref)
|
|
230
376
|
this.interval.unref();
|
|
231
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Method to fetch a client's hit count and reset time.
|
|
380
|
+
*
|
|
381
|
+
* @param key {string} - The identifier for a client.
|
|
382
|
+
*
|
|
383
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
384
|
+
*
|
|
385
|
+
* @public
|
|
386
|
+
*/
|
|
387
|
+
async get(key) {
|
|
388
|
+
if (this.hits[key] !== void 0)
|
|
389
|
+
return {
|
|
390
|
+
totalHits: this.hits[key],
|
|
391
|
+
resetTime: this.resetTime
|
|
392
|
+
};
|
|
393
|
+
return void 0;
|
|
394
|
+
}
|
|
232
395
|
/**
|
|
233
396
|
* Method to increment a client's hit counter.
|
|
234
397
|
*
|
|
235
398
|
* @param key {string} - The identifier for a client.
|
|
236
399
|
*
|
|
237
|
-
* @returns {
|
|
400
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
238
401
|
*
|
|
239
402
|
* @public
|
|
240
403
|
*/
|
|
@@ -301,6 +464,10 @@ var promisifyStore = (passedStore) => {
|
|
|
301
464
|
}
|
|
302
465
|
const legacyStore = passedStore;
|
|
303
466
|
class PromisifiedStore {
|
|
467
|
+
/* istanbul ignore next */
|
|
468
|
+
async get(key) {
|
|
469
|
+
return void 0;
|
|
470
|
+
}
|
|
304
471
|
async increment(key) {
|
|
305
472
|
return new Promise((resolve, reject) => {
|
|
306
473
|
legacyStore.incr(
|
|
@@ -319,6 +486,7 @@ var promisifyStore = (passedStore) => {
|
|
|
319
486
|
async resetKey(key) {
|
|
320
487
|
return legacyStore.resetKey(key);
|
|
321
488
|
}
|
|
489
|
+
/* istanbul ignore next */
|
|
322
490
|
async resetAll() {
|
|
323
491
|
if (typeof legacyStore.resetAll === "function")
|
|
324
492
|
return legacyStore.resetAll();
|
|
@@ -347,13 +515,20 @@ var parseOptions = (passedOptions) => {
|
|
|
347
515
|
var _a, _b, _c, _d;
|
|
348
516
|
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
|
349
517
|
const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
|
|
518
|
+
validations.draftPolliHeaders(
|
|
519
|
+
notUndefinedOptions.draft_polli_ratelimit_headers
|
|
520
|
+
);
|
|
521
|
+
validations.onLimitReached(notUndefinedOptions.onLimitReached);
|
|
522
|
+
let standardHeaders = (_b = notUndefinedOptions.standardHeaders) != null ? _b : false;
|
|
523
|
+
if (standardHeaders === true || standardHeaders === void 0 && notUndefinedOptions.draft_polli_ratelimit_headers) {
|
|
524
|
+
standardHeaders = "draft-6";
|
|
525
|
+
}
|
|
350
526
|
const config = {
|
|
351
527
|
windowMs: 60 * 1e3,
|
|
352
528
|
max: 5,
|
|
353
529
|
message: "Too many requests, please try again later.",
|
|
354
530
|
statusCode: 429,
|
|
355
|
-
legacyHeaders: (
|
|
356
|
-
standardHeaders: (_c = passedOptions.draft_polli_ratelimit_headers) != null ? _c : false,
|
|
531
|
+
legacyHeaders: (_c = passedOptions.headers) != null ? _c : true,
|
|
357
532
|
requestPropertyName: "rateLimit",
|
|
358
533
|
skipFailedRequests: false,
|
|
359
534
|
skipSuccessfulRequests: false,
|
|
@@ -372,13 +547,15 @@ var parseOptions = (passedOptions) => {
|
|
|
372
547
|
response
|
|
373
548
|
) : config.message;
|
|
374
549
|
if (!response.writableEnded) {
|
|
375
|
-
response.send(message
|
|
550
|
+
response.send(message);
|
|
376
551
|
}
|
|
377
552
|
},
|
|
378
553
|
onLimitReached(_request, _response, _optionsUsed) {
|
|
379
554
|
},
|
|
380
|
-
// Allow the options
|
|
555
|
+
// Allow the default options to be overriden by the options passed to the middleware.
|
|
381
556
|
...notUndefinedOptions,
|
|
557
|
+
// `standardHeaders` is resolved into a draft version above, use that.
|
|
558
|
+
standardHeaders,
|
|
382
559
|
// Note that this field is declared after the user's options are spread in,
|
|
383
560
|
// so that this field doesn't get overriden with an un-promisified store!
|
|
384
561
|
store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
|
|
@@ -400,6 +577,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
|
|
|
400
577
|
}
|
|
401
578
|
};
|
|
402
579
|
var rateLimit = (passedOptions) => {
|
|
580
|
+
var _a;
|
|
403
581
|
const config = parseOptions(passedOptions != null ? passedOptions : {});
|
|
404
582
|
const options = getOptionsFromConfig(config);
|
|
405
583
|
if (typeof config.store.init === "function")
|
|
@@ -414,40 +592,27 @@ var rateLimit = (passedOptions) => {
|
|
|
414
592
|
const augmentedRequest = request;
|
|
415
593
|
const key = await config.keyGenerator(request, response);
|
|
416
594
|
const { totalHits, resetTime } = await config.store.increment(key);
|
|
595
|
+
config.validations.positiveHits(totalHits);
|
|
417
596
|
config.validations.singleCount(request, config.store, key);
|
|
418
597
|
const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
|
|
419
598
|
const maxHits = await retrieveQuota;
|
|
420
|
-
|
|
599
|
+
config.validations.max(maxHits);
|
|
600
|
+
const info = {
|
|
421
601
|
limit: maxHits,
|
|
422
602
|
current: totalHits,
|
|
423
603
|
remaining: Math.max(maxHits - totalHits, 0),
|
|
424
604
|
resetTime
|
|
425
605
|
};
|
|
606
|
+
augmentedRequest[config.requestPropertyName] = info;
|
|
426
607
|
if (config.legacyHeaders && !response.headersSent) {
|
|
427
|
-
response
|
|
428
|
-
response.setHeader(
|
|
429
|
-
"X-RateLimit-Remaining",
|
|
430
|
-
augmentedRequest[config.requestPropertyName].remaining
|
|
431
|
-
);
|
|
432
|
-
if (resetTime instanceof Date) {
|
|
433
|
-
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
434
|
-
response.setHeader(
|
|
435
|
-
"X-RateLimit-Reset",
|
|
436
|
-
Math.ceil(resetTime.getTime() / 1e3)
|
|
437
|
-
);
|
|
438
|
-
}
|
|
608
|
+
setLegacyHeaders(response, info);
|
|
439
609
|
}
|
|
440
610
|
if (config.standardHeaders && !response.headersSent) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (resetTime) {
|
|
447
|
-
const deltaSeconds = Math.ceil(
|
|
448
|
-
(resetTime.getTime() - Date.now()) / 1e3
|
|
449
|
-
);
|
|
450
|
-
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
611
|
+
if (config.standardHeaders === "draft-6") {
|
|
612
|
+
setDraft6Headers(response, info, config.windowMs);
|
|
613
|
+
} else if (config.standardHeaders === "draft-7") {
|
|
614
|
+
config.validations.headersResetTime(info.resetTime);
|
|
615
|
+
setDraft7Headers(response, info, config.windowMs);
|
|
451
616
|
}
|
|
452
617
|
}
|
|
453
618
|
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
|
|
@@ -483,8 +648,8 @@ var rateLimit = (passedOptions) => {
|
|
|
483
648
|
}
|
|
484
649
|
config.validations.disable();
|
|
485
650
|
if (maxHits && totalHits > maxHits) {
|
|
486
|
-
if (
|
|
487
|
-
response
|
|
651
|
+
if (config.legacyHeaders || config.standardHeaders) {
|
|
652
|
+
setRetryAfterHeader(response, info, config.windowMs);
|
|
488
653
|
}
|
|
489
654
|
config.handler(request, response, next, options);
|
|
490
655
|
return;
|
|
@@ -493,6 +658,9 @@ var rateLimit = (passedOptions) => {
|
|
|
493
658
|
}
|
|
494
659
|
);
|
|
495
660
|
middleware.resetKey = config.store.resetKey.bind(config.store);
|
|
661
|
+
middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
|
|
662
|
+
config.store
|
|
663
|
+
);
|
|
496
664
|
return middleware;
|
|
497
665
|
};
|
|
498
666
|
var lib_default = rateLimit;
|
package/dist/index.d.cts
CHANGED
|
@@ -46,7 +46,7 @@ export type RateLimitReachedEventHandler = (request: Request, response: Response
|
|
|
46
46
|
* @property totalHits {number} - The number of hits for that client so far.
|
|
47
47
|
* @property resetTime {Date | undefined} - The time when the counter resets.
|
|
48
48
|
*/
|
|
49
|
-
export type
|
|
49
|
+
export type ClientRateLimitInfo = {
|
|
50
50
|
totalHits: number;
|
|
51
51
|
resetTime: Date | undefined;
|
|
52
52
|
};
|
|
@@ -60,6 +60,14 @@ export type RateLimitRequestHandler = RequestHandler & {
|
|
|
60
60
|
* @param key {string} - The identifier for a client.
|
|
61
61
|
*/
|
|
62
62
|
resetKey: (key: string) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Method to fetch a client's hit count and reset time.
|
|
65
|
+
*
|
|
66
|
+
* @param key {string} - The identifier for a client.
|
|
67
|
+
*
|
|
68
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
69
|
+
*/
|
|
70
|
+
getKey?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
|
63
71
|
};
|
|
64
72
|
/**
|
|
65
73
|
* An interface that all hit counter stores must implement.
|
|
@@ -102,14 +110,22 @@ export type Store = {
|
|
|
102
110
|
* @param options {Options} - The options used to setup the middleware.
|
|
103
111
|
*/
|
|
104
112
|
init?: (options: Options) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Method to fetch a client's hit count and reset time.
|
|
115
|
+
*
|
|
116
|
+
* @param key {string} - The identifier for a client.
|
|
117
|
+
*
|
|
118
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
119
|
+
*/
|
|
120
|
+
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
|
105
121
|
/**
|
|
106
122
|
* Method to increment a client's hit counter.
|
|
107
123
|
*
|
|
108
124
|
* @param key {string} - The identifier for a client.
|
|
109
125
|
*
|
|
110
|
-
* @returns {
|
|
126
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
111
127
|
*/
|
|
112
|
-
increment: (key: string) => Promise<
|
|
128
|
+
increment: (key: string) => Promise<ClientRateLimitInfo> | ClientRateLimitInfo;
|
|
113
129
|
/**
|
|
114
130
|
* Method to decrement a client's hit counter.
|
|
115
131
|
*
|
|
@@ -139,6 +155,7 @@ export type Store = {
|
|
|
139
155
|
*/
|
|
140
156
|
localKeys?: boolean;
|
|
141
157
|
};
|
|
158
|
+
export type DraftHeadersVersion = "draft-6" | "draft-7";
|
|
142
159
|
/**
|
|
143
160
|
* The configuration options for the rate limiter.
|
|
144
161
|
*/
|
|
@@ -183,7 +200,7 @@ export type Options = {
|
|
|
183
200
|
*
|
|
184
201
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
|
185
202
|
*/
|
|
186
|
-
standardHeaders: boolean;
|
|
203
|
+
standardHeaders: boolean | DraftHeadersVersion;
|
|
187
204
|
/**
|
|
188
205
|
* The name of the property on the request object to store the rate limit info.
|
|
189
206
|
*
|
|
@@ -328,16 +345,26 @@ export declare class MemoryStore implements Store {
|
|
|
328
345
|
* @param options {Options} - The options used to setup the middleware.
|
|
329
346
|
*/
|
|
330
347
|
init(options: Options): void;
|
|
348
|
+
/**
|
|
349
|
+
* Method to fetch a client's hit count and reset time.
|
|
350
|
+
*
|
|
351
|
+
* @param key {string} - The identifier for a client.
|
|
352
|
+
*
|
|
353
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
354
|
+
*
|
|
355
|
+
* @public
|
|
356
|
+
*/
|
|
357
|
+
get(key: string): Promise<ClientRateLimitInfo | undefined>;
|
|
331
358
|
/**
|
|
332
359
|
* Method to increment a client's hit counter.
|
|
333
360
|
*
|
|
334
361
|
* @param key {string} - The identifier for a client.
|
|
335
362
|
*
|
|
336
|
-
* @returns {
|
|
363
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
337
364
|
*
|
|
338
365
|
* @public
|
|
339
366
|
*/
|
|
340
|
-
increment(key: string): Promise<
|
|
367
|
+
increment(key: string): Promise<ClientRateLimitInfo>;
|
|
341
368
|
/**
|
|
342
369
|
* Method to decrement a client's hit counter.
|
|
343
370
|
*
|
package/dist/index.d.mts
CHANGED
|
@@ -46,7 +46,7 @@ export type RateLimitReachedEventHandler = (request: Request, response: Response
|
|
|
46
46
|
* @property totalHits {number} - The number of hits for that client so far.
|
|
47
47
|
* @property resetTime {Date | undefined} - The time when the counter resets.
|
|
48
48
|
*/
|
|
49
|
-
export type
|
|
49
|
+
export type ClientRateLimitInfo = {
|
|
50
50
|
totalHits: number;
|
|
51
51
|
resetTime: Date | undefined;
|
|
52
52
|
};
|
|
@@ -60,6 +60,14 @@ export type RateLimitRequestHandler = RequestHandler & {
|
|
|
60
60
|
* @param key {string} - The identifier for a client.
|
|
61
61
|
*/
|
|
62
62
|
resetKey: (key: string) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Method to fetch a client's hit count and reset time.
|
|
65
|
+
*
|
|
66
|
+
* @param key {string} - The identifier for a client.
|
|
67
|
+
*
|
|
68
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
69
|
+
*/
|
|
70
|
+
getKey?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
|
63
71
|
};
|
|
64
72
|
/**
|
|
65
73
|
* An interface that all hit counter stores must implement.
|
|
@@ -102,14 +110,22 @@ export type Store = {
|
|
|
102
110
|
* @param options {Options} - The options used to setup the middleware.
|
|
103
111
|
*/
|
|
104
112
|
init?: (options: Options) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Method to fetch a client's hit count and reset time.
|
|
115
|
+
*
|
|
116
|
+
* @param key {string} - The identifier for a client.
|
|
117
|
+
*
|
|
118
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
119
|
+
*/
|
|
120
|
+
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
|
105
121
|
/**
|
|
106
122
|
* Method to increment a client's hit counter.
|
|
107
123
|
*
|
|
108
124
|
* @param key {string} - The identifier for a client.
|
|
109
125
|
*
|
|
110
|
-
* @returns {
|
|
126
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
111
127
|
*/
|
|
112
|
-
increment: (key: string) => Promise<
|
|
128
|
+
increment: (key: string) => Promise<ClientRateLimitInfo> | ClientRateLimitInfo;
|
|
113
129
|
/**
|
|
114
130
|
* Method to decrement a client's hit counter.
|
|
115
131
|
*
|
|
@@ -139,6 +155,7 @@ export type Store = {
|
|
|
139
155
|
*/
|
|
140
156
|
localKeys?: boolean;
|
|
141
157
|
};
|
|
158
|
+
export type DraftHeadersVersion = "draft-6" | "draft-7";
|
|
142
159
|
/**
|
|
143
160
|
* The configuration options for the rate limiter.
|
|
144
161
|
*/
|
|
@@ -183,7 +200,7 @@ export type Options = {
|
|
|
183
200
|
*
|
|
184
201
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
|
185
202
|
*/
|
|
186
|
-
standardHeaders: boolean;
|
|
203
|
+
standardHeaders: boolean | DraftHeadersVersion;
|
|
187
204
|
/**
|
|
188
205
|
* The name of the property on the request object to store the rate limit info.
|
|
189
206
|
*
|
|
@@ -328,16 +345,26 @@ export declare class MemoryStore implements Store {
|
|
|
328
345
|
* @param options {Options} - The options used to setup the middleware.
|
|
329
346
|
*/
|
|
330
347
|
init(options: Options): void;
|
|
348
|
+
/**
|
|
349
|
+
* Method to fetch a client's hit count and reset time.
|
|
350
|
+
*
|
|
351
|
+
* @param key {string} - The identifier for a client.
|
|
352
|
+
*
|
|
353
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
354
|
+
*
|
|
355
|
+
* @public
|
|
356
|
+
*/
|
|
357
|
+
get(key: string): Promise<ClientRateLimitInfo | undefined>;
|
|
331
358
|
/**
|
|
332
359
|
* Method to increment a client's hit counter.
|
|
333
360
|
*
|
|
334
361
|
* @param key {string} - The identifier for a client.
|
|
335
362
|
*
|
|
336
|
-
* @returns {
|
|
363
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
337
364
|
*
|
|
338
365
|
* @public
|
|
339
366
|
*/
|
|
340
|
-
increment(key: string): Promise<
|
|
367
|
+
increment(key: string): Promise<ClientRateLimitInfo>;
|
|
341
368
|
/**
|
|
342
369
|
* Method to decrement a client's hit counter.
|
|
343
370
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export type RateLimitReachedEventHandler = (request: Request, response: Response
|
|
|
46
46
|
* @property totalHits {number} - The number of hits for that client so far.
|
|
47
47
|
* @property resetTime {Date | undefined} - The time when the counter resets.
|
|
48
48
|
*/
|
|
49
|
-
export type
|
|
49
|
+
export type ClientRateLimitInfo = {
|
|
50
50
|
totalHits: number;
|
|
51
51
|
resetTime: Date | undefined;
|
|
52
52
|
};
|
|
@@ -60,6 +60,14 @@ export type RateLimitRequestHandler = RequestHandler & {
|
|
|
60
60
|
* @param key {string} - The identifier for a client.
|
|
61
61
|
*/
|
|
62
62
|
resetKey: (key: string) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Method to fetch a client's hit count and reset time.
|
|
65
|
+
*
|
|
66
|
+
* @param key {string} - The identifier for a client.
|
|
67
|
+
*
|
|
68
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
69
|
+
*/
|
|
70
|
+
getKey?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
|
63
71
|
};
|
|
64
72
|
/**
|
|
65
73
|
* An interface that all hit counter stores must implement.
|
|
@@ -102,14 +110,22 @@ export type Store = {
|
|
|
102
110
|
* @param options {Options} - The options used to setup the middleware.
|
|
103
111
|
*/
|
|
104
112
|
init?: (options: Options) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Method to fetch a client's hit count and reset time.
|
|
115
|
+
*
|
|
116
|
+
* @param key {string} - The identifier for a client.
|
|
117
|
+
*
|
|
118
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
119
|
+
*/
|
|
120
|
+
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
|
105
121
|
/**
|
|
106
122
|
* Method to increment a client's hit counter.
|
|
107
123
|
*
|
|
108
124
|
* @param key {string} - The identifier for a client.
|
|
109
125
|
*
|
|
110
|
-
* @returns {
|
|
126
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
111
127
|
*/
|
|
112
|
-
increment: (key: string) => Promise<
|
|
128
|
+
increment: (key: string) => Promise<ClientRateLimitInfo> | ClientRateLimitInfo;
|
|
113
129
|
/**
|
|
114
130
|
* Method to decrement a client's hit counter.
|
|
115
131
|
*
|
|
@@ -139,6 +155,7 @@ export type Store = {
|
|
|
139
155
|
*/
|
|
140
156
|
localKeys?: boolean;
|
|
141
157
|
};
|
|
158
|
+
export type DraftHeadersVersion = "draft-6" | "draft-7";
|
|
142
159
|
/**
|
|
143
160
|
* The configuration options for the rate limiter.
|
|
144
161
|
*/
|
|
@@ -183,7 +200,7 @@ export type Options = {
|
|
|
183
200
|
*
|
|
184
201
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
|
185
202
|
*/
|
|
186
|
-
standardHeaders: boolean;
|
|
203
|
+
standardHeaders: boolean | DraftHeadersVersion;
|
|
187
204
|
/**
|
|
188
205
|
* The name of the property on the request object to store the rate limit info.
|
|
189
206
|
*
|
|
@@ -328,16 +345,26 @@ export declare class MemoryStore implements Store {
|
|
|
328
345
|
* @param options {Options} - The options used to setup the middleware.
|
|
329
346
|
*/
|
|
330
347
|
init(options: Options): void;
|
|
348
|
+
/**
|
|
349
|
+
* Method to fetch a client's hit count and reset time.
|
|
350
|
+
*
|
|
351
|
+
* @param key {string} - The identifier for a client.
|
|
352
|
+
*
|
|
353
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
354
|
+
*
|
|
355
|
+
* @public
|
|
356
|
+
*/
|
|
357
|
+
get(key: string): Promise<ClientRateLimitInfo | undefined>;
|
|
331
358
|
/**
|
|
332
359
|
* Method to increment a client's hit counter.
|
|
333
360
|
*
|
|
334
361
|
* @param key {string} - The identifier for a client.
|
|
335
362
|
*
|
|
336
|
-
* @returns {
|
|
363
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
337
364
|
*
|
|
338
365
|
* @public
|
|
339
366
|
*/
|
|
340
|
-
increment(key: string): Promise<
|
|
367
|
+
increment(key: string): Promise<ClientRateLimitInfo>;
|
|
341
368
|
/**
|
|
342
369
|
* Method to decrement a client's hit counter.
|
|
343
370
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,59 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// source/headers.ts
|
|
9
|
+
var getResetSeconds = (resetTime, windowMs) => {
|
|
10
|
+
let resetSeconds = void 0;
|
|
11
|
+
if (resetTime) {
|
|
12
|
+
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
|
13
|
+
resetSeconds = Math.max(0, deltaSeconds);
|
|
14
|
+
} else if (windowMs) {
|
|
15
|
+
resetSeconds = Math.ceil(windowMs / 1e3);
|
|
16
|
+
}
|
|
17
|
+
return resetSeconds;
|
|
18
|
+
};
|
|
19
|
+
var setLegacyHeaders = (response, info) => {
|
|
20
|
+
if (response.headersSent)
|
|
21
|
+
return;
|
|
22
|
+
response.setHeader("X-RateLimit-Limit", info.limit);
|
|
23
|
+
response.setHeader("X-RateLimit-Remaining", info.remaining);
|
|
24
|
+
if (info.resetTime instanceof Date) {
|
|
25
|
+
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
26
|
+
response.setHeader(
|
|
27
|
+
"X-RateLimit-Reset",
|
|
28
|
+
Math.ceil(info.resetTime.getTime() / 1e3)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var setDraft6Headers = (response, info, windowMs) => {
|
|
33
|
+
if (response.headersSent)
|
|
34
|
+
return;
|
|
35
|
+
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
36
|
+
const resetSeconds = getResetSeconds(info.resetTime);
|
|
37
|
+
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
38
|
+
response.setHeader("RateLimit-Limit", info.limit);
|
|
39
|
+
response.setHeader("RateLimit-Remaining", info.remaining);
|
|
40
|
+
if (resetSeconds)
|
|
41
|
+
response.setHeader("RateLimit-Reset", resetSeconds);
|
|
42
|
+
};
|
|
43
|
+
var setDraft7Headers = (response, info, windowMs) => {
|
|
44
|
+
if (response.headersSent)
|
|
45
|
+
return;
|
|
46
|
+
const windowSeconds = Math.ceil(windowMs / 1e3);
|
|
47
|
+
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
|
48
|
+
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
|
49
|
+
response.setHeader(
|
|
50
|
+
"RateLimit",
|
|
51
|
+
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
var setRetryAfterHeader = (response, info, windowMs) => {
|
|
55
|
+
if (response.headersSent)
|
|
56
|
+
return;
|
|
57
|
+
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
|
58
|
+
response.setHeader("Retry-After", resetSeconds);
|
|
59
|
+
};
|
|
60
|
+
|
|
8
61
|
// source/validations.ts
|
|
9
62
|
import { isIP } from "net";
|
|
10
63
|
var ValidationError = class extends Error {
|
|
@@ -12,12 +65,12 @@ var ValidationError = class extends Error {
|
|
|
12
65
|
* The code must be a string, in snake case and all capital, that starts with
|
|
13
66
|
* the substring `ERR_ERL_`.
|
|
14
67
|
*
|
|
15
|
-
* The message must be a string, starting with
|
|
68
|
+
* The message must be a string, starting with an uppercase character,
|
|
16
69
|
* describing the issue in detail.
|
|
17
70
|
*/
|
|
18
71
|
constructor(code, message) {
|
|
19
72
|
const url = `https://express-rate-limit.github.io/${code}/`;
|
|
20
|
-
super(`${message} See ${url} for more information
|
|
73
|
+
super(`${message} See ${url} for more information.`);
|
|
21
74
|
__publicField(this, "name");
|
|
22
75
|
__publicField(this, "code");
|
|
23
76
|
__publicField(this, "help");
|
|
@@ -26,6 +79,8 @@ var ValidationError = class extends Error {
|
|
|
26
79
|
this.help = url;
|
|
27
80
|
}
|
|
28
81
|
};
|
|
82
|
+
var ChangeWarning = class extends ValidationError {
|
|
83
|
+
};
|
|
29
84
|
var _Validations = class _Validations {
|
|
30
85
|
constructor(enabled) {
|
|
31
86
|
// eslint-disable-next-line @typescript-eslint/parameter-properties
|
|
@@ -103,6 +158,22 @@ var _Validations = class _Validations {
|
|
|
103
158
|
}
|
|
104
159
|
});
|
|
105
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Ensures totalHits value from store is a positive integer.
|
|
163
|
+
*
|
|
164
|
+
* @param hits {any} - The `totalHits` returned by the store.
|
|
165
|
+
*/
|
|
166
|
+
positiveHits(hits) {
|
|
167
|
+
this.wrap(() => {
|
|
168
|
+
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
|
|
169
|
+
throw new ValidationError(
|
|
170
|
+
"ERR_ERL_INVALID_HITS",
|
|
171
|
+
`The totalHits value returned from the store must be a positive integer, got ${hits}`
|
|
172
|
+
// eslint-disable-line @typescript-eslint/restrict-template-expressions
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
106
177
|
/**
|
|
107
178
|
* Ensures a given key is incremented only once per request.
|
|
108
179
|
*
|
|
@@ -134,6 +205,78 @@ var _Validations = class _Validations {
|
|
|
134
205
|
keys.push(key);
|
|
135
206
|
});
|
|
136
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Warns the user that the behaviour for `max: 0` is changing in the next
|
|
210
|
+
* major release.
|
|
211
|
+
*
|
|
212
|
+
* @param max {number} - The maximum number of hits per client.
|
|
213
|
+
*
|
|
214
|
+
* @returns {void}
|
|
215
|
+
*/
|
|
216
|
+
max(max) {
|
|
217
|
+
this.wrap(() => {
|
|
218
|
+
if (max === 0) {
|
|
219
|
+
throw new ChangeWarning(
|
|
220
|
+
"WRN_ERL_MAX_ZERO",
|
|
221
|
+
`Setting max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
|
228
|
+
* and will be removed in the next major release.
|
|
229
|
+
*
|
|
230
|
+
* @param draft_polli_ratelimit_headers {boolean|undefined} - The now-deprecated setting that was used to enable standard headers.
|
|
231
|
+
*
|
|
232
|
+
* @returns {void}
|
|
233
|
+
*/
|
|
234
|
+
draftPolliHeaders(draft_polli_ratelimit_headers) {
|
|
235
|
+
this.wrap(() => {
|
|
236
|
+
if (draft_polli_ratelimit_headers) {
|
|
237
|
+
throw new ChangeWarning(
|
|
238
|
+
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
|
|
239
|
+
`The draft_polli_ratelimit_headers configuration option is deprecated and will be removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
|
|
246
|
+
* major release.
|
|
247
|
+
*
|
|
248
|
+
* @param onLimitReached {function|undefined} - The maximum number of hits per client.
|
|
249
|
+
*
|
|
250
|
+
* @returns {void}
|
|
251
|
+
*/
|
|
252
|
+
onLimitReached(onLimitReached) {
|
|
253
|
+
this.wrap(() => {
|
|
254
|
+
if (onLimitReached) {
|
|
255
|
+
throw new ChangeWarning(
|
|
256
|
+
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
|
|
257
|
+
`The onLimitReached configuration option is deprecated and will be removed in express-rate-limit v7.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Warns the user when the selected headers option requires a reset time but
|
|
264
|
+
* the store does not provide one.
|
|
265
|
+
*
|
|
266
|
+
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
|
267
|
+
*
|
|
268
|
+
* @returns {void}
|
|
269
|
+
*/
|
|
270
|
+
headersResetTime(resetTime) {
|
|
271
|
+
this.wrap(() => {
|
|
272
|
+
if (!resetTime) {
|
|
273
|
+
throw new ValidationError(
|
|
274
|
+
"ERR_ERL_HEADERS_NO_RESET",
|
|
275
|
+
`standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
137
280
|
wrap(validation) {
|
|
138
281
|
if (!this.enabled) {
|
|
139
282
|
return;
|
|
@@ -141,7 +284,10 @@ var _Validations = class _Validations {
|
|
|
141
284
|
try {
|
|
142
285
|
validation.call(this);
|
|
143
286
|
} catch (error) {
|
|
144
|
-
|
|
287
|
+
if (error instanceof ChangeWarning)
|
|
288
|
+
console.warn(error);
|
|
289
|
+
else
|
|
290
|
+
console.error(error);
|
|
145
291
|
}
|
|
146
292
|
}
|
|
147
293
|
};
|
|
@@ -203,12 +349,29 @@ var MemoryStore = class {
|
|
|
203
349
|
if (this.interval.unref)
|
|
204
350
|
this.interval.unref();
|
|
205
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Method to fetch a client's hit count and reset time.
|
|
354
|
+
*
|
|
355
|
+
* @param key {string} - The identifier for a client.
|
|
356
|
+
*
|
|
357
|
+
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
358
|
+
*
|
|
359
|
+
* @public
|
|
360
|
+
*/
|
|
361
|
+
async get(key) {
|
|
362
|
+
if (this.hits[key] !== void 0)
|
|
363
|
+
return {
|
|
364
|
+
totalHits: this.hits[key],
|
|
365
|
+
resetTime: this.resetTime
|
|
366
|
+
};
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
206
369
|
/**
|
|
207
370
|
* Method to increment a client's hit counter.
|
|
208
371
|
*
|
|
209
372
|
* @param key {string} - The identifier for a client.
|
|
210
373
|
*
|
|
211
|
-
* @returns {
|
|
374
|
+
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
|
212
375
|
*
|
|
213
376
|
* @public
|
|
214
377
|
*/
|
|
@@ -275,6 +438,10 @@ var promisifyStore = (passedStore) => {
|
|
|
275
438
|
}
|
|
276
439
|
const legacyStore = passedStore;
|
|
277
440
|
class PromisifiedStore {
|
|
441
|
+
/* istanbul ignore next */
|
|
442
|
+
async get(key) {
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
278
445
|
async increment(key) {
|
|
279
446
|
return new Promise((resolve, reject) => {
|
|
280
447
|
legacyStore.incr(
|
|
@@ -293,6 +460,7 @@ var promisifyStore = (passedStore) => {
|
|
|
293
460
|
async resetKey(key) {
|
|
294
461
|
return legacyStore.resetKey(key);
|
|
295
462
|
}
|
|
463
|
+
/* istanbul ignore next */
|
|
296
464
|
async resetAll() {
|
|
297
465
|
if (typeof legacyStore.resetAll === "function")
|
|
298
466
|
return legacyStore.resetAll();
|
|
@@ -321,13 +489,20 @@ var parseOptions = (passedOptions) => {
|
|
|
321
489
|
var _a, _b, _c, _d;
|
|
322
490
|
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
|
323
491
|
const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
|
|
492
|
+
validations.draftPolliHeaders(
|
|
493
|
+
notUndefinedOptions.draft_polli_ratelimit_headers
|
|
494
|
+
);
|
|
495
|
+
validations.onLimitReached(notUndefinedOptions.onLimitReached);
|
|
496
|
+
let standardHeaders = (_b = notUndefinedOptions.standardHeaders) != null ? _b : false;
|
|
497
|
+
if (standardHeaders === true || standardHeaders === void 0 && notUndefinedOptions.draft_polli_ratelimit_headers) {
|
|
498
|
+
standardHeaders = "draft-6";
|
|
499
|
+
}
|
|
324
500
|
const config = {
|
|
325
501
|
windowMs: 60 * 1e3,
|
|
326
502
|
max: 5,
|
|
327
503
|
message: "Too many requests, please try again later.",
|
|
328
504
|
statusCode: 429,
|
|
329
|
-
legacyHeaders: (
|
|
330
|
-
standardHeaders: (_c = passedOptions.draft_polli_ratelimit_headers) != null ? _c : false,
|
|
505
|
+
legacyHeaders: (_c = passedOptions.headers) != null ? _c : true,
|
|
331
506
|
requestPropertyName: "rateLimit",
|
|
332
507
|
skipFailedRequests: false,
|
|
333
508
|
skipSuccessfulRequests: false,
|
|
@@ -346,13 +521,15 @@ var parseOptions = (passedOptions) => {
|
|
|
346
521
|
response
|
|
347
522
|
) : config.message;
|
|
348
523
|
if (!response.writableEnded) {
|
|
349
|
-
response.send(message
|
|
524
|
+
response.send(message);
|
|
350
525
|
}
|
|
351
526
|
},
|
|
352
527
|
onLimitReached(_request, _response, _optionsUsed) {
|
|
353
528
|
},
|
|
354
|
-
// Allow the options
|
|
529
|
+
// Allow the default options to be overriden by the options passed to the middleware.
|
|
355
530
|
...notUndefinedOptions,
|
|
531
|
+
// `standardHeaders` is resolved into a draft version above, use that.
|
|
532
|
+
standardHeaders,
|
|
356
533
|
// Note that this field is declared after the user's options are spread in,
|
|
357
534
|
// so that this field doesn't get overriden with an un-promisified store!
|
|
358
535
|
store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
|
|
@@ -374,6 +551,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
|
|
|
374
551
|
}
|
|
375
552
|
};
|
|
376
553
|
var rateLimit = (passedOptions) => {
|
|
554
|
+
var _a;
|
|
377
555
|
const config = parseOptions(passedOptions != null ? passedOptions : {});
|
|
378
556
|
const options = getOptionsFromConfig(config);
|
|
379
557
|
if (typeof config.store.init === "function")
|
|
@@ -388,40 +566,27 @@ var rateLimit = (passedOptions) => {
|
|
|
388
566
|
const augmentedRequest = request;
|
|
389
567
|
const key = await config.keyGenerator(request, response);
|
|
390
568
|
const { totalHits, resetTime } = await config.store.increment(key);
|
|
569
|
+
config.validations.positiveHits(totalHits);
|
|
391
570
|
config.validations.singleCount(request, config.store, key);
|
|
392
571
|
const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
|
|
393
572
|
const maxHits = await retrieveQuota;
|
|
394
|
-
|
|
573
|
+
config.validations.max(maxHits);
|
|
574
|
+
const info = {
|
|
395
575
|
limit: maxHits,
|
|
396
576
|
current: totalHits,
|
|
397
577
|
remaining: Math.max(maxHits - totalHits, 0),
|
|
398
578
|
resetTime
|
|
399
579
|
};
|
|
580
|
+
augmentedRequest[config.requestPropertyName] = info;
|
|
400
581
|
if (config.legacyHeaders && !response.headersSent) {
|
|
401
|
-
response
|
|
402
|
-
response.setHeader(
|
|
403
|
-
"X-RateLimit-Remaining",
|
|
404
|
-
augmentedRequest[config.requestPropertyName].remaining
|
|
405
|
-
);
|
|
406
|
-
if (resetTime instanceof Date) {
|
|
407
|
-
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
408
|
-
response.setHeader(
|
|
409
|
-
"X-RateLimit-Reset",
|
|
410
|
-
Math.ceil(resetTime.getTime() / 1e3)
|
|
411
|
-
);
|
|
412
|
-
}
|
|
582
|
+
setLegacyHeaders(response, info);
|
|
413
583
|
}
|
|
414
584
|
if (config.standardHeaders && !response.headersSent) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (resetTime) {
|
|
421
|
-
const deltaSeconds = Math.ceil(
|
|
422
|
-
(resetTime.getTime() - Date.now()) / 1e3
|
|
423
|
-
);
|
|
424
|
-
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
585
|
+
if (config.standardHeaders === "draft-6") {
|
|
586
|
+
setDraft6Headers(response, info, config.windowMs);
|
|
587
|
+
} else if (config.standardHeaders === "draft-7") {
|
|
588
|
+
config.validations.headersResetTime(info.resetTime);
|
|
589
|
+
setDraft7Headers(response, info, config.windowMs);
|
|
425
590
|
}
|
|
426
591
|
}
|
|
427
592
|
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
|
|
@@ -457,8 +622,8 @@ var rateLimit = (passedOptions) => {
|
|
|
457
622
|
}
|
|
458
623
|
config.validations.disable();
|
|
459
624
|
if (maxHits && totalHits > maxHits) {
|
|
460
|
-
if (
|
|
461
|
-
response
|
|
625
|
+
if (config.legacyHeaders || config.standardHeaders) {
|
|
626
|
+
setRetryAfterHeader(response, info, config.windowMs);
|
|
462
627
|
}
|
|
463
628
|
config.handler(request, response, next, options);
|
|
464
629
|
return;
|
|
@@ -467,6 +632,9 @@ var rateLimit = (passedOptions) => {
|
|
|
467
632
|
}
|
|
468
633
|
);
|
|
469
634
|
middleware.resetKey = config.store.resetKey.bind(config.store);
|
|
635
|
+
middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
|
|
636
|
+
config.store
|
|
637
|
+
);
|
|
470
638
|
return middleware;
|
|
471
639
|
};
|
|
472
640
|
var lib_default = rateLimit;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.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",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"changelog.md"
|
|
53
53
|
],
|
|
54
54
|
"engines": {
|
|
55
|
-
"node": ">= 14
|
|
55
|
+
"node": ">= 14"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
|
|
@@ -60,13 +60,13 @@
|
|
|
60
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
|
-
"lint:code": "xo
|
|
64
|
-
"lint:rest": "prettier --
|
|
63
|
+
"lint:code": "xo",
|
|
64
|
+
"lint:rest": "prettier --check .",
|
|
65
65
|
"lint": "run-s lint:*",
|
|
66
|
-
"format:code": "
|
|
67
|
-
"format:rest": "
|
|
66
|
+
"format:code": "xo --fix",
|
|
67
|
+
"format:rest": "prettier --write .",
|
|
68
68
|
"format": "run-s format:*",
|
|
69
|
-
"test:lib": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-vm-modules jest",
|
|
69
|
+
"test:lib": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-vm-modules jest --config config/jest.json",
|
|
70
70
|
"test:ext": "cd test/external/ && bash run-all-tests",
|
|
71
71
|
"test": "run-s lint test:lib",
|
|
72
72
|
"pre-commit": "lint-staged",
|
|
@@ -76,9 +76,11 @@
|
|
|
76
76
|
"express": "^4 || ^5"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@
|
|
79
|
+
"@express-rate-limit/prettier": "1.0.0",
|
|
80
|
+
"@express-rate-limit/tsconfig": "1.0.0",
|
|
81
|
+
"@jest/globals": "29.6.2",
|
|
80
82
|
"@types/express": "4.17.17",
|
|
81
|
-
"@types/jest": "29.5.
|
|
83
|
+
"@types/jest": "29.5.3",
|
|
82
84
|
"@types/node": "20.4.0",
|
|
83
85
|
"@types/supertest": "2.0.12",
|
|
84
86
|
"cross-env": "7.0.3",
|
|
@@ -87,9 +89,10 @@
|
|
|
87
89
|
"esbuild": "0.18.11",
|
|
88
90
|
"express": "4.18.2",
|
|
89
91
|
"husky": "8.0.3",
|
|
90
|
-
"jest": "29.6.
|
|
92
|
+
"jest": "29.6.2",
|
|
91
93
|
"lint-staged": "13.2.3",
|
|
92
94
|
"npm-run-all": "4.1.5",
|
|
95
|
+
"ratelimit-header-parser": "0.1.0",
|
|
93
96
|
"supertest": "6.3.3",
|
|
94
97
|
"ts-jest": "29.1.1",
|
|
95
98
|
"ts-node": "10.9.1",
|
|
@@ -112,42 +115,18 @@
|
|
|
112
115
|
{
|
|
113
116
|
"files": "test/library/*.ts",
|
|
114
117
|
"rules": {
|
|
115
|
-
"@typescript-eslint/no-unsafe-argument": 0
|
|
118
|
+
"@typescript-eslint/no-unsafe-argument": 0,
|
|
119
|
+
"@typescript-eslint/no-unsafe-assignment": 0
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
|
-
]
|
|
119
|
-
},
|
|
120
|
-
"prettier": {
|
|
121
|
-
"semi": false,
|
|
122
|
-
"useTabs": true,
|
|
123
|
-
"singleQuote": true,
|
|
124
|
-
"bracketSpacing": true,
|
|
125
|
-
"trailingComma": "all",
|
|
126
|
-
"proseWrap": "always"
|
|
127
|
-
},
|
|
128
|
-
"jest": {
|
|
129
|
-
"preset": "ts-jest/presets/default-esm",
|
|
130
|
-
"collectCoverage": true,
|
|
131
|
-
"collectCoverageFrom": [
|
|
132
|
-
"source/**/*.ts"
|
|
133
|
-
],
|
|
134
|
-
"testTimeout": 30000,
|
|
135
|
-
"testMatch": [
|
|
136
|
-
"**/test/library/**/*-test.[jt]s?(x)"
|
|
137
122
|
],
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
|
|
141
|
-
"json",
|
|
142
|
-
"ts",
|
|
143
|
-
"tsx"
|
|
144
|
-
],
|
|
145
|
-
"moduleNameMapper": {
|
|
146
|
-
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
147
|
-
}
|
|
123
|
+
"ignore": [
|
|
124
|
+
"test/external"
|
|
125
|
+
]
|
|
148
126
|
},
|
|
127
|
+
"prettier": "@express-rate-limit/prettier",
|
|
149
128
|
"lint-staged": {
|
|
150
|
-
"{source,test}/**/*.ts": "xo --
|
|
151
|
-
"**/*.{json,yaml,md}": "prettier --
|
|
129
|
+
"{source,test}/**/*.ts": "xo --fix",
|
|
130
|
+
"**/*.{json,yaml,md}": "prettier --write "
|
|
152
131
|
}
|
|
153
132
|
}
|
package/readme.md
CHANGED
|
@@ -12,7 +12,7 @@ authentication and more to any API in minutes. Learn more at
|
|
|
12
12
|
|
|
13
13
|
<div align="center">
|
|
14
14
|
|
|
15
|
-
[](https://github.com/express-rate-limit/express-rate-limit/actions/workflows/ci.yaml)
|
|
16
16
|
[](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
|
|
17
17
|
[](https://www.npmjs.com/package/express-rate-limit)
|
|
18
18
|
|
|
@@ -97,13 +97,13 @@ Import it in a CommonJS project (`type: commonjs` or no `type` field in
|
|
|
97
97
|
`package.json`) as follows:
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
|
-
const rateLimit = require('express-rate-limit')
|
|
100
|
+
const { rateLimit } = require('express-rate-limit')
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Import it in a ESM project (`type: module` in `package.json`) as follows:
|
|
104
104
|
|
|
105
105
|
```ts
|
|
106
|
-
import rateLimit from 'express-rate-limit'
|
|
106
|
+
import { rateLimit } from 'express-rate-limit'
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
### Examples
|
|
@@ -112,13 +112,13 @@ To use it in an API-only server where the rate-limiter should be applied to all
|
|
|
112
112
|
requests:
|
|
113
113
|
|
|
114
114
|
```ts
|
|
115
|
-
import rateLimit from 'express-rate-limit'
|
|
115
|
+
import { rateLimit } from 'express-rate-limit'
|
|
116
116
|
|
|
117
117
|
const limiter = rateLimit({
|
|
118
118
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
119
119
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
120
|
-
standardHeaders:
|
|
121
|
-
legacyHeaders: false, //
|
|
120
|
+
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
|
121
|
+
legacyHeaders: false, // X-RateLimit-* headers
|
|
122
122
|
// store: ... , // Use an external store for more precise rate limiting
|
|
123
123
|
})
|
|
124
124
|
|
|
@@ -131,12 +131,12 @@ To use it in a 'regular' web server (e.g. anything that uses
|
|
|
131
131
|
requests:
|
|
132
132
|
|
|
133
133
|
```ts
|
|
134
|
-
import rateLimit from 'express-rate-limit'
|
|
134
|
+
import { rateLimit } from 'express-rate-limit'
|
|
135
135
|
|
|
136
136
|
const apiLimiter = rateLimit({
|
|
137
137
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
138
138
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
139
|
-
standardHeaders:
|
|
139
|
+
standardHeaders: 'draft-7', // Set `RateLimit` and `RateLimit-Policy`` headers
|
|
140
140
|
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
141
141
|
// store: ... , // Use an external store for more precise rate limiting
|
|
142
142
|
})
|
|
@@ -148,13 +148,13 @@ app.use('/api', apiLimiter)
|
|
|
148
148
|
To create multiple instances to apply different rules to different endpoints:
|
|
149
149
|
|
|
150
150
|
```ts
|
|
151
|
-
import rateLimit from 'express-rate-limit'
|
|
151
|
+
import { rateLimit } from 'express-rate-limit'
|
|
152
152
|
|
|
153
153
|
const apiLimiter = rateLimit({
|
|
154
154
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
155
155
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
156
|
-
standardHeaders:
|
|
157
|
-
legacyHeaders: false, //
|
|
156
|
+
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
|
157
|
+
legacyHeaders: false, // X-RateLimit-* headers
|
|
158
158
|
// store: ... , // Use an external store for more precise rate limiting
|
|
159
159
|
})
|
|
160
160
|
|
|
@@ -165,8 +165,8 @@ const createAccountLimiter = rateLimit({
|
|
|
165
165
|
max: 5, // Limit each IP to 5 create account requests per `window` (here, per hour)
|
|
166
166
|
message:
|
|
167
167
|
'Too many accounts created from this IP, please try again after an hour',
|
|
168
|
-
standardHeaders:
|
|
169
|
-
legacyHeaders: false, //
|
|
168
|
+
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
|
169
|
+
legacyHeaders: false, // X-RateLimit-* headers
|
|
170
170
|
})
|
|
171
171
|
|
|
172
172
|
app.post('/create-account', createAccountLimiter, (request, response) => {
|
|
@@ -177,7 +177,7 @@ app.post('/create-account', createAccountLimiter, (request, response) => {
|
|
|
177
177
|
To use a custom store:
|
|
178
178
|
|
|
179
179
|
```ts
|
|
180
|
-
import rateLimit from 'express-rate-limit'
|
|
180
|
+
import { rateLimit } from 'express-rate-limit'
|
|
181
181
|
import RedisStore from 'rate-limit-redis'
|
|
182
182
|
import RedisClient from 'ioredis'
|
|
183
183
|
|
|
@@ -185,7 +185,8 @@ const redisClient = new RedisClient()
|
|
|
185
185
|
const rateLimiter = rateLimit({
|
|
186
186
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
187
187
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
188
|
-
standardHeaders:
|
|
188
|
+
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
|
189
|
+
legacyHeaders: false, // X-RateLimit-* headers
|
|
189
190
|
store: new RedisStore({
|
|
190
191
|
/* ... */
|
|
191
192
|
}), // Use the external store
|
|
@@ -329,16 +330,38 @@ Defaults to `true` (for backward compatibility).
|
|
|
329
330
|
|
|
330
331
|
### `standardHeaders`
|
|
331
332
|
|
|
332
|
-
> `boolean`
|
|
333
|
+
> `boolean` | `'draft-6'` | `'draft-7'`
|
|
333
334
|
|
|
334
335
|
Whether to enable support for headers conforming to the
|
|
335
|
-
[
|
|
336
|
-
adopted by the IETF
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
[RateLimit header fields for HTTP standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers)
|
|
337
|
+
adopted by the IETF.
|
|
338
|
+
|
|
339
|
+
If set to `draft-6`, separate `RateLimit-Policy` `RateLimit-Limit`,
|
|
340
|
+
`RateLimit-Remaining`, and, if the store supports it, `RateLimit-Reset` headers
|
|
341
|
+
are set on the response, in accordance with
|
|
342
|
+
[draft-ietf-httpapi-ratelimit-headers-06](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-06).
|
|
343
|
+
|
|
344
|
+
If set to `draft-7`, a combined `RateLimit` header is set containing limit,
|
|
345
|
+
remaining, and reset values, and a `RateLimit-Policy` header is set, in
|
|
346
|
+
accordiance with
|
|
347
|
+
[draft-ietf-httpapi-ratelimit-headers-07](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-07).
|
|
348
|
+
`windowMs` is used for the reset value if the store does not provide a reset
|
|
349
|
+
time.
|
|
340
350
|
|
|
341
|
-
|
|
351
|
+
If set to `true`, it is treated as `draft-6`, however this behavior may change
|
|
352
|
+
in a future semver major release.
|
|
353
|
+
|
|
354
|
+
If set to any truthy value, the middleware also sends the `Retry-After` header
|
|
355
|
+
on all blocked requests.
|
|
356
|
+
|
|
357
|
+
The `standardHeaders` option may be used in conjunction with, or instead of the
|
|
358
|
+
`legacyHeaders` option.
|
|
359
|
+
|
|
360
|
+
ℹ️ Tip: use
|
|
361
|
+
[ratelimit-header-parser](https://www.npmjs.com/package/ratelimit-header-parser)
|
|
362
|
+
in clients to read/parse any form of express-rate-limit's headers.
|
|
363
|
+
|
|
364
|
+
Defaults to `false`.
|
|
342
365
|
|
|
343
366
|
> Renamed in `6.x` from `draft_polli_ratelimit_headers` to `standardHeaders`.
|
|
344
367
|
|
|
@@ -430,8 +453,8 @@ const limiter = rateLimit({
|
|
|
430
453
|
> `function`
|
|
431
454
|
|
|
432
455
|
A (sync/async) function that accepts the Express `request` and `response`
|
|
433
|
-
objects that is called
|
|
434
|
-
rate
|
|
456
|
+
objects that is called the on the request where a client has just exceeded their
|
|
457
|
+
rate limit.
|
|
435
458
|
|
|
436
459
|
This method was
|
|
437
460
|
[deprecated in v6](https://github.com/express-rate-limit/express-rate-limit/releases/v6.0.0) -
|
|
@@ -515,7 +538,7 @@ Here is a list of external stores:
|
|
|
515
538
|
| [`rate-limit-redis`](https://npmjs.com/package/rate-limit-redis) | A [Redis](http://redis.io/)-backed store, more suitable for large or demanding deployments. | Modern as of v3.0.0 |
|
|
516
539
|
| [`rate-limit-memcached`](https://npmjs.org/package/rate-limit-memcached) | A [Memcached](https://memcached.org/)-backed store. | Legacy |
|
|
517
540
|
| [`rate-limit-mongo`](https://www.npmjs.com/package/rate-limit-mongo) | A [MongoDB](https://www.mongodb.com/)-backed store. | Legacy |
|
|
518
|
-
| [`precise-memory-rate-limit`](https://www.npmjs.com/package/precise-memory-rate-limit) | A memory store similar to the built-in one, except that it stores a distinct timestamp for each key. |
|
|
541
|
+
| [`precise-memory-rate-limit`](https://www.npmjs.com/package/precise-memory-rate-limit) | A memory store similar to the built-in one, except that it stores a distinct timestamp for each key. | Modern as of v2.0.0 |
|
|
519
542
|
|
|
520
543
|
Take a look at
|
|
521
544
|
[this guide](https://github.com/express-rate-limit/express-rate-limit/wiki/Creating-Your-Own-Store)
|
package/tsconfig.json
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"include": ["source/"],
|
|
3
3
|
"exclude": ["node_modules/"],
|
|
4
|
-
"
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"strict": true,
|
|
7
|
-
"noUnusedLocals": true,
|
|
8
|
-
"noImplicitReturns": true,
|
|
9
|
-
"noFallthroughCasesInSwitch": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"moduleResolution": "node"
|
|
12
|
-
}
|
|
4
|
+
"extends": "@express-rate-limit/tsconfig"
|
|
13
5
|
}
|