express-rate-limit 6.5.2 → 6.7.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 +32 -3
- package/dist/index.cjs +103 -79
- package/dist/index.d.ts +24 -9
- package/dist/index.mjs +103 -79
- package/package.json +18 -17
- package/readme.md +21 -11
package/changelog.md
CHANGED
|
@@ -6,21 +6,50 @@ 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.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.0)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Updated links to point to new express-rate-limit organization on GitHub.
|
|
14
|
+
- Added advertisement to Readme for project sponsor
|
|
15
|
+
[Zuplo](https://zuplo.link/express-rate-limit).
|
|
16
|
+
- Updated TypeScript version and other dev dependencies
|
|
17
|
+
- Changed CI test suite: dropped node.js 12, added node.js 19
|
|
18
|
+
|
|
19
|
+
No functional changes.
|
|
20
|
+
|
|
21
|
+
## [6.6.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.6.0)
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Added `shutdown` method to the Store interface and the MemoryStore.
|
|
26
|
+
|
|
27
|
+
## [6.5.2](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.2)
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Fixed an issue with missing types in ESM monorepos.
|
|
32
|
+
|
|
33
|
+
## [6.5.1](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.1)
|
|
34
|
+
|
|
35
|
+
### Added
|
|
12
36
|
|
|
13
37
|
- The message option can now be a (sync/asynx) function that returns a value
|
|
14
38
|
(#311)
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
15
42
|
- Updated all dependencies
|
|
16
43
|
|
|
44
|
+
Note: 6.5.0 was not released due to CI automation issues.
|
|
45
|
+
|
|
17
46
|
## [6.4.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.3.0)
|
|
18
47
|
|
|
19
48
|
### Added
|
|
20
49
|
|
|
21
50
|
- Adds Express 5 (`5.0.0-beta.1`) as a supported peer dependency (#304)
|
|
22
51
|
|
|
23
|
-
|
|
52
|
+
### Changed
|
|
24
53
|
|
|
25
54
|
- Tests are now run on Node 12, 14, 16 and 18 on CI (#305)
|
|
26
55
|
- Updated all development dependencies (#306)
|
package/dist/index.cjs
CHANGED
|
@@ -37,12 +37,11 @@ var MemoryStore = class {
|
|
|
37
37
|
this.windowMs = options.windowMs;
|
|
38
38
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
39
39
|
this.hits = {};
|
|
40
|
-
|
|
40
|
+
this.interval = setInterval(async () => {
|
|
41
41
|
await this.resetAll();
|
|
42
42
|
}, this.windowMs);
|
|
43
|
-
if (interval.unref)
|
|
44
|
-
interval.unref();
|
|
45
|
-
}
|
|
43
|
+
if (this.interval.unref)
|
|
44
|
+
this.interval.unref();
|
|
46
45
|
}
|
|
47
46
|
async increment(key) {
|
|
48
47
|
var _a;
|
|
@@ -55,9 +54,8 @@ var MemoryStore = class {
|
|
|
55
54
|
}
|
|
56
55
|
async decrement(key) {
|
|
57
56
|
const current = this.hits[key];
|
|
58
|
-
if (current)
|
|
57
|
+
if (current)
|
|
59
58
|
this.hits[key] = current - 1;
|
|
60
|
-
}
|
|
61
59
|
}
|
|
62
60
|
async resetKey(key) {
|
|
63
61
|
delete this.hits[key];
|
|
@@ -66,6 +64,9 @@ var MemoryStore = class {
|
|
|
66
64
|
this.hits = {};
|
|
67
65
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
68
66
|
}
|
|
67
|
+
shutdown() {
|
|
68
|
+
clearInterval(this.interval);
|
|
69
|
+
}
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
// source/lib.ts
|
|
@@ -78,11 +79,14 @@ var promisifyStore = (passedStore) => {
|
|
|
78
79
|
class PromisifiedStore {
|
|
79
80
|
async increment(key) {
|
|
80
81
|
return new Promise((resolve, reject) => {
|
|
81
|
-
legacyStore.incr(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
legacyStore.incr(
|
|
83
|
+
key,
|
|
84
|
+
(error, totalHits, resetTime) => {
|
|
85
|
+
if (error)
|
|
86
|
+
reject(error);
|
|
87
|
+
resolve({ totalHits, resetTime });
|
|
88
|
+
}
|
|
89
|
+
);
|
|
86
90
|
});
|
|
87
91
|
}
|
|
88
92
|
async decrement(key) {
|
|
@@ -115,13 +119,18 @@ var parseOptions = (passedOptions) => {
|
|
|
115
119
|
skip: (_request, _response) => false,
|
|
116
120
|
keyGenerator(request, _response) {
|
|
117
121
|
if (!request.ip) {
|
|
118
|
-
console.error(
|
|
122
|
+
console.error(
|
|
123
|
+
"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."
|
|
124
|
+
);
|
|
119
125
|
}
|
|
120
126
|
return request.ip;
|
|
121
127
|
},
|
|
122
128
|
async handler(request, response, _next, _optionsUsed) {
|
|
123
129
|
response.status(config.statusCode);
|
|
124
|
-
const message = typeof config.message === "function" ? await config.message(
|
|
130
|
+
const message = typeof config.message === "function" ? await config.message(
|
|
131
|
+
request,
|
|
132
|
+
response
|
|
133
|
+
) : config.message;
|
|
125
134
|
if (!response.writableEnded) {
|
|
126
135
|
response.send(message != null ? message : "Too many requests, please try again later.");
|
|
127
136
|
}
|
|
@@ -132,7 +141,9 @@ var parseOptions = (passedOptions) => {
|
|
|
132
141
|
store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
|
|
133
142
|
};
|
|
134
143
|
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
|
|
135
|
-
throw new TypeError(
|
|
144
|
+
throw new TypeError(
|
|
145
|
+
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
|
|
146
|
+
);
|
|
136
147
|
}
|
|
137
148
|
return config;
|
|
138
149
|
};
|
|
@@ -147,79 +158,92 @@ var rateLimit = (passedOptions) => {
|
|
|
147
158
|
const options = parseOptions(passedOptions != null ? passedOptions : {});
|
|
148
159
|
if (typeof options.store.init === "function")
|
|
149
160
|
options.store.init(options);
|
|
150
|
-
const middleware = handleAsyncErrors(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const augmentedRequest = request;
|
|
157
|
-
const key = await options.keyGenerator(request, response);
|
|
158
|
-
const { totalHits, resetTime } = await options.store.increment(key);
|
|
159
|
-
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
160
|
-
const maxHits = await retrieveQuota;
|
|
161
|
-
augmentedRequest[options.requestPropertyName] = {
|
|
162
|
-
limit: maxHits,
|
|
163
|
-
current: totalHits,
|
|
164
|
-
remaining: Math.max(maxHits - totalHits, 0),
|
|
165
|
-
resetTime
|
|
166
|
-
};
|
|
167
|
-
if (options.legacyHeaders && !response.headersSent) {
|
|
168
|
-
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
169
|
-
response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
170
|
-
if (resetTime instanceof Date) {
|
|
171
|
-
response.setHeader("Date", new Date().toUTCString());
|
|
172
|
-
response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
|
|
161
|
+
const middleware = handleAsyncErrors(
|
|
162
|
+
async (request, response, next) => {
|
|
163
|
+
const skip = await options.skip(request, response);
|
|
164
|
+
if (skip) {
|
|
165
|
+
next();
|
|
166
|
+
return;
|
|
173
167
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
const augmentedRequest = request;
|
|
169
|
+
const key = await options.keyGenerator(request, response);
|
|
170
|
+
const { totalHits, resetTime } = await options.store.increment(key);
|
|
171
|
+
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
172
|
+
const maxHits = await retrieveQuota;
|
|
173
|
+
augmentedRequest[options.requestPropertyName] = {
|
|
174
|
+
limit: maxHits,
|
|
175
|
+
current: totalHits,
|
|
176
|
+
remaining: Math.max(maxHits - totalHits, 0),
|
|
177
|
+
resetTime
|
|
178
|
+
};
|
|
179
|
+
if (options.legacyHeaders && !response.headersSent) {
|
|
180
|
+
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
181
|
+
response.setHeader(
|
|
182
|
+
"X-RateLimit-Remaining",
|
|
183
|
+
augmentedRequest[options.requestPropertyName].remaining
|
|
184
|
+
);
|
|
185
|
+
if (resetTime instanceof Date) {
|
|
186
|
+
response.setHeader("Date", new Date().toUTCString());
|
|
187
|
+
response.setHeader(
|
|
188
|
+
"X-RateLimit-Reset",
|
|
189
|
+
Math.ceil(resetTime.getTime() / 1e3)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
181
192
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
if (options.standardHeaders && !response.headersSent) {
|
|
194
|
+
response.setHeader("RateLimit-Limit", maxHits);
|
|
195
|
+
response.setHeader(
|
|
196
|
+
"RateLimit-Remaining",
|
|
197
|
+
augmentedRequest[options.requestPropertyName].remaining
|
|
198
|
+
);
|
|
199
|
+
if (resetTime) {
|
|
200
|
+
const deltaSeconds = Math.ceil(
|
|
201
|
+
(resetTime.getTime() - Date.now()) / 1e3
|
|
202
|
+
);
|
|
203
|
+
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
189
204
|
}
|
|
190
|
-
};
|
|
191
|
-
if (options.skipFailedRequests) {
|
|
192
|
-
response.on("finish", async () => {
|
|
193
|
-
if (!options.requestWasSuccessful(request, response))
|
|
194
|
-
await decrementKey();
|
|
195
|
-
});
|
|
196
|
-
response.on("close", async () => {
|
|
197
|
-
if (!response.writableEnded)
|
|
198
|
-
await decrementKey();
|
|
199
|
-
});
|
|
200
|
-
response.on("error", async () => {
|
|
201
|
-
await decrementKey();
|
|
202
|
-
});
|
|
203
205
|
}
|
|
204
|
-
if (options.skipSuccessfulRequests) {
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
207
|
+
let decremented = false;
|
|
208
|
+
const decrementKey = async () => {
|
|
209
|
+
if (!decremented) {
|
|
210
|
+
await options.store.decrement(key);
|
|
211
|
+
decremented = true;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
if (options.skipFailedRequests) {
|
|
215
|
+
response.on("finish", async () => {
|
|
216
|
+
if (!options.requestWasSuccessful(request, response))
|
|
217
|
+
await decrementKey();
|
|
218
|
+
});
|
|
219
|
+
response.on("close", async () => {
|
|
220
|
+
if (!response.writableEnded)
|
|
221
|
+
await decrementKey();
|
|
222
|
+
});
|
|
223
|
+
response.on("error", async () => {
|
|
207
224
|
await decrementKey();
|
|
208
|
-
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (options.skipSuccessfulRequests) {
|
|
228
|
+
response.on("finish", async () => {
|
|
229
|
+
if (options.requestWasSuccessful(request, response))
|
|
230
|
+
await decrementKey();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
209
233
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
options.onLimitReached(request, response, options);
|
|
213
|
-
}
|
|
214
|
-
if (maxHits && totalHits > maxHits) {
|
|
215
|
-
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
216
|
-
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
234
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
235
|
+
options.onLimitReached(request, response, options);
|
|
217
236
|
}
|
|
218
|
-
|
|
219
|
-
|
|
237
|
+
if (maxHits && totalHits > maxHits) {
|
|
238
|
+
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
239
|
+
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
240
|
+
}
|
|
241
|
+
options.handler(request, response, next, options);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
next();
|
|
220
245
|
}
|
|
221
|
-
|
|
222
|
-
});
|
|
246
|
+
);
|
|
223
247
|
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
224
248
|
return middleware;
|
|
225
249
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generated by dts-bundle-generator
|
|
1
|
+
// Generated by dts-bundle-generator v7.0.0
|
|
2
2
|
|
|
3
3
|
import { NextFunction, Request, RequestHandler, Response } from 'express';
|
|
4
4
|
|
|
@@ -66,7 +66,7 @@ export declare type RateLimitRequestHandler = RequestHandler & {
|
|
|
66
66
|
*
|
|
67
67
|
* @deprecated 6.x - Implement the `Store` interface instead.
|
|
68
68
|
*/
|
|
69
|
-
export
|
|
69
|
+
export declare type LegacyStore = {
|
|
70
70
|
/**
|
|
71
71
|
* Method to increment a client's hit counter.
|
|
72
72
|
*
|
|
@@ -90,11 +90,11 @@ export interface LegacyStore {
|
|
|
90
90
|
* Method to reset everyone's hit counter.
|
|
91
91
|
*/
|
|
92
92
|
resetAll?: () => void;
|
|
93
|
-
}
|
|
93
|
+
};
|
|
94
94
|
/**
|
|
95
95
|
* An interface that all hit counter stores must implement.
|
|
96
96
|
*/
|
|
97
|
-
export
|
|
97
|
+
export declare type Store = {
|
|
98
98
|
/**
|
|
99
99
|
* Method that initializes the store, and has access to the options passed to
|
|
100
100
|
* the middleware too.
|
|
@@ -126,11 +126,15 @@ export interface Store {
|
|
|
126
126
|
* Method to reset everyone's hit counter.
|
|
127
127
|
*/
|
|
128
128
|
resetAll?: () => Promise<void> | void;
|
|
129
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Method to shutdown the store, stop timers, and release all resources.
|
|
131
|
+
*/
|
|
132
|
+
shutdown?: () => Promise<void> | void;
|
|
133
|
+
};
|
|
130
134
|
/**
|
|
131
135
|
* The configuration options for the rate limiter.
|
|
132
136
|
*/
|
|
133
|
-
export
|
|
137
|
+
export declare type Options = {
|
|
134
138
|
/**
|
|
135
139
|
* How long we should remember the requests.
|
|
136
140
|
*
|
|
@@ -248,7 +252,7 @@ export interface Options {
|
|
|
248
252
|
* @deprecated 6.x - This option was renamed to `standardHeaders`.
|
|
249
253
|
*/
|
|
250
254
|
draft_polli_ratelimit_headers?: boolean;
|
|
251
|
-
}
|
|
255
|
+
};
|
|
252
256
|
/**
|
|
253
257
|
* The extended request object that includes information about the client's
|
|
254
258
|
* rate limit.
|
|
@@ -260,12 +264,12 @@ export declare type AugmentedRequest = Request & {
|
|
|
260
264
|
* The rate limit related information for each client included in the
|
|
261
265
|
* Express request object.
|
|
262
266
|
*/
|
|
263
|
-
export
|
|
267
|
+
export declare type RateLimitInfo = {
|
|
264
268
|
readonly limit: number;
|
|
265
269
|
readonly current: number;
|
|
266
270
|
readonly remaining: number;
|
|
267
271
|
readonly resetTime: Date | undefined;
|
|
268
|
-
}
|
|
272
|
+
};
|
|
269
273
|
/**
|
|
270
274
|
*
|
|
271
275
|
* Create an instance of IP rate-limiting middleware for Express.
|
|
@@ -297,6 +301,10 @@ export declare class MemoryStore implements Store {
|
|
|
297
301
|
* The time at which all hit counts will be reset.
|
|
298
302
|
*/
|
|
299
303
|
resetTime: Date;
|
|
304
|
+
/**
|
|
305
|
+
* Reference to the active timer.
|
|
306
|
+
*/
|
|
307
|
+
interval?: NodeJS.Timer;
|
|
300
308
|
/**
|
|
301
309
|
* Method that initializes the store.
|
|
302
310
|
*
|
|
@@ -335,6 +343,13 @@ export declare class MemoryStore implements Store {
|
|
|
335
343
|
* @public
|
|
336
344
|
*/
|
|
337
345
|
resetAll(): Promise<void>;
|
|
346
|
+
/**
|
|
347
|
+
* Method to stop the timer (if currently running) and prevent any memory
|
|
348
|
+
* leaks.
|
|
349
|
+
*
|
|
350
|
+
* @public
|
|
351
|
+
*/
|
|
352
|
+
shutdown(): void;
|
|
338
353
|
}
|
|
339
354
|
|
|
340
355
|
export {
|
package/dist/index.mjs
CHANGED
|
@@ -9,12 +9,11 @@ var MemoryStore = class {
|
|
|
9
9
|
this.windowMs = options.windowMs;
|
|
10
10
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
11
11
|
this.hits = {};
|
|
12
|
-
|
|
12
|
+
this.interval = setInterval(async () => {
|
|
13
13
|
await this.resetAll();
|
|
14
14
|
}, this.windowMs);
|
|
15
|
-
if (interval.unref)
|
|
16
|
-
interval.unref();
|
|
17
|
-
}
|
|
15
|
+
if (this.interval.unref)
|
|
16
|
+
this.interval.unref();
|
|
18
17
|
}
|
|
19
18
|
async increment(key) {
|
|
20
19
|
var _a;
|
|
@@ -27,9 +26,8 @@ var MemoryStore = class {
|
|
|
27
26
|
}
|
|
28
27
|
async decrement(key) {
|
|
29
28
|
const current = this.hits[key];
|
|
30
|
-
if (current)
|
|
29
|
+
if (current)
|
|
31
30
|
this.hits[key] = current - 1;
|
|
32
|
-
}
|
|
33
31
|
}
|
|
34
32
|
async resetKey(key) {
|
|
35
33
|
delete this.hits[key];
|
|
@@ -38,6 +36,9 @@ var MemoryStore = class {
|
|
|
38
36
|
this.hits = {};
|
|
39
37
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
40
38
|
}
|
|
39
|
+
shutdown() {
|
|
40
|
+
clearInterval(this.interval);
|
|
41
|
+
}
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
// source/lib.ts
|
|
@@ -50,11 +51,14 @@ var promisifyStore = (passedStore) => {
|
|
|
50
51
|
class PromisifiedStore {
|
|
51
52
|
async increment(key) {
|
|
52
53
|
return new Promise((resolve, reject) => {
|
|
53
|
-
legacyStore.incr(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
legacyStore.incr(
|
|
55
|
+
key,
|
|
56
|
+
(error, totalHits, resetTime) => {
|
|
57
|
+
if (error)
|
|
58
|
+
reject(error);
|
|
59
|
+
resolve({ totalHits, resetTime });
|
|
60
|
+
}
|
|
61
|
+
);
|
|
58
62
|
});
|
|
59
63
|
}
|
|
60
64
|
async decrement(key) {
|
|
@@ -87,13 +91,18 @@ var parseOptions = (passedOptions) => {
|
|
|
87
91
|
skip: (_request, _response) => false,
|
|
88
92
|
keyGenerator(request, _response) {
|
|
89
93
|
if (!request.ip) {
|
|
90
|
-
console.error(
|
|
94
|
+
console.error(
|
|
95
|
+
"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."
|
|
96
|
+
);
|
|
91
97
|
}
|
|
92
98
|
return request.ip;
|
|
93
99
|
},
|
|
94
100
|
async handler(request, response, _next, _optionsUsed) {
|
|
95
101
|
response.status(config.statusCode);
|
|
96
|
-
const message = typeof config.message === "function" ? await config.message(
|
|
102
|
+
const message = typeof config.message === "function" ? await config.message(
|
|
103
|
+
request,
|
|
104
|
+
response
|
|
105
|
+
) : config.message;
|
|
97
106
|
if (!response.writableEnded) {
|
|
98
107
|
response.send(message != null ? message : "Too many requests, please try again later.");
|
|
99
108
|
}
|
|
@@ -104,7 +113,9 @@ var parseOptions = (passedOptions) => {
|
|
|
104
113
|
store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
|
|
105
114
|
};
|
|
106
115
|
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
|
|
107
|
-
throw new TypeError(
|
|
116
|
+
throw new TypeError(
|
|
117
|
+
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
|
|
118
|
+
);
|
|
108
119
|
}
|
|
109
120
|
return config;
|
|
110
121
|
};
|
|
@@ -119,79 +130,92 @@ var rateLimit = (passedOptions) => {
|
|
|
119
130
|
const options = parseOptions(passedOptions != null ? passedOptions : {});
|
|
120
131
|
if (typeof options.store.init === "function")
|
|
121
132
|
options.store.init(options);
|
|
122
|
-
const middleware = handleAsyncErrors(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const augmentedRequest = request;
|
|
129
|
-
const key = await options.keyGenerator(request, response);
|
|
130
|
-
const { totalHits, resetTime } = await options.store.increment(key);
|
|
131
|
-
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
132
|
-
const maxHits = await retrieveQuota;
|
|
133
|
-
augmentedRequest[options.requestPropertyName] = {
|
|
134
|
-
limit: maxHits,
|
|
135
|
-
current: totalHits,
|
|
136
|
-
remaining: Math.max(maxHits - totalHits, 0),
|
|
137
|
-
resetTime
|
|
138
|
-
};
|
|
139
|
-
if (options.legacyHeaders && !response.headersSent) {
|
|
140
|
-
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
141
|
-
response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
142
|
-
if (resetTime instanceof Date) {
|
|
143
|
-
response.setHeader("Date", new Date().toUTCString());
|
|
144
|
-
response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
|
|
133
|
+
const middleware = handleAsyncErrors(
|
|
134
|
+
async (request, response, next) => {
|
|
135
|
+
const skip = await options.skip(request, response);
|
|
136
|
+
if (skip) {
|
|
137
|
+
next();
|
|
138
|
+
return;
|
|
145
139
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
140
|
+
const augmentedRequest = request;
|
|
141
|
+
const key = await options.keyGenerator(request, response);
|
|
142
|
+
const { totalHits, resetTime } = await options.store.increment(key);
|
|
143
|
+
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
144
|
+
const maxHits = await retrieveQuota;
|
|
145
|
+
augmentedRequest[options.requestPropertyName] = {
|
|
146
|
+
limit: maxHits,
|
|
147
|
+
current: totalHits,
|
|
148
|
+
remaining: Math.max(maxHits - totalHits, 0),
|
|
149
|
+
resetTime
|
|
150
|
+
};
|
|
151
|
+
if (options.legacyHeaders && !response.headersSent) {
|
|
152
|
+
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
153
|
+
response.setHeader(
|
|
154
|
+
"X-RateLimit-Remaining",
|
|
155
|
+
augmentedRequest[options.requestPropertyName].remaining
|
|
156
|
+
);
|
|
157
|
+
if (resetTime instanceof Date) {
|
|
158
|
+
response.setHeader("Date", new Date().toUTCString());
|
|
159
|
+
response.setHeader(
|
|
160
|
+
"X-RateLimit-Reset",
|
|
161
|
+
Math.ceil(resetTime.getTime() / 1e3)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
153
164
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
if (options.standardHeaders && !response.headersSent) {
|
|
166
|
+
response.setHeader("RateLimit-Limit", maxHits);
|
|
167
|
+
response.setHeader(
|
|
168
|
+
"RateLimit-Remaining",
|
|
169
|
+
augmentedRequest[options.requestPropertyName].remaining
|
|
170
|
+
);
|
|
171
|
+
if (resetTime) {
|
|
172
|
+
const deltaSeconds = Math.ceil(
|
|
173
|
+
(resetTime.getTime() - Date.now()) / 1e3
|
|
174
|
+
);
|
|
175
|
+
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
161
176
|
}
|
|
162
|
-
};
|
|
163
|
-
if (options.skipFailedRequests) {
|
|
164
|
-
response.on("finish", async () => {
|
|
165
|
-
if (!options.requestWasSuccessful(request, response))
|
|
166
|
-
await decrementKey();
|
|
167
|
-
});
|
|
168
|
-
response.on("close", async () => {
|
|
169
|
-
if (!response.writableEnded)
|
|
170
|
-
await decrementKey();
|
|
171
|
-
});
|
|
172
|
-
response.on("error", async () => {
|
|
173
|
-
await decrementKey();
|
|
174
|
-
});
|
|
175
177
|
}
|
|
176
|
-
if (options.skipSuccessfulRequests) {
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
179
|
+
let decremented = false;
|
|
180
|
+
const decrementKey = async () => {
|
|
181
|
+
if (!decremented) {
|
|
182
|
+
await options.store.decrement(key);
|
|
183
|
+
decremented = true;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
if (options.skipFailedRequests) {
|
|
187
|
+
response.on("finish", async () => {
|
|
188
|
+
if (!options.requestWasSuccessful(request, response))
|
|
189
|
+
await decrementKey();
|
|
190
|
+
});
|
|
191
|
+
response.on("close", async () => {
|
|
192
|
+
if (!response.writableEnded)
|
|
193
|
+
await decrementKey();
|
|
194
|
+
});
|
|
195
|
+
response.on("error", async () => {
|
|
179
196
|
await decrementKey();
|
|
180
|
-
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (options.skipSuccessfulRequests) {
|
|
200
|
+
response.on("finish", async () => {
|
|
201
|
+
if (options.requestWasSuccessful(request, response))
|
|
202
|
+
await decrementKey();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
181
205
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
options.onLimitReached(request, response, options);
|
|
185
|
-
}
|
|
186
|
-
if (maxHits && totalHits > maxHits) {
|
|
187
|
-
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
188
|
-
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
206
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
207
|
+
options.onLimitReached(request, response, options);
|
|
189
208
|
}
|
|
190
|
-
|
|
191
|
-
|
|
209
|
+
if (maxHits && totalHits > maxHits) {
|
|
210
|
+
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
211
|
+
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
212
|
+
}
|
|
213
|
+
options.handler(request, response, next, options);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
next();
|
|
192
217
|
}
|
|
193
|
-
|
|
194
|
-
});
|
|
218
|
+
);
|
|
195
219
|
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
196
220
|
return middleware;
|
|
197
221
|
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.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",
|
|
7
7
|
"url": "http://nfriedly.com/"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"homepage": "https://github.com/
|
|
11
|
-
"repository": "https://github.com/
|
|
10
|
+
"homepage": "https://github.com/express-rate-limit/express-rate-limit",
|
|
11
|
+
"repository": "https://github.com/express-rate-limit/express-rate-limit",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"express-rate-limit",
|
|
14
14
|
"express",
|
|
@@ -71,24 +71,24 @@
|
|
|
71
71
|
"express": "^4 || ^5"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@jest/globals": "
|
|
75
|
-
"@types/express": "4.17.
|
|
76
|
-
"@types/jest": "
|
|
77
|
-
"@types/node": "18.
|
|
74
|
+
"@jest/globals": "29.3.1",
|
|
75
|
+
"@types/express": "4.17.14",
|
|
76
|
+
"@types/jest": "29.2.3",
|
|
77
|
+
"@types/node": "18.11.9",
|
|
78
78
|
"@types/supertest": "2.0.12",
|
|
79
79
|
"cross-env": "7.0.3",
|
|
80
|
-
"del-cli": "
|
|
81
|
-
"dts-bundle-generator": "
|
|
82
|
-
"esbuild": "0.14
|
|
83
|
-
"express": "4.18.
|
|
84
|
-
"husky": "8.0.
|
|
85
|
-
"jest": "
|
|
80
|
+
"del-cli": "5.0.0",
|
|
81
|
+
"dts-bundle-generator": "7.0.0",
|
|
82
|
+
"esbuild": "0.15.14",
|
|
83
|
+
"express": "4.18.2",
|
|
84
|
+
"husky": "8.0.2",
|
|
85
|
+
"jest": "29.3.1",
|
|
86
86
|
"lint-staged": "13.0.3",
|
|
87
87
|
"npm-run-all": "4.1.5",
|
|
88
|
-
"supertest": "6.
|
|
89
|
-
"ts-jest": "
|
|
88
|
+
"supertest": "6.3.1",
|
|
89
|
+
"ts-jest": "29.0.3",
|
|
90
90
|
"ts-node": "10.9.1",
|
|
91
|
-
"typescript": "4.
|
|
91
|
+
"typescript": "4.8.4",
|
|
92
92
|
"xo": "0.49.0"
|
|
93
93
|
},
|
|
94
94
|
"xo": {
|
|
@@ -100,7 +100,8 @@
|
|
|
100
100
|
"@typescript-eslint/consistent-indexed-object-style": [
|
|
101
101
|
"error",
|
|
102
102
|
"index-signature"
|
|
103
|
-
]
|
|
103
|
+
],
|
|
104
|
+
"n/no-unsupported-features/es-syntax": 0
|
|
104
105
|
}
|
|
105
106
|
},
|
|
106
107
|
"prettier": {
|
package/readme.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# <div align="center"> Express Rate Limit </div>
|
|
2
2
|
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Sponsored by [Zuplo](https://zuplo.link/express-rate-limit) a fully-managed API
|
|
6
|
+
Gateway for developers. Add
|
|
7
|
+
[dynamic rate-limiting](https://zuplo.link/dynamic-rate-limiting),
|
|
8
|
+
authentication and more to any API in minutes. Learn more at
|
|
9
|
+
[zuplo.com](https://zuplo.link/express-rate-limit)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
3
13
|
<div align="center">
|
|
4
14
|
|
|
5
|
-
[](https://github.com/express-rate-limit/express-rate-limit/actions)
|
|
6
16
|
[](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
|
|
7
17
|
[](https://www.npmjs.com/package/express-rate-limit)
|
|
8
18
|
|
|
@@ -41,9 +51,9 @@ From Github Releases:
|
|
|
41
51
|
|
|
42
52
|
```sh
|
|
43
53
|
# Using npm
|
|
44
|
-
> npm install https://github.com/
|
|
54
|
+
> npm install https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
|
|
45
55
|
# Using yarn or pnpm
|
|
46
|
-
> yarn/pnpm add https://github.com/
|
|
56
|
+
> yarn/pnpm add https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
|
|
47
57
|
```
|
|
48
58
|
|
|
49
59
|
Replace `{version}` with the version of the package that you want to your, e.g.:
|
|
@@ -180,10 +190,9 @@ app.get('/ip', (request, response) => response.send(request.ip))
|
|
|
180
190
|
```
|
|
181
191
|
|
|
182
192
|
Go to `/ip` and see the IP address returned in the response. If it matches your
|
|
183
|
-
IP address
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
it does.
|
|
193
|
+
public IP address, then the number of proxies is correct and the rate limiter
|
|
194
|
+
should now work correctly. If not, then keep increasing the number until it
|
|
195
|
+
does.
|
|
187
196
|
|
|
188
197
|
For more information about the `trust proxy` setting, take a look at the
|
|
189
198
|
[official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
|
|
@@ -387,7 +396,7 @@ objects that is called when a client has reached their rate limit, and will be
|
|
|
387
396
|
rate limited on their next request.
|
|
388
397
|
|
|
389
398
|
This method was
|
|
390
|
-
[deprecated in v6](https://github.com/
|
|
399
|
+
[deprecated in v6](https://github.com/express-rate-limit/express-rate-limit/releases/v6.0.0) -
|
|
391
400
|
Please use a custom `handler` that checks the number of hits instead.
|
|
392
401
|
|
|
393
402
|
### `skip`
|
|
@@ -456,7 +465,7 @@ Here is a list of external stores:
|
|
|
456
465
|
| [`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. | Legacy |
|
|
457
466
|
|
|
458
467
|
Take a look at
|
|
459
|
-
[this guide](https://github.com/
|
|
468
|
+
[this guide](https://github.com/express-rate-limit/express-rate-limit/wiki/Creating-Your-Own-Store)
|
|
460
469
|
if you wish to create your own store.
|
|
461
470
|
|
|
462
471
|
## Request API
|
|
@@ -480,9 +489,10 @@ method.
|
|
|
480
489
|
## Issues and Contributing
|
|
481
490
|
|
|
482
491
|
If you encounter a bug or want to see something added/changed, please go ahead
|
|
483
|
-
and
|
|
492
|
+
and
|
|
493
|
+
[open an issue](https://github.com/nfriexpress-rate-limitedly/express-rate-limit/issues/new)!
|
|
484
494
|
If you need help with something, feel free to
|
|
485
|
-
[start a discussion](https://github.com/
|
|
495
|
+
[start a discussion](https://github.com/express-rate-limit/express-rate-limit/discussions/new)!
|
|
486
496
|
|
|
487
497
|
If you wish to contribute to the library, thanks! First, please read
|
|
488
498
|
[the contributing guide](contributing.md). Then you can pick up any issue and
|