express-rate-limit 6.0.0 → 6.0.4
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 +40 -2
- package/dist/index.cjs +226 -0
- package/dist/index.d.ts +248 -0
- package/dist/index.mjs +198 -0
- package/package.json +27 -37
- package/readme.md +89 -28
- package/tsconfig.json +1 -4
- package/dist/cjs/index.d.ts +0 -2
- package/dist/cjs/index.js +0 -19
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/lib.d.ts +0 -15
- package/dist/cjs/lib.js +0 -366
- package/dist/cjs/lib.js.map +0 -1
- package/dist/cjs/memory-store.d.ts +0 -61
- package/dist/cjs/memory-store.js +0 -167
- package/dist/cjs/memory-store.js.map +0 -1
- package/dist/cjs/types.d.ts +0 -239
- package/dist/cjs/types.js +0 -5
- package/dist/cjs/types.js.map +0 -1
- package/dist/esm/index.d.ts +0 -2
- package/dist/esm/index.js +0 -5
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/lib.d.ts +0 -15
- package/dist/esm/lib.js +0 -361
- package/dist/esm/lib.js.map +0 -1
- package/dist/esm/memory-store.d.ts +0 -61
- package/dist/esm/memory-store.js +0 -165
- package/dist/esm/memory-store.js.map +0 -1
- package/dist/esm/types.d.ts +0 -239
- package/dist/esm/types.js +0 -4
- package/dist/esm/types.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// source/memory-store.ts
|
|
2
|
+
var calculateNextResetTime = (windowMs) => {
|
|
3
|
+
const resetTime = new Date();
|
|
4
|
+
resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
|
|
5
|
+
return resetTime;
|
|
6
|
+
};
|
|
7
|
+
var MemoryStore = class {
|
|
8
|
+
init(options) {
|
|
9
|
+
this.windowMs = options.windowMs;
|
|
10
|
+
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
11
|
+
this.hits = {};
|
|
12
|
+
const interval = setInterval(async () => {
|
|
13
|
+
await this.resetAll();
|
|
14
|
+
}, this.windowMs);
|
|
15
|
+
if (interval.unref) {
|
|
16
|
+
interval.unref();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async increment(key) {
|
|
20
|
+
const totalHits = (this.hits[key] ?? 0) + 1;
|
|
21
|
+
this.hits[key] = totalHits;
|
|
22
|
+
return {
|
|
23
|
+
totalHits,
|
|
24
|
+
resetTime: this.resetTime
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async decrement(key) {
|
|
28
|
+
const current = this.hits[key];
|
|
29
|
+
if (current) {
|
|
30
|
+
this.hits[key] = current - 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async resetKey(key) {
|
|
34
|
+
delete this.hits[key];
|
|
35
|
+
}
|
|
36
|
+
async resetAll() {
|
|
37
|
+
this.hits = {};
|
|
38
|
+
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// source/lib.ts
|
|
43
|
+
var isLegacyStore = (store) => typeof store.incr === "function" && typeof store.increment !== "function";
|
|
44
|
+
var promisifyStore = (passedStore) => {
|
|
45
|
+
if (!isLegacyStore(passedStore)) {
|
|
46
|
+
return passedStore;
|
|
47
|
+
}
|
|
48
|
+
const legacyStore = passedStore;
|
|
49
|
+
class PromisifiedStore {
|
|
50
|
+
async increment(key) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
legacyStore.incr(key, (error, totalHits, resetTime) => {
|
|
53
|
+
if (error)
|
|
54
|
+
reject(error);
|
|
55
|
+
resolve({ totalHits, resetTime });
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async decrement(key) {
|
|
60
|
+
return Promise.resolve(legacyStore.decrement(key));
|
|
61
|
+
}
|
|
62
|
+
async resetKey(key) {
|
|
63
|
+
return Promise.resolve(legacyStore.resetKey(key));
|
|
64
|
+
}
|
|
65
|
+
async resetAll() {
|
|
66
|
+
if (typeof legacyStore.resetAll === "function")
|
|
67
|
+
return Promise.resolve(legacyStore.resetAll());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return new PromisifiedStore();
|
|
71
|
+
};
|
|
72
|
+
var parseOptions = (passedOptions) => {
|
|
73
|
+
const options = {
|
|
74
|
+
windowMs: 60 * 1e3,
|
|
75
|
+
store: new MemoryStore(),
|
|
76
|
+
max: 5,
|
|
77
|
+
message: "Too many requests, please try again later.",
|
|
78
|
+
statusCode: 429,
|
|
79
|
+
legacyHeaders: passedOptions.headers ?? true,
|
|
80
|
+
standardHeaders: passedOptions.draft_polli_ratelimit_headers ?? false,
|
|
81
|
+
requestPropertyName: "rateLimit",
|
|
82
|
+
skipFailedRequests: false,
|
|
83
|
+
skipSuccessfulRequests: false,
|
|
84
|
+
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
|
85
|
+
skip: (_request, _response) => false,
|
|
86
|
+
keyGenerator: (request, _response) => {
|
|
87
|
+
if (!request.ip) {
|
|
88
|
+
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.");
|
|
89
|
+
}
|
|
90
|
+
return request.ip;
|
|
91
|
+
},
|
|
92
|
+
handler: (_request, response, _next, _optionsUsed) => {
|
|
93
|
+
response.status(options.statusCode).send(options.message);
|
|
94
|
+
},
|
|
95
|
+
onLimitReached: (_request, _response, _optionsUsed) => {
|
|
96
|
+
},
|
|
97
|
+
...passedOptions
|
|
98
|
+
};
|
|
99
|
+
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") {
|
|
100
|
+
throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
|
|
101
|
+
}
|
|
102
|
+
options.store = promisifyStore(options.store);
|
|
103
|
+
return options;
|
|
104
|
+
};
|
|
105
|
+
var handleAsyncErrors = (fn) => async (request, response, next) => {
|
|
106
|
+
try {
|
|
107
|
+
await Promise.resolve(fn(request, response, next)).catch(next);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
next(error);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var rateLimit = (passedOptions) => {
|
|
113
|
+
const options = parseOptions(passedOptions ?? {});
|
|
114
|
+
if (typeof options.store.init === "function")
|
|
115
|
+
options.store.init(options);
|
|
116
|
+
const middleware = handleAsyncErrors(async (request, response, next) => {
|
|
117
|
+
const skip = await options.skip(request, response);
|
|
118
|
+
if (skip) {
|
|
119
|
+
next();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const augmentedRequest = request;
|
|
123
|
+
const key = await options.keyGenerator(request, response);
|
|
124
|
+
const { totalHits, resetTime } = await options.store.increment(key);
|
|
125
|
+
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
126
|
+
const maxHits = await retrieveQuota;
|
|
127
|
+
augmentedRequest[options.requestPropertyName] = {
|
|
128
|
+
limit: maxHits,
|
|
129
|
+
current: totalHits,
|
|
130
|
+
remaining: Math.max(maxHits - totalHits, 0),
|
|
131
|
+
resetTime
|
|
132
|
+
};
|
|
133
|
+
if (options.legacyHeaders && !response.headersSent) {
|
|
134
|
+
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
135
|
+
response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
136
|
+
if (resetTime instanceof Date) {
|
|
137
|
+
response.setHeader("Date", new Date().toUTCString());
|
|
138
|
+
response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (options.standardHeaders && !response.headersSent) {
|
|
142
|
+
response.setHeader("RateLimit-Limit", maxHits);
|
|
143
|
+
response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
144
|
+
if (resetTime) {
|
|
145
|
+
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
|
146
|
+
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
150
|
+
let decremented = false;
|
|
151
|
+
const decrementKey = async () => {
|
|
152
|
+
if (!decremented) {
|
|
153
|
+
await options.store.decrement(key);
|
|
154
|
+
decremented = true;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
if (options.skipFailedRequests) {
|
|
158
|
+
response.on("finish", async () => {
|
|
159
|
+
if (!options.requestWasSuccessful(request, response))
|
|
160
|
+
await decrementKey();
|
|
161
|
+
});
|
|
162
|
+
response.on("close", async () => {
|
|
163
|
+
if (!response.writableEnded)
|
|
164
|
+
await decrementKey();
|
|
165
|
+
});
|
|
166
|
+
response.on("error", async () => {
|
|
167
|
+
await decrementKey();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (options.skipSuccessfulRequests) {
|
|
171
|
+
response.on("finish", async () => {
|
|
172
|
+
if (options.requestWasSuccessful(request, response))
|
|
173
|
+
await decrementKey();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (maxHits && totalHits === maxHits + 1) {
|
|
178
|
+
options.onLimitReached(request, response, options);
|
|
179
|
+
}
|
|
180
|
+
if (maxHits && totalHits > maxHits) {
|
|
181
|
+
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
182
|
+
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
183
|
+
}
|
|
184
|
+
options.handler(request, response, next, options);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
next();
|
|
188
|
+
});
|
|
189
|
+
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
190
|
+
return middleware;
|
|
191
|
+
};
|
|
192
|
+
var lib_default = rateLimit;
|
|
193
|
+
|
|
194
|
+
// source/index.ts
|
|
195
|
+
var source_default = lib_default;
|
|
196
|
+
export {
|
|
197
|
+
source_default as default
|
|
198
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-rate-limit",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.4",
|
|
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",
|
|
@@ -28,53 +28,43 @@
|
|
|
28
28
|
"attack"
|
|
29
29
|
],
|
|
30
30
|
"type": "module",
|
|
31
|
-
"module": "dist/esm/index.js",
|
|
32
|
-
"main": "dist/cjs/index.js",
|
|
33
31
|
"exports": {
|
|
34
32
|
".": {
|
|
35
|
-
"import": "./dist/
|
|
36
|
-
"require": "./dist/
|
|
37
|
-
},
|
|
38
|
-
"./memory-store": {
|
|
39
|
-
"import": "./dist/esm/memory-store.js",
|
|
40
|
-
"require": "./dist/cjs/memory-store.js"
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"typesVersions": {
|
|
44
|
-
"*": {
|
|
45
|
-
".": [
|
|
46
|
-
"./dist/esm/index.d.ts"
|
|
47
|
-
],
|
|
48
|
-
"./memory-store": [
|
|
49
|
-
"./dist/esm/memory-store.d.ts"
|
|
50
|
-
]
|
|
33
|
+
"import": "./dist/index.mjs",
|
|
34
|
+
"require": "./dist/index.cjs"
|
|
51
35
|
}
|
|
52
36
|
},
|
|
37
|
+
"main": "./dist/index.cjs",
|
|
38
|
+
"module": "./dist/index.mjs",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
53
40
|
"files": [
|
|
54
41
|
"dist/",
|
|
55
42
|
"tsconfig.json",
|
|
56
43
|
"package.json",
|
|
57
|
-
"package-lock.json",
|
|
58
44
|
"readme.md",
|
|
59
45
|
"license.md",
|
|
60
46
|
"changelog.md"
|
|
61
47
|
],
|
|
62
48
|
"engines": {
|
|
63
|
-
"node": ">=
|
|
49
|
+
"node": ">= 14.5.0"
|
|
64
50
|
},
|
|
65
51
|
"scripts": {
|
|
66
52
|
"clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
|
|
67
|
-
"build:cjs": "
|
|
68
|
-
"build:esm": "
|
|
69
|
-
"build": "
|
|
70
|
-
"compile": "run-s clean build",
|
|
71
|
-
"lint": "xo",
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
53
|
+
"build:cjs": "esbuild --bundle --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit;\" source/index.ts",
|
|
54
|
+
"build:esm": "esbuild --bundle --format=esm --outfile=dist/index.mjs source/index.ts",
|
|
55
|
+
"build:types": "dts-bundle-generator --out-file=dist/index.d.ts source/index.ts",
|
|
56
|
+
"compile": "run-s clean build:*",
|
|
57
|
+
"lint:code": "xo --ignore test/external/",
|
|
58
|
+
"lint:rest": "prettier --ignore-path .gitignore --ignore-unknown --check .",
|
|
59
|
+
"lint": "run-s lint:*",
|
|
60
|
+
"autofix:code": "xo --ignore test/external/ --fix",
|
|
61
|
+
"autofix:rest": "prettier --ignore-path .gitignore --ignore-unknown --write .",
|
|
62
|
+
"autofix": "run-s autofix:*",
|
|
63
|
+
"test:lib": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
|
|
64
|
+
"test:ext": "npm pack && cd test/external/ && bash run-all-tests",
|
|
65
|
+
"test": "run-s lint test:*",
|
|
76
66
|
"pre-commit": "lint-staged",
|
|
77
|
-
"prepare": "
|
|
67
|
+
"prepare": "run-s compile && husky install config/husky"
|
|
78
68
|
},
|
|
79
69
|
"peerDependencies": {
|
|
80
70
|
"express": "^4"
|
|
@@ -87,6 +77,8 @@
|
|
|
87
77
|
"@types/supertest": "^2.0.11",
|
|
88
78
|
"cross-env": "^7.0.3",
|
|
89
79
|
"del-cli": "^4.0.1",
|
|
80
|
+
"dts-bundle-generator": "^6.2.0",
|
|
81
|
+
"esbuild": "^0.14.8",
|
|
90
82
|
"express": "^4.17.1",
|
|
91
83
|
"husky": "^7.0.4",
|
|
92
84
|
"jest": "^27.4.3",
|
|
@@ -107,9 +99,7 @@
|
|
|
107
99
|
"@typescript-eslint/consistent-indexed-object-style": [
|
|
108
100
|
"error",
|
|
109
101
|
"index-signature"
|
|
110
|
-
]
|
|
111
|
-
"import/no-named-as-default-member": 0,
|
|
112
|
-
"import/no-cycle": 0
|
|
102
|
+
]
|
|
113
103
|
}
|
|
114
104
|
},
|
|
115
105
|
"prettier": {
|
|
@@ -134,7 +124,7 @@
|
|
|
134
124
|
],
|
|
135
125
|
"testTimeout": 30000,
|
|
136
126
|
"testMatch": [
|
|
137
|
-
"**/test/**/*-test.[jt]s?(x)"
|
|
127
|
+
"**/test/library/**/*-test.[jt]s?(x)"
|
|
138
128
|
],
|
|
139
129
|
"moduleFileExtensions": [
|
|
140
130
|
"js",
|
|
@@ -148,7 +138,7 @@
|
|
|
148
138
|
}
|
|
149
139
|
},
|
|
150
140
|
"lint-staged": {
|
|
151
|
-
"{source,test}/**/*.ts": "xo --fix",
|
|
152
|
-
"**/*.{json,yaml,md}": "prettier --write"
|
|
141
|
+
"{source,test}/**/*.ts": "xo --ignore test/external/ --fix",
|
|
142
|
+
"**/*.{json,yaml,md}": "prettier --ignore-path .gitignore --ignore-unknown --write "
|
|
153
143
|
}
|
|
154
144
|
}
|
package/readme.md
CHANGED
|
@@ -12,11 +12,11 @@ public APIs and/or endpoints such as password reset. Plays nice with
|
|
|
12
12
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
-
### Alternate Rate
|
|
15
|
+
### Alternate Rate Limiters
|
|
16
16
|
|
|
17
|
-
> This module does not share state with other processes/servers by
|
|
18
|
-
>
|
|
19
|
-
>
|
|
17
|
+
> This module does not share state with other processes/servers by default. If
|
|
18
|
+
> you need a more robust solution, I recommend using an external store. See the
|
|
19
|
+
> [`stores` section](#store) below for a list of external stores.
|
|
20
20
|
|
|
21
21
|
This module was designed to only handle the basics and didn't even support
|
|
22
22
|
external stores initially. These other options all are excellent pieces of
|
|
@@ -26,7 +26,7 @@ software and may be more appropriate for some situations:
|
|
|
26
26
|
- [express-brute](https://www.npmjs.com/package/express-brute)
|
|
27
27
|
- [rate-limiter](https://www.npmjs.com/package/express-limiter)
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Installation
|
|
30
30
|
|
|
31
31
|
From the npm registry:
|
|
32
32
|
|
|
@@ -51,19 +51,64 @@ Replace `{version}` with the version of the package that you want to your, e.g.:
|
|
|
51
51
|
|
|
52
52
|
## Usage
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
### Importing
|
|
55
|
+
|
|
56
|
+
This library is provided in ESM as well as CJS forms, and works with both
|
|
57
|
+
Javascript and Typescript projects.
|
|
58
|
+
|
|
59
|
+
**This package requires you to use Node 14 or above.**
|
|
60
|
+
|
|
61
|
+
#### Javascript
|
|
62
|
+
|
|
63
|
+
Import it in a CommonJS project as follows:
|
|
56
64
|
|
|
57
65
|
```ts
|
|
58
66
|
const rateLimit = require('express-rate-limit')
|
|
59
67
|
```
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
Import it in a ESM project as follows:
|
|
62
70
|
|
|
63
71
|
```ts
|
|
64
72
|
import rateLimit from 'express-rate-limit'
|
|
65
73
|
```
|
|
66
74
|
|
|
75
|
+
#### Typescript
|
|
76
|
+
|
|
77
|
+
If you are using this library in a Typescript project that outputs CommonJS (no
|
|
78
|
+
`type: module` in `package.json` and `module: commonjs` in `tsconfig.json`), set
|
|
79
|
+
`esModuleInterop` to `true` in the `compilerOptions` of your `tsconfig.json` and
|
|
80
|
+
then import it as follows:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import rateLimit from 'express-rate-limit'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If you cannot set `esModuleInterop` to true, import it as follows instead:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const rateLimit = require('express-rate-limit')
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
And use the following to import any types if you need to:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { Store, IncrementResponse, ... } from 'express-rate-limit'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If you are using this library in a Typescript project that outputs ESM
|
|
99
|
+
(`type: module` in `package.json` and `module: esnext` in `tsconfig.json`),
|
|
100
|
+
import it as follows:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import rateLimit from 'express-rate-limit'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
And use the following to import any types if you need to:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import rateLimit, { Store, IncrementResponse, ... } from 'express-rate-limit'
|
|
110
|
+
```
|
|
111
|
+
|
|
67
112
|
### Examples
|
|
68
113
|
|
|
69
114
|
To use it in an API-only server where the rate-limiter should be applied to all
|
|
@@ -72,10 +117,6 @@ requests:
|
|
|
72
117
|
```ts
|
|
73
118
|
import rateLimit from 'express-rate-limit'
|
|
74
119
|
|
|
75
|
-
// Enable if you're behind a reverse proxy (Heroku, Bluemix, AWS ELB, Nginx, etc)
|
|
76
|
-
// see https://expressjs.com/en/guide/behind-proxies.html
|
|
77
|
-
// app.set('trust proxy', 1);
|
|
78
|
-
|
|
79
120
|
const limiter = rateLimit({
|
|
80
121
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
81
122
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
@@ -94,10 +135,6 @@ requests:
|
|
|
94
135
|
```ts
|
|
95
136
|
import rateLimit from 'express-rate-limit'
|
|
96
137
|
|
|
97
|
-
// Enable if you're behind a reverse proxy (Heroku, Bluemix, AWS ELB, Nginx, etc)
|
|
98
|
-
// see https://expressjs.com/en/guide/behind-proxies.html
|
|
99
|
-
// app.set('trust proxy', 1);
|
|
100
|
-
|
|
101
138
|
const apiLimiter = rateLimit({
|
|
102
139
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
103
140
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
@@ -114,10 +151,6 @@ To create multiple instances to apply different rules to different endpoints:
|
|
|
114
151
|
```ts
|
|
115
152
|
import rateLimit from 'express-rate-limit'
|
|
116
153
|
|
|
117
|
-
// Enable if you're behind a reverse proxy (Heroku, Bluemix, AWS ELB, Nginx, etc)
|
|
118
|
-
// see https://expressjs.com/en/guide/behind-proxies.html
|
|
119
|
-
// app.set('trust proxy', 1);
|
|
120
|
-
|
|
121
154
|
const apiLimiter = rateLimit({
|
|
122
155
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
123
156
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
@@ -147,10 +180,6 @@ To use a custom store:
|
|
|
147
180
|
import rateLimit from 'express-rate-limit'
|
|
148
181
|
import MemoryStore from 'express-rate-limit/memory-store.js'
|
|
149
182
|
|
|
150
|
-
// Enable if you're behind a reverse proxy (Heroku, Bluemix, AWS ELB, Nginx, etc)
|
|
151
|
-
// see https://expressjs.com/en/guide/behind-proxies.html
|
|
152
|
-
// app.set('trust proxy', 1);
|
|
153
|
-
|
|
154
183
|
const apiLimiter = rateLimit({
|
|
155
184
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
156
185
|
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
@@ -166,6 +195,38 @@ app.use('/api', apiLimiter)
|
|
|
166
195
|
> prefixes, when using multiple instances. The default built-in memory store is
|
|
167
196
|
> an exception to this rule.
|
|
168
197
|
|
|
198
|
+
### Troubleshooting Proxy Issues
|
|
199
|
+
|
|
200
|
+
If you are behind a proxy/load balancer (usually the case with most hosting
|
|
201
|
+
services, e.g. Heroku, Bluemix, AWS ELB, Nginx, Cloudflare, Akamai, Fastly,
|
|
202
|
+
Firebase Hosting, Rackspace LB, Riverbed Stingray, etc.), the IP address of the
|
|
203
|
+
request might be the IP of the load balancer/reverse proxy (making the rate
|
|
204
|
+
limiter effectively a global one and blocking all requests once the limit is
|
|
205
|
+
reached) or `undefined`. To solve this issue, add the following line to your
|
|
206
|
+
code (right after you create the express application):
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
app.set('trust proxy', numberOfProxies)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Where `numberOfProxies` is the number of proxies between the user and the
|
|
213
|
+
server. To find the correct number, create a test endpoint that returns the
|
|
214
|
+
client IP:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
app.set('trust proxy', 1)
|
|
218
|
+
app.get('/ip', (request, response) => response.send(request.ip))
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Go to `/ip` and see the IP address returned in the response. If it matches your
|
|
222
|
+
IP address (which you can get by going to http://ip.nfriedly.com/ or
|
|
223
|
+
https://api.ipify.org/), then the number of proxies is correct and the rate
|
|
224
|
+
limiter should now work correctly. If not, then keep increasing the number until
|
|
225
|
+
it does.
|
|
226
|
+
|
|
227
|
+
For more information about the `trust proxy` setting, take a look at the
|
|
228
|
+
[official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
|
|
229
|
+
|
|
169
230
|
## Request API
|
|
170
231
|
|
|
171
232
|
A `request.rateLimit` property is added to all requests with the `limit`,
|
|
@@ -275,9 +336,9 @@ const keyGenerator = (request /*, response*/) => request.ip
|
|
|
275
336
|
|
|
276
337
|
The function to handle requests once the max limit is exceeded. It receives the
|
|
277
338
|
`request` and the `response` objects. The `next` param is available if you need
|
|
278
|
-
to pass to the next middleware/route. Finally, the `
|
|
279
|
-
|
|
280
|
-
|
|
339
|
+
to pass to the next middleware/route. Finally, the `options` param has all of
|
|
340
|
+
the options that originally passed in when creating the current limiter and the
|
|
341
|
+
default values for other options.
|
|
281
342
|
|
|
282
343
|
The `request.rateLimit` object has `limit`, `current`, and `remaining` number of
|
|
283
344
|
requests and, if the store provides it, a `resetTime` Date object.
|
|
@@ -285,7 +346,7 @@ requests and, if the store provides it, a `resetTime` Date object.
|
|
|
285
346
|
Defaults to:
|
|
286
347
|
|
|
287
348
|
```ts
|
|
288
|
-
const handler = (request, response, next,
|
|
349
|
+
const handler = (request, response, next, options) => {
|
|
289
350
|
response.status(options.statusCode).send(options.message)
|
|
290
351
|
}
|
|
291
352
|
```
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"declaration": true,
|
|
4
|
-
"sourceMap": true,
|
|
5
4
|
|
|
6
5
|
"strict": true,
|
|
7
6
|
"noUnusedLocals": true,
|
|
@@ -9,9 +8,7 @@
|
|
|
9
8
|
"noFallthroughCasesInSwitch": true,
|
|
10
9
|
|
|
11
10
|
"moduleResolution": "node",
|
|
12
|
-
"esModuleInterop": true
|
|
13
|
-
|
|
14
|
-
"inlineSources": true
|
|
11
|
+
"esModuleInterop": true
|
|
15
12
|
},
|
|
16
13
|
"include": ["./source/**/*.ts"],
|
|
17
14
|
"exclude": ["./node_modules"]
|
package/dist/cjs/index.d.ts
DELETED
package/dist/cjs/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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
|
package/dist/cjs/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"]}
|
package/dist/cjs/lib.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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;
|