@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.esm.js CHANGED
@@ -3,6 +3,7 @@ import { ApplicationService, TraceUtils, ConfigurationStrategy, Args, HttpForbid
3
3
  import { rateLimit } from 'express-rate-limit';
4
4
  import express from 'express';
5
5
  import path from 'path';
6
+ import { BehaviorSubject } from 'rxjs';
6
7
  import slowDown from 'express-slow-down';
7
8
  import { RedisStore } from 'rate-limit-redis';
8
9
  import { Redis } from 'ioredis';
@@ -10,6 +11,8 @@ import '@themost/promise-sequence';
10
11
  import url, { URL } from 'url';
11
12
  import { Request } from 'superagent';
12
13
  import jwt from 'jsonwebtoken';
14
+ import BearerStrategy from 'passport-http-bearer';
15
+ import passport from 'passport';
13
16
 
14
17
  class RateLimitService extends ApplicationService {
15
18
  /**
@@ -17,87 +20,71 @@ class RateLimitService extends ApplicationService {
17
20
  */
18
21
  constructor(app) {
19
22
  super(app);
20
- app.serviceRouter.subscribe((serviceRouter) => {
21
- 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 BehaviorSubject(null);
32
+
33
+ const serviceContainer = this.getServiceContainer();
34
+ if (serviceContainer == null) {
35
+ 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) {
22
40
  return;
23
41
  }
24
42
  try {
25
- const addRouter = express.Router();
26
- let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/rateLimit') || {
27
- profiles: [],
28
- paths: []
29
- };
30
- if (serviceConfiguration.extends) {
31
- // get additional configuration
32
- const configurationPath = app.getConfiguration().getConfigurationPath();
33
- const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);
34
- TraceUtils.log(`@universis/janitor#RateLimitService will try to extend service configuration from ${extendsPath}`);
35
- serviceConfiguration = require(extendsPath);
36
- }
37
- const pathsArray = serviceConfiguration.paths || [];
38
- 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();
39
51
  // create maps
40
- const paths = new Map(pathsArray);
41
- const profiles = new Map(profilesArray);
52
+ const paths = serviceConfiguration.paths;
42
53
  if (paths.size === 0) {
43
- TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
44
- }
45
- // get proxy address forwarding option
46
- let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
47
- if (typeof proxyAddressForwarding !== 'boolean') {
48
- proxyAddressForwarding = false;
54
+ TraceUtils.warn(`${this.getServiceName()} is being started but the collection of paths is empty.`);
49
55
  }
50
56
  paths.forEach((value, path) => {
51
- let profile;
52
- // get profile
53
- if (value.profile) {
54
- profile = profiles.get(value.profile);
55
- } else {
56
- // or options defined inline
57
- profile = value;
58
- }
59
- if (profile != null) {
60
- const rateLimitOptions = Object.assign({
61
- windowMs: 5 * 60 * 1000, // 5 minutes
62
- limit: 50, // 50 requests
63
- legacyHeaders: true // send headers
64
- }, profile, {
65
- keyGenerator: (req) => {
66
- let remoteAddress;
67
- if (proxyAddressForwarding) {
68
- // get proxy headers or remote address
69
- remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
70
- } else {
71
- // get remote address
72
- remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
73
- }
74
- return `${path}:${remoteAddress}`;
75
- }
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';
76
70
  });
77
- if (typeof rateLimitOptions.store === 'string') {
78
- // load store
79
- const store = rateLimitOptions.store.split('#');
80
- let StoreClass;
81
- if (store.length === 2) {
82
- const storeModule = require(store[0]);
83
- if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
84
- StoreClass = storeModule[store[1]];
85
- rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
86
- } else {
87
- throw new Error(`${store} cannot be found or is inaccessible`);
88
- }
89
- } else {
90
- StoreClass = require(store[0]);
91
- // create store
92
- rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
93
- }
71
+ if (index === -1) {
72
+ // stage #2 find expressInit middleware
73
+ index = stack.findIndex((item) => {
74
+ return item.name === 'expressInit';
75
+ });
94
76
  }
95
- addRouter.use(path, 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
+ TraceUtils.warn(`${this.getServiceName()} is being started but the container stack is not available.`);
96
84
  }
97
- });
98
- if (addRouter.stack.length) {
99
- serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
100
85
  }
86
+ // notify that the service is loaded
87
+ this.loaded.next({ target: this });
101
88
  } catch (err) {
102
89
  TraceUtils.error('An error occurred while validating rate limit configuration.');
103
90
  TraceUtils.error(err);
@@ -106,106 +93,218 @@ class RateLimitService extends ApplicationService {
106
93
  });
107
94
  }
108
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
+ 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
+ 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, 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
+
109
242
  }
110
243
 
111
244
  class SpeedLimitService extends ApplicationService {
112
245
  constructor(app) {
113
246
  super(app);
114
247
 
115
- app.serviceRouter.subscribe((serviceRouter) => {
116
- 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 BehaviorSubject(null);
256
+
257
+ const serviceContainer = this.getServiceContainer();
258
+ if (serviceContainer == null) {
259
+ 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) {
117
265
  return;
118
266
  }
119
267
  try {
120
- const addRouter = express.Router();
121
- let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {
122
- profiles: [],
123
- paths: []
124
- };
125
- if (serviceConfiguration.extends) {
126
- // get additional configuration
127
- const configurationPath = app.getConfiguration().getConfigurationPath();
128
- const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);
129
- TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);
130
- serviceConfiguration = require(extendsPath);
131
- }
132
- const pathsArray = serviceConfiguration.paths || [];
133
- 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();
134
276
  // create maps
135
- const paths = new Map(pathsArray);
136
- const profiles = new Map(profilesArray);
277
+ const paths = serviceConfiguration.paths;
137
278
  if (paths.size === 0) {
138
- TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
139
- }
140
- // get proxy address forwarding option
141
- let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
142
- if (typeof proxyAddressForwarding !== 'boolean') {
143
- proxyAddressForwarding = false;
279
+ TraceUtils.warn(`${this.getServiceName()} is being started but the collection of paths is empty.`);
144
280
  }
145
281
  paths.forEach((value, path) => {
146
- let profile;
147
- // get profile
148
- if (value.profile) {
149
- profile = profiles.get(value.profile);
150
- } else {
151
- // or options defined inline
152
- profile = value;
153
- }
154
- if (profile != null) {
155
- const slowDownOptions = Object.assign({
156
- windowMs: 5 * 60 * 1000, // 5 minutes
157
- delayAfter: 20, // 20 requests
158
- delayMs: 500, // 500 ms
159
- maxDelayMs: 10000 // 10 seconds
160
- }, profile, {
161
- keyGenerator: (req) => {
162
- let remoteAddress;
163
- if (proxyAddressForwarding) {
164
- // get proxy headers or remote address
165
- remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
166
- } else {
167
- // get remote address
168
- remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
169
- }
170
- return `${path}:${remoteAddress}`;
171
- }
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';
172
295
  });
173
- if (Array.isArray(slowDownOptions.randomDelayMs)) {
174
- slowDownOptions.delayMs = () => {
175
- const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
176
- return delayMs;
177
- };
178
- }
179
- if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
180
- slowDownOptions.maxDelayMs = () => {
181
- const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
182
- return maxDelayMs;
183
- };
184
- }
185
- if (typeof slowDownOptions.store === 'string') {
186
- // load store
187
- const store = slowDownOptions.store.split('#');
188
- let StoreClass;
189
- if (store.length === 2) {
190
- const storeModule = require(store[0]);
191
- if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
192
- StoreClass = storeModule[store[1]];
193
- slowDownOptions.store = new StoreClass(this, slowDownOptions);
194
- } else {
195
- throw new Error(`${store} cannot be found or is inaccessible`);
196
- }
197
- } else {
198
- StoreClass = require(store[0]);
199
- // create store
200
- slowDownOptions.store = new StoreClass(this, slowDownOptions);
201
- }
296
+ if (index === -1) {
297
+ // stage #2 find expressInit middleware
298
+ index = stack.findIndex((item) => {
299
+ return item.name === 'expressInit';
300
+ });
202
301
  }
203
- addRouter.use(path, slowDown(slowDownOptions));
302
+ } else {
303
+ TraceUtils.warn(`${this.getServiceName()} is being started but the container stack is not available.`);
204
304
  }
205
- });
206
- if (addRouter.stack.length) {
207
- serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
208
305
  }
306
+ // notify that the service is loaded
307
+ this.loaded.next({ target: this });
209
308
  } catch (err) {
210
309
  TraceUtils.error('An error occurred while validating speed limit configuration.');
211
310
  TraceUtils.error(err);
@@ -214,6 +313,163 @@ class SpeedLimitService extends ApplicationService {
214
313
  });
215
314
  }
216
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
+ 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
+ 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
+
217
473
  }
218
474
 
219
475
  function _defineProperty(e, r, t) {
@@ -892,5 +1148,186 @@ class RemoteAddressValidator extends ApplicationService {
892
1148
 
893
1149
  }
894
1150
 
895
- export { DefaultScopeAccessConfiguration, EnableScopeAccessConfiguration, ExtendScopeAccessConfiguration, HttpRemoteAddrForbiddenError, OAuth2ClientService, RateLimitService, RedisClientStore, RemoteAddressValidator, ScopeAccessConfiguration, ScopeString, SpeedLimitService, validateScope };
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
+ class HttpBearerTokenRequired extends HttpError {
1188
+ constructor() {
1189
+ super(499, 'A token is required to fulfill the request.');
1190
+ this.code = 'E_TOKEN_REQUIRED';
1191
+ this.title = 'Token Required';
1192
+ }
1193
+ }
1194
+
1195
+ class HttpBearerTokenNotFound extends HttpError {
1196
+ constructor() {
1197
+ super(498, 'Token was not found.');
1198
+ this.code = 'E_TOKEN_NOT_FOUND';
1199
+ this.title = 'Invalid token';
1200
+ }
1201
+ }
1202
+
1203
+ class HttpBearerTokenExpired extends HttpError {
1204
+ constructor() {
1205
+ super(498, 'Token was expired or is in invalid state.');
1206
+ this.code = 'E_TOKEN_EXPIRED';
1207
+ this.title = 'Invalid token';
1208
+ }
1209
+ }
1210
+
1211
+ class HttpAccountDisabled extends HttpForbiddenError {
1212
+ constructor() {
1213
+ super('Access is denied. User account is disabled.');
1214
+ this.code = 'E_ACCOUNT_DISABLED';
1215
+ this.statusCode = 403.2;
1216
+ this.title = 'Disabled account';
1217
+ }
1218
+ }
1219
+
1220
+ class HttpBearerStrategy extends BearerStrategy {
1221
+ constructor() {
1222
+ super({
1223
+ passReqToCallback: true
1224
+ },
1225
+ /**
1226
+ * @param {Request} req
1227
+ * @param {string} token
1228
+ * @param {Function} done
1229
+ */
1230
+ function (req, token, done) {
1231
+ /**
1232
+ * Gets OAuth2 client services
1233
+ * @type {import('./OAuth2ClientService').OAuth2ClientService}
1234
+ */
1235
+ let client = req.context.getApplication().getStrategy(function OAuth2ClientService() {});
1236
+ // if client cannot be found
1237
+ if (client == null) {
1238
+ // throw configuration error
1239
+ return done(new Error('Invalid application configuration. OAuth2 client service cannot be found.'));
1240
+ }
1241
+ if (token == null) {
1242
+ // throw 499 Token Required error
1243
+ return done(new HttpBearerTokenRequired());
1244
+ }
1245
+ // get token info
1246
+ client.getTokenInfo(req.context, token).then((info) => {
1247
+ if (info == null) {
1248
+ // the specified token cannot be found - 498 invalid token with specific code
1249
+ return done(new HttpBearerTokenNotFound());
1250
+ }
1251
+ // if the given token is not active throw token expired - 498 invalid token with specific code
1252
+ if (!info.active) {
1253
+ return done(new HttpBearerTokenExpired());
1254
+ }
1255
+ // find user from token info
1256
+ return function () {
1257
+ /**
1258
+ * @type {import('./services/user-provisioning-mapper-service').UserProvisioningMapperService}
1259
+ */
1260
+ const mapper = req.context.getApplication().getService(function UserProvisioningMapperService() {});
1261
+ if (mapper == null) {
1262
+ return req.context.model('User').where('name').equal(info.username).silent().getItem();
1263
+ }
1264
+ return mapper.getUser(req.context, info);
1265
+ }().then((user) => {
1266
+ // check if userProvisioning service is installed and try to find related user only if user not found
1267
+ if (user == null) {
1268
+ /**
1269
+ * @type {import('./services/user-provisioning-service').UserProvisioningService}
1270
+ */
1271
+ const service = req.context.getApplication().getService(function UserProvisioningService() {});
1272
+ if (service == null) {
1273
+ return user;
1274
+ }
1275
+ return service.validateUser(req.context, info);
1276
+ }
1277
+ return user;
1278
+ }).then((user) => {
1279
+ // user cannot be found and of course cannot be authenticated (throw forbidden error)
1280
+ if (user == null) {
1281
+ // write access log for forbidden
1282
+ return done(new HttpForbiddenError());
1283
+ }
1284
+ // check if user has enabled attribute
1285
+ if (Object.prototype.hasOwnProperty.call(user, 'enabled') && !user.enabled) {
1286
+ //if user.enabled is off throw forbidden error
1287
+ return done(new HttpAccountDisabled('Access is denied. User account is disabled.'));
1288
+ }
1289
+ // otherwise return user data
1290
+ return done(null, {
1291
+ 'name': user.name,
1292
+ 'authenticationProviderKey': user.id,
1293
+ 'authenticationType': 'Bearer',
1294
+ 'authenticationToken': token,
1295
+ 'authenticationScope': info.scope
1296
+ });
1297
+ });
1298
+ }).catch((err) => {
1299
+ // end log token info request with error
1300
+ if (err && err.statusCode === 404) {
1301
+ // revert 404 not found returned by auth server to 498 invalid token
1302
+ return done(new HttpBearerTokenNotFound());
1303
+ }
1304
+ // otherwise continue with error
1305
+ return done(err);
1306
+ });
1307
+ });
1308
+ }
1309
+ }
1310
+
1311
+ class PassportService extends ApplicationService {
1312
+ constructor(app) {
1313
+ super(app);
1314
+ const authenticator = new passport.Authenticator();
1315
+ Object.defineProperty(this, 'authenticator', {
1316
+ configurable: true,
1317
+ enumerable: false,
1318
+ writable: false,
1319
+ value: authenticator
1320
+ });
1321
+ }
1322
+
1323
+ /**
1324
+ * @returns {import('passport').Authenticator}
1325
+ */
1326
+ getInstance() {
1327
+ return this.authenticator;
1328
+ }
1329
+
1330
+ }
1331
+
1332
+ export { AppRateLimitService, AppSpeedLimitService, DefaultScopeAccessConfiguration, EnableScopeAccessConfiguration, ExtendScopeAccessConfiguration, HttpAccountDisabled, HttpBearerStrategy, HttpBearerTokenExpired, HttpBearerTokenNotFound, HttpBearerTokenRequired, HttpRemoteAddrForbiddenError, OAuth2ClientService, PassportService, RateLimitService, RedisClientStore, RemoteAddressValidator, ScopeAccessConfiguration, ScopeString, SpeedLimitService, validateScope };
896
1333
  //# sourceMappingURL=index.esm.js.map