@universis/janitor 1.9.0 → 1.11.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
@@ -5,6 +5,7 @@ var common = require('@themost/common');
5
5
  var expressRateLimit = require('express-rate-limit');
6
6
  var express = require('express');
7
7
  var path = require('path');
8
+ var rxjs = require('rxjs');
8
9
  var slowDown = require('express-slow-down');
9
10
  var rateLimitRedis = require('rate-limit-redis');
10
11
  var ioredis = require('ioredis');
@@ -12,6 +13,8 @@ require('@themost/promise-sequence');
12
13
  var url = require('url');
13
14
  var superagent = require('superagent');
14
15
  var jwt = require('jsonwebtoken');
16
+ var BearerStrategy = require('passport-http-bearer');
17
+ var passport = require('passport');
15
18
 
16
19
  class RateLimitService extends common.ApplicationService {
17
20
  /**
@@ -19,87 +22,71 @@ class RateLimitService extends common.ApplicationService {
19
22
  */
20
23
  constructor(app) {
21
24
  super(app);
22
- app.serviceRouter.subscribe((serviceRouter) => {
23
- if (serviceRouter == null) {
25
+
26
+ // get proxy address forwarding option
27
+ const proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
28
+ this.proxyAddressForwarding = typeof proxyAddressForwarding !== 'boolean' ? false : proxyAddressForwarding;
29
+
30
+ /**
31
+ * @type {BehaviorSubject<{ target: RateLimitService }>}
32
+ */
33
+ this.loaded = new rxjs.BehaviorSubject(null);
34
+
35
+ const serviceContainer = this.getServiceContainer();
36
+ if (serviceContainer == null) {
37
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the parent router seems to be unavailable.`);
38
+ return;
39
+ }
40
+ serviceContainer.subscribe((router) => {
41
+ if (router == null) {
24
42
  return;
25
43
  }
26
44
  try {
27
- const addRouter = express.Router();
28
- let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/rateLimit') || {
29
- profiles: [],
30
- paths: []
31
- };
32
- if (serviceConfiguration.extends) {
33
- // get additional configuration
34
- const configurationPath = app.getConfiguration().getConfigurationPath();
35
- const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);
36
- common.TraceUtils.log(`@universis/janitor#RateLimitService will try to extend service configuration from ${extendsPath}`);
37
- serviceConfiguration = require(extendsPath);
38
- }
39
- const pathsArray = serviceConfiguration.paths || [];
40
- const profilesArray = serviceConfiguration.profiles || [];
45
+ // set router for further processing
46
+ Object.defineProperty(this, 'router', {
47
+ value: express.Router(),
48
+ writable: false,
49
+ enumerable: false,
50
+ configurable: true
51
+ });
52
+ const serviceConfiguration = this.getServiceConfiguration();
41
53
  // create maps
42
- const paths = new Map(pathsArray);
43
- const profiles = new Map(profilesArray);
54
+ const paths = serviceConfiguration.paths;
44
55
  if (paths.size === 0) {
45
- common.TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
46
- }
47
- // get proxy address forwarding option
48
- let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
49
- if (typeof proxyAddressForwarding !== 'boolean') {
50
- proxyAddressForwarding = false;
56
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the collection of paths is empty.`);
51
57
  }
52
58
  paths.forEach((value, path) => {
53
- let profile;
54
- // get profile
55
- if (value.profile) {
56
- profile = profiles.get(value.profile);
57
- } else {
58
- // or options defined inline
59
- profile = value;
60
- }
61
- if (profile != null) {
62
- const rateLimitOptions = Object.assign({
63
- windowMs: 5 * 60 * 1000, // 5 minutes
64
- limit: 50, // 50 requests
65
- legacyHeaders: true // send headers
66
- }, profile, {
67
- keyGenerator: (req) => {
68
- let remoteAddress;
69
- if (proxyAddressForwarding) {
70
- // get proxy headers or remote address
71
- remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
72
- } else {
73
- // get remote address
74
- remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
75
- }
76
- return `${path}:${remoteAddress}`;
77
- }
59
+ this.set(path, value);
60
+ });
61
+ if (router.stack) {
62
+ router.stack.unshift.apply(router.stack, this.router.stack);
63
+ } else {
64
+ // use router
65
+ router.use(this.router);
66
+ // get router stack (use a workaround for express 4.x)
67
+ const stack = router._router && router._router.stack;
68
+ if (Array.isArray(stack)) {
69
+ // stage #1 find logger middleware (for supporting request logging)
70
+ let index = stack.findIndex((item) => {
71
+ return item.name === 'logger';
78
72
  });
79
- if (typeof rateLimitOptions.store === 'string') {
80
- // load store
81
- const store = rateLimitOptions.store.split('#');
82
- let StoreClass;
83
- if (store.length === 2) {
84
- const storeModule = require(store[0]);
85
- if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
86
- StoreClass = storeModule[store[1]];
87
- rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
88
- } else {
89
- throw new Error(`${store} cannot be found or is inaccessible`);
90
- }
91
- } else {
92
- StoreClass = require(store[0]);
93
- // create store
94
- rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
95
- }
73
+ if (index === -1) {
74
+ // stage #2 find expressInit middleware
75
+ index = stack.findIndex((item) => {
76
+ return item.name === 'expressInit';
77
+ });
96
78
  }
97
- addRouter.use(path, expressRateLimit.rateLimit(rateLimitOptions));
79
+ // if found, move the last middleware to be after expressInit
80
+ if (index > -1) {
81
+ // move the last middleware to be after expressInit
82
+ stack.splice(index + 1, 0, stack.pop());
83
+ }
84
+ } else {
85
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the container stack is not available.`);
98
86
  }
99
- });
100
- if (addRouter.stack.length) {
101
- serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
102
87
  }
88
+ // notify that the service is loaded
89
+ this.loaded.next({ target: this });
103
90
  } catch (err) {
104
91
  common.TraceUtils.error('An error occurred while validating rate limit configuration.');
105
92
  common.TraceUtils.error(err);
@@ -108,106 +95,218 @@ class RateLimitService extends common.ApplicationService {
108
95
  });
109
96
  }
