@universis/janitor 1.8.0 → 1.10.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/dist/index.js CHANGED
@@ -1,115 +1,90 @@
1
1
  'use strict';
2
2
 
3
+ require('core-js/stable/string/replace-all');
3
4
  var common = require('@themost/common');
4
5
  var expressRateLimit = require('express-rate-limit');
5
6
  var express = require('express');
6
7
  var path = require('path');
8
+ var rxjs = require('rxjs');
7
9
  var slowDown = require('express-slow-down');
8
- var RedisStore = require('rate-limit-redis');
10
+ var rateLimitRedis = require('rate-limit-redis');
9
11
  var ioredis = require('ioredis');
10
12
  require('@themost/promise-sequence');
11
13
  var url = require('url');
12
14
  var superagent = require('superagent');
13
15
  var jwt = require('jsonwebtoken');
14
16
 
15
- if (!String.prototype.replaceAll) {
16
- String.prototype.replaceAll = function (str, newStr) {
17
- // If a regex pattern
18
- if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
19
- return this.replace(str, newStr);
20
- }
21
- // If a string
22
- return this.replace(new RegExp(str, 'g'), newStr);
23
- };
24
- }
25
-
26
17
  class RateLimitService extends common.ApplicationService {
27
18
  /**
28
19
  * @param {import('@themost/express').ExpressDataApplication} app
29
20
  */
30
21
  constructor(app) {
31
22
  super(app);
32
- app.serviceRouter.subscribe((serviceRouter) => {
33
- if (serviceRouter == null) {
23
+
24
+ // get proxy address forwarding option
25
+ const proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
26
+ this.proxyAddressForwarding = typeof proxyAddressForwarding !== 'boolean' ? false : proxyAddressForwarding;
27
+
28
+ /**
29
+ * @type {BehaviorSubject<{ target: RateLimitService }>}
30
+ */
31
+ this.loaded = new rxjs.BehaviorSubject(null);
32
+
33
+ const serviceContainer = this.getServiceContainer();
34
+ if (serviceContainer == null) {
35
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the parent router seems to be unavailable.`);
36
+ return;
37
+ }
38
+ serviceContainer.subscribe((router) => {
39
+ if (router == null) {
34
40
  return;
35
41
  }
36
42
  try {
37
- const addRouter = express.Router();
38
- let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/rateLimit') || {
39
- profiles: [],
40
- paths: []
41
- };
42
- if (serviceConfiguration.extends) {
43
- // get additional configuration
44
- const configurationPath = app.getConfiguration().getConfigurationPath();
45
- const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);
46
- common.TraceUtils.log(`@universis/janitor#RateLimitService will try to extend service configuration from ${extendsPath}`);
47
- serviceConfiguration = require(extendsPath);
48
- }
49
- const pathsArray = serviceConfiguration.paths || [];
50
- const profilesArray = serviceConfiguration.profiles || [];
43
+ // set router for further processing
44
+ Object.defineProperty(this, 'router', {
45
+ value: express.Router(),
46
+ writable: false,
47
+ enumerable: false,
48
+ configurable: true
49
+ });
50
+ const serviceConfiguration = this.getServiceConfiguration();
51
51
  // create maps
52
- const paths = new Map(pathsArray);
53
- const profiles = new Map(profilesArray);
52
+ const paths = serviceConfiguration.paths;
54
53
  if (paths.size === 0) {
55
- common.TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
56
- }
57
- // get proxy address forwarding option
58
- let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
59
- if (typeof proxyAddressForwarding !== 'boolean') {
60
- proxyAddressForwarding = false;
54
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the collection of paths is empty.`);
61
55
  }
62
56
  paths.forEach((value, path) => {
63
- let profile;
64
- // get profile
65
- if (value.profile) {
66
- profile = profiles.get(value.profile);
67
- } else {
68
- // or options defined inline
69
- profile = value;
70
- }
71
- if (profile != null) {
72
- const rateLimitOptions = Object.assign({
73
- windowMs: 5 * 60 * 1000, // 5 minutes
74
- limit: 50, // 50 requests
75
- legacyHeaders: true // send headers
76
- }, profile, {
77
- keyGenerator: (req) => {
78
- let remoteAddress;
79
- if (proxyAddressForwarding) {
80
- // get proxy headers or remote address
81
- remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
82
- } else {
83
- // get remote address
84
- remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
85
- }
86
- return `${path}:${remoteAddress}`;
87
- }
57
+ this.set(path, value);
58
+ });
59
+ if (router.stack) {
60
+ router.stack.unshift.apply(router.stack, this.router.stack);
61
+ } else {
62
+ // use router
63
+ router.use(this.router);
64
+ // get router stack (use a workaround for express 4.x)
65
+ const stack = router._router && router._router.stack;
66
+ if (Array.isArray(stack)) {
67
+ // stage #1 find logger middleware (for supporting request logging)
68
+ let index = stack.findIndex((item) => {
69
+ return item.name === 'logger';
88
70
  });
89
- if (typeof rateLimitOptions.store === 'string') {
90
- // load store
91
- const store = rateLimitOptions.store.split('#');
92
- let StoreClass;
93
- if (store.length === 2) {
94
- const storeModule = require(store[0]);
95
- if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
96
- StoreClass = storeModule[store[1]];
97
- rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
98
- } else {
99
- throw new Error(`${store} cannot be found or is inaccessible`);
100
- }
101
- } else {
102
- StoreClass = require(store[0]);
103
- // create store
104
- rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
105
- }
71
+ if (index === -1) {
72
+ // stage #2 find expressInit middleware
73
+ index = stack.findIndex((item) => {
74
+ return item.name === 'expressInit';
75
+ });
106
76
  }
107
- addRouter.use(path, expressRateLimit.rateLimit(rateLimitOptions));
77
+ // if found, move the last middleware to be after expressInit
78
+ if (index > -1) {
79
+ // move the last middleware to be after expressInit
80
+ stack.splice(index + 1, 0, stack.pop());
81
+ }
82
+ } else {
83
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the container stack is not available.`);
108
84
  }
109
- });
110
- if (addRouter.stack.length) {
111
- serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
112
85
  }
86
+ // notify that the service is loaded
87
+ this.loaded.next({ target: this });
113
88
  } catch (err) {
114
89
  common.TraceUtils.error('An error occurred while validating rate limit configuration.');
115
90
  common.TraceUtils.error(err);
@@ -118,106 +93,218 @@ class RateLimitService extends common.ApplicationService {
118
93
  });
119
94
  }
