express-rate-limit 5.4.1 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +97 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib.d.ts +15 -0
- package/dist/cjs/lib.js +366 -0
- package/dist/cjs/lib.js.map +1 -0
- package/dist/cjs/memory-store.d.ts +61 -0
- package/dist/cjs/memory-store.js +167 -0
- package/dist/cjs/memory-store.js.map +1 -0
- package/dist/cjs/package.json +12 -0
- package/dist/cjs/types.d.ts +239 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib.d.ts +15 -0
- package/dist/esm/lib.js +361 -0
- package/dist/esm/lib.js.map +1 -0
- package/dist/esm/memory-store.d.ts +61 -0
- package/dist/esm/memory-store.js +165 -0
- package/dist/esm/memory-store.js.map +1 -0
- package/dist/esm/package.json +13 -0
- package/dist/esm/types.d.ts +239 -0
- package/dist/esm/types.js +4 -0
- package/dist/esm/types.js.map +1 -0
- package/license.md +20 -0
- package/package.json +137 -51
- package/readme.md +492 -0
- package/tsconfig.json +18 -0
- package/LICENSE +0 -7
- package/README.md +0 -349
- package/lib/express-rate-limit.js +0 -190
- package/lib/memory-store.js +0 -47
package/changelog.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
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.0](https://github.com/nfriedly/express-rate-limit/releases/v6.0.0)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `express` 4.x as a peer dependency.
|
|
14
|
+
- Better Typescript support (the library was rewritten in Typescript).
|
|
15
|
+
- Export the package as both ESM and CJS.
|
|
16
|
+
- Publish the built package (`.tgz` file) on GitHub releases as well as the npm
|
|
17
|
+
registry.
|
|
18
|
+
- Issue and PR templates.
|
|
19
|
+
- A contributing guide.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Rename the `draft_polli_ratelimit_headers` option to `standardHeaders`.
|
|
24
|
+
- Rename the `headers` option to `legacyHeaders`.
|
|
25
|
+
- `Retry-After` header is now sent if either `legacyHeaders` or
|
|
26
|
+
`standardHeaders` is set.
|
|
27
|
+
- Allow `keyGenerator` to be an async function/return a promise.
|
|
28
|
+
- Change the way custom stores are defined.
|
|
29
|
+
- Add the `init` method for stores to set themselves up using options passed
|
|
30
|
+
to the middleware.
|
|
31
|
+
- Rename the `incr` method to `increment`.
|
|
32
|
+
- Allow the `increment`, `decrement`, `resetKey` and `resetAll` methods to
|
|
33
|
+
return a promise.
|
|
34
|
+
- Old stores will automatically be promisified and used.
|
|
35
|
+
- The package can now only be used with NodeJS version 12.9.0 or greater.
|
|
36
|
+
- The `onLimitReached` configuration option is now deprecated. Replace it with a
|
|
37
|
+
custom `handler` that checks the number of hits.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
- Remove the deprecated `limiter.resetIp` method (use the `limiter.resetKey`
|
|
42
|
+
method instead).
|
|
43
|
+
- Remove the deprecated options `delayMs`, `delayAfter` (the delay functionality
|
|
44
|
+
was moved to the
|
|
45
|
+
[`express-slow-down`](https://github.com/nfriedly/express-slow-down) package)
|
|
46
|
+
and `global` (use a key generator that returns a constant value).
|
|
47
|
+
|
|
48
|
+
## [5.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v5.5.1)
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- The middleware ~throws~ logs an error if `request.ip` is undefined.
|
|
53
|
+
|
|
54
|
+
### Removed
|
|
55
|
+
|
|
56
|
+
- Removes typescript typings. (See
|
|
57
|
+
[#138](https://github.com/nfriedly/express-rate-limit/issues/138))
|
|
58
|
+
|
|
59
|
+
## [4.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v4.0.4)
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- The library no longer modifies the passed-in options object, it instead makes
|
|
64
|
+
a clone of it.
|
|
65
|
+
|
|
66
|
+
## [3.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v3.5.2)
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- Simplifies the default `handler` function so that it no longer changes the
|
|
71
|
+
response format. The default handler also uses
|
|
72
|
+
[response.send](https://expressjs.com/en/4x/api.html#response.send).
|
|
73
|
+
|
|
74
|
+
### Changes
|
|
75
|
+
|
|
76
|
+
- `onLimitReached` now only triggers once for a client and window. However, the
|
|
77
|
+
`handle` method is called for every blocked request.
|
|
78
|
+
|
|
79
|
+
### Removed
|
|
80
|
+
|
|
81
|
+
- The `delayAfter` and `delayMs` options; they were moved to the
|
|
82
|
+
[express-slow-down](https://npmjs.org/package/express-slow-down) package.
|
|
83
|
+
|
|
84
|
+
## [2.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v2.14.2)
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
|
|
88
|
+
- A `limiter.resetKey()` method to reset the hit counter for a particular client
|
|
89
|
+
|
|
90
|
+
### Changes
|
|
91
|
+
|
|
92
|
+
- The rate limiter now uses a less precise but less resource intensive method of
|
|
93
|
+
tracking hits from a client.
|
|
94
|
+
|
|
95
|
+
### Removed
|
|
96
|
+
|
|
97
|
+
- The `global` option.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// /source/index.ts
|
|
3
|
+
// Export away!
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
7
|
+
}) : (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
o[k2] = m[k];
|
|
10
|
+
}));
|
|
11
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
12
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
13
|
+
};
|
|
14
|
+
exports.__esModule = true;
|
|
15
|
+
exports["default"] = void 0;
|
|
16
|
+
__exportStar(require("./types.js"), exports);
|
|
17
|
+
var lib_js_1 = require("./lib.js");
|
|
18
|
+
__createBinding(exports, lib_js_1, "default");
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../source/index.ts"],"names":[],"mappings":";AAAA,mBAAmB;AACnB,eAAe;;;;;;;;;;;;;AAEf,6CAA0B;AAC1B,mCAAkC;AAAzB,8CAAO","sourcesContent":["// /source/index.ts\n// Export away!\n\nexport * from './types.js'\nexport { default } from './lib.js'\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Options, RateLimitRequestHandler, LegacyStore, Store } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* Create an instance of IP rate-limiting middleware for Express.
|
|
5
|
+
*
|
|
6
|
+
* @param passedOptions {Options} - Options to configure the rate limiter
|
|
7
|
+
*
|
|
8
|
+
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
declare const rateLimit: (passedOptions?: (Omit<Partial<Options>, "store"> & {
|
|
13
|
+
store?: LegacyStore | Store | undefined;
|
|
14
|
+
}) | undefined) => RateLimitRequestHandler;
|
|
15
|
+
export default rateLimit;
|
package/dist/cjs/lib.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// /source/lib.ts
|
|
3
|
+
// The option parser and rate limiting middleware
|
|
4
|
+
var __assign = (this && this.__assign) || function () {
|
|
5
|
+
__assign = Object.assign || function(t) {
|
|
6
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
7
|
+
s = arguments[i];
|
|
8
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
9
|
+
t[p] = s[p];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
return __assign.apply(this, arguments);
|
|
14
|
+
};
|
|
15
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
16
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
17
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
18
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
19
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
20
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
21
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
25
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
26
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
27
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
28
|
+
function step(op) {
|
|
29
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
30
|
+
while (_) try {
|
|
31
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
32
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
33
|
+
switch (op[0]) {
|
|
34
|
+
case 0: case 1: t = op; break;
|
|
35
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
36
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
37
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
38
|
+
default:
|
|
39
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
40
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
41
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
42
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
43
|
+
if (t[2]) _.ops.pop();
|
|
44
|
+
_.trys.pop(); continue;
|
|
45
|
+
}
|
|
46
|
+
op = body.call(thisArg, _);
|
|
47
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
48
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
52
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
53
|
+
};
|
|
54
|
+
exports.__esModule = true;
|
|
55
|
+
var memory_store_js_1 = __importDefault(require("./memory-store.js"));
|
|
56
|
+
/**
|
|
57
|
+
* Type guard to check if a store is legacy store.
|
|
58
|
+
*
|
|
59
|
+
* @param store {LegacyStore | Store} - The store to check
|
|
60
|
+
*
|
|
61
|
+
* @return {boolean} - Whether the store is a legacy store
|
|
62
|
+
*/
|
|
63
|
+
var isLegacyStore = function (store) {
|
|
64
|
+
// Check that `incr` exists but `increment` does not - store authors might want
|
|
65
|
+
// to keep both around for backwards compatibility.
|
|
66
|
+
return typeof store.incr === 'function' &&
|
|
67
|
+
typeof store.increment !== 'function';
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Converts a legacy store to the promisified version.
|
|
71
|
+
*
|
|
72
|
+
* @param store {LegacyStore | Store} - The legacy store or even a modern store
|
|
73
|
+
*
|
|
74
|
+
* @returns {Store} - The promisified version of the store
|
|
75
|
+
*/
|
|
76
|
+
var promisifyStore = function (passedStore) {
|
|
77
|
+
if (!isLegacyStore(passedStore)) {
|
|
78
|
+
// It's not an old store, return as is
|
|
79
|
+
return passedStore;
|
|
80
|
+
}
|
|
81
|
+
// Why can't Typescript understand this?
|
|
82
|
+
var legacyStore = passedStore;
|
|
83
|
+
// A promisified version of the store
|
|
84
|
+
var PromisifiedStore = /** @class */ (function () {
|
|
85
|
+
function PromisifiedStore() {
|
|
86
|
+
}
|
|
87
|
+
PromisifiedStore.prototype.increment = function (key) {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
89
|
+
return __generator(this, function (_a) {
|
|
90
|
+
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
91
|
+
legacyStore.incr(key, function (error, totalHits, resetTime) {
|
|
92
|
+
if (error)
|
|
93
|
+
reject(error);
|
|
94
|
+
resolve({ totalHits: totalHits, resetTime: resetTime });
|
|
95
|
+
});
|
|
96
|
+
})];
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
PromisifiedStore.prototype.decrement = function (key) {
|
|
101
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
102
|
+
return __generator(this, function (_a) {
|
|
103
|
+
return [2 /*return*/, Promise.resolve(legacyStore.decrement(key))];
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
PromisifiedStore.prototype.resetKey = function (key) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
109
|
+
return __generator(this, function (_a) {
|
|
110
|
+
return [2 /*return*/, Promise.resolve(legacyStore.resetKey(key))];
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
PromisifiedStore.prototype.resetAll = function () {
|
|
115
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
116
|
+
return __generator(this, function (_a) {
|
|
117
|
+
if (typeof legacyStore.resetAll === 'function')
|
|
118
|
+
return [2 /*return*/, Promise.resolve(legacyStore.resetAll())];
|
|
119
|
+
return [2 /*return*/];
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
return PromisifiedStore;
|
|
124
|
+
}());
|
|
125
|
+
return new PromisifiedStore();
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Adds the defaults for options the user has not specified.
|
|
129
|
+
*
|
|
130
|
+
* @param options {Options} - The options the user specifies
|
|
131
|
+
*
|
|
132
|
+
* @returns {Options} - A complete configuration object
|
|
133
|
+
*/
|
|
134
|
+
var parseOptions = function (passedOptions) {
|
|
135
|
+
// Now add the defaults for the other options
|
|
136
|
+
var _a, _b;
|
|
137
|
+
var options = __assign({ windowMs: 60 * 1000, store: new memory_store_js_1["default"](), max: 5, message: 'Too many requests, please try again later.', statusCode: 429, legacyHeaders: (_a = passedOptions.headers) !== null && _a !== void 0 ? _a : true, standardHeaders: (_b = passedOptions.draft_polli_ratelimit_headers) !== null && _b !== void 0 ? _b : false, requestPropertyName: 'rateLimit', skipFailedRequests: false, skipSuccessfulRequests: false, requestWasSuccessful: function (_request, response) { return response.statusCode < 400; }, skip: function (_request, _response) {
|
|
138
|
+
return false;
|
|
139
|
+
}, keyGenerator: function (request, _response) {
|
|
140
|
+
if (!request.ip) {
|
|
141
|
+
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.');
|
|
142
|
+
}
|
|
143
|
+
return request.ip;
|
|
144
|
+
}, handler: function (_request, response, _next, _optionsUsed) {
|
|
145
|
+
response.status(options.statusCode).send(options.message);
|
|
146
|
+
}, onLimitReached: function (_request, _response, _optionsUsed) { } }, passedOptions);
|
|
147
|
+
// Ensure that the store passed implements the either the `Store` or `LegacyStore`
|
|
148
|
+
// interface
|
|
149
|
+
if ((typeof options.store.incr !== 'function' &&
|
|
150
|
+
typeof options.store.increment !== 'function') ||
|
|
151
|
+
typeof options.store.decrement !== 'function' ||
|
|
152
|
+
typeof options.store.resetKey !== 'function' ||
|
|
153
|
+
(typeof options.store.resetAll !== 'undefined' &&
|
|
154
|
+
typeof options.store.resetAll !== 'function') ||
|
|
155
|
+
(typeof options.store.init !== 'undefined' &&
|
|
156
|
+
typeof options.store.init !== 'function')) {
|
|
157
|
+
throw new TypeError('An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.');
|
|
158
|
+
}
|
|
159
|
+
// Promisify the store, if it is not already
|
|
160
|
+
options.store = promisifyStore(options.store);
|
|
161
|
+
// Return the 'clean' options
|
|
162
|
+
return options;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Just pass on any errors for the developer to handle, usually as a HTTP 500
|
|
166
|
+
* Internal Server Error.
|
|
167
|
+
*
|
|
168
|
+
* @param fn {Express.RequestHandler} - The request handler for which to handle errors
|
|
169
|
+
*
|
|
170
|
+
* @returns {Express.RequestHandler} - The request handler wrapped with a `.catch` clause
|
|
171
|
+
*
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
var handleAsyncErrors = function (fn) {
|
|
175
|
+
return function (request, response, next) { return __awaiter(void 0, void 0, void 0, function () {
|
|
176
|
+
var error_1;
|
|
177
|
+
return __generator(this, function (_a) {
|
|
178
|
+
switch (_a.label) {
|
|
179
|
+
case 0:
|
|
180
|
+
_a.trys.push([0, 2, , 3]);
|
|
181
|
+
return [4 /*yield*/, Promise.resolve(fn(request, response, next))["catch"](next)];
|
|
182
|
+
case 1:
|
|
183
|
+
_a.sent();
|
|
184
|
+
return [3 /*break*/, 3];
|
|
185
|
+
case 2:
|
|
186
|
+
error_1 = _a.sent();
|
|
187
|
+
next(error_1);
|
|
188
|
+
return [3 /*break*/, 3];
|
|
189
|
+
case 3: return [2 /*return*/];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}); };
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
*
|
|
196
|
+
* Create an instance of IP rate-limiting middleware for Express.
|
|
197
|
+
*
|
|
198
|
+
* @param passedOptions {Options} - Options to configure the rate limiter
|
|
199
|
+
*
|
|
200
|
+
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration
|
|
201
|
+
*
|
|
202
|
+
* @public
|
|
203
|
+
*/
|
|
204
|
+
var rateLimit = function (passedOptions) {
|
|
205
|
+
// Parse the options and add the default values for unspecified options
|
|
206
|
+
var options = parseOptions(passedOptions !== null && passedOptions !== void 0 ? passedOptions : {});
|
|
207
|
+
// Call the `init` method on the store, if it exists
|
|
208
|
+
if (typeof options.store.init === 'function')
|
|
209
|
+
options.store.init(options);
|
|
210
|
+
// Then return the actual middleware
|
|
211
|
+
var middleware = handleAsyncErrors(function (request, response, next) { return __awaiter(void 0, void 0, void 0, function () {
|
|
212
|
+
var skip, augmentedRequest, key, _a, totalHits, resetTime, retrieveQuota, maxHits, deltaSeconds, decremented_1, decrementKey_1;
|
|
213
|
+
return __generator(this, function (_b) {
|
|
214
|
+
switch (_b.label) {
|
|
215
|
+
case 0: return [4 /*yield*/, options.skip(request, response)];
|
|
216
|
+
case 1:
|
|
217
|
+
skip = _b.sent();
|
|
218
|
+
if (skip) {
|
|
219
|
+
next();
|
|
220
|
+
return [2 /*return*/];
|
|
221
|
+
}
|
|
222
|
+
augmentedRequest = request;
|
|
223
|
+
return [4 /*yield*/, options.keyGenerator(request, response)
|
|
224
|
+
// Increment the client's hit counter by one
|
|
225
|
+
];
|
|
226
|
+
case 2:
|
|
227
|
+
key = _b.sent();
|
|
228
|
+
return [4 /*yield*/, options.store.increment(key)
|
|
229
|
+
// Get the quota (max number of hits) for each client
|
|
230
|
+
];
|
|
231
|
+
case 3:
|
|
232
|
+
_a = _b.sent(), totalHits = _a.totalHits, resetTime = _a.resetTime;
|
|
233
|
+
retrieveQuota = typeof options.max === 'function'
|
|
234
|
+
? options.max(request, response)
|
|
235
|
+
: options.max;
|
|
236
|
+
return [4 /*yield*/, retrieveQuota
|
|
237
|
+
// Set the rate limit information on the augmented request object
|
|
238
|
+
];
|
|
239
|
+
case 4:
|
|
240
|
+
maxHits = _b.sent();
|
|
241
|
+
// Set the rate limit information on the augmented request object
|
|
242
|
+
augmentedRequest[options.requestPropertyName] = {
|
|
243
|
+
limit: maxHits,
|
|
244
|
+
current: totalHits,
|
|
245
|
+
remaining: Math.max(maxHits - totalHits, 0),
|
|
246
|
+
resetTime: resetTime
|
|
247
|
+
};
|
|
248
|
+
// Set the X-RateLimit headers on the response object if enabled
|
|
249
|
+
if (options.legacyHeaders && !response.headersSent) {
|
|
250
|
+
response.setHeader('X-RateLimit-Limit', maxHits);
|
|
251
|
+
response.setHeader('X-RateLimit-Remaining', augmentedRequest[options.requestPropertyName].remaining);
|
|
252
|
+
// If we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
|
|
253
|
+
if (resetTime instanceof Date) {
|
|
254
|
+
response.setHeader('Date', new Date().toUTCString());
|
|
255
|
+
response.setHeader('X-RateLimit-Reset', Math.ceil(resetTime.getTime() / 1000));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Set the standardized RateLimit headers on the response object
|
|
259
|
+
// if enabled
|
|
260
|
+
if (options.standardHeaders && !response.headersSent) {
|
|
261
|
+
response.setHeader('RateLimit-Limit', maxHits);
|
|
262
|
+
response.setHeader('RateLimit-Remaining', augmentedRequest[options.requestPropertyName].remaining);
|
|
263
|
+
if (resetTime) {
|
|
264
|
+
deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1000);
|
|
265
|
+
response.setHeader('RateLimit-Reset', Math.max(0, deltaSeconds));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// If we are to skip failed/successfull requests, decrement the
|
|
269
|
+
// counter accordingly once we know the status code of the request
|
|
270
|
+
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
271
|
+
decremented_1 = false;
|
|
272
|
+
decrementKey_1 = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
273
|
+
return __generator(this, function (_a) {
|
|
274
|
+
switch (_a.label) {
|
|
275
|
+
case 0:
|
|
276
|
+
if (!!decremented_1) return [3 /*break*/, 2];
|
|
277
|
+
return [4 /*yield*/, options.store.decrement(key)];
|
|
278
|
+
case 1:
|
|
279
|
+
_a.sent();
|
|
280
|
+
decremented_1 = true;
|
|
281
|
+
_a.label = 2;
|
|
282
|
+
case 2: return [2 /*return*/];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}); };
|
|
286
|
+
if (options.skipFailedRequests) {
|
|
287
|
+
response.on('finish', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
288
|
+
return __generator(this, function (_a) {
|
|
289
|
+
switch (_a.label) {
|
|
290
|
+
case 0:
|
|
291
|
+
if (!!options.requestWasSuccessful(request, response)) return [3 /*break*/, 2];
|
|
292
|
+
return [4 /*yield*/, decrementKey_1()];
|
|
293
|
+
case 1:
|
|
294
|
+
_a.sent();
|
|
295
|
+
_a.label = 2;
|
|
296
|
+
case 2: return [2 /*return*/];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}); });
|
|
300
|
+
response.on('close', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
301
|
+
return __generator(this, function (_a) {
|
|
302
|
+
switch (_a.label) {
|
|
303
|
+
case 0:
|
|
304
|
+
if (!!response.writableEnded) return [3 /*break*/, 2];
|
|
305
|
+
return [4 /*yield*/, decrementKey_1()];
|
|
306
|
+
case 1:
|
|
307
|
+
_a.sent();
|
|
308
|
+
_a.label = 2;
|
|
309
|
+
case 2: return [2 /*return*/];
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}); });
|
|
313
|
+
response.on('error', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
314
|
+
return __generator(this, function (_a) {
|
|
315
|
+
switch (_a.label) {
|
|
316
|
+
case 0: return [4 /*yield*/, decrementKey_1()];
|
|
317
|
+
case 1:
|
|
318
|
+
_a.sent();
|
|
319
|
+
return [2 /*return*/];
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}); });
|
|
323
|
+
}
|
|
324
|
+
if (options.skipSuccessfulRequests) {
|
|
325
|
+
response.on('finish', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
326
|
+
return __generator(this, function (_a) {
|
|
327
|
+
switch (_a.label) {
|
|
328
|
+
case 0:
|
|
329
|
+
if (!options.requestWasSuccessful(request, response)) return [3 /*break*/, 2];
|
|
330
|
+
return [4 /*yield*/, decrementKey_1()];
|
|
331
|
+
case 1:
|
|
332
|
+
_a.sent();
|
|
333
|
+
_a.label = 2;
|
|
334
|
+
case 2: return [2 /*return*/];
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}); });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Call the {@link Options.onLimitReached} callback on
|
|
341
|
+
// the first request where client exceeds their rate limit.
|
|
342
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
343
|
+
options.onLimitReached(request, response, options);
|
|
344
|
+
}
|
|
345
|
+
// If the client has exceeded their rate limit, set the Retry-After
|
|
346
|
+
// header and call the {@link Options.handler} function
|
|
347
|
+
if (maxHits && totalHits > maxHits) {
|
|
348
|
+
if ((options.legacyHeaders || options.standardHeaders) &&
|
|
349
|
+
!response.headersSent) {
|
|
350
|
+
response.setHeader('Retry-After', Math.ceil(options.windowMs / 1000));
|
|
351
|
+
}
|
|
352
|
+
options.handler(request, response, next, options);
|
|
353
|
+
return [2 /*return*/];
|
|
354
|
+
}
|
|
355
|
+
next();
|
|
356
|
+
return [2 /*return*/];
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}); });
|
|
360
|
+
middleware.resetKey =
|
|
361
|
+
options.store.resetKey.bind(options.store);
|
|
362
|
+
return middleware;
|
|
363
|
+
};
|
|
364
|
+
// Export it to the world!
|
|
365
|
+
exports["default"] = rateLimit;
|
|
366
|
+
//# sourceMappingURL=lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../source/lib.ts"],"names":[],"mappings":"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","sourcesContent":["// /source/lib.ts\n// The option parser and rate limiting middleware\n\nimport Express from 'express'\n\nimport {\n\tOptions,\n\tAugmentedRequest,\n\tRateLimitRequestHandler,\n\tLegacyStore,\n\tStore,\n\tIncrementResponse,\n} from './types.js'\nimport MemoryStore from './memory-store.js'\n\n/**\n * Type guard to check if a store is legacy store.\n *\n * @param store {LegacyStore | Store} - The store to check\n *\n * @return {boolean} - Whether the store is a legacy store\n */\nconst isLegacyStore = (store: LegacyStore | Store): store is LegacyStore =>\n\t// Check that `incr` exists but `increment` does not - store authors might want\n\t// to keep both around for backwards compatibility.\n\ttypeof (store as any).incr === 'function' &&\n\ttypeof (store as any).increment !== 'function'\n\n/**\n * Converts a legacy store to the promisified version.\n *\n * @param store {LegacyStore | Store} - The legacy store or even a modern store\n *\n * @returns {Store} - The promisified version of the store\n */\nconst promisifyStore = (passedStore: LegacyStore | Store): Store => {\n\tif (!isLegacyStore(passedStore)) {\n\t\t// It's not an old store, return as is\n\t\treturn passedStore\n\t}\n\n\t// Why can't Typescript understand this?\n\tconst legacyStore = passedStore\n\n\t// A promisified version of the store\n\tclass PromisifiedStore implements Store {\n\t\tasync increment(key: string): Promise<IncrementResponse> {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tlegacyStore.incr(\n\t\t\t\t\tkey,\n\t\t\t\t\t(\n\t\t\t\t\t\terror: Error | undefined,\n\t\t\t\t\t\ttotalHits: number,\n\t\t\t\t\t\tresetTime: Date | undefined,\n\t\t\t\t\t) => {\n\t\t\t\t\t\tif (error) reject(error)\n\t\t\t\t\t\tresolve({ totalHits, resetTime })\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\n\t\tasync decrement(key: string): Promise<void> {\n\t\t\treturn Promise.resolve(legacyStore.decrement(key))\n\t\t}\n\n\t\tasync resetKey(key: string): Promise<void> {\n\t\t\treturn Promise.resolve(legacyStore.resetKey(key))\n\t\t}\n\n\t\tasync resetAll(): Promise<void> {\n\t\t\tif (typeof legacyStore.resetAll === 'function')\n\t\t\t\treturn Promise.resolve(legacyStore.resetAll())\n\t\t}\n\t}\n\n\treturn new PromisifiedStore()\n}\n\n/**\n * Adds the defaults for options the user has not specified.\n *\n * @param options {Options} - The options the user specifies\n *\n * @returns {Options} - A complete configuration object\n */\nconst parseOptions = (\n\tpassedOptions: Omit<Partial<Options>, 'store'> & {\n\t\tstore?: Store | LegacyStore\n\t},\n): Options => {\n\t// Now add the defaults for the other options\n\n\tconst options = {\n\t\twindowMs: 60 * 1000,\n\t\tstore: new MemoryStore(),\n\t\tmax: 5,\n\t\tmessage: 'Too many requests, please try again later.',\n\t\tstatusCode: 429,\n\t\tlegacyHeaders: passedOptions.headers ?? true,\n\t\tstandardHeaders: passedOptions.draft_polli_ratelimit_headers ?? false,\n\t\trequestPropertyName: 'rateLimit',\n\t\tskipFailedRequests: false,\n\t\tskipSuccessfulRequests: false,\n\t\trequestWasSuccessful: (\n\t\t\t_request: Express.Request,\n\t\t\tresponse: Express.Response,\n\t\t): boolean => response.statusCode < 400,\n\t\tskip: (_request: Express.Request, _response: Express.Response): boolean =>\n\t\t\tfalse,\n\t\tkeyGenerator: (\n\t\t\trequest: Express.Request,\n\t\t\t_response: Express.Response,\n\t\t): string => {\n\t\t\tif (!request.ip) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t'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.',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn request.ip\n\t\t},\n\t\thandler: (\n\t\t\t_request: Express.Request,\n\t\t\tresponse: Express.Response,\n\t\t\t_next: Express.NextFunction,\n\t\t\t_optionsUsed: Options,\n\t\t): void => {\n\t\t\tresponse.status(options.statusCode).send(options.message)\n\t\t},\n\t\tonLimitReached: (\n\t\t\t_request: Express.Request,\n\t\t\t_response: Express.Response,\n\t\t\t_optionsUsed: Options,\n\t\t): void => {},\n\t\t...passedOptions,\n\t}\n\n\t// Ensure that the store passed implements the either the `Store` or `LegacyStore`\n\t// interface\n\tif (\n\t\t(typeof (options.store as LegacyStore).incr !== 'function' &&\n\t\t\ttypeof (options.store as Store).increment !== 'function') ||\n\t\ttypeof options.store.decrement !== 'function' ||\n\t\ttypeof options.store.resetKey !== 'function' ||\n\t\t(typeof options.store.resetAll !== 'undefined' &&\n\t\t\ttypeof options.store.resetAll !== 'function') ||\n\t\t(typeof (options.store as Store).init !== 'undefined' &&\n\t\t\ttypeof (options.store as Store).init !== 'function')\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.',\n\t\t)\n\t}\n\n\t// Promisify the store, if it is not already\n\toptions.store = promisifyStore(options.store)\n\n\t// Return the 'clean' options\n\treturn options as Options\n}\n\n/**\n * Just pass on any errors for the developer to handle, usually as a HTTP 500\n * Internal Server Error.\n *\n * @param fn {Express.RequestHandler} - The request handler for which to handle errors\n *\n * @returns {Express.RequestHandler} - The request handler wrapped with a `.catch` clause\n *\n * @private\n */\nconst handleAsyncErrors =\n\t(fn: Express.RequestHandler): Express.RequestHandler =>\n\tasync (\n\t\trequest: Express.Request,\n\t\tresponse: Express.Response,\n\t\tnext: Express.NextFunction,\n\t) => {\n\t\ttry {\n\t\t\tawait Promise.resolve(fn(request, response, next)).catch(next)\n\t\t} catch (error: unknown) {\n\t\t\tnext(error)\n\t\t}\n\t}\n\n/**\n *\n * Create an instance of IP rate-limiting middleware for Express.\n *\n * @param passedOptions {Options} - Options to configure the rate limiter\n *\n * @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration\n *\n * @public\n */\nconst rateLimit = (\n\tpassedOptions?: Omit<Partial<Options>, 'store'> & {\n\t\tstore?: Store | LegacyStore\n\t},\n): RateLimitRequestHandler => {\n\t// Parse the options and add the default values for unspecified options\n\tconst options = parseOptions(passedOptions ?? {})\n\t// Call the `init` method on the store, if it exists\n\tif (typeof options.store.init === 'function') options.store.init(options)\n\n\t// Then return the actual middleware\n\tconst middleware = handleAsyncErrors(\n\t\tasync (\n\t\t\trequest: Express.Request,\n\t\t\tresponse: Express.Response,\n\t\t\tnext: Express.NextFunction,\n\t\t) => {\n\t\t\t// First check if we should skip the request\n\t\t\tconst skip = await options.skip(request, response)\n\t\t\tif (skip) {\n\t\t\t\tnext()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Create an augmented request\n\t\t\tconst augmentedRequest = request as AugmentedRequest\n\n\t\t\t// Get a unique key for the client\n\t\t\tconst key = await options.keyGenerator(request, response)\n\t\t\t// Increment the client's hit counter by one\n\t\t\tconst { totalHits, resetTime } = await options.store.increment(key)\n\n\t\t\t// Get the quota (max number of hits) for each client\n\t\t\tconst retrieveQuota =\n\t\t\t\ttypeof options.max === 'function'\n\t\t\t\t\t? options.max(request, response)\n\t\t\t\t\t: options.max\n\n\t\t\tconst maxHits = await retrieveQuota\n\t\t\t// Set the rate limit information on the augmented request object\n\t\t\taugmentedRequest[options.requestPropertyName] = {\n\t\t\t\tlimit: maxHits,\n\t\t\t\tcurrent: totalHits,\n\t\t\t\tremaining: Math.max(maxHits - totalHits, 0),\n\t\t\t\tresetTime,\n\t\t\t}\n\n\t\t\t// Set the X-RateLimit headers on the response object if enabled\n\t\t\tif (options.legacyHeaders && !response.headersSent) {\n\t\t\t\tresponse.setHeader('X-RateLimit-Limit', maxHits)\n\t\t\t\tresponse.setHeader(\n\t\t\t\t\t'X-RateLimit-Remaining',\n\t\t\t\t\taugmentedRequest[options.requestPropertyName].remaining,\n\t\t\t\t)\n\n\t\t\t\t// If we have a resetTime, also provide the current date to help avoid issues with incorrect clocks\n\t\t\t\tif (resetTime instanceof Date) {\n\t\t\t\t\tresponse.setHeader('Date', new Date().toUTCString())\n\t\t\t\t\tresponse.setHeader(\n\t\t\t\t\t\t'X-RateLimit-Reset',\n\t\t\t\t\t\tMath.ceil(resetTime.getTime() / 1000),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set the standardized RateLimit headers on the response object\n\t\t\t// if enabled\n\t\t\tif (options.standardHeaders && !response.headersSent) {\n\t\t\t\tresponse.setHeader('RateLimit-Limit', maxHits)\n\t\t\t\tresponse.setHeader(\n\t\t\t\t\t'RateLimit-Remaining',\n\t\t\t\t\taugmentedRequest[options.requestPropertyName].remaining,\n\t\t\t\t)\n\n\t\t\t\tif (resetTime) {\n\t\t\t\t\tconst deltaSeconds = Math.ceil(\n\t\t\t\t\t\t(resetTime.getTime() - Date.now()) / 1000,\n\t\t\t\t\t)\n\t\t\t\t\tresponse.setHeader('RateLimit-Reset', Math.max(0, deltaSeconds))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we are to skip failed/successfull requests, decrement the\n\t\t\t// counter accordingly once we know the status code of the request\n\t\t\tif (options.skipFailedRequests || options.skipSuccessfulRequests) {\n\t\t\t\tlet decremented = false\n\t\t\t\tconst decrementKey = async () => {\n\t\t\t\t\tif (!decremented) {\n\t\t\t\t\t\tawait options.store.decrement(key)\n\t\t\t\t\t\tdecremented = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.skipFailedRequests) {\n\t\t\t\t\tresponse.on('finish', async () => {\n\t\t\t\t\t\tif (!options.requestWasSuccessful(request, response))\n\t\t\t\t\t\t\tawait decrementKey()\n\t\t\t\t\t})\n\t\t\t\t\tresponse.on('close', async () => {\n\t\t\t\t\t\tif (!response.writableEnded) await decrementKey()\n\t\t\t\t\t})\n\t\t\t\t\tresponse.on('error', async () => {\n\t\t\t\t\t\tawait decrementKey()\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (options.skipSuccessfulRequests) {\n\t\t\t\t\tresponse.on('finish', async () => {\n\t\t\t\t\t\tif (options.requestWasSuccessful(request, response))\n\t\t\t\t\t\t\tawait decrementKey()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Call the {@link Options.onLimitReached} callback on\n\t\t\t// the first request where client exceeds their rate limit.\n\t\t\tif (maxHits && totalHits === maxHits + 1) {\n\t\t\t\toptions.onLimitReached(request, response, options)\n\t\t\t}\n\n\t\t\t// If the client has exceeded their rate limit, set the Retry-After\n\t\t\t// header and call the {@link Options.handler} function\n\t\t\tif (maxHits && totalHits > maxHits) {\n\t\t\t\tif (\n\t\t\t\t\t(options.legacyHeaders || options.standardHeaders) &&\n\t\t\t\t\t!response.headersSent\n\t\t\t\t) {\n\t\t\t\t\tresponse.setHeader('Retry-After', Math.ceil(options.windowMs / 1000))\n\t\t\t\t}\n\n\t\t\t\toptions.handler(request, response, next, options)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnext()\n\t\t},\n\t)\n\n\t// Export the store's function to reset the hit counter for a particular\n\t// client based on their identifier\n\t;(middleware as RateLimitRequestHandler).resetKey =\n\t\toptions.store.resetKey.bind(options.store)\n\n\treturn middleware as RateLimitRequestHandler\n}\n\n// Export it to the world!\nexport default rateLimit\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Store, Options, IncrementResponse } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A {@link Store} that stores the hit count for each client in
|
|
4
|
+
* memory.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export default class MemoryStore implements Store {
|
|
9
|
+
/**
|
|
10
|
+
* The duration of time before which all hit counts are reset (in milliseconds).
|
|
11
|
+
*/
|
|
12
|
+
windowMs: number;
|
|
13
|
+
/**
|
|
14
|
+
* The map that stores the number of hits for each client in memory.
|
|
15
|
+
*/
|
|
16
|
+
hits: {
|
|
17
|
+
[key: string]: number | undefined;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The time at which all hit counts will be reset.
|
|
21
|
+
*/
|
|
22
|
+
resetTime: Date;
|
|
23
|
+
/**
|
|
24
|
+
* Method that initializes the store.
|
|
25
|
+
*
|
|
26
|
+
* @param options {Options} - The options used to setup the middleware
|
|
27
|
+
*/
|
|
28
|
+
init(options: Options): void;
|
|
29
|
+
/**
|
|
30
|
+
* Method to increment a client's hit counter.
|
|
31
|
+
*
|
|
32
|
+
* @param key {string} - The identifier for a client
|
|
33
|
+
*
|
|
34
|
+
* @returns {IncrementResponse} - The number of hits and reset time for that client
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
increment(key: string): Promise<IncrementResponse>;
|
|
39
|
+
/**
|
|
40
|
+
* Method to decrement a client's hit counter.
|
|
41
|
+
*
|
|
42
|
+
* @param key {string} - The identifier for a client
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
decrement(key: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Method to reset a client's hit counter.
|
|
49
|
+
*
|
|
50
|
+
* @param key {string} - The identifier for a client
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
resetKey(key: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Method to reset everyone's hit counter.
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
resetAll(): Promise<void>;
|
|
61
|
+
}
|