110
97
 
98
+ /**
99
+ * Returns the service router that is used to register rate limit middleware.
100
+ * @returns {import('rxjs').BehaviorSubject<import('express').Router | import('express').Application>} The service router.
101
+ */
102
+ getServiceContainer() {
103
+ return this.getApplication() && this.getApplication().serviceRouter;
104
+ }
105
+
106
+ /**
107
+ * Returns the service name.
108
+ * @returns {string} The service name.
109
+ */
110
+ getServiceName() {
111
+ return '@universis/janitor#RateLimitService';
112
+ }
113
+ /**
114
+ * Returns the service configuration.
115
+ * @returns {{extends?: string, profiles?: Array, paths?: Array}} The service configuration.
116
+ */
117
+ getServiceConfiguration() {
118
+ if (this.serviceConfiguration) {
119
+ return this.serviceConfiguration;
120
+ }
121
+ let serviceConfiguration = {
122
+ profiles: [],
123
+ paths: []
124
+ };
125
+ // get service configuration
126
+ const serviceConfigurationSource = this.getApplication().getConfiguration().getSourceAt('settings/universis/janitor/rateLimit');
127
+ if (serviceConfigurationSource) {
128
+ if (typeof serviceConfigurationSource.extends === 'string') {
129
+ // get additional configuration
130
+ const configurationPath = this.getApplication().getConfiguration().getConfigurationPath();
131
+ const extendsPath = path.resolve(configurationPath, serviceConfigurationSource.extends);
132
+ common.TraceUtils.log(`${this.getServiceName()} will try to extend service configuration using ${extendsPath}`);
133
+ serviceConfiguration = Object.assign({}, {
134
+ profiles: [],
135
+ paths: []
136
+ }, require(extendsPath));
137
+ } else {
138
+ common.TraceUtils.log(`${this.getServiceName()} will use service configuration from settings/universis/janitor/rateLimit`);
139
+ serviceConfiguration = Object.assign({}, {
140
+ profiles: [],
141
+ paths: []
142
+ }, serviceConfigurationSource);
143
+ }
144
+ }
145
+ const pathsArray = serviceConfiguration.paths || [];
146
+ const profilesArray = serviceConfiguration.profiles || [];
147
+ // create maps
148
+ serviceConfiguration.paths = new Map(pathsArray);
149
+ serviceConfiguration.profiles = new Map(profilesArray);
150
+ // set service configuration
151
+ Object.defineProperty(this, 'serviceConfiguration', {
152
+ value: serviceConfiguration,
153
+ writable: false,
154
+ enumerable: false,
155
+ configurable: true
156
+ });
157
+ return this.serviceConfiguration;
158
+ }
159
+
160
+ /**
161
+ * Sets the rate limit configuration for a specific path.
162
+ * @param {string} path
163
+ * @param {{ profile: string } | import('express-rate-limit').Options} options
164
+ * @returns {RateLimitService} The service instance for chaining.
165
+ */
166
+ set(path, options) {
167
+ let opts;
168
+ // get profile
169
+ if (options.profile) {
170
+ opts = this.serviceConfiguration.profiles.get(options.profile);
171
+ } else {
172
+ // or options defined inline
173
+ opts = options;
174
+ }
175
+ /**
176
+ * @type { import('express-rate-limit').Options }
177
+ */
178
+ const rateLimitOptions = Object.assign({
179
+ windowMs: 5 * 60 * 1000, // 5 minutes
180
+ limit: 50, // 50 requests
181
+ legacyHeaders: true // send headers
182
+ }, opts, {
183
+ keyGenerator: (req) => {
184
+ let remoteAddress;
185
+ if (this.proxyAddressForwarding) {
186
+ // get proxy headers or remote address
187
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
188
+ } else {
189
+ // get remote address
190
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
191
+ }
192
+ return `${path}:${remoteAddress}`;
193
+ }
194
+ });
195
+ if (typeof rateLimitOptions.store === 'undefined') {
196
+ const StoreClass = this.getStoreType();
197
+ if (typeof StoreClass === 'function') {
198
+ rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
199
+ }
200
+ }
201
+ this.router.use(path, expressRateLimit.rateLimit(rateLimitOptions));
202
+ return this;
203
+ }
204
+
205
+ /**
206
+ * @returns {function} The type of store used for rate limiting.
207
+ */
208
+ getStoreType() {
209
+ const serviceConfiguration = this.getServiceConfiguration();
210
+ if (typeof serviceConfiguration.storeType !== 'string') {
211
+ return;
212
+ }
213
+ let StoreClass;
214
+ const store = serviceConfiguration.storeType.split('#');
215
+ if (store.length === 2) {
216
+ const storeModule = require(store[0]);
217
+ if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
218
+ StoreClass = storeModule[store[1]];
219
+ return StoreClass;
220
+ } else {
221
+ throw new Error(`${store} cannot be found or is inaccessible`);
222
+ }
223
+ } else {
224
+ StoreClass = require(store[0]);
225
+ return StoreClass;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Unsets the rate limit configuration for a specific path.
231
+ * @param {string} path
232
+ * @returns {RateLimitService} The service instance for chaining.
233
+ */
234
+ unset(path) {
235
+ const index = this.router.stack.findIndex((layer) => {
236
+ return layer.route && layer.route.path === path;
237
+ });
238
+ if (index !== -1) {
239
+ this.router.stack.splice(index, 1);
240
+ }
241
+ return this;
242
+ }
243
+
111
244
  }
