@universis/janitor 1.3.0 → 1.4.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/.gitlab-ci.yml CHANGED
@@ -6,10 +6,8 @@ stages:
6
6
 
7
7
  npm_publish:
8
8
  stage: releases
9
- only:
10
- - /^(\d+\.)(\d+\.)(\d+)$/
11
- except:
12
- - branches
9
+ rules:
10
+ - if: $CI_PROJECT_NAMESPACE == "universis" && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG =~ /^v(\d+.)(\d+.)(\d+)$/
13
11
  script:
14
12
  - npm i
15
13
  - npm run build
package/README.md CHANGED
@@ -58,6 +58,30 @@ and start configuring rate limited endpoints under `settings/universis/rateLimit
58
58
 
59
59
  Read more about rate limit configuration at [express-rate-limit documentation](https://github.com/express-rate-limit/express-rate-limit#configuration)
60
60
 
61
+ ### Enable rate limit headers over CORS
62
+
63
+ Each `RateLimitService` profile has an option to include a set of headers in the response. This can be done by setting the `headers` option to `true` in the profile configuration. This operation will add the following headers to the response:
64
+
65
+ - `X-RateLimit-Limit` - the maximum number of requests allowed in the current window
66
+ - `X-RateLimit-Remaining` - the number of requests remaining in the current window
67
+ - `X-RateLimit-Reset` - the number of milliseconds remaining until the window resets
68
+
69
+ Rate limit headers will be available only if the request is made from the same origin. If the request is made from a different origin, the headers will not be included in the response and should be configured to be exposed by CORS.
70
+
71
+ ```json
72
+ {
73
+ "settings": {
74
+ "cors": {
75
+ "exposedHeaders": [
76
+ "X-Rate-Limit",
77
+ "X-Rate-Remaining",
78
+ "X-Rate-Reset"
79
+ ]
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
61
85
 
62
86
  ## SpeedLimitService
63
87
 
@@ -180,6 +204,94 @@ Read more about speed limit configuration at [express-slow-down documentation](h
180
204
  }
181
205
  }
182
206
  }
207
+ ```
208
+
209
+ ### Enable speed limit headers over CORS
210
+
211
+ Each `SpeedLimitService` profile has an option to include a set of headers in the response. This can be done by setting the `headers` option to `true` in the profile configuration. This operation will add the following headers to the response:
212
+
213
+ - `X-SlowDown-Limit` - the maximum number of requests allowed in the current window
214
+ - `X-SlowDown-Remaining` - the number of requests remaining in the current window
215
+ - `X-SlowDown-Reset` - the number of milliseconds remaining until the window resets
216
+
217
+ Speed limit headers will be available only if the request is made from the same origin. If the request is made from a different origin, the headers will not be included in the response and should be configured to be exposed by CORS.
218
+
219
+ ```json
220
+ {
221
+ "settings": {
222
+ "cors": {
223
+ "exposedHeaders": [
224
+ "X-SlowDown-Limit",
225
+ "X-SlowDown-Remaining",
226
+ "X-SlowDown-Reset"
227
+ ]
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ## ScopeAccessConfiguration
183
234
 
235
+ `ScopeAccessConfiguration` is a configurable application service for limiting access to service endpoints based on user scopes.
184
236
 
237
+ Register service under application services:
238
+
239
+ ```json
240
+ {
241
+ "services": [
242
+ {
243
+ "serviceType": "@universis/janitor#ScopeAccessConfiguration"
244
+ }
245
+ ]
246
+ }
247
+ ```
248
+
249
+ add `<config directory>/scope.access.json` and start configuring scope limited endpoints:
250
+
251
+ ```json
252
+ [
253
+ {
254
+ "scope": [
255
+ "registrar"
256
+ ],
257
+ "resource": "/api/",
258
+ "access": [
259
+ "read",
260
+ "write"
261
+ ]
262
+ },
263
+ {
264
+ "scope": [
265
+ "students",
266
+ "teachers",
267
+ "registrar"
268
+ ],
269
+ "resource": "/api/workspaces/locales",
270
+ "access": [
271
+ "read"
272
+ ]
273
+ }
274
+ ]
275
+ ```
276
+
277
+ Each configuration element consists of `scope` array, `resource` string and `access` array.
278
+ If the user has at least one of the scopes from the `scope` array,
279
+ and the user has at least one of the `access` array, the user will be granted access to the resource.
280
+
281
+ There are two different access types:
282
+
283
+ - `read` - grants access to read the resource (GET, HEAD, OPTIONS)
284
+ - `write` - grants access to write the resource (POST, PUT, PATCH, DELETE)
285
+
286
+ The resource string can be a path or a regular expression e.g. `/api/instructors/me/exams/(\d+)/types`
287
+
288
+ `validateScope` express.js middleware is available for validating user scope access to the resource:
289
+
290
+ ```javascript
291
+ import { validateScope } from '@universis/janitor';
292
+ app.use('/api', passport.authenticate('bearer', {session: false}), validateScope(), (req, res) => {
293
+ res.send('Hello World!')
294
+ });
295
+ ```
185
296
 
297
+ A `403 - Access denied due to authorization scopes` error will be thrown if the user does not have access to the resource.
@@ -48,7 +48,6 @@ class RedisClientStore extends _rateLimitRedis.default {
48
48
  const args = Array.from(arguments);
49
49
  const self = this;
50
50
  if (args[0] === 'SCRIPT') {
51
- _common.TraceUtils.debug('RedisClientStore: Creating new client for sending script command');
52
51
  const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
53
52
  host: '127.0.0.1',
54
53
  port: 6379
@@ -63,7 +62,6 @@ class RedisClientStore extends _rateLimitRedis.default {
63
62
  return Promise.reject(error);
64
63
  }).finally(() => {
65
64
  if (client.isOpen) {
66
- _common.TraceUtils.debug('RedisClientStore: Closing client of sending script command');
67
65
  client.disconnect().catch((errDisconnect) => {
68
66
  _common.TraceUtils.error(errDisconnect);
69
67
  });
@@ -71,7 +69,6 @@ class RedisClientStore extends _rateLimitRedis.default {
71
69
  });
72
70
  }
73
71
  if (self.client == null) {
74
- _common.TraceUtils.debug('RedisClientStore: Creating store client for sending commands');
75
72
  const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
76
73
  host: '127.0.0.1',
77
74
  port: 6379
@@ -86,7 +83,6 @@ class RedisClientStore extends _rateLimitRedis.default {
86
83
  return Promise.reject(error);
87
84
  });
88
85
  }
89
- _common.TraceUtils.debug('RedisClientStore: Opening redis store client for sending commands');
90
86
  return self.client.connect().then(() => {
91
87
  // send load script commands once
92
88
  return (() => {
@@ -1 +1 @@
1
- {"version":3,"file":"RedisClientStore.js","names":["_common","require","_rateLimitRedis","_interopRequireDefault","_redis","obj","__esModule","default","_defineProperty","key","value","_toPropertyKey","Object","defineProperty","enumerable","configurable","writable","arg","_toPrimitive","String","input","hint","prim","Symbol","toPrimitive","undefined","res","call","TypeError","Number","superLoadIncrementScript","superLoadGetScript","noLoadGetScript","noLoadIncrementScript","RedisStore","prototype","loadIncrementScript","name","loadGetScript","RedisClientStore","constructor","service","options","sendCommand","args","Array","from","arguments","self","TraceUtils","debug","connectOptions","getApplication","getConfiguration","getSourceAt","host","port","client","createClient","connect","then","catch","error","message","warn","JSON","stringify","Promise","reject","finally","isOpen","disconnect","errDisconnect","incrementScriptSha","postInit","resolve","init","err","getScriptSha","sequence","exports"],"sources":["../src/RedisClientStore.js"],"sourcesContent":["import { TraceUtils } from '@themost/common';\nimport RedisStore from 'rate-limit-redis';\nimport { createClient } from 'redis';\nimport '@themost/promise-sequence';\n\nlet superLoadIncrementScript;\nlet superLoadGetScript;\n\nfunction noLoadGetScript() {\n //\n}\n\nfunction noLoadIncrementScript() {\n //\n}\n\nif (RedisStore.prototype.loadIncrementScript.name === 'loadIncrementScript') {\n // get super method for future use\n superLoadIncrementScript = RedisStore.prototype.loadIncrementScript;\n RedisStore.prototype.loadIncrementScript = noLoadIncrementScript;\n}\n\nif (RedisStore.prototype.loadGetScript.name === 'loadGetScript') {\n // get super method\n superLoadGetScript = RedisStore.prototype.loadGetScript;\n RedisStore.prototype.loadGetScript = noLoadGetScript;\n}\n\nclass RedisClientStore extends RedisStore {\n\n /**\n * @type {import('redis').RedisClientType}\n */\n client;\n\n /**\n * \n * @param {import('@themost/common').ApplicationService} service\n * @param {{windowMs: number}} options\n */\n constructor(service, options) {\n super({\n /**\n * @param {...string} args\n * @returns {Promise<*>}\n */\n sendCommand: function () {\n const args = Array.from(arguments);\n const self = this;\n if (args[0] === 'SCRIPT') {\n TraceUtils.debug('RedisClientStore: Creating new client for sending script command');\n const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {\n host: '127.0.0.1',\n port: 6379\n };\n const client = createClient(connectOptions);\n return client.connect().then(() => {\n return client.sendCommand(args);\n }).catch((error) => {\n if (error instanceof TypeError && error.message === 'Invalid argument type') {\n TraceUtils.warn('RedisClientStore: Invalid argument type: ' + JSON.stringify(args));\n }\n return Promise.reject(error);\n }).finally(() => {\n if (client.isOpen) {\n TraceUtils.debug('RedisClientStore: Closing client of sending script command');\n client.disconnect().catch((errDisconnect) => {\n TraceUtils.error(errDisconnect);\n });\n }\n });\n }\n if (self.client == null) {\n TraceUtils.debug('RedisClientStore: Creating store client for sending commands');\n const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {\n host: '127.0.0.1',\n port: 6379\n };\n self.client = createClient(connectOptions);\n }\n if (self.client.isOpen) {\n return self.client.sendCommand(args).catch((error) => {\n if (error instanceof TypeError && error.message === 'Invalid argument type') {\n TraceUtils.warn('RedisClientStore: Invalid argument type: ' + JSON.stringify(args));\n }\n return Promise.reject(error);\n });\n }\n TraceUtils.debug('RedisClientStore: Opening redis store client for sending commands');\n return self.client.connect().then(() => {\n // send load script commands once\n return (() => {\n if (self.incrementScriptSha == null) {\n return this.postInit();\n }\n return Promise.resolve();\n })().then(() => {\n // send command \n args[1] = self.incrementScriptSha;\n return self.client.sendCommand(args).catch((error) => {\n if (error instanceof TypeError && error.message === 'Invalid argument type') {\n TraceUtils.warn('RedisClientStore: Invalid argument type: ' + JSON.stringify(args));\n }\n return Promise.reject(error);\n });\n });\n });\n }\n });\n this.init(options);\n TraceUtils.debug('RedisClientStore: Starting up and loading increment and get scripts.');\n void this.postInit().then(() => {\n TraceUtils.debug('RedisClientStore: Successfully loaded increment and get scripts.');\n }).catch((err) => {\n TraceUtils.error('RedisClientStore: Failed to load increment and get scripts.');\n TraceUtils.error(err);\n });\n }\n async postInit() {\n const [incrementScriptSha, getScriptSha] = await Promise.sequence([\n () => superLoadIncrementScript.call(this),\n () => superLoadGetScript.call(this)\n ]);\n this.incrementScriptSha = incrementScriptSha;\n this.getScriptSha = getScriptSha;\n }\n\n}\n\nexport {\n RedisClientStore\n}"],"mappings":"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"}
1
+ {"version":3,"file":"RedisClientStore.js","names":["_common","require","_rateLimitRedis","_interopRequireDefault","_redis","obj","__esModule","default","_defineProperty","key","value","_toPropertyKey","Object","defineProperty","enumerable","configurable","writable","arg","_toPrimitive","String","input","hint","prim","Symbol","toPrimitive","undefined","res","call","TypeError","Number","superLoadIncrementScript","superLoadGetScript","noLoadGetScript","noLoadIncrementScript","RedisStore","prototype","loadIncrementScript","name","loadGetScript","RedisClientStore","constructor","service","options","sendCommand","args","Array","from","arguments","self","connectOptions","getApplication","getConfiguration","getSourceAt","host","port","client","createClient","connect","then","catch","error","message","TraceUtils","warn","JSON","stringify","Promise","reject","finally","isOpen","disconnect","errDisconnect","incrementScriptSha","postInit","resolve","init","debug","err","getScriptSha","sequence","exports"],"sources":["../src/RedisClientStore.js"],"sourcesContent":["import { TraceUtils } from '@themost/common';\nimport RedisStore from 'rate-limit-redis';\nimport { createClient } from 'redis';\nimport '@themost/promise-sequence';\n\nlet superLoadIncrementScript;\nlet superLoadGetScript;\n\nfunction noLoadGetScript() {\n //\n}\n\nfunction noLoadIncrementScript() {\n //\n}\n\nif (RedisStore.prototype.loadIncrementScript.name === 'loadIncrementScript') {\n // get super method for future use\n superLoadIncrementScript = RedisStore.prototype.loadIncrementScript;\n RedisStore.prototype.loadIncrementScript = noLoadIncrementScript;\n}\n\nif (RedisStore.prototype.loadGetScript.name === 'loadGetScript') {\n // get super method\n superLoadGetScript = RedisStore.prototype.loadGetScript;\n RedisStore.prototype.loadGetScript = noLoadGetScript;\n}\n\nclass RedisClientStore extends RedisStore {\n\n /**\n * @type {import('redis').RedisClientType}\n */\n client;\n\n /**\n * \n * @param {import('@themost/common').ApplicationService} service\n * @param {{windowMs: number}} options\n */\n constructor(service, options) {\n super({\n /**\n * @param {...string} args\n * @returns {Promise<*>}\n */\n sendCommand: function () {\n const args = Array.from(arguments);\n const self = this;\n if (args[0] === 'SCRIPT') {\n const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {\n host: '127.0.0.1',\n port: 6379\n };\n const client = createClient(connectOptions);\n return client.connect().then(() => {\n return client.sendCommand(args);\n }).catch((error) => {\n if (error instanceof TypeError && error.message === 'Invalid argument type') {\n TraceUtils.warn('RedisClientStore: Invalid argument type: ' + JSON.stringify(args));\n }\n return Promise.reject(error);\n }).finally(() => {\n if (client.isOpen) {\n client.disconnect().catch((errDisconnect) => {\n TraceUtils.error(errDisconnect);\n });\n }\n });\n }\n if (self.client == null) {\n const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {\n host: '127.0.0.1',\n port: 6379\n };\n self.client = createClient(connectOptions);\n }\n if (self.client.isOpen) {\n return self.client.sendCommand(args).catch((error) => {\n if (error instanceof TypeError && error.message === 'Invalid argument type') {\n TraceUtils.warn('RedisClientStore: Invalid argument type: ' + JSON.stringify(args));\n }\n return Promise.reject(error);\n });\n }\n return self.client.connect().then(() => {\n // send load script commands once\n return (() => {\n if (self.incrementScriptSha == null) {\n return this.postInit();\n }\n return Promise.resolve();\n })().then(() => {\n // send command \n args[1] = self.incrementScriptSha;\n return self.client.sendCommand(args).catch((error) => {\n if (error instanceof TypeError && error.message === 'Invalid argument type') {\n TraceUtils.warn('RedisClientStore: Invalid argument type: ' + JSON.stringify(args));\n }\n return Promise.reject(error);\n });\n });\n });\n }\n });\n this.init(options);\n TraceUtils.debug('RedisClientStore: Starting up and loading increment and get scripts.');\n void this.postInit().then(() => {\n TraceUtils.debug('RedisClientStore: Successfully loaded increment and get scripts.');\n }).catch((err) => {\n TraceUtils.error('RedisClientStore: Failed to load increment and get scripts.');\n TraceUtils.error(err);\n });\n }\n async postInit() {\n const [incrementScriptSha, getScriptSha] = await Promise.sequence([\n () => superLoadIncrementScript.call(this),\n () => superLoadGetScript.call(this)\n ]);\n this.incrementScriptSha = incrementScriptSha;\n this.getScriptSha = getScriptSha;\n }\n\n}\n\nexport {\n RedisClientStore\n}"],"mappings":"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"}
@@ -8,22 +8,27 @@
8
8
  */
9
9
  import {ConfigurationBase, ConfigurationStrategy} from '@themost/common';
10
10
 
11
+ export declare class ScopeString {
12
+ constructor(str: string);
13
+ split(): string[];
14
+ }
15
+
11
16
  /**
12
17
  * Declares a configuration element for managing scope-based permissions on server resources
13
18
  */
14
- declare interface ScopeAccessConfigurationElement {
19
+ export declare interface ScopeAccessConfigurationElement {
15
20
  /**
16
21
  * Gets or sets an array of strings that holds an array of scopes e.g. students or students:read or students,teachers etc
17
22
  */
18
- scope: Array<string>,
23
+ scope: string[],
19
24
  /**
20
- * Gets or sets a string which represents the regular expression that is going to used to validate endpoint
25
+ * Gets or sets a string which represents the regular expression that is going to be used for validating endpoint
21
26
  */
22
27
  resource: string;
23
28
  /**
24
29
  * Gets or sets an array of strings which represents the access levels for the given scopes e.g. READ or READ,WRITE etc
25
30
  */
26
- access: Array<string>;
31
+ access: string[];
27
32
  /**
28
33
  * Gets or sets a string which represents a short description for this item
29
34
  */
@@ -37,7 +42,7 @@ export declare class ScopeAccessConfiguration extends ConfigurationStrategy {
37
42
  /**
38
43
  * Gets an array of scope access configuration elements
39
44
  */
40
- public elements: Array<ScopeAccessConfigurationElement>;
45
+ public elements: ScopeAccessConfigurationElement[];
41
46
 
42
47
  /**
43
48
  * Verifies the given request and returns a promise that resolves with a scope access configuration element
@@ -51,10 +56,10 @@ export declare class DefaultScopeAccessConfiguration extends ScopeAccessConfigur
51
56
  /**
52
57
  * Gets an array of scope access configuration elements
53
58
  */
54
- public elements: Array<ScopeAccessConfigurationElement>;
59
+ public elements: ScopeAccessConfigurationElement[];
55
60
  /**
56
61
  * Verifies the given request and returns a promise that resolves with a scope access configuration element
57
62
  */
58
63
  verify(req: Request): Promise<ScopeAccessConfigurationElement>;
59
64
 
60
- }
65
+ }
@@ -1,12 +1,42 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.ScopeAccessConfiguration = exports.DefaultScopeAccessConfiguration = void 0;var _common = require("@themost/common");
1
+ "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.ScopeString = exports.ScopeAccessConfiguration = exports.DefaultScopeAccessConfiguration = void 0;var _common = require("@themost/common");
2
2
  var _path = _interopRequireDefault(require("path"));
3
3
  var _url = _interopRequireDefault(require("url"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}
4
4
 
5
5
  const HTTP_METHOD_REGEXP = /^\b(POST|PUT|PATCH|DELETE)\b$/i;
6
6
 
7
+ class ScopeString {
8
+ constructor(str) {
9
+ this.value = str;
10
+ }
11
+ toString() {
12
+ return this.value;
13
+ }
14
+ /**
15
+ * Splits a comma-separated or space-separated scope string e.g. "profile email" or "profile,email"
16
+ *
17
+ * Important note: https://www.rfc-editor.org/rfc/rfc6749#section-3.3 defines the regular expression of access token scopes
18
+ * which is a space separated string. Several OAuth2 servers use a comma-separated list instead.
19
+ *
20
+ * The operation will try to use both implementations by excluding comma ',' from access token regular expressions
21
+ * @returns {Array<string>}
22
+ */
23
+ split() {
24
+ // the default regular expression includes comma /([\x21\x23-\x5B\x5D-\x7E]+)/g
25
+ // the modified regular expression excludes comma /x2C /([\x21\x23-\x2B\x2D-\x5B\x5D-\x7E]+)/g
26
+ const re = /([\x21\x23-\x2B\x2D-\x5B\x5D-\x7E]+)/g;
27
+ const results = [];
28
+ let match = re.exec(this.value);
29
+ while (match !== null) {
30
+ results.push(match[0]);
31
+ match = re.exec(this.value);
32
+ }
33
+ return results;
34
+ }
35
+ }exports.ScopeString = ScopeString;
36
+
7
37
  class ScopeAccessConfiguration extends _common.ConfigurationStrategy {
8
38
  /**
9
- * @param {ConfigurationBase} configuration
39
+ * @param {import('@themost/common').ConfigurationBase} configuration
10
40
  */
11
41
  constructor(configuration) {
12
42
  super(configuration);
@@ -35,7 +65,7 @@ class ScopeAccessConfiguration extends _common.ConfigurationStrategy {
35
65
  // get original url
36
66
  let reqUrl = _url.default.parse(req.originalUrl).pathname;
37
67
  // get user context scopes as array e.g, ['students', 'students:read']
38
- let reqScopes = req.context.user.authenticationScope.split(',');
68
+ let reqScopes = new ScopeString(req.context.user.authenticationScope).split();
39
69
  // get user access based on HTTP method e.g. GET -> read access
40
70
  let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';
41
71
  let result = this.elements.find((x) => {
@@ -67,7 +97,7 @@ class ScopeAccessConfiguration extends _common.ConfigurationStrategy {
67
97
  */exports.ScopeAccessConfiguration = ScopeAccessConfiguration;
68
98
  class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
69
99
  /**
70
- * @param {ConfigurationBase} configuration
100
+ * @param {import('@themost/common').ConfigurationBase} configuration
71
101
  */
72
102
  constructor(configuration) {
73
103
  super(configuration);
@@ -1 +1 @@
1
- {"version":3,"file":"ScopeAccessConfiguration.js","names":["_common","require","_path","_interopRequireDefault","_url","obj","__esModule","default","HTTP_METHOD_REGEXP","ScopeAccessConfiguration","ConfigurationStrategy","constructor","configuration","elements","Object","defineProperty","get","enumerable","verify","req","Promise","resolve","reject","Args","notNull","context","user","authenticationScope","length","reqUrl","url","parse","originalUrl","pathname","reqScopes","split","reqAccess","test","method","result","find","x","access","indexOf","RegExp","resource","scope","y","err","exports","DefaultScopeAccessConfiguration","defaults","path","getConfigurationPath","code","push","apply"],"sources":["../src/ScopeAccessConfiguration.js"],"sourcesContent":["import {ConfigurationStrategy, Args} from '@themost/common';\nimport path from 'path';\nimport url from 'url';\n\nconst HTTP_METHOD_REGEXP = /^\\b(POST|PUT|PATCH|DELETE)\\b$/i;\n\nclass ScopeAccessConfiguration extends ConfigurationStrategy {\n /**\n * @param {ConfigurationBase} configuration\n */\n constructor(configuration) {\n super(configuration);\n let elements = [];\n // define property\n Object.defineProperty(this, 'elements', {\n get: () => {\n return elements;\n },\n enumerable: true\n });\n }\n\n /**\n * @param {Request} req\n * @returns Promise<ScopeAccessConfigurationElement>\n */\n verify(req) {\n return new Promise((resolve, reject) => {\n try {\n // validate request context\n Args.notNull(req.context,'Context');\n // validate request context user\n Args.notNull(req.context.user,'User');\n if (req.context.user.authenticationScope && req.context.user.authenticationScope.length>0) {\n // get original url\n let reqUrl = url.parse(req.originalUrl).pathname;\n // get user context scopes as array e.g, ['students', 'students:read']\n let reqScopes = req.context.user.authenticationScope.split(',');\n // get user access based on HTTP method e.g. GET -> read access\n let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';\n let result = this.elements.find(x => {\n // filter element by access level\n return x.access.indexOf(reqAccess)>=0\n // resource path\n && new RegExp( '^' + x.resource, 'i').test(reqUrl)\n // and scopes\n && x.scope.find(y => {\n // search user scopes (validate wildcard scope)\n return y === '*' || reqScopes.indexOf(y)>=0;\n });\n });\n return resolve(result);\n }\n return resolve();\n }\n catch(err) {\n return reject(err);\n }\n\n });\n }\n\n}\n\n/**\n * @class\n */\nclass DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {\n /**\n * @param {ConfigurationBase} configuration\n */\n constructor(configuration) {\n super(configuration);\n let defaults = [];\n // load scope access from configuration resource\n try {\n /**\n * @type {Array<ScopeAccessConfigurationElement>}\n */\n defaults = require(path.resolve(configuration.getConfigurationPath(), 'scope.access.json'));\n }\n catch(err) {\n // if an error occurred other than module not found (there are no default access policies)\n if (err.code !== 'MODULE_NOT_FOUND') {\n // throw error\n throw err;\n }\n // otherwise continue\n }\n this.elements.push.apply(this.elements, defaults);\n }\n}\n\nexport {\n ScopeAccessConfiguration,\n DefaultScopeAccessConfiguration\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ScopeAccessConfiguration.js","names":["_common","require","_path","_interopRequireDefault","_url","obj","__esModule","default","HTTP_METHOD_REGEXP","ScopeString","constructor","str","value","toString","split","re","results","match","exec","push","exports","ScopeAccessConfiguration","ConfigurationStrategy","configuration","elements","Object","defineProperty","get","enumerable","verify","req","Promise","resolve","reject","Args","notNull","context","user","authenticationScope","length","reqUrl","url","parse","originalUrl","pathname","reqScopes","reqAccess","test","method","result","find","x","access","indexOf","RegExp","resource","scope","y","err","DefaultScopeAccessConfiguration","defaults","path","getConfigurationPath","code","apply"],"sources":["../src/ScopeAccessConfiguration.js"],"sourcesContent":["import {ConfigurationStrategy, Args} from '@themost/common';\nimport path from 'path';\nimport url from 'url';\n\nconst HTTP_METHOD_REGEXP = /^\\b(POST|PUT|PATCH|DELETE)\\b$/i;\n\nclass ScopeString {\n constructor(str) {\n this.value = str;\n }\n toString() {\n return this.value;\n }\n /**\n * Splits a comma-separated or space-separated scope string e.g. \"profile email\" or \"profile,email\"\n *\n * Important note: https://www.rfc-editor.org/rfc/rfc6749#section-3.3 defines the regular expression of access token scopes\n * which is a space separated string. Several OAuth2 servers use a comma-separated list instead.\n *\n * The operation will try to use both implementations by excluding comma ',' from access token regular expressions\n * @returns {Array<string>}\n */\n split() {\n // the default regular expression includes comma /([\\x21\\x23-\\x5B\\x5D-\\x7E]+)/g\n // the modified regular expression excludes comma /x2C /([\\x21\\x23-\\x2B\\x2D-\\x5B\\x5D-\\x7E]+)/g\n const re = /([\\x21\\x23-\\x2B\\x2D-\\x5B\\x5D-\\x7E]+)/g\n const results = [];\n let match = re.exec(this.value);\n while(match !== null) {\n results.push(match[0]);\n match = re.exec(this.value);\n }\n return results;\n }\n}\n\nclass ScopeAccessConfiguration extends ConfigurationStrategy {\n /**\n * @param {import('@themost/common').ConfigurationBase} configuration\n */\n constructor(configuration) {\n super(configuration);\n let elements = [];\n // define property\n Object.defineProperty(this, 'elements', {\n get: () => {\n return elements;\n },\n enumerable: true\n });\n }\n\n /**\n * @param {Request} req\n * @returns Promise<ScopeAccessConfigurationElement>\n */\n verify(req) {\n return new Promise((resolve, reject) => {\n try {\n // validate request context\n Args.notNull(req.context,'Context');\n // validate request context user\n Args.notNull(req.context.user,'User');\n if (req.context.user.authenticationScope && req.context.user.authenticationScope.length>0) {\n // get original url\n let reqUrl = url.parse(req.originalUrl).pathname;\n // get user context scopes as array e.g, ['students', 'students:read']\n let reqScopes = new ScopeString(req.context.user.authenticationScope).split();\n // get user access based on HTTP method e.g. GET -> read access\n let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';\n let result = this.elements.find(x => {\n // filter element by access level\n return x.access.indexOf(reqAccess)>=0\n // resource path\n && new RegExp( '^' + x.resource, 'i').test(reqUrl)\n // and scopes\n && x.scope.find(y => {\n // search user scopes (validate wildcard scope)\n return y === '*' || reqScopes.indexOf(y)>=0;\n });\n });\n return resolve(result);\n }\n return resolve();\n }\n catch(err) {\n return reject(err);\n }\n\n });\n }\n\n}\n\n/**\n * @class\n */\nclass DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {\n /**\n * @param {import('@themost/common').ConfigurationBase} configuration\n */\n constructor(configuration) {\n super(configuration);\n let defaults = [];\n // load scope access from configuration resource\n try {\n /**\n * @type {Array<ScopeAccessConfigurationElement>}\n */\n defaults = require(path.resolve(configuration.getConfigurationPath(), 'scope.access.json'));\n }\n catch(err) {\n // if an error occurred other than module not found (there are no default access policies)\n if (err.code !== 'MODULE_NOT_FOUND') {\n // throw error\n throw err;\n }\n // otherwise continue\n }\n this.elements.push.apply(this.elements, defaults);\n }\n}\n\nexport {\n ScopeString,\n ScopeAccessConfiguration,\n DefaultScopeAccessConfiguration\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './RateLimitService';
2
2
  export * from './SpeedLimitService';
3
3
  export * from './RedisClientStore';
4
- export * from './ScopeAccessConfiguration';
4
+ export * from './ScopeAccessConfiguration';
5
+ export * from './validateScope';
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ var _RateLimitService = require("./RateLimitService");Object.keys(_RateLimitServ
3
3
  var _SpeedLimitService = require("./SpeedLimitService");Object.keys(_SpeedLimitService).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _SpeedLimitService[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _SpeedLimitService[key];} });});
4
4
  var _RedisClientStore = require("./RedisClientStore");Object.keys(_RedisClientStore).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _RedisClientStore[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _RedisClientStore[key];} });});
5
5
  var _ScopeAccessConfiguration = require("./ScopeAccessConfiguration");Object.keys(_ScopeAccessConfiguration).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _ScopeAccessConfiguration[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _ScopeAccessConfiguration[key];} });});
6
+ var _validateScope = require("./validateScope");Object.keys(_validateScope).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _validateScope[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _validateScope[key];} });});
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["require","_RateLimitService","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_SpeedLimitService","_RedisClientStore","_ScopeAccessConfiguration"],"sources":["../src/index.js"],"sourcesContent":["import './polyfills';\nexport * from './RateLimitService';\nexport * from './SpeedLimitService';\nexport * from './RedisClientStore';\nexport * from './ScopeAccessConfiguration';"],"mappings":"2EAAAA,OAAA;AACA,IAAAC,iBAAA,GAAAD,OAAA,uBAAAE,MAAA,CAAAC,IAAA,CAAAF,iBAAA,EAAAG,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAJ,iBAAA,CAAAI,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAR,iBAAA,CAAAI,GAAA;AACA,IAAAK,kBAAA,GAAAV,OAAA,wBAAAE,MAAA,CAAAC,IAAA,CAAAO,kBAAA,EAAAN,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,kBAAA,CAAAL,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAC,kBAAA,CAAAL,GAAA;AACA,IAAAM,iBAAA,GAAAX,OAAA,uBAAAE,MAAA,CAAAC,IAAA,CAAAQ,iBAAA,EAAAP,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,iBAAA,CAAAN,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAE,iBAAA,CAAAN,GAAA;AACA,IAAAO,yBAAA,GAAAZ,OAAA,+BAAAE,MAAA,CAAAC,IAAA,CAAAS,yBAAA,EAAAR,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,yBAAA,CAAAP,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAG,yBAAA,CAAAP,GAAA"}
1
+ {"version":3,"file":"index.js","names":["require","_RateLimitService","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_SpeedLimitService","_RedisClientStore","_ScopeAccessConfiguration","_validateScope"],"sources":["../src/index.js"],"sourcesContent":["import './polyfills';\nexport * from './RateLimitService';\nexport * from './SpeedLimitService';\nexport * from './RedisClientStore';\nexport * from './ScopeAccessConfiguration';\nexport * from './validateScope';\n"],"mappings":"2EAAAA,OAAA;AACA,IAAAC,iBAAA,GAAAD,OAAA,uBAAAE,MAAA,CAAAC,IAAA,CAAAF,iBAAA,EAAAG,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAJ,iBAAA,CAAAI,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAR,iBAAA,CAAAI,GAAA;AACA,IAAAK,kBAAA,GAAAV,OAAA,wBAAAE,MAAA,CAAAC,IAAA,CAAAO,kBAAA,EAAAN,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,kBAAA,CAAAL,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAC,kBAAA,CAAAL,GAAA;AACA,IAAAM,iBAAA,GAAAX,OAAA,uBAAAE,MAAA,CAAAC,IAAA,CAAAQ,iBAAA,EAAAP,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,iBAAA,CAAAN,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAE,iBAAA,CAAAN,GAAA;AACA,IAAAO,yBAAA,GAAAZ,OAAA,+BAAAE,MAAA,CAAAC,IAAA,CAAAS,yBAAA,EAAAR,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,yBAAA,CAAAP,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAG,yBAAA,CAAAP,GAAA;AACA,IAAAQ,cAAA,GAAAb,OAAA,oBAAAE,MAAA,CAAAC,IAAA,CAAAU,cAAA,EAAAT,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAQ,cAAA,CAAAR,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAI,cAAA,CAAAR,GAAA"}
@@ -0,0 +1,2 @@
1
+ import { Handler } from 'express';
2
+ export declare function validateScope(): Handler;
@@ -0,0 +1,23 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.validateScope = validateScope;var _ScopeAccessConfiguration = require("./ScopeAccessConfiguration");
2
+ var _common = require("@themost/common");
3
+
4
+ function validateScope() {
5
+ return (req, res, next) => {
6
+ /**
7
+ * @type {ScopeAccessConfiguration}
8
+ */
9
+ let scopeAccessConfiguration = req.context.getApplication().getConfiguration().getStrategy(_ScopeAccessConfiguration.ScopeAccessConfiguration);
10
+ if (typeof scopeAccessConfiguration === 'undefined') {
11
+ return next(new Error('Invalid application configuration. Scope access configuration strategy is missing or is in accessible.'));
12
+ }
13
+ scopeAccessConfiguration.verify(req).then((value) => {
14
+ if (value) {
15
+ return next();
16
+ }
17
+ return next(new _common.HttpForbiddenError('Access denied due to authorization scopes.'));
18
+ }).catch((reason) => {
19
+ return next(reason);
20
+ });
21
+ };
22
+ }
23
+ //# sourceMappingURL=validateScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateScope.js","names":["_ScopeAccessConfiguration","require","_common","validateScope","req","res","next","scopeAccessConfiguration","context","getApplication","getConfiguration","getStrategy","ScopeAccessConfiguration","Error","verify","then","value","HttpForbiddenError","catch","reason"],"sources":["../src/validateScope.js"],"sourcesContent":["import { ScopeAccessConfiguration } from './ScopeAccessConfiguration';\nimport {HttpForbiddenError} from '@themost/common';\n\nfunction validateScope() {\n return (req, res, next) => {\n /**\n * @type {ScopeAccessConfiguration}\n */\n let scopeAccessConfiguration = req.context.getApplication().getConfiguration().getStrategy(ScopeAccessConfiguration);\n if (typeof scopeAccessConfiguration === 'undefined') {\n return next(new Error('Invalid application configuration. Scope access configuration strategy is missing or is in accessible.'));\n }\n scopeAccessConfiguration.verify(req).then(value => {\n if (value) {\n return next();\n }\n return next(new HttpForbiddenError('Access denied due to authorization scopes.'))\n }).catch(reason => {\n return next(reason);\n });\n };\n}\n\nexport {\n validateScope\n}\n"],"mappings":"iHAAA,IAAAA,yBAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;;AAEA,SAASE,aAAaA,CAAA,EAAG;EACrB,OAAO,CAACC,GAAG,EAAEC,GAAG,EAAEC,IAAI,KAAK;IACvB;AACR;AACA;IACQ,IAAIC,wBAAwB,GAAGH,GAAG,CAACI,OAAO,CAACC,cAAc,EAAE,CAACC,gBAAgB,EAAE,CAACC,WAAW,CAACC,kDAAwB,CAAC;IACpH,IAAI,OAAOL,wBAAwB,KAAK,WAAW,EAAE;MACjD,OAAOD,IAAI,CAAC,IAAIO,KAAK,CAAC,wGAAwG,CAAC,CAAC;IACpI;IACAN,wBAAwB,CAACO,MAAM,CAACV,GAAG,CAAC,CAACW,IAAI,CAAC,CAAAC,KAAK,KAAI;MAC/C,IAAIA,KAAK,EAAE;QACP,OAAOV,IAAI,EAAE;MACjB;MACA,OAAOA,IAAI,CAAC,IAAIW,0BAAkB,CAAC,4CAA4C,CAAC,CAAC;IACrF,CAAC,CAAC,CAACC,KAAK,CAAC,CAAAC,MAAM,KAAI;MACf,OAAOb,IAAI,CAACa,MAAM,CAAC;IACvB,CAAC,CAAC;EACN,CAAC;AACL"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@universis/janitor",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Universis api plugin for rate limiting requests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,7 +48,6 @@ class RedisClientStore extends RedisStore {
48
48
  const args = Array.from(arguments);
49
49
  const self = this;
50
50
  if (args[0] === 'SCRIPT') {
51
- TraceUtils.debug('RedisClientStore: Creating new client for sending script command');
52
51
  const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
53
52
  host: '127.0.0.1',
54
53
  port: 6379
@@ -63,7 +62,6 @@ class RedisClientStore extends RedisStore {
63
62
  return Promise.reject(error);
64
63
  }).finally(() => {
65
64
  if (client.isOpen) {
66
- TraceUtils.debug('RedisClientStore: Closing client of sending script command');
67
65
  client.disconnect().catch((errDisconnect) => {
68
66
  TraceUtils.error(errDisconnect);
69
67
  });
@@ -71,7 +69,6 @@ class RedisClientStore extends RedisStore {
71
69
  });
72
70
  }
73
71
  if (self.client == null) {
74
- TraceUtils.debug('RedisClientStore: Creating store client for sending commands');
75
72
  const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
76
73
  host: '127.0.0.1',
77
74
  port: 6379
@@ -86,7 +83,6 @@ class RedisClientStore extends RedisStore {
86
83
  return Promise.reject(error);
87
84
  });
88
85
  }
89
- TraceUtils.debug('RedisClientStore: Opening redis store client for sending commands');
90
86
  return self.client.connect().then(() => {
91
87
  // send load script commands once
92
88
  return (() => {
@@ -8,22 +8,27 @@
8
8
  */
9
9
  import {ConfigurationBase, ConfigurationStrategy} from '@themost/common';
10
10
 
11
+ export declare class ScopeString {
12
+ constructor(str: string);
13
+ split(): string[];
14
+ }
15
+
11
16
  /**
12
17
  * Declares a configuration element for managing scope-based permissions on server resources
13
18
  */
14
- declare interface ScopeAccessConfigurationElement {
19
+ export declare interface ScopeAccessConfigurationElement {
15
20
  /**
16
21
  * Gets or sets an array of strings that holds an array of scopes e.g. students or students:read or students,teachers etc
17
22
  */
18
- scope: Array<string>,
23
+ scope: string[],
19
24
  /**
20
- * Gets or sets a string which represents the regular expression that is going to used to validate endpoint
25
+ * Gets or sets a string which represents the regular expression that is going to be used for validating endpoint
21
26
  */
22
27
  resource: string;
23
28
  /**
24
29
  * Gets or sets an array of strings which represents the access levels for the given scopes e.g. READ or READ,WRITE etc
25
30
  */
26
- access: Array<string>;
31
+ access: string[];
27
32
  /**
28
33
  * Gets or sets a string which represents a short description for this item
29
34
  */
@@ -37,7 +42,7 @@ export declare class ScopeAccessConfiguration extends ConfigurationStrategy {
37
42
  /**
38
43
  * Gets an array of scope access configuration elements
39
44
  */
40
- public elements: Array<ScopeAccessConfigurationElement>;
45
+ public elements: ScopeAccessConfigurationElement[];
41
46
 
42
47
  /**
43
48
  * Verifies the given request and returns a promise that resolves with a scope access configuration element
@@ -51,10 +56,10 @@ export declare class DefaultScopeAccessConfiguration extends ScopeAccessConfigur
51
56
  /**
52
57
  * Gets an array of scope access configuration elements
53
58
  */
54
- public elements: Array<ScopeAccessConfigurationElement>;
59
+ public elements: ScopeAccessConfigurationElement[];
55
60
  /**
56
61
  * Verifies the given request and returns a promise that resolves with a scope access configuration element
57
62
  */
58
63
  verify(req: Request): Promise<ScopeAccessConfigurationElement>;
59
64
 
60
- }
65
+ }
@@ -4,9 +4,39 @@ import url from 'url';
4
4
 
5
5
  const HTTP_METHOD_REGEXP = /^\b(POST|PUT|PATCH|DELETE)\b$/i;
6
6
 
7
+ class ScopeString {
8
+ constructor(str) {
9
+ this.value = str;
10
+ }
11
+ toString() {
12
+ return this.value;
13
+ }
14
+ /**
15
+ * Splits a comma-separated or space-separated scope string e.g. "profile email" or "profile,email"
16
+ *
17
+ * Important note: https://www.rfc-editor.org/rfc/rfc6749#section-3.3 defines the regular expression of access token scopes
18
+ * which is a space separated string. Several OAuth2 servers use a comma-separated list instead.
19
+ *
20
+ * The operation will try to use both implementations by excluding comma ',' from access token regular expressions
21
+ * @returns {Array<string>}
22
+ */
23
+ split() {
24
+ // the default regular expression includes comma /([\x21\x23-\x5B\x5D-\x7E]+)/g
25
+ // the modified regular expression excludes comma /x2C /([\x21\x23-\x2B\x2D-\x5B\x5D-\x7E]+)/g
26
+ const re = /([\x21\x23-\x2B\x2D-\x5B\x5D-\x7E]+)/g
27
+ const results = [];
28
+ let match = re.exec(this.value);
29
+ while(match !== null) {
30
+ results.push(match[0]);
31
+ match = re.exec(this.value);
32
+ }
33
+ return results;
34
+ }
35
+ }
36
+
7
37
  class ScopeAccessConfiguration extends ConfigurationStrategy {
8
38
  /**
9
- * @param {ConfigurationBase} configuration
39
+ * @param {import('@themost/common').ConfigurationBase} configuration
10
40
  */
11
41
  constructor(configuration) {
12
42
  super(configuration);
@@ -35,7 +65,7 @@ class ScopeAccessConfiguration extends ConfigurationStrategy {
35
65
  // get original url
36
66
  let reqUrl = url.parse(req.originalUrl).pathname;
37
67
  // get user context scopes as array e.g, ['students', 'students:read']
38
- let reqScopes = req.context.user.authenticationScope.split(',');
68
+ let reqScopes = new ScopeString(req.context.user.authenticationScope).split();
39
69
  // get user access based on HTTP method e.g. GET -> read access
40
70
  let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';
41
71
  let result = this.elements.find(x => {
@@ -67,7 +97,7 @@ class ScopeAccessConfiguration extends ConfigurationStrategy {
67
97
  */
68
98
  class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
69
99
  /**
70
- * @param {ConfigurationBase} configuration
100
+ * @param {import('@themost/common').ConfigurationBase} configuration
71
101
  */
72
102
  constructor(configuration) {
73
103
  super(configuration);
@@ -92,6 +122,7 @@ class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
92
122
  }
93
123
 
94
124
  export {
125
+ ScopeString,
95
126
  ScopeAccessConfiguration,
96
127
  DefaultScopeAccessConfiguration
97
128
  }
package/src/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './RateLimitService';
2
2
  export * from './SpeedLimitService';
3
3
  export * from './RedisClientStore';
4
- export * from './ScopeAccessConfiguration';
4
+ export * from './ScopeAccessConfiguration';
5
+ export * from './validateScope';
package/src/index.js CHANGED
@@ -2,4 +2,5 @@ import './polyfills';
2
2
  export * from './RateLimitService';
3
3
  export * from './SpeedLimitService';
4
4
  export * from './RedisClientStore';
5
- export * from './ScopeAccessConfiguration';
5
+ export * from './ScopeAccessConfiguration';
6
+ export * from './validateScope';
@@ -0,0 +1,2 @@
1
+ import { Handler } from 'express';
2
+ export declare function validateScope(): Handler;
@@ -0,0 +1,26 @@
1
+ import { ScopeAccessConfiguration } from './ScopeAccessConfiguration';
2
+ import {HttpForbiddenError} from '@themost/common';
3
+
4
+ function validateScope() {
5
+ return (req, res, next) => {
6
+ /**
7
+ * @type {ScopeAccessConfiguration}
8
+ */
9
+ let scopeAccessConfiguration = req.context.getApplication().getConfiguration().getStrategy(ScopeAccessConfiguration);
10
+ if (typeof scopeAccessConfiguration === 'undefined') {
11
+ return next(new Error('Invalid application configuration. Scope access configuration strategy is missing or is in accessible.'));
12
+ }
13
+ scopeAccessConfiguration.verify(req).then(value => {
14
+ if (value) {
15
+ return next();
16
+ }
17
+ return next(new HttpForbiddenError('Access denied due to authorization scopes.'))
18
+ }).catch(reason => {
19
+ return next(reason);
20
+ });
21
+ };
22
+ }
23
+
24
+ export {
25
+ validateScope
26
+ }