express-rate-limit 6.6.0 → 6.7.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 +48 -3
- package/dist/index.cjs +168 -76
- package/dist/index.d.cts +359 -0
- package/dist/index.d.mts +359 -0
- package/dist/index.d.ts +16 -16
- package/dist/index.mjs +170 -76
- package/package.json +32 -26
- package/readme.md +27 -12
package/changelog.md
CHANGED
|
@@ -6,21 +6,66 @@ 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.
|
|
9
|
+
## [6.7.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.1)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed compatibility with TypeScript's TypeScript new `node16` module
|
|
14
|
+
resolution strategy (See
|
|
15
|
+
[#355](https://github.com/express-rate-limit/express-rate-limit/issues/355))
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Bumped development dependencies.
|
|
20
|
+
- Added `node` 20 to list of versions the CI jobs run on.
|
|
21
|
+
|
|
22
|
+
No functional changes.
|
|
23
|
+
|
|
24
|
+
## [6.7.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.0)
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Updated links to point to the new `express-rate-limit` organization on GitHub.
|
|
29
|
+
- Added advertisement to `readme.md` for project sponsor
|
|
30
|
+
[Zuplo](https://zuplo.link/express-rate-limit).
|
|
31
|
+
- Updated to `typescript` version 5 and bumped other dependencies.
|
|
32
|
+
- Dropped `node` 12, and added `node` 19 to the list of versions the CI jobs run
|
|
33
|
+
on.
|
|
34
|
+
|
|
35
|
+
No functional changes.
|
|
36
|
+
|
|
37
|
+
## [6.6.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.6.0)
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Added `shutdown` method to the Store interface and the MemoryStore.
|
|
42
|
+
|
|
43
|
+
## [6.5.2](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.2)
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- Fixed an issue with missing types in ESM monorepos.
|
|
48
|
+
|
|
49
|
+
## [6.5.1](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.1)
|
|
50
|
+
|
|
51
|
+
### Added
|
|
12
52
|
|
|
13
53
|
- The message option can now be a (sync/asynx) function that returns a value
|
|
14
54
|
(#311)
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
15
58
|
- Updated all dependencies
|
|
16
59
|
|
|
60
|
+
Note: 6.5.0 was not released due to CI automation issues.
|
|
61
|
+
|
|
17
62
|
## [6.4.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.3.0)
|
|
18
63
|
|
|
19
64
|
### Added
|
|
20
65
|
|
|
21
66
|
- Adds Express 5 (`5.0.0-beta.1`) as a supported peer dependency (#304)
|
|
22
67
|
|
|
23
|
-
|
|
68
|
+
### Changed
|
|
24
69
|
|
|
25
70
|
- Tests are now run on Node 12, 14, 16 and 18 on CI (#305)
|
|
26
71
|
- Updated all development dependencies (#306)
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -16,6 +17,10 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
17
|
return to;
|
|
17
18
|
};
|
|
18
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => {
|
|
21
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
19
24
|
|
|
20
25
|
// source/index.ts
|
|
21
26
|
var source_exports = {};
|
|
@@ -28,11 +33,34 @@ module.exports = __toCommonJS(source_exports);
|
|
|
28
33
|
|
|
29
34
|
// source/memory-store.ts
|
|
30
35
|
var calculateNextResetTime = (windowMs) => {
|
|
31
|
-
const resetTime = new Date();
|
|
36
|
+
const resetTime = /* @__PURE__ */ new Date();
|
|
32
37
|
resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
|
|
33
38
|
return resetTime;
|
|
34
39
|
};
|
|
35
40
|
var MemoryStore = class {
|
|
41
|
+
constructor() {
|
|
42
|
+
/**
|
|
43
|
+
* The duration of time before which all hit counts are reset (in milliseconds).
|
|
44
|
+
*/
|
|
45
|
+
__publicField(this, "windowMs");
|
|
46
|
+
/**
|
|
47
|
+
* The map that stores the number of hits for each client in memory.
|
|
48
|
+
*/
|
|
49
|
+
__publicField(this, "hits");
|
|
50
|
+
/**
|
|
51
|
+
* The time at which all hit counts will be reset.
|
|
52
|
+
*/
|
|
53
|
+
__publicField(this, "resetTime");
|
|
54
|
+
/**
|
|
55
|
+
* Reference to the active timer.
|
|
56
|
+
*/
|
|
57
|
+
__publicField(this, "interval");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Method that initializes the store.
|
|
61
|
+
*
|
|
62
|
+
* @param options {Options} - The options used to setup the middleware.
|
|
63
|
+
*/
|
|
36
64
|
init(options) {
|
|
37
65
|
this.windowMs = options.windowMs;
|
|
38
66
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
@@ -43,6 +71,15 @@ var MemoryStore = class {
|
|
|
43
71
|
if (this.interval.unref)
|
|
44
72
|
this.interval.unref();
|
|
45
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Method to increment a client's hit counter.
|
|
76
|
+
*
|
|
77
|
+
* @param key {string} - The identifier for a client.
|
|
78
|
+
*
|
|
79
|
+
* @returns {IncrementResponse} - The number of hits and reset time for that client.
|
|
80
|
+
*
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
46
83
|
async increment(key) {
|
|
47
84
|
var _a;
|
|
48
85
|
const totalHits = ((_a = this.hits[key]) != null ? _a : 0) + 1;
|
|
@@ -52,25 +89,54 @@ var MemoryStore = class {
|
|
|
52
89
|
resetTime: this.resetTime
|
|
53
90
|
};
|
|
54
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Method to decrement a client's hit counter.
|
|
94
|
+
*
|
|
95
|
+
* @param key {string} - The identifier for a client.
|
|
96
|
+
*
|
|
97
|
+
* @public
|
|
98
|
+
*/
|
|
55
99
|
async decrement(key) {
|
|
56
100
|
const current = this.hits[key];
|
|
57
101
|
if (current)
|
|
58
102
|
this.hits[key] = current - 1;
|
|
59
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Method to reset a client's hit counter.
|
|
106
|
+
*
|
|
107
|
+
* @param key {string} - The identifier for a client.
|
|
108
|
+
*
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
60
111
|
async resetKey(key) {
|
|
61
112
|
delete this.hits[key];
|
|
62
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Method to reset everyone's hit counter.
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
63
119
|
async resetAll() {
|
|
64
120
|
this.hits = {};
|
|
65
121
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
66
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Method to stop the timer (if currently running) and prevent any memory
|
|
125
|
+
* leaks.
|
|
126
|
+
*
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
67
129
|
shutdown() {
|
|
68
130
|
clearInterval(this.interval);
|
|
69
131
|
}
|
|
70
132
|
};
|
|
71
133
|
|
|
72
134
|
// source/lib.ts
|
|
73
|
-
var isLegacyStore = (store) =>
|
|
135
|
+
var isLegacyStore = (store) => (
|
|
136
|
+
// Check that `incr` exists but `increment` does not - store authors might want
|
|
137
|
+
// to keep both around for backwards compatibility.
|
|
138
|
+
typeof store.incr === "function" && typeof store.increment !== "function"
|
|
139
|
+
);
|
|
74
140
|
var promisifyStore = (passedStore) => {
|
|
75
141
|
if (!isLegacyStore(passedStore)) {
|
|
76
142
|
return passedStore;
|
|
@@ -79,11 +145,14 @@ var promisifyStore = (passedStore) => {
|
|
|
79
145
|
class PromisifiedStore {
|
|
80
146
|
async increment(key) {
|
|
81
147
|
return new Promise((resolve, reject) => {
|
|
82
|
-
legacyStore.incr(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
148
|
+
legacyStore.incr(
|
|
149
|
+
key,
|
|
150
|
+
(error, totalHits, resetTime) => {
|
|
151
|
+
if (error)
|
|
152
|
+
reject(error);
|
|
153
|
+
resolve({ totalHits, resetTime });
|
|
154
|
+
}
|
|
155
|
+
);
|
|
87
156
|
});
|
|
88
157
|
}
|
|
89
158
|
async decrement(key) {
|
|
@@ -116,24 +185,34 @@ var parseOptions = (passedOptions) => {
|
|
|
116
185
|
skip: (_request, _response) => false,
|
|
117
186
|
keyGenerator(request, _response) {
|
|
118
187
|
if (!request.ip) {
|
|
119
|
-
console.error(
|
|
188
|
+
console.error(
|
|
189
|
+
"WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue."
|
|
190
|
+
);
|
|
120
191
|
}
|
|
121
192
|
return request.ip;
|
|
122
193
|
},
|
|
123
194
|
async handler(request, response, _next, _optionsUsed) {
|
|
124
195
|
response.status(config.statusCode);
|
|
125
|
-
const message = typeof config.message === "function" ? await config.message(
|
|
196
|
+
const message = typeof config.message === "function" ? await config.message(
|
|
197
|
+
request,
|
|
198
|
+
response
|
|
199
|
+
) : config.message;
|
|
126
200
|
if (!response.writableEnded) {
|
|
127
201
|
response.send(message != null ? message : "Too many requests, please try again later.");
|
|
128
202
|
}
|
|
129
203
|
},
|
|
130
204
|
onLimitReached(_request, _response, _optionsUsed) {
|
|
131
205
|
},
|
|
206
|
+
// Allow the options object to be overriden by the options passed to the middleware.
|
|
132
207
|
...notUndefinedOptions,
|
|
208
|
+
// Note that this field is declared after the user's options are spread in,
|
|
209
|
+
// so that this field doesn't get overriden with an un-promisified store!
|
|
133
210
|
store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
|
|
134
211
|
};
|
|
135
|
-
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" ||
|
|
136
|
-
throw new TypeError(
|
|
212
|
+
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
|
+
throw new TypeError(
|
|
214
|
+
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
|
|
215
|
+
);
|
|
137
216
|
}
|
|
138
217
|
return config;
|
|
139
218
|
};
|
|
@@ -148,79 +227,92 @@ var rateLimit = (passedOptions) => {
|
|
|
148
227
|
const options = parseOptions(passedOptions != null ? passedOptions : {});
|
|
149
228
|
if (typeof options.store.init === "function")
|
|
150
229
|
options.store.init(options);
|
|
151
|
-
const middleware = handleAsyncErrors(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const augmentedRequest = request;
|
|
158
|
-
const key = await options.keyGenerator(request, response);
|
|
159
|
-
const { totalHits, resetTime } = await options.store.increment(key);
|
|
160
|
-
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
161
|
-
const maxHits = await retrieveQuota;
|
|
162
|
-
augmentedRequest[options.requestPropertyName] = {
|
|
163
|
-
limit: maxHits,
|
|
164
|
-
current: totalHits,
|
|
165
|
-
remaining: Math.max(maxHits - totalHits, 0),
|
|
166
|
-
resetTime
|
|
167
|
-
};
|
|
168
|
-
if (options.legacyHeaders && !response.headersSent) {
|
|
169
|
-
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
170
|
-
response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
171
|
-
if (resetTime instanceof Date) {
|
|
172
|
-
response.setHeader("Date", new Date().toUTCString());
|
|
173
|
-
response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
|
|
230
|
+
const middleware = handleAsyncErrors(
|
|
231
|
+
async (request, response, next) => {
|
|
232
|
+
const skip = await options.skip(request, response);
|
|
233
|
+
if (skip) {
|
|
234
|
+
next();
|
|
235
|
+
return;
|
|
174
236
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
237
|
+
const augmentedRequest = request;
|
|
238
|
+
const key = await options.keyGenerator(request, response);
|
|
239
|
+
const { totalHits, resetTime } = await options.store.increment(key);
|
|
240
|
+
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
241
|
+
const maxHits = await retrieveQuota;
|
|
242
|
+
augmentedRequest[options.requestPropertyName] = {
|
|
243
|
+
limit: maxHits,
|
|
244
|
+
current: totalHits,
|
|
245
|
+
remaining: Math.max(maxHits - totalHits, 0),
|
|
246
|
+
resetTime
|
|
247
|
+
};
|
|
248
|
+
if (options.legacyHeaders && !response.headersSent) {
|
|
249
|
+
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
250
|
+
response.setHeader(
|
|
251
|
+
"X-RateLimit-Remaining",
|
|
252
|
+
augmentedRequest[options.requestPropertyName].remaining
|
|
253
|
+
);
|
|
254
|
+
if (resetTime instanceof Date) {
|
|
255
|
+
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
|
256
|
+
response.setHeader(
|
|
257
|
+
"X-RateLimit-Reset",
|
|
258
|
+
Math.ceil(resetTime.getTime() / 1e3)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
182
261
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
262
|
+
if (options.standardHeaders && !response.headersSent) {
|
|
263
|
+
response.setHeader("RateLimit-Limit", maxHits);
|
|
264
|
+
response.setHeader(
|
|
265
|
+
"RateLimit-Remaining",
|
|
266
|
+
augmentedRequest[options.requestPropertyName].remaining
|
|
267
|
+
);
|
|
268
|
+
if (resetTime) {
|
|
269
|
+
const deltaSeconds = Math.ceil(
|
|
270
|
+
(resetTime.getTime() - Date.now()) / 1e3
|
|
271
|
+
);
|
|
272
|
+
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
190
273
|
}
|
|
191
|
-
};
|
|
192
|
-
if (options.skipFailedRequests) {
|
|
193
|
-
response.on("finish", async () => {
|
|
194
|
-
if (!options.requestWasSuccessful(request, response))
|
|
195
|
-
await decrementKey();
|
|
196
|
-
});
|
|
197
|
-
response.on("close", async () => {
|
|
198
|
-
if (!response.writableEnded)
|
|
199
|
-
await decrementKey();
|
|
200
|
-
});
|
|
201
|
-
response.on("error", async () => {
|
|
202
|
-
await decrementKey();
|
|
203
|
-
});
|
|
204
274
|
}
|
|
205
|
-
if (options.skipSuccessfulRequests) {
|
|
206
|
-
|
|
207
|
-
|
|
275
|
+
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
276
|
+
let decremented = false;
|
|
277
|
+
const decrementKey = async () => {
|
|
278
|
+
if (!decremented) {
|
|
279
|
+
await options.store.decrement(key);
|
|
280
|
+
decremented = true;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
if (options.skipFailedRequests) {
|
|
284
|
+
response.on("finish", async () => {
|
|
285
|
+
if (!options.requestWasSuccessful(request, response))
|
|
286
|
+
await decrementKey();
|
|
287
|
+
});
|
|
288
|
+
response.on("close", async () => {
|
|
289
|
+
if (!response.writableEnded)
|
|
290
|
+
await decrementKey();
|
|
291
|
+
});
|
|
292
|
+
response.on("error", async () => {
|
|
208
293
|
await decrementKey();
|
|
209
|
-
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (options.skipSuccessfulRequests) {
|
|
297
|
+
response.on("finish", async () => {
|
|
298
|
+
if (options.requestWasSuccessful(request, response))
|
|
299
|
+
await decrementKey();
|
|
300
|
+
});
|
|
301
|
+
}
|
|
210
302
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
303
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
304
|
+
options.onLimitReached(request, response, options);
|
|
305
|
+
}
|
|
306
|
+
if (maxHits && totalHits > maxHits) {
|
|
307
|
+
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
308
|
+
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
309
|
+
}
|
|
310
|
+
options.handler(request, response, next, options);
|
|
311
|
+
return;
|
|
218
312
|
}
|
|
219
|
-
|
|
220
|
-
return;
|
|
313
|
+
next();
|
|
221
314
|
}
|
|
222
|
-
|
|
223
|
-
});
|
|
315
|
+
);
|
|
224
316
|
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
225
317
|
return middleware;
|
|
226
318
|
};
|