112
245
 
113
246
  class SpeedLimitService extends common.ApplicationService {
114
247
  constructor(app) {
115
248
  super(app);
116
249
 
117
- app.serviceRouter.subscribe((serviceRouter) => {
118
- if (serviceRouter == null) {
250
+ // get proxy address forwarding option
251
+ const proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
252
+ this.proxyAddressForwarding = typeof proxyAddressForwarding !== 'boolean' ? false : proxyAddressForwarding;
253
+
254
+ /**
255
+ * @type {BehaviorSubject<{ target: SpeedLimitService }>}
256
+ */
257
+ this.loaded = new rxjs.BehaviorSubject(null);
258
+
259
+ const serviceContainer = this.getServiceContainer();
260
+ if (serviceContainer == null) {
261
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the parent router seems to be unavailable.`);
262
+ return;
263
+ }
264
+
265
+ serviceContainer.subscribe((router) => {
266
+ if (router == null) {
119
267
  return;
120
268
  }
121
269
  try {
122
- const addRouter = express.Router();
123
- let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {
124
- profiles: [],
125
- paths: []
126
- };
127
- if (serviceConfiguration.extends) {
128
- // get additional configuration
129
- const configurationPath = app.getConfiguration().getConfigurationPath();
130
- const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);
131
- common.TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);
132
- serviceConfiguration = require(extendsPath);
133
- }
134
- const pathsArray = serviceConfiguration.paths || [];
135
- const profilesArray = serviceConfiguration.profiles || [];
270
+ // set router for further processing
271
+ Object.defineProperty(this, 'router', {
272
+ value: express.Router(),
273
+ writable: false,
274
+ enumerable: false,
275
+ configurable: true
276
+ });
277
+ const serviceConfiguration = this.getServiceConfiguration();
136
278
  // create maps
137
- const paths = new Map(pathsArray);
138
- const profiles = new Map(profilesArray);
279
+ const paths = serviceConfiguration.paths;
139
280
  if (paths.size === 0) {
140
- common.TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
141
- }
142
- // get proxy address forwarding option
143
- let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
144
- if (typeof proxyAddressForwarding !== 'boolean') {
145
- proxyAddressForwarding = false;
281
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the collection of paths is empty.`);
146
282
  }
147
283
  paths.forEach((value, path) => {
148
- let profile;
149
- // get profile
150
- if (value.profile) {
151
- profile = profiles.get(value.profile);
152
- } else {
153
- // or options defined inline
154
- profile = value;
155
- }
156
- if (profile != null) {
157
- const slowDownOptions = Object.assign({
158
- windowMs: 5 * 60 * 1000, // 5 minutes
159
- delayAfter: 20, // 20 requests
160
- delayMs: 500, // 500 ms
161
- maxDelayMs: 10000 // 10 seconds
162
- }, profile, {
163
- keyGenerator: (req) => {
164
- let remoteAddress;
165
- if (proxyAddressForwarding) {
166
- // get proxy headers or remote address
167
- remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
168
- } else {
169
- // get remote address
170
- remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
171
- }
172
- return `${path}:${remoteAddress}`;
173
- }
284
+ this.set(path, value);
285
+ });
286
+ if (router.stack) {
287
+ router.stack.unshift.apply(router.stack, this.router.stack);
288
+ } else {
289
+ // use router
290
+ router.use(this.router);
291
+ // get router stack (use a workaround for express 4.x)
292
+ const stack = router._router && router._router.stack;
293
+ if (Array.isArray(stack)) {
294
+ // stage #1 find logger middleware (for supporting request logging)
295
+ let index = stack.findIndex((item) => {
296
+ return item.name === 'logger';
174
297
  });
175
- if (Array.isArray(slowDownOptions.randomDelayMs)) {
176
- slowDownOptions.delayMs = () => {
177
- const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
178
- return delayMs;
179
- };
180
- }
181
- if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
182
- slowDownOptions.maxDelayMs = () => {
183
- const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
184
- return maxDelayMs;
185
- };
186
- }
187
- if (typeof slowDownOptions.store === 'string') {
188
- // load store
189
- const store = slowDownOptions.store.split('#');
190
- let StoreClass;
191
- if (store.length === 2) {
192
- const storeModule = require(store[0]);
193
- if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
194
- StoreClass = storeModule[store[1]];
195
- slowDownOptions.store = new StoreClass(this, slowDownOptions);
196
- } else {
197
- throw new Error(`${store} cannot be found or is inaccessible`);
198
- }
199
- } else {
200
- StoreClass = require(store[0]);
201
- // create store
202
- slowDownOptions.store = new StoreClass(this, slowDownOptions);
203
- }
298
+ if (index === -1) {
299
+ // stage #2 find expressInit middleware
300
+ index = stack.findIndex((item) => {
301
+ return item.name === 'expressInit';
302
+ });
204
303
  }
205
- addRouter.use(path, slowDown(slowDownOptions));
304
+ } else {
305
+ common.TraceUtils.warn(`${this.getServiceName()} is being started but the container stack is not available.`);
206
306
  }
207
- });
208
- if (addRouter.stack.length) {
209
- serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
210
307
  }
308
+ // notify that the service is loaded
309
+ this.loaded.next({ target: this });
211
310
  } catch (err) {
212
311
  common.TraceUtils.error('An error occurred while validating speed limit configuration.');
213
312
  common.TraceUtils.error(err);
@@ -216,6 +315,163 @@ class SpeedLimitService extends common.ApplicationService {
216
315
  });
217
316
  }
218
317
 
318
+ /**
319
+ * @returns {function} The type of store used for rate limiting.
320
+ */
321
+ getStoreType() {
322
+ const serviceConfiguration = this.getServiceConfiguration();
323
+ if (typeof serviceConfiguration.storeType !== 'string') {
324
+ return;
325
+ }
326
+ let StoreClass;
327
+ const store = serviceConfiguration.storeType.split('#');
328
+ if (store.length === 2) {
329
+ const storeModule = require(store[0]);
330
+ if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
331
+ StoreClass = storeModule[store[1]];
332
+ return StoreClass;
333
+ } else {
334
+ throw new Error(`${store} cannot be found or is inaccessible`);
335
+ }
336
+ } else {
337
+ StoreClass = require(store[0]);
338
+ return StoreClass;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Returns the service name.
344
+ * @returns {string} The service name.
345
+ */
346
+ getServiceName() {
347
+ return '@universis/janitor#SpeedLimitService';
348
+ }
349
+
350
+ /**
351
+ * Returns the service router that is used to register speed limit middleware.
352
+ * @returns {import('express').Router | import('express').Application} The service router.
353
+ */
354
+ getServiceContainer() {
355
+ return this.getApplication() && this.getApplication().serviceRouter;
356
+ }
357
+
358
+ /**
359
+ * Sets the speed limit configuration for a specific path.
360
+ * @param {string} path
361
+ * @param {{ profile: string } | import('express-slow-down').Options} options
362
+ * @returns {SpeedLimitService} The service instance for chaining.
363
+ */
364
+ set(path, options) {
365
+ let opts;
366
+ // get profile
367
+ if (options.profile) {
368
+ opts = this.serviceConfiguration.profiles.get(options.profile);
369
+ } else {
370
+ // or options defined inline
371
+ opts = options;
372
+ }
373
+ const slowDownOptions = Object.assign({
374
+ windowMs: 5 * 60 * 1000, // 5 minutes
375
+ delayAfter: 20, // 20 requests
376
+ delayMs: 500, // 500 ms
377
+ maxDelayMs: 10000 // 10 seconds
378
+ }, opts, {
379
+ keyGenerator: (req) => {
380
+ let remoteAddress;
381
+ if (this.proxyAddressForwarding) {
382
+ // get proxy headers or remote address
383
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
384
+ } else {
385
+ // get remote address
386
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
387
+ }
388
+ return `${path}:${remoteAddress}`;
389
+ }
390
+ });
391
+ if (Array.isArray(slowDownOptions.randomDelayMs)) {
392
+ slowDownOptions.delayMs = () => {
393
+ const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
394
+ return delayMs;
395
+ };
396
+ }
397
+ if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
398
+ slowDownOptions.maxDelayMs = () => {
399
+ const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
400
+ return maxDelayMs;
401
+ };
402
+ }
403
+ if (typeof slowDownOptions.store === 'undefined') {
404
+ const StoreClass = this.getStoreType();
405
+ if (typeof StoreClass === 'function') {
406
+ slowDownOptions.store = new StoreClass(this, slowDownOptions);
407
+ }
408
+ }
409
+ this.router.use(path, slowDown(slowDownOptions));
410
+ return this;
411
+ }
412
+
413
+
414
+ /**
415
+ * Unsets the speed limit configuration for a specific path.
416
+ * @param {string} path
417
+ * @return {SpeedLimitService} The service instance for chaining.
418
+ */
419
+ unset(path) {
420
+ const index = this.router.stack.findIndex((layer) => {
421
+ return layer.route && layer.route.path === path;
422
+ });
423
+ if (index !== -1) {
424
+ this.router.stack.splice(index, 1);
425
+ }
426
+ return this;
427
+ }
428
+
429
+ /**
430
+ *
431
+ * @returns {{extends?: string, profiles?: Array, paths?: Array}} The service configuration.
432
+ */
433
+ getServiceConfiguration() {
434
+ if (this.serviceConfiguration) {
435
+ return this.serviceConfiguration;
436
+ }
437
+ let serviceConfiguration = {
438
+ profiles: [],
439
+ paths: []
440
+ };
441
+ // get service configuration
442
+ const serviceConfigurationSource = this.getApplication().getConfiguration().getSourceAt('settings/universis/janitor/speedLimit');
443
+ if (serviceConfigurationSource) {
444
+ if (typeof serviceConfigurationSource.extends === 'string') {
445
+ // get additional configuration
446
+ const configurationPath = this.getApplication().getConfiguration().getConfigurationPath();
447
+ const extendsPath = path.resolve(configurationPath, serviceConfigurationSource.extends);
448
+ common.TraceUtils.log(`${this.getServiceName()} will try to extend service configuration using ${extendsPath}`);
449
+ serviceConfiguration = Object.assign({}, {
450
+ profiles: [],
451
+ paths: []
452
+ }, require(extendsPath));
453
+ } else {
454
+ common.TraceUtils.log(`${this.getServiceName()} will use service configuration from settings/universis/janitor/speedLimit`);
455
+ serviceConfiguration = Object.assign({}, {
456
+ profiles: [],
457
+ paths: []
458
+ }, serviceConfigurationSource);
459
+ }
460
+ }
461
+ const profilesArray = serviceConfiguration.profiles || [];
462
+ serviceConfiguration.profiles = new Map(profilesArray);
463
+ const pathsArray = serviceConfiguration.paths || [];
464
+ serviceConfiguration.paths = new Map(pathsArray);
465
+
466
+ Object.defineProperty(this, 'serviceConfiguration', {
467
+ value: serviceConfiguration,
468
+ writable: false,
469
+ enumerable: false,
470
+ configurable: true
471
+ });
472
+ return this.serviceConfiguration;
473
+ }
474
+
219
475
  }
220
476
 
221
477
  function _defineProperty(e, r, t) {
@@ -894,11 +1150,200 @@ class RemoteAddressValidator extends common.ApplicationService {
894
1150
 
895
1151
  }
896
1152
 
1153
+ class AppRateLimitService extends RateLimitService {
1154
+ /**
1155
+ *
1156
+ * @param {import('@themost/common').ApplicationBase} app
1157
+ */
1158
+ constructor(app) {
1159
+ super(app);
1160
+ }
1161
+
1162
+ getServiceName() {
1163
+ return '@universis/janitor#AppRateLimitService';
1164
+ }
1165
+
1166
+ getServiceContainer() {
1167
+ return this.getApplication() && this.getApplication().container;
1168
+ }
1169
+ }
1170
+
1171
+ class AppSpeedLimitService extends SpeedLimitService {
1172
+ /**
1173
+ *
1174
+ * @param {import('@themost/common').ApplicationBase} app
1175
+ */
1176
+ constructor(app) {
1177
+ super(app);
1178
+ }
1179
+
1180
+ getServiceName() {
1181
+ return '@universis/janitor#AppSpeedLimitService';
1182
+ }
1183
+
1184
+ getServiceContainer() {
1185
+ return this.getApplication() && this.getApplication().container;
1186
+ }
1187
+ }
1188
+
1189
+ class HttpBearerTokenRequired extends common.HttpError {
1190
+ constructor() {
1191
+ super(499, 'A token is required to fulfill the request.');
1192
+ this.code = 'E_TOKEN_REQUIRED';
1193
+ this.title = 'Token Required';
1194
+ }
1195
+ }
1196
+
1197
+ class HttpBearerTokenNotFound extends common.HttpError {
1198
+ constructor() {
1199
+ super(498, 'Token was not found.');
1200
+ this.code = 'E_TOKEN_NOT_FOUND';
1201
+ this.title = 'Invalid token';
1202
+ }
1203
+ }
1204
+
1205
+ class HttpBearerTokenExpired extends common.HttpError {
1206
+ constructor() {
1207
+ super(498, 'Token was expired or is in invalid state.');
1208
+ this.code = 'E_TOKEN_EXPIRED';
1209
+ this.title = 'Invalid token';
1210
+ }
1211
+ }
1212
+
1213
+ class HttpAccountDisabled extends common.HttpForbiddenError {
1214
+ constructor() {
1215
+ super('Access is denied. User account is disabled.');
1216
+ this.code = 'E_ACCOUNT_DISABLED';
1217
+ this.statusCode = 403.2;
1218
+ this.title = 'Disabled account';
1219
+ }
1220
+ }
1221
+
1222
+ class HttpBearerStrategy extends BearerStrategy {
1223
+ constructor() {
1224
+ super({
1225
+ passReqToCallback: true
1226
+ },
1227
+ /**
1228
+ * @param {Request} req
1229
+ * @param {string} token
1230
+ * @param {Function} done
1231
+ */
1232
+ function (req, token, done) {
1233
+ /**
1234
+ * Gets OAuth2 client services
1235
+ * @type {import('./OAuth2ClientService').OAuth2ClientService}
1236
+ */
1237
+ let client = req.context.getApplication().getStrategy(function OAuth2ClientService() {});
1238
+ // if client cannot be found
1239
+ if (client == null) {
1240
+ // throw configuration error
1241
+ return done(new Error('Invalid application configuration. OAuth2 client service cannot be found.'));
1242
+ }
1243
+ if (token == null) {
1244
+ // throw 499 Token Required error
1245
+ return done(new HttpBearerTokenRequired());
1246
+ }
1247
+ // get token info
1248
+ client.getTokenInfo(req.context, token).then((info) => {
1249
+ if (info == null) {
1250
+ // the specified token cannot be found - 498 invalid token with specific code
1251
+ return done(new HttpBearerTokenNotFound());
1252
+ }
1253
+ // if the given token is not active throw token expired - 498 invalid token with specific code
1254
+ if (!info.active) {
1255
+ return done(new HttpBearerTokenExpired());
1256
+ }
1257
+ // find user from token info
1258
+ return function () {
1259
+ /**
1260
+ * @type {import('./services/user-provisioning-mapper-service').UserProvisioningMapperService}
1261
+ */
1262
+ const mapper = req.context.getApplication().getService(function UserProvisioningMapperService() {});
1263
+ if (mapper == null) {
1264
+ return req.context.model('User').where('name').equal(info.username).silent().getItem();
1265
+ }
1266
+ return mapper.getUser(req.context, info);
1267
+ }().then((user) => {
1268
+ // check if userProvisioning service is installed and try to find related user only if user not found
1269
+ if (user == null) {
1270
+ /**
1271
+ * @type {import('./services/user-provisioning-service').UserProvisioningService}
1272
+ */
1273
+ const service = req.context.getApplication().getService(function UserProvisioningService() {});
1274
+ if (service == null) {
1275
+ return user;
1276
+ }
1277
+ return service.validateUser(req.context, info);
1278
+ }
1279
+ return user;
1280
+ }).then((user) => {
1281
+ // user cannot be found and of course cannot be authenticated (throw forbidden error)
1282
+ if (user == null) {
1283
+ // write access log for forbidden
1284
+ return done(new common.HttpForbiddenError());
1285
+ }
1286
+ // check if user has enabled attribute
1287
+ if (Object.prototype.hasOwnProperty.call(user, 'enabled') && !user.enabled) {
1288
+ //if user.enabled is off throw forbidden error
1289
+ return done(new HttpAccountDisabled('Access is denied. User account is disabled.'));
1290
+ }
1291
+ // otherwise return user data
1292
+ return done(null, {
1293
+ 'name': user.name,
1294
+ 'authenticationProviderKey': user.id,
1295
+ 'authenticationType': 'Bearer',
1296
+ 'authenticationToken': token,
1297
+ 'authenticationScope': info.scope
1298
+ });
1299
+ });
1300
+ }).catch((err) => {
1301
+ // end log token info request with error
1302
+ if (err && err.statusCode === 404) {
1303
+ // revert 404 not found returned by auth server to 498 invalid token
1304
+ return done(new HttpBearerTokenNotFound());
1305
+ }
1306
+ // otherwise continue with error
1307
+ return done(err);
1308
+ });
1309
+ });
1310
+ }
1311
+ }
1312
+
1313
+ class PassportService extends common.ApplicationService {
1314
+ constructor(app) {
1315
+ super(app);
1316
+ const authenticator = new passport.Authenticator();
1317
+ Object.defineProperty(this, 'authenticator', {
1318
+ configurable: true,
1319
+ enumerable: false,
1320
+ writable: false,
1321
+ value: authenticator
1322
+ });
1323
+ }
1324
+
1325
+ /**
1326
+ * @returns {import('passport').Authenticator}
1327
+ */
1328
+ getInstance() {
1329
+ return this.authenticator;
1330
+ }
1331
+
1332
+ }
1333
+
1334
+ exports.AppRateLimitService = AppRateLimitService;
1335
+ exports.AppSpeedLimitService = AppSpeedLimitService;
897
1336
  exports.DefaultScopeAccessConfiguration = DefaultScopeAccessConfiguration;
898
1337
  exports.EnableScopeAccessConfiguration = EnableScopeAccessConfiguration;
899
1338
  exports.ExtendScopeAccessConfiguration = ExtendScopeAccessConfiguration;
1339
+ exports.HttpAccountDisabled = HttpAccountDisabled;
1340
+ exports.HttpBearerStrategy = HttpBearerStrategy;
1341
+ exports.HttpBearerTokenExpired = HttpBearerTokenExpired;
1342
+ exports.HttpBearerTokenNotFound = HttpBearerTokenNotFound;
1343
+ exports.HttpBearerTokenRequired = HttpBearerTokenRequired;
900
1344
  exports.HttpRemoteAddrForbiddenError = HttpRemoteAddrForbiddenError;
901
1345
  exports.OAuth2ClientService = OAuth2ClientService;
1346
+ exports.PassportService = PassportService;
902
1347
  exports.RateLimitService = RateLimitService;
903
1348
  exports.RedisClientStore = RedisClientStore;
904
1349
  exports.RemoteAddressValidator = RemoteAddressValidator;