express-rate-limit 6.0.1 → 6.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -6,6 +6,62 @@ 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.0.5](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.5)
10
+
11
+ ### Fixed
12
+
13
+ - Use named imports for ExpressJS types so users do not need to enable the
14
+ `esModuleInterop` flag in their Typescript compiler configuration.
15
+
16
+ ## [6.0.4](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.4)
17
+
18
+ ### Fixed
19
+
20
+ - Upload the built package as a `.tgz` to GitHub releases.
21
+
22
+ ### Changed
23
+
24
+ - Add ` main` and `module` fields to `package.json`. This helps tools such as
25
+ ESLint that do not yet support the `exports` field.
26
+ - Bumped the minimum node.js version in `package-lock.json` to match
27
+ `package.json`
28
+
29
+ ## [6.0.3](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.3)
30
+
31
+ ### Changed
32
+
33
+ - Bumped minimum Node version from 12.9 to 14.5 in `package.json` because the
34
+ transpiled output uses the nullish coalescing operator (`??`), which
35
+ [isn't supported in node.js prior to 14.x](https://node.green/#ES2020-features--nullish-coalescing-operator-----).
36
+
37
+ ## [6.0.2](https://github.com/nfriedly/express-rate-limit/releases/v6.0.2)
38
+
39
+ ### Fixed
40
+
41
+ - Ensure CommonJS projects can import the module.
42
+
43
+ ### Added
44
+
45
+ - Add additional tests that test:
46
+ - importing the library in `js-cjs`, `js-esm`, `ts-cjs`, `ts-esm`
47
+ environments.
48
+ - usage of the library with external stores (`redis`, `mongo`, `memcached`,
49
+ `precise`).
50
+
51
+ ### Changed
52
+
53
+ - Use [`esbuild`](https://esbuild.github.io/) to generate ESM and CJS output.
54
+ This reduces the size of the built package from 138 kb to 13kb and build time
55
+ to 4 ms! :rocket:
56
+ - Use [`dts-bundle-generator`](https://github.com/timocov/dts-bundle-generator)
57
+ to generate a single Typescript declaration file.
58
+
59
+ ## [6.0.1](https://github.com/nfriedly/express-rate-limit/releases/v6.0.1)
60
+
61
+ ### Fixed
62
+
63
+ - Ensure CommonJS projects can import the module.
64
+
9
65
  ## [6.0.0](https://github.com/nfriedly/express-rate-limit/releases/v6.0.0)
10
66
 
11
67
  ### Added
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.3.0
2
+
3
+ import { NextFunction, Request, RequestHandler, Response } 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 {Request} - The Express request object
18
+ * @param response {Response} - The Express response object
19
+ *
20
+ * @returns {T} - The value needed
21
+ */
22
+ export declare type ValueDeterminingMiddleware<T> = (request: Request, response: 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 {Request} - The Express request object
28
+ * @param response {Response} - The Express response object
29
+ * @param next {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: Request, response: Response, next: 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 {Request} - The Express request object
39
+ * @param response {Response} - The Express response object
40
+ * @param optionsUsed {Options} - The options used to set up the middleware
41
+ */
42
+ export declare type RateLimitReachedEventHandler = (request: Request, response: 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 = 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 = 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 {};