120
95
 
96
+ /**
97
+ * Returns the service router that is used to register rate limit middleware.
98
+ * @returns {import('rxjs').BehaviorSubject<import('express').Router | import('express').Application>} The service router.
99
+ */
100
+ getServiceContainer() {
101
+ return this.getApplication() && this.getApplication().serviceRouter;
102
+ }
103
+
104
+ /**
105
+ * Returns the service name.
106
+ * @returns {string} The service name.
107
+ */
108
+ getServiceName() {
109
+ return '@universis/janitor#RateLimitService';
110
+ }
111
+ /**
112
+ * Returns the service configuration.
113
+ * @returns {{extends?: string, profiles?: Array, paths?: Array}} The service configuration.
114
+ */
115
+ getServiceConfiguration() {
116
+ if (this.serviceConfiguration) {
117
+ return this.serviceConfiguration;
118
+ }
119
+ let serviceConfiguration = {
120
+ profiles: [],
121
+ paths: []
122
+ };
123
+ // get service configuration
124
+ const serviceConfigurationSource = this.getApplication().getConfiguration().getSourceAt('settings/universis/janitor/rateLimit');
125
+ if (serviceConfigurationSource) {
126
+ if (typeof serviceConfigurationSource.extends === 'string') {
127
+ // get additional configuration
128
+ const configurationPath = this.getApplication().getConfiguration().getConfigurationPath();
129
+ const extendsPath = path.resolve(configurationPath, serviceConfigurationSource.extends);
130
+ common.TraceUtils.log(`${this.getServiceName()} will try to extend service configuration using ${extendsPath}`);
131
+ serviceConfiguration = Object.assign({}, {
132
+ profiles: [],
133
+ paths: []
134
+ }, require(extendsPath));
135
+ } else {
136
+ common.TraceUtils.log(`${this.getServiceName()} will use service configuration from settings/universis/janitor/rateLimit`);
137
+ serviceConfiguration = Object.assign({}, {
138
+ profiles: [],
139
+ paths: []
140
+ }, serviceConfigurationSource);
141
+ }
142
+ }
143
+ const pathsArray = serviceConfiguration.paths || [];
144
+ const profilesArray = serviceConfiguration.profiles || [];
145
+ // create maps
146
+ serviceConfiguration.paths = new Map(pathsArray);
147
+ serviceConfiguration.profiles = new Map(profilesArray);
148
+ // set service configuration
149
+ Object.defineProperty(this, 'serviceConfiguration', {
150
+ value: serviceConfiguration,
151
+ writable: false,
152
+ enumerable: false,
153
+ configurable: true
154
+ });
155
+ return this.serviceConfiguration;
156
+ }
157
+
158
+ /**
159
+ * Sets the rate limit configuration for a specific path.
160
+ * @param {string} path
161
+ * @param {{ profile: string } | import('express-rate-limit').Options} options
162
+ * @returns {RateLimitService} The service instance for chaining.
163
+ */
164
+ set(path, options) {
165
+ let opts;
166
+ // get profile
167
+ if (options.profile) {
168
+ opts = this.serviceConfiguration.profiles.get(options.profile);
169
+ } else {
170
+ // or options defined inline
171
+ opts = options;
172
+ }
173
+ /**
174
+ * @type { import('express-rate-limit').Options }
175
+ */
176
+ const rateLimitOptions = Object.assign({
177
+ windowMs: 5 * 60 * 1000, // 5 minutes
178
+ limit: 50, // 50 requests
179
+ legacyHeaders: true // send headers
180
+ }, opts, {
181
+ keyGenerator: (req) => {
182
+ let remoteAddress;
183
+ if (this.proxyAddressForwarding) {
184
+ // get proxy headers or remote address
185
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
186
+ } else {
187
+ // get remote address
188
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
189
+ }
190
+ return `${path}:${remoteAddress}`;
191
+ }
192
+ });
193
+ if (typeof rateLimitOptions.store === 'undefined') {
194
+ const StoreClass = this.getStoreType();
195
+ if (typeof StoreClass === 'function') {
196
+ rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
197
+ }
198
+ }
199
+ this.router.use(path, expressRateLimit.rateLimit(rateLimitOptions));
200
+ return this;
201
+ }
202
+
203
+ /**
204
+ * @returns {function} The type of store used for rate limiting.
205
+ */
206
+ getStoreType() {
207
+ const serviceConfiguration = this.getServiceConfiguration();
208
+ if (typeof serviceConfiguration.storeType !== 'string') {
209
+ return;
210
+ }
211
+ let StoreClass;
212
+ const store = serviceConfiguration.storeType.split('#');
213
+ if (store.length === 2) {
214
+ const storeModule = require(store[0]);
215
+ if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
216
+ StoreClass = storeModule[store[1]];
217
+ return StoreClass;
218
+ } else {
219
+ throw new Error(`${store} cannot be found or is inaccessible`);
220
+ }
221
+ } else {
222
+ StoreClass = require(store[0]);
223
+ return StoreClass;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Unsets the rate limit configuration for a specific path.
229
+ * @param {string} path
230
+ * @returns {RateLimitService} The service instance for chaining.
231
+ */
232
+ unset(path) {
233
+ const index = this.router.stack.findIndex((layer) => {
234
+ return layer.route && layer.route.path === path;
235
+ });
236
+ if (index !== -1) {
237
+ this.router.stack.splice(index, 1);
238
+ }
239
+ return this;
240
+ }
241
+
121
242
  }
