express-rate-limit 5.5.1 → 6.0.3

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 ADDED
@@ -0,0 +1,125 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to
7
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
+
9
+ ## [6.0.2](https://github.com/nfriedly/express-rate-limit/releases/v6.0.2)
10
+
11
+ ### Fixed
12
+
13
+ - Ensure CommonJS projects can import the module.
14
+
15
+ ### Added
16
+
17
+ - Add additional tests that test:
18
+ - importing the library in `js-cjs`, `js-esm`, `ts-cjs`, `ts-esm`
19
+ environments.
20
+ - usage of the library with external stores (`redis`, `mongo`, `memcached`,
21
+ `precise`).
22
+
23
+ ### Changed
24
+
25
+ - Use [`esbuild`](https://esbuild.github.io/) to generate ESM and CJS output.
26
+ This reduces the size of the built package from 138 kb to 13kb and build time
27
+ to 4 ms! :rocket:
28
+ - Use [`dts-bundle-generator`](https://github.com/timocov/dts-bundle-generator)
29
+ to generate a single Typescript declaration file.
30
+
31
+ ## [6.0.1](https://github.com/nfriedly/express-rate-limit/releases/v6.0.1)
32
+
33
+ ### Fixed
34
+
35
+ - Ensure CommonJS projects can import the module.
36
+
37
+ ## [6.0.0](https://github.com/nfriedly/express-rate-limit/releases/v6.0.0)
38
+
39
+ ### Added
40
+
41
+ - `express` 4.x as a peer dependency.
42
+ - Better Typescript support (the library was rewritten in Typescript).
43
+ - Export the package as both ESM and CJS.
44
+ - Publish the built package (`.tgz` file) on GitHub releases as well as the npm
45
+ registry.
46
+ - Issue and PR templates.
47
+ - A contributing guide.
48
+
49
+ ### Changed
50
+
51
+ - Rename the `draft_polli_ratelimit_headers` option to `standardHeaders`.
52
+ - Rename the `headers` option to `legacyHeaders`.
53
+ - `Retry-After` header is now sent if either `legacyHeaders` or
54
+ `standardHeaders` is set.
55
+ - Allow `keyGenerator` to be an async function/return a promise.
56
+ - Change the way custom stores are defined.
57
+ - Add the `init` method for stores to set themselves up using options passed
58
+ to the middleware.
59
+ - Rename the `incr` method to `increment`.
60
+ - Allow the `increment`, `decrement`, `resetKey` and `resetAll` methods to
61
+ return a promise.
62
+ - Old stores will automatically be promisified and used.
63
+ - The package can now only be used with NodeJS version 12.9.0 or greater.
64
+ - The `onLimitReached` configuration option is now deprecated. Replace it with a
65
+ custom `handler` that checks the number of hits.
66
+
67
+ ### Removed
68
+
69
+ - Remove the deprecated `limiter.resetIp` method (use the `limiter.resetKey`
70
+ method instead).
71
+ - Remove the deprecated options `delayMs`, `delayAfter` (the delay functionality
72
+ was moved to the
73
+ [`express-slow-down`](https://github.com/nfriedly/express-slow-down) package)
74
+ and `global` (use a key generator that returns a constant value).
75
+
76
+ ## [5.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v5.5.1)
77
+
78
+ ### Added
79
+
80
+ - The middleware ~throws~ logs an error if `request.ip` is undefined.
81
+
82
+ ### Removed
83
+
84
+ - Removes typescript typings. (See
85
+ [#138](https://github.com/nfriedly/express-rate-limit/issues/138))
86
+
87
+ ## [4.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v4.0.4)
88
+
89
+ ### Changed
90
+
91
+ - The library no longer modifies the passed-in options object, it instead makes
92
+ a clone of it.
93
+
94
+ ## [3.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v3.5.2)
95
+
96
+ ### Added
97
+
98
+ - Simplifies the default `handler` function so that it no longer changes the
99
+ response format. The default handler also uses
100
+ [response.send](https://expressjs.com/en/4x/api.html#response.send).
101
+
102
+ ### Changes
103
+
104
+ - `onLimitReached` now only triggers once for a client and window. However, the
105
+ `handle` method is called for every blocked request.
106
+
107
+ ### Removed
108
+
109
+ - The `delayAfter` and `delayMs` options; they were moved to the
110
+ [express-slow-down](https://npmjs.org/package/express-slow-down) package.
111
+
112
+ ## [2.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v2.14.2)
113
+
114
+ ### Added
115
+
116
+ - A `limiter.resetKey()` method to reset the hit counter for a particular client
117
+
118
+ ### Changes
119
+
120
+ - The rate limiter now uses a less precise but less resource intensive method of
121
+ tracking hits from a client.
122
+
123
+ ### Removed
124
+
125
+ - The `global` option.
package/dist/index.cjs ADDED
@@ -0,0 +1,226 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __reExport = (target, module2, copyDefault, desc) => {
11
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
12
+ for (let key of __getOwnPropNames(module2))
13
+ if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default"))
14
+ __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
15
+ }
16
+ return target;
17
+ };
18
+ var __toCommonJS = /* @__PURE__ */ ((cache) => {
19
+ return (module2, temp) => {
20
+ return cache && cache.get(module2) || (temp = __reExport(__markAsModule({}), module2, 1), cache && cache.set(module2, temp), temp);
21
+ };
22
+ })(typeof WeakMap !== "undefined" ? /* @__PURE__ */ new WeakMap() : 0);
23
+
24
+ // source/index.ts
25
+ var source_exports = {};
26
+ __export(source_exports, {
27
+ default: () => source_default
28
+ });
29
+
30
+ // source/memory-store.ts
31
+ var calculateNextResetTime = (windowMs) => {
32
+ const resetTime = new Date();
33
+ resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
34
+ return resetTime;
35
+ };
36
+ var MemoryStore = class {
37
+ init(options) {
38
+ this.windowMs = options.windowMs;
39
+ this.resetTime = calculateNextResetTime(this.windowMs);
40
+ this.hits = {};
41
+ const interval = setInterval(async () => {
42
+ await this.resetAll();
43
+ }, this.windowMs);
44
+ if (interval.unref) {
45
+ interval.unref();
46
+ }
47
+ }
48
+ async increment(key) {
49
+ const totalHits = (this.hits[key] ?? 0) + 1;
50
+ this.hits[key] = totalHits;
51
+ return {
52
+ totalHits,
53
+ resetTime: this.resetTime
54
+ };
55
+ }
56
+ async decrement(key) {
57
+ const current = this.hits[key];
58
+ if (current) {
59
+ this.hits[key] = current - 1;
60
+ }
61
+ }
62
+ async resetKey(key) {
63
+ delete this.hits[key];
64
+ }
65
+ async resetAll() {
66
+ this.hits = {};
67
+ this.resetTime = calculateNextResetTime(this.windowMs);
68
+ }
69
+ };
70
+
71
+ // source/lib.ts
72
+ var isLegacyStore = (store) => typeof store.incr === "function" && typeof store.increment !== "function";
73
+ var promisifyStore = (passedStore) => {
74
+ if (!isLegacyStore(passedStore)) {
75
+ return passedStore;
76
+ }
77
+ const legacyStore = passedStore;
78
+ class PromisifiedStore {
79
+ async increment(key) {
80
+ return new Promise((resolve, reject) => {
81
+ legacyStore.incr(key, (error, totalHits, resetTime) => {
82
+ if (error)
83
+ reject(error);
84
+ resolve({ totalHits, resetTime });
85
+ });
86
+ });
87
+ }
88
+ async decrement(key) {
89
+ return Promise.resolve(legacyStore.decrement(key));
90
+ }
91
+ async resetKey(key) {
92
+ return Promise.resolve(legacyStore.resetKey(key));
93
+ }
94
+ async resetAll() {
95
+ if (typeof legacyStore.resetAll === "function")
96
+ return Promise.resolve(legacyStore.resetAll());
97
+ }
98
+ }
99
+ return new PromisifiedStore();
100
+ };
101
+ var parseOptions = (passedOptions) => {
102
+ const options = {
103
+ windowMs: 60 * 1e3,
104
+ store: new MemoryStore(),
105
+ max: 5,
106
+ message: "Too many requests, please try again later.",
107
+ statusCode: 429,
108
+ legacyHeaders: passedOptions.headers ?? true,
109
+ standardHeaders: passedOptions.draft_polli_ratelimit_headers ?? false,
110
+ requestPropertyName: "rateLimit",
111
+ skipFailedRequests: false,
112
+ skipSuccessfulRequests: false,
113
+ requestWasSuccessful: (_request, response) => response.statusCode < 400,
114
+ skip: (_request, _response) => false,
115
+ keyGenerator: (request, _response) => {
116
+ if (!request.ip) {
117
+ console.error("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.");
118
+ }
119
+ return request.ip;
120
+ },
121
+ handler: (_request, response, _next, _optionsUsed) => {
122
+ response.status(options.statusCode).send(options.message);
123
+ },
124
+ onLimitReached: (_request, _response, _optionsUsed) => {
125
+ },
126
+ ...passedOptions
127
+ };
128
+ if (typeof options.store.incr !== "function" && typeof options.store.increment !== "function" || typeof options.store.decrement !== "function" || typeof options.store.resetKey !== "function" || typeof options.store.resetAll !== "undefined" && typeof options.store.resetAll !== "function" || typeof options.store.init !== "undefined" && typeof options.store.init !== "function") {
129
+ throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
130
+ }
131
+ options.store = promisifyStore(options.store);
132
+ return options;
133
+ };
134
+ var handleAsyncErrors = (fn) => async (request, response, next) => {
135
+ try {
136
+ await Promise.resolve(fn(request, response, next)).catch(next);
137
+ } catch (error) {
138
+ next(error);
139
+ }
140
+ };
141
+ var rateLimit = (passedOptions) => {
142
+ const options = parseOptions(passedOptions ?? {});
143
+ if (typeof options.store.init === "function")
144
+ options.store.init(options);
145
+ const middleware = handleAsyncErrors(async (request, response, next) => {
146
+ const skip = await options.skip(request, response);
147
+ if (skip) {
148
+ next();
149
+ return;
150
+ }
151
+ const augmentedRequest = request;
152
+ const key = await options.keyGenerator(request, response);
153
+ const { totalHits, resetTime } = await options.store.increment(key);
154
+ const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
155
+ const maxHits = await retrieveQuota;
156
+ augmentedRequest[options.requestPropertyName] = {
157
+ limit: maxHits,
158
+ current: totalHits,
159
+ remaining: Math.max(maxHits - totalHits, 0),
160
+ resetTime
161
+ };
162
+ if (options.legacyHeaders && !response.headersSent) {
163
+ response.setHeader("X-RateLimit-Limit", maxHits);
164
+ response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
165
+ if (resetTime instanceof Date) {
166
+ response.setHeader("Date", new Date().toUTCString());
167
+ response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
168
+ }
169
+ }
170
+ if (options.standardHeaders && !response.headersSent) {
171
+ response.setHeader("RateLimit-Limit", maxHits);
172
+ response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
173
+ if (resetTime) {
174
+ const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
175
+ response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
176
+ }
177
+ }
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 () => {
196
+ await decrementKey();
197
+ });
198
+ }
199
+ if (options.skipSuccessfulRequests) {
200
+ response.on("finish", async () => {
201
+ if (options.requestWasSuccessful(request, response))
202
+ await decrementKey();
203
+ });
204
+ }
205
+ }
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;
215
+ }
216
+ next();
217
+ });
218
+ middleware.resetKey = options.store.resetKey.bind(options.store);
219
+ return middleware;
220
+ };
221
+ var lib_default = rateLimit;
222
+
223
+ // source/index.ts
224
+ var source_default = lib_default;
225
+ module.exports = __toCommonJS(source_exports);
226
+ module.exports = rateLimit;
@@ -0,0 +1,248 @@
1
+ // Generated by dts-bundle-generator v6.2.0
2
+
3
+ import Express from 'express';
4
+
5
+ /**
6
+ * Callback that fires when a client's hit counter is incremented.
7
+ *
8
+ * @param error {Error | undefined} - The error that occurred, if any
9
+ * @param totalHits {number} - The number of hits for that client so far
10
+ * @param resetTime {Date | undefined} - The time when the counter resets
11
+ */
12
+ export declare type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
13
+ /**
14
+ * Method (in the form of middleware) to generate/retrieve a value based on the
15
+ * incoming request
16
+ *
17
+ * @param request {Express.Request} - The Express request object
18
+ * @param response {Express.Response} - The Express response object
19
+ *
20
+ * @returns {T} - The value needed
21
+ */
22
+ export declare type ValueDeterminingMiddleware<T> = (request: Express.Request, response: Express.Response) => T | Promise<T>;
23
+ /**
24
+ * Express request handler that sends back a response when a client is
25
+ * rate-limited.
26
+ *
27
+ * @param request {Express.Request} - The Express request object
28
+ * @param response {Express.Response} - The Express response object
29
+ * @param next {Express.NextFunction} - The Express `next` function, can be called to skip responding
30
+ * @param optionsUsed {Options} - The options used to set up the middleware
31
+ */
32
+ export declare type RateLimitExceededEventHandler = (request: Express.Request, response: Express.Response, next: Express.NextFunction, optionsUsed: Options) => void;
33
+ /**
34
+ * Event callback that is triggered on a client's first request that exceeds the limit
35
+ * but not for subsequent requests. May be used for logging, etc. Should *not*
36
+ * send a response.
37
+ *
38
+ * @param request {Express.Request} - The Express request object
39
+ * @param response {Express.Response} - The Express response object
40
+ * @param optionsUsed {Options} - The options used to set up the middleware
41
+ */
42
+ export declare type RateLimitReachedEventHandler = (request: Express.Request, response: Express.Response, optionsUsed: Options) => void;
43
+ /**
44
+ * Data returned from the `Store` when a client's hit counter is incremented.
45
+ *
46
+ * @property totalHits {number} - The number of hits for that client so far
47
+ * @property resetTime {Date | undefined} - The time when the counter resets
48
+ */
49
+ export declare type IncrementResponse = {
50
+ totalHits: number;
51
+ resetTime: Date | undefined;
52
+ };
53
+ /**
54
+ * A modified Express request handler with the rate limit functions.
55
+ */
56
+ export declare type RateLimitRequestHandler = Express.RequestHandler & {
57
+ /**
58
+ * Method to reset a client's hit counter.
59
+ *
60
+ * @param key {string} - The identifier for a client
61
+ */
62
+ resetKey: (key: string) => void;
63
+ };
64
+ /**
65
+ * An interface that all hit counter stores must implement.
66
+ *
67
+ * @deprecated 6.x - Implement the `Store` interface instead.
68
+ */
69
+ export interface LegacyStore {
70
+ /**
71
+ * Method to increment a client's hit counter.
72
+ *
73
+ * @param key {string} - The identifier for a client
74
+ * @param callback {IncrementCallback} - The callback to call once the counter is incremented
75
+ */
76
+ incr: (key: string, callback: IncrementCallback) => void;
77
+ /**
78
+ * Method to decrement a client's hit counter.
79
+ *
80
+ * @param key {string} - The identifier for a client
81
+ */
82
+ decrement: (key: string) => void;
83
+ /**
84
+ * Method to reset a client's hit counter.
85
+ *
86
+ * @param key {string} - The identifier for a client
87
+ */
88
+ resetKey: (key: string) => void;
89
+ /**
90
+ * Method to reset everyone's hit counter.
91
+ */
92
+ resetAll?: () => void;
93
+ }
94
+ /**
95
+ * An interface that all hit counter stores must implement.
96
+ */
97
+ export interface Store {
98
+ /**
99
+ * Method that initializes the store, and has access to the options passed to
100
+ * the middleware too.
101
+ *
102
+ * @param options {Options} - The options used to setup the middleware
103
+ */
104
+ init?: (options: Options) => void;
105
+ /**
106
+ * Method to increment a client's hit counter.
107
+ *
108
+ * @param key {string} - The identifier for a client
109
+ *
110
+ * @returns {IncrementResponse} - The number of hits and reset time for that client
111
+ */
112
+ increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
113
+ /**
114
+ * Method to decrement a client's hit counter.
115
+ *
116
+ * @param key {string} - The identifier for a client
117
+ */
118
+ decrement: (key: string) => Promise<void> | void;
119
+ /**
120
+ * Method to reset a client's hit counter.
121
+ *
122
+ * @param key {string} - The identifier for a client
123
+ */
124
+ resetKey: (key: string) => Promise<void> | void;
125
+ /**
126
+ * Method to reset everyone's hit counter.
127
+ */
128
+ resetAll?: () => Promise<void> | void;
129
+ }
130
+ /**
131
+ * The configuration options for the rate limiter.
132
+ */
133
+ export interface Options {
134
+ /**
135
+ * How long we should remember the requests.
136
+ */
137
+ readonly windowMs: number;
138
+ /**
139
+ * The maximum number of connection to allow during the `window` before
140
+ * rate limiting the client.
141
+ *
142
+ * Can be the limit itself as a number or express middleware that parses
143
+ * the request and then figures out the limit.
144
+ */
145
+ readonly max: number | ValueDeterminingMiddleware<number>;
146
+ /**
147
+ * The response body to send back when a client is rate limited.
148
+ */
149
+ readonly message: any;
150
+ /**
151
+ * The HTTP status code to send back when a client is rate limited.
152
+ *
153
+ * Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
154
+ */
155
+ readonly statusCode: number;
156
+ /**
157
+ * Whether to send `X-RateLimit-*` headers with the rate limit and the number
158
+ * of requests.
159
+ */
160
+ readonly legacyHeaders: boolean;
161
+ /**
162
+ * Whether to enable support for the rate limit standardization headers (`RateLimit-*`).
163
+ */
164
+ readonly standardHeaders: boolean;
165
+ /**
166
+ * The name of the property on the request object to store the rate limit info.
167
+ *
168
+ * Defaults to `rateLimit`.
169
+ */
170
+ readonly requestPropertyName: string;
171
+ /**
172
+ * If `true`, the library will (by default) skip all requests that have a 4XX
173
+ * or 5XX status.
174
+ */
175
+ readonly skipFailedRequests: boolean;
176
+ /**
177
+ * If `true`, the library will (by default) skip all requests that have a
178
+ * status code less than 400.
179
+ */
180
+ readonly skipSuccessfulRequests: boolean;
181
+ /**
182
+ * Method to determine whether or not the request counts as 'succesful'. Used
183
+ * when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
184
+ */
185
+ readonly requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
186
+ /**
187
+ * Method to generate custom identifiers for clients.
188
+ *
189
+ * By default, the client's IP address is used.
190
+ */
191
+ readonly keyGenerator: ValueDeterminingMiddleware<string>;
192
+ /**
193
+ * Method (in the form of middleware) to determine whether or not this request
194
+ * counts towards a client's quota.
195
+ */
196
+ readonly skip: ValueDeterminingMiddleware<boolean>;
197
+ /**
198
+ * Express request handler that sends back a response when a client is
199
+ * rate-limited.
200
+ */
201
+ readonly handler: RateLimitExceededEventHandler;
202
+ /**
203
+ * Express request handler that sends back a response when a client has
204
+ * reached their rate limit, and will be rate limited on their next request.
205
+ */
206
+ readonly onLimitReached: RateLimitReachedEventHandler;
207
+ /**
208
+ * The {@link Store} to use to store the hit count for each client.
209
+ */
210
+ store: Store;
211
+ /**
212
+ * Whether to send `X-RateLimit-*` headers with the rate limit and the number
213
+ * of requests.
214
+ *
215
+ * @deprecated 6.x - This option was renamed to `legacyHeaders`.
216
+ */
217
+ headers?: boolean;
218
+ /**
219
+ * Whether to send `RateLimit-*` headers with the rate limit and the number
220
+ * of requests.
221
+ *
222
+ * @deprecated 6.x - This option was renamed to `standardHeaders`.
223
+ */
224
+ draft_polli_ratelimit_headers?: boolean;
225
+ }
226
+ /**
227
+ * The extended request object that includes information about the client's
228
+ * rate limit.
229
+ */
230
+ export declare type AugmentedRequest = Express.Request & {
231
+ [key: string]: RateLimitInfo;
232
+ };
233
+ /**
234
+ * The rate limit related information for each client included in the
235
+ * Express request object.
236
+ */
237
+ export interface RateLimitInfo {
238
+ readonly limit: number;
239
+ readonly current: number;
240
+ readonly remaining: number;
241
+ readonly resetTime: Date | undefined;
242
+ }
243
+ declare const rateLimit: (passedOptions?: (Omit<Partial<Options>, "store"> & {
244
+ store?: LegacyStore | Store | undefined;
245
+ }) | undefined) => RateLimitRequestHandler;
246
+ export default rateLimit;
247
+
248
+ export {};