express-rate-limit 6.10.0 → 6.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -6,6 +6,22 @@ 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.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.11.1)
10
+
11
+ ### Fixed
12
+
13
+ - Check for prefixed keys when validating that the stores have single counted
14
+ keys (See
15
+ [#395](https://github.com/express-rate-limit/express-rate-limit/issues/395)).
16
+
17
+ ## [6.11.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.11.0)
18
+
19
+ ### Added
20
+
21
+ - Support for retrieving the current hit count and reset time for a given key
22
+ from a store (See
23
+ [#390](https://github.com/express-rate-limit/express-rate-limit/issues/389)).
24
+
9
25
  ## [6.10.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.10.0)
10
26
 
11
27
  ### Added
package/dist/index.cjs CHANGED
@@ -211,6 +211,7 @@ var _Validations = class _Validations {
211
211
  */
212
212
  singleCount(request, store, key) {
213
213
  this.wrap(() => {
214
+ var _a;
214
215
  let storeKeys = _Validations.singleCountKeys.get(request);
215
216
  if (!storeKeys) {
216
217
  storeKeys = /* @__PURE__ */ new Map();
@@ -222,13 +223,14 @@ var _Validations = class _Validations {
222
223
  keys = [];
223
224
  storeKeys.set(storeKey, keys);
224
225
  }
225
- if (keys.includes(key)) {
226
+ const prefixedKey = `${(_a = store.prefix) != null ? _a : ""}${key}`;
227
+ if (keys.includes(prefixedKey)) {
226
228
  throw new ValidationError(
227
229
  "ERR_ERL_DOUBLE_COUNT",
228
230
  `The hit count for ${key} was incremented more than once for a single request.`
229
231
  );
230
232
  }
231
- keys.push(key);
233
+ keys.push(prefixedKey);
232
234
  });
233
235
  }
234
236
  /**
@@ -375,12 +377,29 @@ var MemoryStore = class {
375
377
  if (this.interval.unref)
376
378
  this.interval.unref();
377
379
  }
380
+ /**
381
+ * Method to fetch a client's hit count and reset time.
382
+ *
383
+ * @param key {string} - The identifier for a client.
384
+ *
385
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
386
+ *
387
+ * @public
388
+ */
389
+ async get(key) {
390
+ if (this.hits[key] !== void 0)
391
+ return {
392
+ totalHits: this.hits[key],
393
+ resetTime: this.resetTime
394
+ };
395
+ return void 0;
396
+ }
378
397
  /**
379
398
  * Method to increment a client's hit counter.
380
399
  *
381
400
  * @param key {string} - The identifier for a client.
382
401
  *
383
- * @returns {IncrementResponse} - The number of hits and reset time for that client.
402
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
384
403
  *
385
404
  * @public
386
405
  */
@@ -447,6 +466,10 @@ var promisifyStore = (passedStore) => {
447
466
  }
448
467
  const legacyStore = passedStore;
449
468
  class PromisifiedStore {
469
+ /* istanbul ignore next */
470
+ async get(key) {
471
+ return void 0;
472
+ }
450
473
  async increment(key) {
451
474
  return new Promise((resolve, reject) => {
452
475
  legacyStore.incr(
@@ -556,6 +579,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
556
579
  }
557
580
  };
558
581
  var rateLimit = (passedOptions) => {
582
+ var _a;
559
583
  const config = parseOptions(passedOptions != null ? passedOptions : {});
560
584
  const options = getOptionsFromConfig(config);
561
585
  if (typeof config.store.init === "function")
@@ -636,6 +660,9 @@ var rateLimit = (passedOptions) => {
636
660
  }
637
661
  );
638
662
  middleware.resetKey = config.store.resetKey.bind(config.store);
663
+ middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
664
+ config.store
665
+ );
639
666
  return middleware;
640
667
  };
641
668
  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 IncrementResponse = {
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 {IncrementResponse} - The number of hits and reset time for that client.
126
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
111
127
  */
112
- increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
128
+ increment: (key: string) => Promise<ClientRateLimitInfo> | ClientRateLimitInfo;
113
129
  /**
114
130
  * Method to decrement a client's hit counter.
115
131
  *
@@ -138,6 +154,12 @@ export type Store = {
138
154
  * Used to help detect double-counting misconfigurations.
139
155
  */
140
156
  localKeys?: boolean;
157
+ /**
158
+ * Optional value that the store prepends to keys
159
+ *
160
+ * Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
161
+ */
162
+ prefix?: string;
141
163
  };
142
164
  export type DraftHeadersVersion = "draft-6" | "draft-7";
143
165
  /**
@@ -329,16 +351,26 @@ export declare class MemoryStore implements Store {
329
351
  * @param options {Options} - The options used to setup the middleware.
330
352
  */
331
353
  init(options: Options): void;
354
+ /**
355
+ * Method to fetch a client's hit count and reset time.
356
+ *
357
+ * @param key {string} - The identifier for a client.
358
+ *
359
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
360
+ *
361
+ * @public
362
+ */
363
+ get(key: string): Promise<ClientRateLimitInfo | undefined>;
332
364
  /**
333
365
  * Method to increment a client's hit counter.
334
366
  *
335
367
  * @param key {string} - The identifier for a client.
336
368
  *
337
- * @returns {IncrementResponse} - The number of hits and reset time for that client.
369
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
338
370
  *
339
371
  * @public
340
372
  */
341
- increment(key: string): Promise<IncrementResponse>;
373
+ increment(key: string): Promise<ClientRateLimitInfo>;
342
374
  /**
343
375
  * Method to decrement a client's hit counter.
344
376
  *
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 IncrementResponse = {
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 {IncrementResponse} - The number of hits and reset time for that client.
126
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
111
127
  */
112
- increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
128
+ increment: (key: string) => Promise<ClientRateLimitInfo> | ClientRateLimitInfo;
113
129
  /**
114
130
  * Method to decrement a client's hit counter.
115
131
  *
@@ -138,6 +154,12 @@ export type Store = {
138
154
  * Used to help detect double-counting misconfigurations.
139
155
  */
140
156
  localKeys?: boolean;
157
+ /**
158
+ * Optional value that the store prepends to keys
159
+ *
160
+ * Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
161
+ */
162
+ prefix?: string;
141
163
  };
142
164
  export type DraftHeadersVersion = "draft-6" | "draft-7";
143
165
  /**
@@ -329,16 +351,26 @@ export declare class MemoryStore implements Store {
329
351
  * @param options {Options} - The options used to setup the middleware.
330
352
  */
331
353
  init(options: Options): void;
354
+ /**
355
+ * Method to fetch a client's hit count and reset time.
356
+ *
357
+ * @param key {string} - The identifier for a client.
358
+ *
359
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
360
+ *
361
+ * @public
362
+ */
363
+ get(key: string): Promise<ClientRateLimitInfo | undefined>;
332
364
  /**
333
365
  * Method to increment a client's hit counter.
334
366
  *
335
367
  * @param key {string} - The identifier for a client.
336
368
  *
337
- * @returns {IncrementResponse} - The number of hits and reset time for that client.
369
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
338
370
  *
339
371
  * @public
340
372
  */
341
- increment(key: string): Promise<IncrementResponse>;
373
+ increment(key: string): Promise<ClientRateLimitInfo>;
342
374
  /**
343
375
  * Method to decrement a client's hit counter.
344
376
  *
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 IncrementResponse = {
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 {IncrementResponse} - The number of hits and reset time for that client.
126
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
111
127
  */
112
- increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
128
+ increment: (key: string) => Promise<ClientRateLimitInfo> | ClientRateLimitInfo;
113
129
  /**
114
130
  * Method to decrement a client's hit counter.
115
131
  *
@@ -138,6 +154,12 @@ export type Store = {
138
154
  * Used to help detect double-counting misconfigurations.
139
155
  */
140
156
  localKeys?: boolean;
157
+ /**
158
+ * Optional value that the store prepends to keys
159
+ *
160
+ * Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
161
+ */
162
+ prefix?: string;
141
163
  };
142
164
  export type DraftHeadersVersion = "draft-6" | "draft-7";
143
165
  /**
@@ -329,16 +351,26 @@ export declare class MemoryStore implements Store {
329
351
  * @param options {Options} - The options used to setup the middleware.
330
352
  */
331
353
  init(options: Options): void;
354
+ /**
355
+ * Method to fetch a client's hit count and reset time.
356
+ *
357
+ * @param key {string} - The identifier for a client.
358
+ *
359
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
360
+ *
361
+ * @public
362
+ */
363
+ get(key: string): Promise<ClientRateLimitInfo | undefined>;
332
364
  /**
333
365
  * Method to increment a client's hit counter.
334
366
  *
335
367
  * @param key {string} - The identifier for a client.
336
368
  *
337
- * @returns {IncrementResponse} - The number of hits and reset time for that client.
369
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
338
370
  *
339
371
  * @public
340
372
  */
341
- increment(key: string): Promise<IncrementResponse>;
373
+ increment(key: string): Promise<ClientRateLimitInfo>;
342
374
  /**
343
375
  * Method to decrement a client's hit counter.
344
376
  *
package/dist/index.mjs CHANGED
@@ -185,6 +185,7 @@ var _Validations = class _Validations {
185
185
  */
186
186
  singleCount(request, store, key) {
187
187
  this.wrap(() => {
188
+ var _a;
188
189
  let storeKeys = _Validations.singleCountKeys.get(request);
189
190
  if (!storeKeys) {
190
191
  storeKeys = /* @__PURE__ */ new Map();
@@ -196,13 +197,14 @@ var _Validations = class _Validations {
196
197
  keys = [];
197
198
  storeKeys.set(storeKey, keys);
198
199
  }
199
- if (keys.includes(key)) {
200
+ const prefixedKey = `${(_a = store.prefix) != null ? _a : ""}${key}`;
201
+ if (keys.includes(prefixedKey)) {
200
202
  throw new ValidationError(
201
203
  "ERR_ERL_DOUBLE_COUNT",
202
204
  `The hit count for ${key} was incremented more than once for a single request.`
203
205
  );
204
206
  }
205
- keys.push(key);
207
+ keys.push(prefixedKey);
206
208
  });
207
209
  }
208
210
  /**
@@ -349,12 +351,29 @@ var MemoryStore = class {
349
351
  if (this.interval.unref)
350
352
  this.interval.unref();
351
353
  }
354
+ /**
355
+ * Method to fetch a client's hit count and reset time.
356
+ *
357
+ * @param key {string} - The identifier for a client.
358
+ *
359
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
360
+ *
361
+ * @public
362
+ */
363
+ async get(key) {
364
+ if (this.hits[key] !== void 0)
365
+ return {
366
+ totalHits: this.hits[key],
367
+ resetTime: this.resetTime
368
+ };
369
+ return void 0;
370
+ }
352
371
  /**
353
372
  * Method to increment a client's hit counter.
354
373
  *
355
374
  * @param key {string} - The identifier for a client.
356
375
  *
357
- * @returns {IncrementResponse} - The number of hits and reset time for that client.
376
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
358
377
  *
359
378
  * @public
360
379
  */
@@ -421,6 +440,10 @@ var promisifyStore = (passedStore) => {
421
440
  }
422
441
  const legacyStore = passedStore;
423
442
  class PromisifiedStore {
443
+ /* istanbul ignore next */
444
+ async get(key) {
445
+ return void 0;
446
+ }
424
447
  async increment(key) {
425
448
  return new Promise((resolve, reject) => {
426
449
  legacyStore.incr(
@@ -530,6 +553,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
530
553
  }
531
554
  };
532
555
  var rateLimit = (passedOptions) => {
556
+ var _a;
533
557
  const config = parseOptions(passedOptions != null ? passedOptions : {});
534
558
  const options = getOptionsFromConfig(config);
535
559
  if (typeof config.store.init === "function")
@@ -610,6 +634,9 @@ var rateLimit = (passedOptions) => {
610
634
  }
611
635
  );
612
636
  middleware.resetKey = config.store.resetKey.bind(config.store);
637
+ middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
638
+ config.store
639
+ );
613
640
  return middleware;
614
641
  };
615
642
  var lib_default = rateLimit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "6.10.0",
3
+ "version": "6.11.1",
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",
@@ -60,11 +60,11 @@
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 --ignore test/external/",
64
- "lint:rest": "prettier --ignore-path .gitignore --ignore-unknown --check .",
63
+ "lint:code": "xo",
64
+ "lint:rest": "prettier --check .",
65
65
  "lint": "run-s lint:*",
66
- "format:code": "npm run lint:code -- --fix",
67
- "format:rest": "npm run lint:rest -- --write .",
66
+ "format:code": "xo --fix",
67
+ "format:rest": "prettier --write .",
68
68
  "format": "run-s format:*",
69
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",
@@ -119,11 +119,14 @@
119
119
  "@typescript-eslint/no-unsafe-assignment": 0
120
120
  }
121
121
  }
122
+ ],
123
+ "ignore": [
124
+ "test/external"
122
125
  ]
123
126
  },
124
127
  "prettier": "@express-rate-limit/prettier",
125
128
  "lint-staged": {
126
- "{source,test}/**/*.ts": "xo --ignore test/external/ --fix",
127
- "**/*.{json,yaml,md}": "prettier --ignore-path .gitignore --ignore-unknown --write "
129
+ "{source,test}/**/*.ts": "xo --fix",
130
+ "**/*.{json,yaml,md}": "prettier --write "
128
131
  }
129
132
  }