@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 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
+
@@ -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
- const ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
54
- return `${path}:${ip}`;
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","ip","headers","connection","remoteAddress","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 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 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 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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"}
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: 5, // 5 requests
52
+ delayAfter: 20, // 20 requests
48
53
  delayMs: 500, // 500 ms
49
- maxDelayMs: 20000 // 20 seconds
54
+ maxDelayMs: 10000 // 10 seconds
50
55
  }, profile, {
51
56
  keyGenerator: (req) => {
52
- const ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
53
- return `${path}:${ip}`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@universis/janitor",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Universis api plugin for rate limiting requests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
  ]
@@ -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
- const ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
54
- return `${path}:${ip}`;
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') {
@@ -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: 5, // 5 requests
52
+ delayAfter: 20, // 20 requests
48
53
  delayMs: 500, // 500 ms
49
- maxDelayMs: 20000 // 20 seconds
54
+ maxDelayMs: 10000 // 10 seconds
50
55
  }, profile, {
51
56
  keyGenerator: (req) => {
52
- const ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
53
- return `${path}:${ip}`;
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('#');