@universis/janitor 1.3.1 → 1.4.1
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 +2 -4
- package/README.md +112 -0
- package/dist/RedisClientStore.js +13 -0
- package/dist/RedisClientStore.js.map +1 -1
- package/dist/ScopeAccessConfiguration.d.ts +12 -7
- package/dist/ScopeAccessConfiguration.js +34 -4
- package/dist/ScopeAccessConfiguration.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/validateScope.d.ts +2 -0
- package/dist/validateScope.js +23 -0
- package/dist/validateScope.js.map +1 -0
- package/package.json +1 -1
- package/src/RedisClientStore.js +13 -0
- package/src/ScopeAccessConfiguration.d.ts +12 -7
- package/src/ScopeAccessConfiguration.js +34 -3
- package/src/index.d.ts +2 -1
- package/src/index.js +2 -1
- package/src/validateScope.d.ts +2 -0
- package/src/validateScope.js +26 -0
- /package/dist/{RateLimtiService.d.ts → RateLimitService.d.ts} +0 -0
- /package/src/{RateLimtiService.d.ts → RateLimitService.d.ts} +0 -0
package/.gitlab-ci.yml
CHANGED
|
@@ -6,10 +6,8 @@ stages:
|
|
|
6
6
|
|
|
7
7
|
npm_publish:
|
|
8
8
|
stage: releases
|
|
9
|
-
|
|
10
|
-
- /^(\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.
|
package/dist/RedisClientStore.js
CHANGED
|
@@ -74,6 +74,19 @@ class RedisClientStore extends _rateLimitRedis.default {
|
|
|
74
74
|
port: 6379
|
|
75
75
|
};
|
|
76
76
|
self.client = (0, _redis.createClient)(connectOptions);
|
|
77
|
+
// handle socket close error
|
|
78
|
+
self.client.on('error', (err) => {
|
|
79
|
+
_common.TraceUtils.error('An error occurred while using redis client as speed limit service client store.');
|
|
80
|
+
_common.TraceUtils.error(err);
|
|
81
|
+
// handle socket close error
|
|
82
|
+
if (err.message === 'Socket closed unexpectedly') {
|
|
83
|
+
_common.TraceUtils.info('Socket closed unexpectedly. Disconnecting...');
|
|
84
|
+
// delete increment script sha
|
|
85
|
+
self.incrementScriptSha = null;
|
|
86
|
+
// quit client
|
|
87
|
+
self.client.disconnect();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
77
90
|
}
|
|
78
91
|
if (self.client.isOpen) {
|
|
79
92
|
return self.client.sendCommand(args).catch((error) => {
|
|
@@ -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","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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"}
|
|
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","on","err","info","incrementScriptSha","postInit","resolve","init","debug","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 // handle socket close error\n self.client.on('error', (err) => {\n TraceUtils.error('An error occurred while using redis client as speed limit service client store.');\n TraceUtils.error(err);\n // handle socket close error\n if (err.message === 'Socket closed unexpectedly') {\n TraceUtils.info('Socket closed unexpectedly. Disconnecting...');\n // delete increment script sha\n self.incrementScriptSha = null;\n // quit client\n self.client.disconnect();\n }\n });\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:
|
|
23
|
+
scope: string[],
|
|
19
24
|
/**
|
|
20
|
-
* Gets or sets a string which represents the regular expression that is going to used
|
|
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:
|
|
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:
|
|
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:
|
|
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","
|
|
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
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'
|
|
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,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
package/src/RedisClientStore.js
CHANGED
|
@@ -74,6 +74,19 @@ class RedisClientStore extends RedisStore {
|
|
|
74
74
|
port: 6379
|
|
75
75
|
};
|
|
76
76
|
self.client = createClient(connectOptions);
|
|
77
|
+
// handle socket close error
|
|
78
|
+
self.client.on('error', (err) => {
|
|
79
|
+
TraceUtils.error('An error occurred while using redis client as speed limit service client store.');
|
|
80
|
+
TraceUtils.error(err);
|
|
81
|
+
// handle socket close error
|
|
82
|
+
if (err.message === 'Socket closed unexpectedly') {
|
|
83
|
+
TraceUtils.info('Socket closed unexpectedly. Disconnecting...');
|
|
84
|
+
// delete increment script sha
|
|
85
|
+
self.incrementScriptSha = null;
|
|
86
|
+
// quit client
|
|
87
|
+
self.client.disconnect();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
77
90
|
}
|
|
78
91
|
if (self.client.isOpen) {
|
|
79
92
|
return self.client.sendCommand(args).catch((error) => {
|
|
@@ -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:
|
|
23
|
+
scope: string[],
|
|
19
24
|
/**
|
|
20
|
-
* Gets or sets a string which represents the regular expression that is going to used
|
|
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:
|
|
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:
|
|
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:
|
|
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
package/src/index.js
CHANGED
|
@@ -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
|
+
}
|
|
File without changes
|
|
File without changes
|