@universis/janitor 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/RateLimitService.js +14 -2
- package/dist/RateLimitService.js.map +1 -1
- package/dist/SpeedLimitService.js +28 -4
- package/dist/SpeedLimitService.js.map +1 -1
- package/package.json +1 -1
- package/public/schemas/v1/speed-limit-service.json +25 -2
- package/src/RateLimitService.js +14 -2
- package/src/SpeedLimitService.js +28 -4
package/README.md
CHANGED
|
@@ -111,3 +111,75 @@ and start configuring speed limited endpoints under `settings/universis/speedLim
|
|
|
111
111
|
|
|
112
112
|
Read more about speed limit configuration at [express-slow-down documentation](https://github.com/express-rate-limit/express-slow-down#configuration)
|
|
113
113
|
|
|
114
|
+
`SpeedLimitService` offers two additional options for delaying response: `randomDelayMs` and `randomMaxDelayMs`. These options are used for adding random delay to the response. They define the range of random delay in milliseconds.
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"settings": {
|
|
118
|
+
"universis": {
|
|
119
|
+
"speedLimit": {
|
|
120
|
+
"profiles": [
|
|
121
|
+
[
|
|
122
|
+
"userSpeedLimitProfile",
|
|
123
|
+
{
|
|
124
|
+
"windowMs": 120000,
|
|
125
|
+
"delayAfter": 5,
|
|
126
|
+
"randomDelayMs": [
|
|
127
|
+
500,
|
|
128
|
+
1000
|
|
129
|
+
],
|
|
130
|
+
"headers": false
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
],
|
|
134
|
+
"paths": [
|
|
135
|
+
[
|
|
136
|
+
"/users/me",
|
|
137
|
+
{
|
|
138
|
+
"profile": "userSpeedLimitProfile"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
If `randomDelayMs` is set, `delayMs` is ignored.
|
|
148
|
+
|
|
149
|
+
`randomMaxDelayMs` is used for setting the maximum random delay. If `randomMaxDelayMs` is not set, the maximum delay is set to `maxDelayMs` is ignored e.g.
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"settings": {
|
|
154
|
+
"universis": {
|
|
155
|
+
"speedLimit": {
|
|
156
|
+
"profiles": [
|
|
157
|
+
[
|
|
158
|
+
"userSpeedLimitProfile",
|
|
159
|
+
{
|
|
160
|
+
"windowMs": 120000,
|
|
161
|
+
"delayAfter": 5,
|
|
162
|
+
"delayMs": 500,
|
|
163
|
+
"randomMaxDelayMs": [
|
|
164
|
+
7000,
|
|
165
|
+
12000
|
|
166
|
+
],
|
|
167
|
+
"headers": false
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
],
|
|
171
|
+
"paths": [
|
|
172
|
+
[
|
|
173
|
+
"/users/me",
|
|
174
|
+
{
|
|
175
|
+
"profile": "userSpeedLimitProfile"
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
package/dist/RateLimitService.js
CHANGED
|
@@ -34,6 +34,11 @@ class RateLimitService extends _common.ApplicationService {
|
|
|
34
34
|
if (paths.size === 0) {
|
|
35
35
|
_common.TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
|
|
36
36
|
}
|
|
37
|
+
// get proxy address forwarding option
|
|
38
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
39
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
40
|
+
proxyAddressForwarding = false;
|
|
41
|
+
}
|
|
37
42
|
paths.forEach((value, path) => {
|
|
38
43
|
let profile;
|
|
39
44
|
// get profile
|
|
@@ -50,8 +55,15 @@ class RateLimitService extends _common.ApplicationService {
|
|
|
50
55
|
legacyHeaders: true // send headers
|
|
51
56
|
}, profile, {
|
|
52
57
|
keyGenerator: (req) => {
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
let remoteAddress;
|
|
59
|
+
if (proxyAddressForwarding) {
|
|
60
|
+
// get proxy headers or remote address
|
|
61
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
62
|
+
} else {
|
|
63
|
+
// get remote address
|
|
64
|
+
remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
|
|
65
|
+
}
|
|
66
|
+
return `${path}:${remoteAddress}`;
|
|
55
67
|
}
|
|
56
68
|
});
|
|
57
69
|
if (typeof rateLimitOptions.store === 'string') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimitService.js","names":["_common","require","_expressRateLimit","_express","_interopRequireDefault","_path","obj","__esModule","default","RateLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","forEach","value","profile","get","rateLimitOptions","Object","assign","windowMs","limit","legacyHeaders","keyGenerator","req","
|
|
1
|
+
{"version":3,"file":"RateLimitService.js","names":["_common","require","_expressRateLimit","_express","_interopRequireDefault","_path","obj","__esModule","default","RateLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","proxyAddressForwarding","forEach","value","profile","get","rateLimitOptions","Object","assign","windowMs","limit","legacyHeaders","keyGenerator","req","remoteAddress","headers","connection","socket","store","split","StoreClass","length","storeModule","prototype","hasOwnProperty","call","Error","use","rateLimit","stack","unshift","apply","err","error","exports"],"sources":["../src/RateLimitService.js"],"sourcesContent":["import { ApplicationService, TraceUtils } from '@themost/common';\nimport { rateLimit } from 'express-rate-limit';\nimport express from 'express';\nimport path from 'path';\n\nexport class RateLimitService extends ApplicationService {\n /**\n * @param {import('@themost/express').ExpressDataApplication} app \n */\n constructor(app) {\n super(app);\n app.serviceRouter.subscribe(serviceRouter => {\n if (serviceRouter == null) {\n return;\n }\n try {\n const addRouter = express.Router();\n let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/rateLimit') || {\n profiles: [],\n paths: []\n };\n if (serviceConfiguration.extends) {\n // get additional configuration\n const configurationPath = app.getConfiguration().getConfigurationPath();\n const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);\n TraceUtils.log(`@universis/janitor#RateLimitService will try to extend service configuration from ${extendsPath}`);\n serviceConfiguration = require(extendsPath);\n }\n const pathsArray = serviceConfiguration.paths || [];\n const profilesArray = serviceConfiguration.profiles || [];\n // create maps\n const paths = new Map(pathsArray);\n const profiles = new Map(profilesArray);\n if (paths.size === 0) {\n TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');\n }\n // get proxy address forwarding option\n let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');\n if (typeof proxyAddressForwarding !== 'boolean') {\n proxyAddressForwarding = false;\n }\n paths.forEach((value, path) => {\n let profile;\n // get profile\n if (value.profile) {\n profile = profiles.get(value.profile);\n } else {\n // or options defined inline\n profile = value\n }\n if (profile != null) {\n const rateLimitOptions = Object.assign({\n windowMs: 5 * 60 * 1000, // 5 minutes\n limit: 50, // 50 requests\n legacyHeaders: true // send headers\n }, profile, {\n keyGenerator: (req) => {\n let remoteAddress;\n if (proxyAddressForwarding) {\n // get proxy headers or remote address\n remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n } else {\n // get remote address\n remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n }\n return `${path}:${remoteAddress}`;\n }\n });\n if (typeof rateLimitOptions.store === 'string') {\n // load store\n const store = rateLimitOptions.store.split('#');\n let StoreClass;\n if (store.length === 2) {\n const storeModule = require(store[0]);\n if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {\n StoreClass = storeModule[store[1]];\n rateLimitOptions.store = new StoreClass(this, rateLimitOptions);\n } else {\n throw new Error(`${store} cannot be found or is inaccessible`);\n }\n } else {\n StoreClass = require(store[0]);\n // create store\n rateLimitOptions.store = new StoreClass(this, rateLimitOptions);\n }\n }\n addRouter.use(path, rateLimit(rateLimitOptions));\n }\n });\n if (addRouter.stack.length) {\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n }\n } catch (err) {\n TraceUtils.error('An error occurred while validating rate limit configuration.');\n TraceUtils.error(err);\n TraceUtils.warn('Rate limit service is inactive due to an error occured while loading configuration.')\n }\n });\n }\n\n}"],"mappings":"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"}
|
|
@@ -32,6 +32,11 @@ class SpeedLimitService extends _common.ApplicationService {
|
|
|
32
32
|
if (paths.size === 0) {
|
|
33
33
|
_common.TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
|
|
34
34
|
}
|
|
35
|
+
// get proxy address forwarding option
|
|
36
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
37
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
38
|
+
proxyAddressForwarding = false;
|
|
39
|
+
}
|
|
35
40
|
paths.forEach((value, path) => {
|
|
36
41
|
let profile;
|
|
37
42
|
// get profile
|
|
@@ -44,15 +49,34 @@ class SpeedLimitService extends _common.ApplicationService {
|
|
|
44
49
|
if (profile != null) {
|
|
45
50
|
const slowDownOptions = Object.assign({
|
|
46
51
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
47
|
-
delayAfter:
|
|
52
|
+
delayAfter: 20, // 20 requests
|
|
48
53
|
delayMs: 500, // 500 ms
|
|
49
|
-
maxDelayMs:
|
|
54
|
+
maxDelayMs: 10000 // 10 seconds
|
|
50
55
|
}, profile, {
|
|
51
56
|
keyGenerator: (req) => {
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
let remoteAddress;
|
|
58
|
+
if (proxyAddressForwarding) {
|
|
59
|
+
// get proxy headers or remote address
|
|
60
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
61
|
+
} else {
|
|
62
|
+
// get remote address
|
|
63
|
+
remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
|
|
64
|
+
}
|
|
65
|
+
return `${path}:${remoteAddress}`;
|
|
54
66
|
}
|
|
55
67
|
});
|
|
68
|
+
if (Array.isArray(slowDownOptions.randomDelayMs)) {
|
|
69
|
+
slowDownOptions.delayMs = () => {
|
|
70
|
+
const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
|
|
71
|
+
return delayMs;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
|
|
75
|
+
slowDownOptions.maxDelayMs = () => {
|
|
76
|
+
const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
|
|
77
|
+
return maxDelayMs;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
56
80
|
if (typeof slowDownOptions.store === 'string') {
|
|
57
81
|
// load store
|
|
58
82
|
const store = slowDownOptions.store.split('#');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpeedLimitService.js","names":["_common","require","_expressSlowDown","_interopRequireDefault","_express","_path","obj","__esModule","default","SpeedLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","forEach","value","profile","get","slowDownOptions","Object","assign","windowMs","delayAfter","delayMs","maxDelayMs","keyGenerator","req","ip","headers","connection","remoteAddress","socket","store","split","StoreClass","length","storeModule","prototype","hasOwnProperty","call","Error","use","slowDown","stack","unshift","apply","err","error","exports"],"sources":["../src/SpeedLimitService.js"],"sourcesContent":["import { ApplicationService, TraceUtils } from '@themost/common';\nimport slowDown from 'express-slow-down';\nimport express from 'express';\nimport path from 'path';\n\nexport class SpeedLimitService extends ApplicationService {\n constructor(app) {\n super(app);\n\n app.serviceRouter.subscribe(serviceRouter => {\n if (serviceRouter == null) {\n return;\n }\n try {\n const addRouter = express.Router();\n let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {\n profiles: [],\n paths: []\n };\n if (serviceConfiguration.extends) {\n // get additional configuration\n const configurationPath = app.getConfiguration().getConfigurationPath();\n const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);\n TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);\n serviceConfiguration = require(extendsPath);\n }\n const pathsArray = serviceConfiguration.paths || [];\n const profilesArray = serviceConfiguration.profiles || [];\n // create maps\n const paths = new Map(pathsArray);\n const profiles = new Map(profilesArray);\n if (paths.size === 0) {\n TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');\n }\n paths.forEach((value, path) => {\n let profile;\n // get profile\n if (value.profile) {\n profile = profiles.get(value.profile);\n } else {\n // or options defined inline\n profile = value\n }\n if (profile != null) {\n const slowDownOptions = Object.assign({\n windowMs: 5 * 60 * 1000, // 5 minutes\n delayAfter: 5, // 5 requests\n delayMs: 500, // 500 ms\n maxDelayMs: 20000 // 20 seconds\n }, profile, {\n keyGenerator: (req) => {\n const ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n return `${path}:${ip}`;\n }\n });\n if (typeof slowDownOptions.store === 'string') {\n // load store\n const store = slowDownOptions.store.split('#');\n let StoreClass;\n if (store.length === 2) {\n const storeModule = require(store[0]);\n if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {\n StoreClass = storeModule[store[1]];\n slowDownOptions.store = new StoreClass(this, slowDownOptions);\n } else {\n throw new Error(`${store} cannot be found or is inaccessible`);\n }\n } else {\n StoreClass = require(store[0]);\n // create store\n slowDownOptions.store = new StoreClass(this, slowDownOptions);\n }\n }\n addRouter.use(path, slowDown(slowDownOptions));\n }\n });\n if (addRouter.stack.length) {\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n }\n } catch (err) {\n TraceUtils.error('An error occurred while validating speed limit configuration.');\n TraceUtils.error(err);\n TraceUtils.warn('Speed limit service is inactive due to an error occured while loading configuration.')\n }\n });\n }\n\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SpeedLimitService.js","names":["_common","require","_expressSlowDown","_interopRequireDefault","_express","_path","obj","__esModule","default","SpeedLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","proxyAddressForwarding","forEach","value","profile","get","slowDownOptions","Object","assign","windowMs","delayAfter","delayMs","maxDelayMs","keyGenerator","req","remoteAddress","headers","connection","socket","Array","isArray","randomDelayMs","Math","floor","random","randomMaxDelayMs","store","split","StoreClass","length","storeModule","prototype","hasOwnProperty","call","Error","use","slowDown","stack","unshift","apply","err","error","exports"],"sources":["../src/SpeedLimitService.js"],"sourcesContent":["import { ApplicationService, TraceUtils } from '@themost/common';\nimport slowDown from 'express-slow-down';\nimport express from 'express';\nimport path from 'path';\n\nexport class SpeedLimitService extends ApplicationService {\n constructor(app) {\n super(app);\n\n app.serviceRouter.subscribe(serviceRouter => {\n if (serviceRouter == null) {\n return;\n }\n try {\n const addRouter = express.Router();\n let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {\n profiles: [],\n paths: []\n };\n if (serviceConfiguration.extends) {\n // get additional configuration\n const configurationPath = app.getConfiguration().getConfigurationPath();\n const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);\n TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);\n serviceConfiguration = require(extendsPath);\n }\n const pathsArray = serviceConfiguration.paths || [];\n const profilesArray = serviceConfiguration.profiles || [];\n // create maps\n const paths = new Map(pathsArray);\n const profiles = new Map(profilesArray);\n if (paths.size === 0) {\n TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');\n }\n // get proxy address forwarding option\n let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');\n if (typeof proxyAddressForwarding !== 'boolean') {\n proxyAddressForwarding = false;\n }\n paths.forEach((value, path) => {\n let profile;\n // get profile\n if (value.profile) {\n profile = profiles.get(value.profile);\n } else {\n // or options defined inline\n profile = value\n }\n if (profile != null) {\n const slowDownOptions = Object.assign({\n windowMs: 5 * 60 * 1000, // 5 minutes\n delayAfter: 20, // 20 requests\n delayMs: 500, // 500 ms\n maxDelayMs: 10000 // 10 seconds\n }, profile, {\n keyGenerator: (req) => {\n let remoteAddress;\n if (proxyAddressForwarding) {\n // get proxy headers or remote address\n remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n } else {\n // get remote address\n remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n }\n return `${path}:${remoteAddress}`;\n }\n });\n if (Array.isArray(slowDownOptions.randomDelayMs)) {\n slowDownOptions.delayMs = () => {\n const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);\n return delayMs;\n }\n }\n if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {\n slowDownOptions.maxDelayMs = () => {\n const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);\n return maxDelayMs;\n }\n }\n if (typeof slowDownOptions.store === 'string') {\n // load store\n const store = slowDownOptions.store.split('#');\n let StoreClass;\n if (store.length === 2) {\n const storeModule = require(store[0]);\n if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {\n StoreClass = storeModule[store[1]];\n slowDownOptions.store = new StoreClass(this, slowDownOptions);\n } else {\n throw new Error(`${store} cannot be found or is inaccessible`);\n }\n } else {\n StoreClass = require(store[0]);\n // create store\n slowDownOptions.store = new StoreClass(this, slowDownOptions);\n }\n }\n addRouter.use(path, slowDown(slowDownOptions));\n }\n });\n if (addRouter.stack.length) {\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n }\n } catch (err) {\n TraceUtils.error('An error occurred while validating speed limit configuration.');\n TraceUtils.error(err);\n TraceUtils.warn('Speed limit service is inactive due to an error occured while loading configuration.')\n }\n });\n }\n\n}"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -30,10 +30,34 @@
|
|
|
30
30
|
"description": "max number of connections during windowMs before starting to delay responses. Number or function that returns a number. Defaults to 1.",
|
|
31
31
|
"type": "integer"
|
|
32
32
|
},
|
|
33
|
+
"randomDelayMs": {
|
|
34
|
+
"description": "random milliseconds - a random value between the given range for defining delayMs",
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": [
|
|
37
|
+
{
|
|
38
|
+
"type": "integer"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "integer"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
33
45
|
"maxDelayMs": {
|
|
34
46
|
"description": "milliseconds - maximum value for delayMs after many consecutive attempts, that is, after the n-th request, the delay will be always maxDelayMs. Important when your application is running behind a load balancer or reverse proxy that has a request timeout. Defaults to Infinity.",
|
|
35
47
|
"type": "integer"
|
|
36
48
|
},
|
|
49
|
+
"randomMaxDelayMs": {
|
|
50
|
+
"description": "random milliseconds - a random value between the given range for delayMs after many consecutive attempts, that is, after the n-th request, the delay will be always maxDelayMs.",
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": [
|
|
53
|
+
{
|
|
54
|
+
"type": "integer"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "integer"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
37
61
|
"skipFailedRequests": {
|
|
38
62
|
"description": "when true failed requests (response status >= 400) won't be counted. Defaults to false.",
|
|
39
63
|
"type": "boolean"
|
|
@@ -53,8 +77,7 @@
|
|
|
53
77
|
},
|
|
54
78
|
"required": [
|
|
55
79
|
"windowMs",
|
|
56
|
-
"delayAfter"
|
|
57
|
-
"delayMs"
|
|
80
|
+
"delayAfter"
|
|
58
81
|
]
|
|
59
82
|
}
|
|
60
83
|
]
|
package/src/RateLimitService.js
CHANGED
|
@@ -34,6 +34,11 @@ export class RateLimitService extends ApplicationService {
|
|
|
34
34
|
if (paths.size === 0) {
|
|
35
35
|
TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
|
|
36
36
|
}
|
|
37
|
+
// get proxy address forwarding option
|
|
38
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
39
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
40
|
+
proxyAddressForwarding = false;
|
|
41
|
+
}
|
|
37
42
|
paths.forEach((value, path) => {
|
|
38
43
|
let profile;
|
|
39
44
|
// get profile
|
|
@@ -50,8 +55,15 @@ export class RateLimitService extends ApplicationService {
|
|
|
50
55
|
legacyHeaders: true // send headers
|
|
51
56
|
}, profile, {
|
|
52
57
|
keyGenerator: (req) => {
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
let remoteAddress;
|
|
59
|
+
if (proxyAddressForwarding) {
|
|
60
|
+
// get proxy headers or remote address
|
|
61
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
62
|
+
} else {
|
|
63
|
+
// get remote address
|
|
64
|
+
remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
65
|
+
}
|
|
66
|
+
return `${path}:${remoteAddress}`;
|
|
55
67
|
}
|
|
56
68
|
});
|
|
57
69
|
if (typeof rateLimitOptions.store === 'string') {
|
package/src/SpeedLimitService.js
CHANGED
|
@@ -32,6 +32,11 @@ export class SpeedLimitService extends ApplicationService {
|
|
|
32
32
|
if (paths.size === 0) {
|
|
33
33
|
TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
|
|
34
34
|
}
|
|
35
|
+
// get proxy address forwarding option
|
|
36
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
37
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
38
|
+
proxyAddressForwarding = false;
|
|
39
|
+
}
|
|
35
40
|
paths.forEach((value, path) => {
|
|
36
41
|
let profile;
|
|
37
42
|
// get profile
|
|
@@ -44,15 +49,34 @@ export class SpeedLimitService extends ApplicationService {
|
|
|
44
49
|
if (profile != null) {
|
|
45
50
|
const slowDownOptions = Object.assign({
|
|
46
51
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
47
|
-
delayAfter:
|
|
52
|
+
delayAfter: 20, // 20 requests
|
|
48
53
|
delayMs: 500, // 500 ms
|
|
49
|
-
maxDelayMs:
|
|
54
|
+
maxDelayMs: 10000 // 10 seconds
|
|
50
55
|
}, profile, {
|
|
51
56
|
keyGenerator: (req) => {
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
let remoteAddress;
|
|
58
|
+
if (proxyAddressForwarding) {
|
|
59
|
+
// get proxy headers or remote address
|
|
60
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
61
|
+
} else {
|
|
62
|
+
// get remote address
|
|
63
|
+
remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
64
|
+
}
|
|
65
|
+
return `${path}:${remoteAddress}`;
|
|
54
66
|
}
|
|
55
67
|
});
|
|
68
|
+
if (Array.isArray(slowDownOptions.randomDelayMs)) {
|
|
69
|
+
slowDownOptions.delayMs = () => {
|
|
70
|
+
const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
|
|
71
|
+
return delayMs;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
|
|
75
|
+
slowDownOptions.maxDelayMs = () => {
|
|
76
|
+
const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
|
|
77
|
+
return maxDelayMs;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
56
80
|
if (typeof slowDownOptions.store === 'string') {
|
|
57
81
|
// load store
|
|
58
82
|
const store = slowDownOptions.store.split('#');
|