express-rate-limit 6.6.0 → 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 +96 -73
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +96 -73
- 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
|
@@ -79,11 +79,14 @@ var promisifyStore = (passedStore) => {
|
|
|
79
79
|
class PromisifiedStore {
|
|
80
80
|
async increment(key) {
|
|
81
81
|
return new Promise((resolve, reject) => {
|
|
82
|
-
legacyStore.incr(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
legacyStore.incr(
|
|
83
|
+
key,
|
|
84
|
+
(error, totalHits, resetTime) => {
|
|
85
|
+
if (error)
|
|
86
|
+
reject(error);
|
|
87
|
+
resolve({ totalHits, resetTime });
|
|
88
|
+
}
|
|
89
|
+
);
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
92
|
async decrement(key) {
|
|
@@ -116,13 +119,18 @@ var parseOptions = (passedOptions) => {
|
|
|
116
119
|
skip: (_request, _response) => false,
|
|
117
120
|
keyGenerator(request, _response) {
|
|
118
121
|
if (!request.ip) {
|
|
119
|
-
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
|
+
);
|
|
120
125
|
}
|
|
121
126
|
return request.ip;
|
|
122
127
|
},
|
|
123
128
|
async handler(request, response, _next, _optionsUsed) {
|
|
124
129
|
response.status(config.statusCode);
|
|
125
|
-
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;
|
|
126
134
|
if (!response.writableEnded) {
|
|
127
135
|
response.send(message != null ? message : "Too many requests, please try again later.");
|
|
128
136
|
}
|
|
@@ -133,7 +141,9 @@ var parseOptions = (passedOptions) => {
|
|
|
133
141
|
store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
|
|
134
142
|
};
|
|
135
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") {
|
|
136
|
-
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
|
+
);
|
|
137
147
|
}
|
|
138
148
|
return config;
|
|
139
149
|
};
|
|
@@ -148,79 +158,92 @@ var rateLimit = (passedOptions) => {
|
|
|
148
158
|
const options = parseOptions(passedOptions != null ? passedOptions : {});
|
|
149
159
|
if (typeof options.store.init === "function")
|
|
150
160
|
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));
|
|
161
|
+
const middleware = handleAsyncErrors(
|
|
162
|
+
async (request, response, next) => {
|
|
163
|
+
const skip = await options.skip(request, response);
|
|
164
|
+
if (skip) {
|
|
165
|
+
next();
|
|
166
|
+
return;
|
|
174
167
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
}
|
|
182
192
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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));
|
|
190
204
|
}
|
|
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
205
|
}
|
|
205
|
-
if (options.skipSuccessfulRequests) {
|
|
206
|
-
|
|
207
|
-
|
|
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 () => {
|
|
208
224
|
await decrementKey();
|
|
209
|
-
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (options.skipSuccessfulRequests) {
|
|
228
|
+
response.on("finish", async () => {
|
|
229
|
+
if (options.requestWasSuccessful(request, response))
|
|
230
|
+
await decrementKey();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
210
233
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
235
|
+
options.onLimitReached(request, response, options);
|
|
236
|
+
}
|
|
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;
|
|
218
243
|
}
|
|
219
|
-
|
|
220
|
-
return;
|
|
244
|
+
next();
|
|
221
245
|
}
|
|
222
|
-
|
|
223
|
-
});
|
|
246
|
+
);
|
|
224
247
|
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
225
248
|
return middleware;
|
|
226
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.
|
|
@@ -130,11 +130,11 @@ export interface Store {
|
|
|
130
130
|
* Method to shutdown the store, stop timers, and release all resources.
|
|
131
131
|
*/
|
|
132
132
|
shutdown?: () => Promise<void> | void;
|
|
133
|
-
}
|
|
133
|
+
};
|
|
134
134
|
/**
|
|
135
135
|
* The configuration options for the rate limiter.
|
|
136
136
|
*/
|
|
137
|
-
export
|
|
137
|
+
export declare type Options = {
|
|
138
138
|
/**
|
|
139
139
|
* How long we should remember the requests.
|
|
140
140
|
*
|
|
@@ -252,7 +252,7 @@ export interface Options {
|
|
|
252
252
|
* @deprecated 6.x - This option was renamed to `standardHeaders`.
|
|
253
253
|
*/
|
|
254
254
|
draft_polli_ratelimit_headers?: boolean;
|
|
255
|
-
}
|
|
255
|
+
};
|
|
256
256
|
/**
|
|
257
257
|
* The extended request object that includes information about the client's
|
|
258
258
|
* rate limit.
|
|
@@ -264,12 +264,12 @@ export declare type AugmentedRequest = Request & {
|
|
|
264
264
|
* The rate limit related information for each client included in the
|
|
265
265
|
* Express request object.
|
|
266
266
|
*/
|
|
267
|
-
export
|
|
267
|
+
export declare type RateLimitInfo = {
|
|
268
268
|
readonly limit: number;
|
|
269
269
|
readonly current: number;
|
|
270
270
|
readonly remaining: number;
|
|
271
271
|
readonly resetTime: Date | undefined;
|
|
272
|
-
}
|
|
272
|
+
};
|
|
273
273
|
/**
|
|
274
274
|
*
|
|
275
275
|
* Create an instance of IP rate-limiting middleware for Express.
|
package/dist/index.mjs
CHANGED
|
@@ -51,11 +51,14 @@ var promisifyStore = (passedStore) => {
|
|
|
51
51
|
class PromisifiedStore {
|
|
52
52
|
async increment(key) {
|
|
53
53
|
return new Promise((resolve, reject) => {
|
|
54
|
-
legacyStore.incr(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
legacyStore.incr(
|
|
55
|
+
key,
|
|
56
|
+
(error, totalHits, resetTime) => {
|
|
57
|
+
if (error)
|
|
58
|
+
reject(error);
|
|
59
|
+
resolve({ totalHits, resetTime });
|
|
60
|
+
}
|
|
61
|
+
);
|
|
59
62
|
});
|
|
60
63
|
}
|
|
61
64
|
async decrement(key) {
|
|
@@ -88,13 +91,18 @@ var parseOptions = (passedOptions) => {
|
|
|
88
91
|
skip: (_request, _response) => false,
|
|
89
92
|
keyGenerator(request, _response) {
|
|
90
93
|
if (!request.ip) {
|
|
91
|
-
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
|
+
);
|
|
92
97
|
}
|
|
93
98
|
return request.ip;
|
|
94
99
|
},
|
|
95
100
|
async handler(request, response, _next, _optionsUsed) {
|
|
96
101
|
response.status(config.statusCode);
|
|
97
|
-
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;
|
|
98
106
|
if (!response.writableEnded) {
|
|
99
107
|
response.send(message != null ? message : "Too many requests, please try again later.");
|
|
100
108
|
}
|
|
@@ -105,7 +113,9 @@ var parseOptions = (passedOptions) => {
|
|
|
105
113
|
store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
|
|
106
114
|
};
|
|
107
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") {
|
|
108
|
-
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
|
+
);
|
|
109
119
|
}
|
|
110
120
|
return config;
|
|
111
121
|
};
|
|
@@ -120,79 +130,92 @@ var rateLimit = (passedOptions) => {
|
|
|
120
130
|
const options = parseOptions(passedOptions != null ? passedOptions : {});
|
|
121
131
|
if (typeof options.store.init === "function")
|
|
122
132
|
options.store.init(options);
|
|
123
|
-
const middleware = handleAsyncErrors(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const augmentedRequest = request;
|
|
130
|
-
const key = await options.keyGenerator(request, response);
|
|
131
|
-
const { totalHits, resetTime } = await options.store.increment(key);
|
|
132
|
-
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
133
|
-
const maxHits = await retrieveQuota;
|
|
134
|
-
augmentedRequest[options.requestPropertyName] = {
|
|
135
|
-
limit: maxHits,
|
|
136
|
-
current: totalHits,
|
|
137
|
-
remaining: Math.max(maxHits - totalHits, 0),
|
|
138
|
-
resetTime
|
|
139
|
-
};
|
|
140
|
-
if (options.legacyHeaders && !response.headersSent) {
|
|
141
|
-
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
142
|
-
response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
143
|
-
if (resetTime instanceof Date) {
|
|
144
|
-
response.setHeader("Date", new Date().toUTCString());
|
|
145
|
-
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;
|
|
146
139
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
}
|
|
154
164
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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));
|
|
162
176
|
}
|
|
163
|
-
};
|
|
164
|
-
if (options.skipFailedRequests) {
|
|
165
|
-
response.on("finish", async () => {
|
|
166
|
-
if (!options.requestWasSuccessful(request, response))
|
|
167
|
-
await decrementKey();
|
|
168
|
-
});
|
|
169
|
-
response.on("close", async () => {
|
|
170
|
-
if (!response.writableEnded)
|
|
171
|
-
await decrementKey();
|
|
172
|
-
});
|
|
173
|
-
response.on("error", async () => {
|
|
174
|
-
await decrementKey();
|
|
175
|
-
});
|
|
176
177
|
}
|
|
177
|
-
if (options.skipSuccessfulRequests) {
|
|
178
|
-
|
|
179
|
-
|
|
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 () => {
|
|
180
196
|
await decrementKey();
|
|
181
|
-
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (options.skipSuccessfulRequests) {
|
|
200
|
+
response.on("finish", async () => {
|
|
201
|
+
if (options.requestWasSuccessful(request, response))
|
|
202
|
+
await decrementKey();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
182
205
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
206
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
207
|
+
options.onLimitReached(request, response, options);
|
|
208
|
+
}
|
|
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;
|
|
190
215
|
}
|
|
191
|
-
|
|
192
|
-
return;
|
|
216
|
+
next();
|
|
193
217
|
}
|
|
194
|
-
|
|
195
|
-
});
|
|
218
|
+
);
|
|
196
219
|
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
197
220
|
return middleware;
|
|
198
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
|