122
243
 
123
244
  class SpeedLimitService extends common.ApplicationService {
124
245
  constructor(app) {
125
246
  super(app);
126
247
 
127
- app.serviceRouter.subscribe((serviceRouter) => {
128
- if (serviceRouter == null) {
248
+ // get proxy address forwarding option
249
+ const proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
250
+ this.proxyAddressForwarding = typeof proxyAddressForwarding !== 'boolean' ? false : proxyAddressForwarding;
251
+
252
+ /**
253
+ * @type {BehaviorSubject<{ target: SpeedLimitService }>}
254
+ */
255
+ this.loaded = new rxjs.BehaviorSubject(null);
256
+
257
+ const serviceContainer = this.getServiceContainer();
258
+ if (serviceContainer == null) {
259
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the parent router seems to be unavailable.`);
260
+ return;
261
+ }
262
+
263
+ serviceContainer.subscribe((router) => {
264
+ if (router == null) {
129
265
  return;
130
266
  }
131
267
  try {
132
- const addRouter = express.Router();
133
- let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {
134
- profiles: [],
135
- paths: []
136
- };
137
- if (serviceConfiguration.extends) {
138
- // get additional configuration
139
- const configurationPath = app.getConfiguration().getConfigurationPath();
140
- const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);
141
- common.TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);
142
- serviceConfiguration = require(extendsPath);
143
- }
144
- const pathsArray = serviceConfiguration.paths || [];
145
- const profilesArray = serviceConfiguration.profiles || [];
268
+ // set router for further processing
269
+ Object.defineProperty(this, 'router', {
270
+ value: express.Router(),
271
+ writable: false,
272
+ enumerable: false,
273
+ configurable: true
274
+ });
275
+ const serviceConfiguration = this.getServiceConfiguration();
146
276
  // create maps
147
- const paths = new Map(pathsArray);
148
- const profiles = new Map(profilesArray);
277
+ const paths = serviceConfiguration.paths;
149
278
  if (paths.size === 0) {
150
- common.TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
151
- }
152
- // get proxy address forwarding option
153
- let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
154
- if (typeof proxyAddressForwarding !== 'boolean') {
155
- proxyAddressForwarding = false;
279
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the collection of paths is empty.`);
156
280
  }
157
281
  paths.forEach((value, path) => {
158
- let profile;
159
- // get profile
160
- if (value.profile) {
161
- profile = profiles.get(value.profile);
162
- } else {
163
- // or options defined inline
164
- profile = value;
165
- }
166
- if (profile != null) {
167
- const slowDownOptions = Object.assign({
168
- windowMs: 5 * 60 * 1000, // 5 minutes
169
- delayAfter: 20, // 20 requests
170
- delayMs: 500, // 500 ms
171
- maxDelayMs: 10000 // 10 seconds
172
- }, profile, {
173
- keyGenerator: (req) => {
174
- let remoteAddress;
175
- if (proxyAddressForwarding) {
176
- // get proxy headers or remote address
177
- remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
178
- } else {
179
- // get remote address
180
- remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
181
- }
182
- return `${path}:${remoteAddress}`;
183
- }
282
+ this.set(path, value);
283
+ });
284
+ if (router.stack) {
285
+ router.stack.unshift.apply(router.stack, this.router.stack);
286
+ } else {
287
+ // use router
288
+ router.use(this.router);
289
+ // get router stack (use a workaround for express 4.x)
290
+ const stack = router._router && router._router.stack;
291
+ if (Array.isArray(stack)) {
292
+ // stage #1 find logger middleware (for supporting request logging)
293
+ let index = stack.findIndex((item) => {
294
+ return item.name === 'logger';
184
295
  });
185
- if (Array.isArray(slowDownOptions.randomDelayMs)) {
186
- slowDownOptions.delayMs = () => {
187
- const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
188
- return delayMs;
189
- };
190
- }
191
- if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
192
- slowDownOptions.maxDelayMs = () => {
193
- const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
194
- return maxDelayMs;
195
- };
196
- }
197
- if (typeof slowDownOptions.store === 'string') {
198
- // load store
199
- const store = slowDownOptions.store.split('#');
200
- let StoreClass;
201
- if (store.length === 2) {
202
- const storeModule = require(store[0]);
203
- if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
204
- StoreClass = storeModule[store[1]];
205
- slowDownOptions.store = new StoreClass(this, slowDownOptions);
206
- } else {
207
- throw new Error(`${store} cannot be found or is inaccessible`);
208
- }
209
- } else {
210
- StoreClass = require(store[0]);
211
- // create store
212
- slowDownOptions.store = new StoreClass(this, slowDownOptions);
213
- }
296
+ if (index === -1) {
297
+ // stage #2 find expressInit middleware
298
+ index = stack.findIndex((item) => {
299
+ return item.name === 'expressInit';
300
+ });
214
301
  }
215
- addRouter.use(path, slowDown(slowDownOptions));
302
+ } else {
303
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the container stack is not available.`);
216
304
  }
217
- });
218
- if (addRouter.stack.length) {
219
- serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
220
305
  }
306
+ // notify that the service is loaded
307
+ this.loaded.next({ target: this });
221
308
  } catch (err) {
222
309
  common.TraceUtils.error('An error occurred while validating speed limit configuration.');
223
310
  common.TraceUtils.error(err);
@@ -226,6 +313,163 @@ class SpeedLimitService extends common.ApplicationService {
226
313
  });
227
314
  }
228
315
 
316
+ /**
317
+ * @returns {function} The type of store used for rate limiting.
318
+ */
319
+ getStoreType() {
320
+ const serviceConfiguration = this.getServiceConfiguration();
321
+ if (typeof serviceConfiguration.storeType !== 'string') {
322
+ return;
323
+ }
324
+ let StoreClass;
325
+ const store = serviceConfiguration.storeType.split('#');
326
+ if (store.length === 2) {
327
+ const storeModule = require(store[0]);
328
+ if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
329
+ StoreClass = storeModule[store[1]];
330
+ return StoreClass;
331
+ } else {
332
+ throw new Error(`${store} cannot be found or is inaccessible`);
333
+ }
334
+ } else {
335
+ StoreClass = require(store[0]);
336
+ return StoreClass;
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Returns the service name.
342
+ * @returns {string} The service name.
343
+ */
344
+ getServiceName() {
345
+ return '@universis/janitor#SpeedLimitService';
346
+ }
347
+
348
+ /**
349
+ * Returns the service router that is used to register speed limit middleware.
350
+ * @returns {import('express').Router | import('express').Application} The service router.
351
+ */
352
+ getServiceContainer() {
353
+ return this.getApplication() && this.getApplication().serviceRouter;
354
+ }
355
+
356
+ /**
357
+ * Sets the speed limit configuration for a specific path.
358
+ * @param {string} path
359
+ * @param {{ profile: string } | import('express-slow-down').Options} options
360
+ * @returns {SpeedLimitService} The service instance for chaining.
361
+ */
362
+ set(path, options) {
363
+ let opts;
364
+ // get profile
365
+ if (options.profile) {
366
+ opts = this.serviceConfiguration.profiles.get(options.profile);
367
+ } else {
368
+ // or options defined inline
369
+ opts = options;
370
+ }
371
+ const slowDownOptions = Object.assign({
372
+ windowMs: 5 * 60 * 1000, // 5 minutes
373
+ delayAfter: 20, // 20 requests
374
+ delayMs: 500, // 500 ms
375
+ maxDelayMs: 10000 // 10 seconds
376
+ }, opts, {
377
+ keyGenerator: (req) => {
378
+ let remoteAddress;
379
+ if (this.proxyAddressForwarding) {
380
+ // get proxy headers or remote address
381
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
382
+ } else {
383
+ // get remote address
384
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
385
+ }
386
+ return `${path}:${remoteAddress}`;
387
+ }
388
+ });
389
+ if (Array.isArray(slowDownOptions.randomDelayMs)) {
390
+ slowDownOptions.delayMs = () => {
391
+ const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
392
+ return delayMs;
393
+ };
394
+ }
395
+ if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
396
+ slowDownOptions.maxDelayMs = () => {
397
+ const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
398
+ return maxDelayMs;
399
+ };
400
+ }
401
+ if (typeof slowDownOptions.store === 'undefined') {
402
+ const StoreClass = this.getStoreType();
403
+ if (typeof StoreClass === 'function') {
404
+ slowDownOptions.store = new StoreClass(this, slowDownOptions);
405
+ }
406
+ }
407
+ this.router.use(path, slowDown(slowDownOptions));
408
+ return this;
409
+ }
410
+
411
+
412
+ /**
413
+ * Unsets the speed limit configuration for a specific path.
414
+ * @param {string} path
415
+ * @return {SpeedLimitService} The service instance for chaining.
416
+ */
417
+ unset(path) {
418
+ const index = this.router.stack.findIndex((layer) => {
419
+ return layer.route && layer.route.path === path;
420
+ });
421
+ if (index !== -1) {
422
+ this.router.stack.splice(index, 1);
423
+ }
424
+ return this;
425
+ }
426
+
427
+ /**
428
+ *
429
+ * @returns {{extends?: string, profiles?: Array, paths?: Array}} The service configuration.
430
+ */
431
+ getServiceConfiguration() {
432
+ if (this.serviceConfiguration) {
433
+ return this.serviceConfiguration;
434
+ }
435
+ let serviceConfiguration = {
436
+ profiles: [],
437
+ paths: []
438
+ };
439
+ // get service configuration
440
+ const serviceConfigurationSource = this.getApplication().getConfiguration().getSourceAt('settings/universis/janitor/speedLimit');
441
+ if (serviceConfigurationSource) {
442
+ if (typeof serviceConfigurationSource.extends === 'string') {
443
+ // get additional configuration
444
+ const configurationPath = this.getApplication().getConfiguration().getConfigurationPath();
445
+ const extendsPath = path.resolve(configurationPath, serviceConfigurationSource.extends);
446
+ common.TraceUtils.log(`${this.getServiceName()} will try to extend service configuration using ${extendsPath}`);
447
+ serviceConfiguration = Object.assign({}, {
448
+ profiles: [],
449
+ paths: []
450
+ }, require(extendsPath));
451
+ } else {
452
+ common.TraceUtils.log(`${this.getServiceName()} will use service configuration from settings/universis/janitor/speedLimit`);
453
+ serviceConfiguration = Object.assign({}, {
454
+ profiles: [],
455
+ paths: []
456
+ }, serviceConfigurationSource);
457
+ }
458
+ }
459
+ const profilesArray = serviceConfiguration.profiles || [];
460
+ serviceConfiguration.profiles = new Map(profilesArray);
461
+ const pathsArray = serviceConfiguration.paths || [];
462
+ serviceConfiguration.paths = new Map(pathsArray);
463
+
464
+ Object.defineProperty(this, 'serviceConfiguration', {
465
+ value: serviceConfiguration,
466
+ writable: false,
467
+ enumerable: false,
468
+ configurable: true
469
+ });
470
+ return this.serviceConfiguration;
471
+ }
472
+
229
473
  }
230
474
 
231
475
  function _defineProperty(e, r, t) {
@@ -262,19 +506,19 @@ function noLoadIncrementScript() {
262
506
 
263
507
  //
264
508
  }
265
- if (RedisStore.prototype.loadIncrementScript.name === 'loadIncrementScript') {
509
+ if (rateLimitRedis.RedisStore.prototype.loadIncrementScript.name === 'loadIncrementScript') {
266
510
  // get super method for future use
267
- superLoadIncrementScript = RedisStore.prototype.loadIncrementScript;
268
- RedisStore.prototype.loadIncrementScript = noLoadIncrementScript;
511
+ superLoadIncrementScript = rateLimitRedis.RedisStore.prototype.loadIncrementScript;
512
+ rateLimitRedis.RedisStore.prototype.loadIncrementScript = noLoadIncrementScript;
269
513
  }
270
514
 
271
- if (RedisStore.prototype.loadGetScript.name === 'loadGetScript') {
515
+ if (rateLimitRedis.RedisStore.prototype.loadGetScript.name === 'loadGetScript') {
272
516
  // get super method
273
- superLoadGetScript = RedisStore.prototype.loadGetScript;
274
- RedisStore.prototype.loadGetScript = noLoadGetScript;
517
+ superLoadGetScript = rateLimitRedis.RedisStore.prototype.loadGetScript;
518
+ rateLimitRedis.RedisStore.prototype.loadGetScript = noLoadGetScript;
275
519
  }
276
520
 
277
- class RedisClientStore extends RedisStore {
521
+ class RedisClientStore extends rateLimitRedis.RedisStore {
278
522
 
279
523
 
280
524
 
@@ -904,6 +1148,44 @@ class RemoteAddressValidator extends common.ApplicationService {
904
1148
 
905
1149
  }
906
1150
 
1151
+ class AppRateLimitService extends RateLimitService {
1152
+ /**
1153
+ *
1154
+ * @param {import('@themost/common').ApplicationBase} app
1155
+ */
1156
+ constructor(app) {
1157
+ super(app);
1158
+ }
1159
+
1160
+ getServiceName() {
1161
+ return '@universis/janitor#AppRateLimitService';
1162
+ }
1163
+
1164
+ getServiceContainer() {
1165
+ return this.getApplication() && this.getApplication().container;
1166
+ }
1167
+ }
1168
+
1169
+ class AppSpeedLimitService extends SpeedLimitService {
1170
+ /**
1171
+ *
1172
+ * @param {import('@themost/common').ApplicationBase} app
1173
+ */
1174
+ constructor(app) {
1175
+ super(app);
1176
+ }
1177
+
1178
+ getServiceName() {
1179
+ return '@universis/janitor#AppSpeedLimitService';
1180
+ }
1181
+
1182
+ getServiceContainer() {
1183
+ return this.getApplication() && this.getApplication().container;
1184
+ }
1185
+ }
1186
+
1187
+ exports.AppRateLimitService = AppRateLimitService;
1188
+ exports.AppSpeedLimitService = AppSpeedLimitService;
907
1189
  exports.DefaultScopeAccessConfiguration = DefaultScopeAccessConfiguration;
908
1190
  exports.EnableScopeAccessConfiguration = EnableScopeAccessConfiguration;
909
1191
  exports.ExtendScopeAccessConfiguration = ExtendScopeAccessConfiguration;