@mimik/oauth-helper 1.4.5 → 1.9.6
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/.eslintrc +20 -3
- package/.husky/pre-commit +4 -0
- package/.husky/pre-push +4 -0
- package/.nycrc +4 -0
- package/Gulpfile.js +17 -9
- package/README.md +27 -2
- package/index.js +174 -92
- package/manual-test/testUrl.js +10 -0
- package/package.json +33 -17
- package/test/oauthHelper.spec.js +401 -0
- package/test/serversMock.js +115 -0
- package/test/testConfig.js +314 -0
- package/test/testEnv.js +19 -0
package/.eslintrc
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
|
-
// Use this file as a starting point for your project's .eslintrc.
|
|
2
|
-
// Copy this file, and add rule overrides as needed.
|
|
3
1
|
{
|
|
2
|
+
"plugins": [
|
|
3
|
+
"@mimik/document-env",
|
|
4
|
+
"@mimik/dependencies"
|
|
5
|
+
],
|
|
4
6
|
"env": {
|
|
5
7
|
"node": true
|
|
6
8
|
},
|
|
9
|
+
"parserOptions": {
|
|
10
|
+
"ecmaVersion": 2020
|
|
11
|
+
},
|
|
7
12
|
"extends": "airbnb",
|
|
8
13
|
"rules": {
|
|
14
|
+
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
|
|
9
15
|
"brace-style": [1, "stroustrup", {"allowSingleLine": true}],
|
|
10
16
|
"no-confusing-arrow": [0], // arrow isnt confusing
|
|
11
17
|
"max-len": [1, 180, { "ignoreComments": true }],
|
|
12
18
|
"linebreak-style": 0,
|
|
13
19
|
"quotes": [1, "single"],
|
|
14
|
-
"semi": [1, "always"]
|
|
20
|
+
"semi": [1, "always"],
|
|
21
|
+
"no-process-env": ["error"],
|
|
22
|
+
"@mimik/document-env/validate-document-env": 2,
|
|
23
|
+
"@mimik/dependencies/case-sensitive": 2,
|
|
24
|
+
"@mimik/dependencies/no-cycles": 2,
|
|
25
|
+
"@mimik/dependencies/no-unresolved": 2,
|
|
26
|
+
"@mimik/dependencies/require-json-ext": 2
|
|
27
|
+
},
|
|
28
|
+
"settings":{
|
|
29
|
+
"react": {
|
|
30
|
+
"version": "latest"
|
|
31
|
+
}
|
|
15
32
|
},
|
|
16
33
|
"globals": {
|
|
17
34
|
"module": true,
|
package/.husky/pre-push
ADDED
package/.nycrc
ADDED
package/Gulpfile.js
CHANGED
|
@@ -5,29 +5,37 @@ const gulp = require('gulp');
|
|
|
5
5
|
const eslint = require('gulp-eslint');
|
|
6
6
|
const git = require('gulp-git');
|
|
7
7
|
const jsdoc2md = require('jsdoc-to-markdown');
|
|
8
|
+
const mocha = require('gulp-spawn-mocha');
|
|
8
9
|
|
|
9
10
|
const files = [
|
|
10
11
|
'index.js',
|
|
11
12
|
'Gulpfile.js',
|
|
12
|
-
'test
|
|
13
|
+
'test/**.js',
|
|
13
14
|
'lib/**.js',
|
|
14
15
|
];
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const createDocs = (done) => {
|
|
17
18
|
jsdoc2md.render({ files: 'index.js' })
|
|
18
|
-
.then(output => fs.writeFileSync('README.md', output))
|
|
19
|
-
.catch(err => log.error('docs creation failed:', err.message))
|
|
20
|
-
|
|
19
|
+
.then((output) => fs.writeFileSync('README.md', output))
|
|
20
|
+
.catch((err) => log.error('docs creation failed:', err.message))
|
|
21
|
+
.finally(() => done());
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const lint = () => gulp.src(files)
|
|
24
25
|
.pipe(eslint({}))
|
|
25
|
-
.pipe(eslint.format())
|
|
26
|
-
.pipe(eslint.failOnError());
|
|
26
|
+
.pipe(eslint.format());
|
|
27
27
|
|
|
28
28
|
const add = () => gulp.src('README.md')
|
|
29
29
|
.pipe(git.add({ quiet: true }));
|
|
30
30
|
|
|
31
|
-
gulp.
|
|
31
|
+
const test = () => gulp.src(['test/*.spec.js'])
|
|
32
|
+
.pipe(mocha({
|
|
33
|
+
reporter: 'mochawesome',
|
|
34
|
+
exit: true,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const docs = gulp.series(createDocs, add);
|
|
38
|
+
|
|
32
39
|
gulp.task('lint', lint);
|
|
33
|
-
gulp.task('
|
|
40
|
+
gulp.task('docs', docs);
|
|
41
|
+
gulp.task('test', test);
|
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ const oauthHelper = require('@mimik/oauth-helper');
|
|
|
26
26
|
* [~rpAuth(type, options)](#module_oauth-helper..rpAuth) ⇒ <code>Promise</code>
|
|
27
27
|
* [~authProfile(method, id, correlationId)](#module_oauth-helper..authProfile) ⇒ <code>Promise</code>
|
|
28
28
|
* _callback_
|
|
29
|
+
* [~apiKeySecurityHelper(request, definitions, apiKey, next)](#module_oauth-helper..apiKeySecurityHelper)
|
|
29
30
|
* [~apiTokenSecurityHelper(request, definitions, scopes, next)](#module_oauth-helper..apiTokenSecurityHelper)
|
|
30
31
|
* [~apiTokenAdminSecurityHelper(request, definitions, scopes, next)](#module_oauth-helper..apiTokenAdminSecurityHelper)
|
|
31
32
|
|
|
@@ -41,6 +42,15 @@ Make an authorized request.
|
|
|
41
42
|
|
|
42
43
|
- <code>Promise</code> Will throw the same error than [request-promise](https://www.npmjs.com/package/request-promise).
|
|
43
44
|
|
|
45
|
+
The property `token` may be added to options, in order to set up how the token is retrieved from the token manager. The structure is:
|
|
46
|
+
```
|
|
47
|
+
{
|
|
48
|
+
"retry": "object to specify how the retry to the token manager will be done. similar to rp-retry retry property",
|
|
49
|
+
"customerName": "name of the customer for which the token is entended for, see mST API",
|
|
50
|
+
"cluster": "to set the token to be a cluster token"
|
|
51
|
+
}
|
|
52
|
+
````
|
|
53
|
+
|
|
44
54
|
**Requires**: <code>module:@mimik/sumologic-winston-logger</code>
|
|
45
55
|
**Fulfil**: <code>object</code> - Response of the [request-promise](https://www.npmjs.com/package/request-promise) request.
|
|
46
56
|
|
|
@@ -59,7 +69,7 @@ Make an authProfile request to mID.
|
|
|
59
69
|
**Category**: async
|
|
60
70
|
**Throws**:
|
|
61
71
|
|
|
62
|
-
- <code>Promise</code> Will throw
|
|
72
|
+
- <code>Promise</code> Will throw a rich error is the request fails or an error if the id is not identified
|
|
63
73
|
|
|
64
74
|
**Requires**: <code>module:@mimik/sumologic-winston-logger</code>
|
|
65
75
|
**Fulfil**: <code>object</code> The response of the request made to `mID`.
|
|
@@ -70,6 +80,21 @@ Make an authProfile request to mID.
|
|
|
70
80
|
| id | <code>string</code> | `UserId` to associated witht the request. |
|
|
71
81
|
| correlationId | <code>UUID.<string></code> | CorrelationId to associated with the request. |
|
|
72
82
|
|
|
83
|
+
<a name="module_oauth-helper..apiKeySecurityHelper"></a>
|
|
84
|
+
|
|
85
|
+
### oauth-helper~apiKeySecurityHelper(request, definitions, apiKey, next)
|
|
86
|
+
Validate the request by checking the apiKey. To be used for `apikey`;
|
|
87
|
+
|
|
88
|
+
**Kind**: inner method of [<code>oauth-helper</code>](#module_oauth-helper)
|
|
89
|
+
**Category**: callback
|
|
90
|
+
|
|
91
|
+
| Param | Type | Description |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| request | <code>object</code> | The request with a token in the header. |
|
|
94
|
+
| definitions | <code>object</code> | Swagger security definitions to compare with. |
|
|
95
|
+
| apiKey | <code>Array.<string></code> | APIky extracted from the defined location. |
|
|
96
|
+
| next | [<code>requestCallback</code>](#requestCallback) | The callback that handles the response. The validation adds the following properties to the request: - apiKey Only the header location is allowed. |
|
|
97
|
+
|
|
73
98
|
<a name="module_oauth-helper..apiTokenSecurityHelper"></a>
|
|
74
99
|
|
|
75
100
|
### oauth-helper~apiTokenSecurityHelper(request, definitions, scopes, next)
|
|
@@ -83,7 +108,7 @@ Validate the request by inspecting the token. To be used for `system` and `user`
|
|
|
83
108
|
| request | <code>object</code> | The request with a token in the header. |
|
|
84
109
|
| definitions | <code>object</code> | Swagger security definitions to compare with. |
|
|
85
110
|
| scopes | <code>Array.<string></code> | List of swagger scopes associated whith the requested endpoint. |
|
|
86
|
-
| next | [<code>requestCallback</code>](#requestCallback) | The callback that handles the response. The validation adds the following properties to the request: - in case of 'system' token: 'clientId', 'tokenType', 'customer', 'onBehlaf'. - in case of 'user' token: userId, appId, onBehalfId, 'onBehalf'. |
|
|
111
|
+
| next | [<code>requestCallback</code>](#requestCallback) | The callback that handles the response. The validation adds the following properties to the request: - in case of 'system' token: 'clientId', 'tokenType', 'customer', 'onBehlaf'. If the token is a cluster token the property 'cluster' is set to true. - in case of 'user' token: userId, appId, onBehalfId, 'onBehalf'. |
|
|
87
112
|
|
|
88
113
|
<a name="module_oauth-helper..apiTokenAdminSecurityHelper"></a>
|
|
89
114
|
|
package/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
const Promise = require('bluebird');
|
|
1
2
|
const jwt = require('jsonwebtoken');
|
|
2
|
-
const rp = require('request-promise');
|
|
3
3
|
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const { rpRetry } = require('@mimik/request-retry');
|
|
4
6
|
const logger = require('@mimik/sumologic-winston-logger');
|
|
7
|
+
const { getRichError } = require('@mimik/response-helper');
|
|
8
|
+
const { TOKEN_PARAMS } = require('@mimik/swagger-helper');
|
|
9
|
+
|
|
10
|
+
Promise.config({ cancellation: true });
|
|
5
11
|
/**
|
|
6
12
|
* @module oauth-helper
|
|
7
13
|
* @example
|
|
@@ -12,9 +18,14 @@ const TOKEN_REFRESH_TOLERANCE = 900; // in seconds
|
|
|
12
18
|
const ON_BEHALF = 'onBehalf';
|
|
13
19
|
const SUB_ADMIN = 'subAdmin';
|
|
14
20
|
const ADMIN = 'admin';
|
|
21
|
+
const USER = 'user';
|
|
15
22
|
const NO_GENERIC = 'noGeneric';
|
|
16
|
-
const APPLICATION = 'application';
|
|
23
|
+
// const APPLICATION = 'application';
|
|
17
24
|
const IMPLICIT = 'implicit';
|
|
25
|
+
const CLIENT = '@clients';
|
|
26
|
+
const AUTHORIZATION = 'authorization';
|
|
27
|
+
const HEADER = 'header';
|
|
28
|
+
const CLUSTER = 'cluster';
|
|
18
29
|
|
|
19
30
|
const tokens = {};
|
|
20
31
|
|
|
@@ -29,45 +40,47 @@ const createToken = (value) => {
|
|
|
29
40
|
value,
|
|
30
41
|
exp: jwt.decode(value).exp,
|
|
31
42
|
};
|
|
32
|
-
|
|
33
43
|
return token;
|
|
34
44
|
};
|
|
35
45
|
|
|
36
46
|
module.exports = (config) => {
|
|
37
|
-
const
|
|
38
|
-
|
|
47
|
+
const { implicit, server, generic } = config.security;
|
|
48
|
+
const jwtOptions = (fl) => {
|
|
49
|
+
if (fl === IMPLICIT) { // user flow
|
|
39
50
|
return {
|
|
40
|
-
audience: (
|
|
41
|
-
issuer: (
|
|
51
|
+
audience: (implicit && implicit.audience) || server.audience,
|
|
52
|
+
issuer: (implicit && implicit.issuer) || server.issuer,
|
|
42
53
|
};
|
|
43
54
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
throw new Error(`flow ${flow} not implemented`);
|
|
55
|
+
// server to server or admin flow (application)
|
|
56
|
+
return {
|
|
57
|
+
audience: (generic.audience === NO_GENERIC) ? server.audience : generic.audience,
|
|
58
|
+
issuer: server.issuer,
|
|
59
|
+
// subject: `${config.serverSettings.id}@clients`,
|
|
60
|
+
};
|
|
52
61
|
};
|
|
53
|
-
const keys = (
|
|
54
|
-
if (
|
|
55
|
-
return (
|
|
56
|
-
|| ((
|
|
62
|
+
const keys = (fl) => {
|
|
63
|
+
if (fl === IMPLICIT) { // user flow
|
|
64
|
+
return (implicit && implicit.key)
|
|
65
|
+
|| ((generic.key === NO_GENERIC) ? server.accessKey : generic.key);
|
|
57
66
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
throw new Error(`flow ${flow} not implemented`);
|
|
67
|
+
// server to server or admin flow (application)
|
|
68
|
+
return (generic.key === NO_GENERIC) ? server.accessKey : generic.key;
|
|
62
69
|
};
|
|
63
70
|
const checkHeaders = (headers) => {
|
|
64
71
|
if (!headers) {
|
|
65
72
|
throw new Error('missing header');
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
const authNames = Object.keys(headers).filter((key) => key.toLowerCase() === AUTHORIZATION);
|
|
75
|
+
const authNamesLength = authNames.length;
|
|
76
|
+
|
|
77
|
+
if (authNamesLength === 0) {
|
|
78
|
+
throw new Error(`missing ${AUTHORIZATION} header`);
|
|
79
|
+
}
|
|
80
|
+
if (authNamesLength !== 1) {
|
|
81
|
+
throw new Error(`duplicated ${AUTHORIZATION} header`);
|
|
69
82
|
}
|
|
70
|
-
const auth = headers.
|
|
83
|
+
const auth = headers[authNames[0]].split(' ');
|
|
71
84
|
|
|
72
85
|
if ((auth[0] !== 'bearer' && auth[0] !== 'Bearer') || auth.length !== 2) {
|
|
73
86
|
throw new Error(`authorization type incorrect: ${auth[0]}`);
|
|
@@ -84,53 +97,60 @@ module.exports = (config) => {
|
|
|
84
97
|
const intersects = _.intersection(currentScopes, defScopes);
|
|
85
98
|
|
|
86
99
|
if (intersects.length === 0) {
|
|
87
|
-
throw new Error(`incorrect scopes: ${
|
|
100
|
+
throw new Error(`incorrect scopes: ${tokenScopes}`);
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
if (_.find(intersects, scope => scope.split(':')[0] === ON_BEHALF)) {
|
|
103
|
+
if (_.find(intersects, (scope) => scope.split(':')[0] === ON_BEHALF)) {
|
|
91
104
|
return true;
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
107
|
return false;
|
|
95
108
|
};
|
|
96
109
|
|
|
97
|
-
const getToken = (type, correlationId) => {
|
|
110
|
+
const getToken = (type, origin, options, correlationId) => {
|
|
98
111
|
const tokenOptions = {
|
|
99
112
|
method: 'POST',
|
|
100
|
-
|
|
101
|
-
json: true,
|
|
113
|
+
url: server.issuer,
|
|
102
114
|
};
|
|
115
|
+
|
|
116
|
+
if (correlationId) tokenOptions.headers = { 'x-correlation-id': correlationId };
|
|
103
117
|
const getCredential = {
|
|
104
|
-
client_id:
|
|
105
|
-
client_secret:
|
|
118
|
+
client_id: server.id,
|
|
119
|
+
client_secret: server.secret,
|
|
106
120
|
audience: config.dependencies[type].audience,
|
|
107
121
|
grant_type: 'client_credentials',
|
|
108
122
|
};
|
|
109
123
|
|
|
124
|
+
if (options) {
|
|
125
|
+
if (options.customerName) getCredential.customer_name = options.customerName;
|
|
126
|
+
if (options.retry) tokenOptions.retry = options.retry;
|
|
127
|
+
if (options.cluster) getCredential.type = CLUSTER;
|
|
128
|
+
}
|
|
110
129
|
if (!tokens[type]) tokens[type] = {};
|
|
111
|
-
if (
|
|
112
|
-
if (valid(tokens[type].
|
|
113
|
-
|
|
130
|
+
if (!tokens[type][origin]) tokens[type][origin] = {};
|
|
131
|
+
if (valid(tokens[type][origin].accessToken)) return Promise.resolve(tokens[type][origin].accessToken.value);
|
|
132
|
+
if (valid(tokens[type][origin].refreshToken)) {
|
|
133
|
+
logger.silly('valid refresh_token, refresh_token', { type, origin }, correlationId);
|
|
114
134
|
tokenOptions.body = {
|
|
115
|
-
refresh_token: tokens[type].refreshToken.value,
|
|
135
|
+
refresh_token: tokens[type][origin].refreshToken.value,
|
|
116
136
|
grant_type: 'refresh_token',
|
|
117
137
|
};
|
|
118
138
|
}
|
|
119
139
|
else {
|
|
120
|
-
logger.silly('invalid refresh_token, client_credentials', { type }, correlationId);
|
|
140
|
+
logger.silly('invalid refresh_token, client_credentials', { type, origin }, correlationId);
|
|
121
141
|
tokenOptions.body = getCredential;
|
|
122
142
|
}
|
|
123
|
-
return
|
|
143
|
+
return rpRetry(tokenOptions).catch((err) => {
|
|
124
144
|
if (err.statusCode !== 400 || tokenOptions.body.grant_type === 'client_credentials') {
|
|
125
145
|
throw err;
|
|
126
146
|
}
|
|
127
147
|
logger.silly('moving from refresh_token to client_credential', { error: err.message }, correlationId);
|
|
128
148
|
tokenOptions.body = getCredential;
|
|
129
|
-
return
|
|
149
|
+
return rpRetry(tokenOptions);
|
|
130
150
|
}).then((response) => {
|
|
131
|
-
tokens[type].accessToken = createToken(response.data.access_token);
|
|
132
|
-
tokens[type].refreshToken = createToken(response.data.refresh_token);
|
|
133
|
-
return tokens[type].accessToken.value;
|
|
151
|
+
tokens[type][origin].accessToken = createToken(response.data.access_token);
|
|
152
|
+
tokens[type][origin].refreshToken = createToken(response.data.refresh_token);
|
|
153
|
+
return tokens[type][origin].accessToken.value;
|
|
134
154
|
});
|
|
135
155
|
};
|
|
136
156
|
|
|
@@ -145,29 +165,42 @@ module.exports = (config) => {
|
|
|
145
165
|
* @return {Promise}.
|
|
146
166
|
* @fulfil {object} - Response of the [request-promise](https://www.npmjs.com/package/request-promise) request.
|
|
147
167
|
* @throws {Promise} Will throw the same error than [request-promise](https://www.npmjs.com/package/request-promise).
|
|
168
|
+
*
|
|
169
|
+
* The property `token` may be added to options, in order to set up how the token is retrieved from the token manager. The structure is:
|
|
170
|
+
* ```
|
|
171
|
+
* {
|
|
172
|
+
* "retry": "object to specify how the retry to the token manager will be done. similar to rp-retry retry property",
|
|
173
|
+
* "customerName": "name of the customer for which the token is entended for, see mST API",
|
|
174
|
+
* "cluster": "to set the token to be a cluster token"
|
|
175
|
+
* }
|
|
176
|
+
* ````
|
|
148
177
|
*/
|
|
149
178
|
const rpAuth = (type, options) => {
|
|
150
|
-
|
|
179
|
+
const enteredUrl = options.uri || options.url; // uri takes precedence over url in request-promise, conserving the order
|
|
180
|
+
let url;
|
|
181
|
+
|
|
182
|
+
if (!type) return Promise.reject(new Error(`rpAuth type non existent for ${options.method || 'GET'} on ${enteredUrl}`));
|
|
151
183
|
if (!config.dependencies[type]) return Promise.reject(new Error(`type ${type} used in rpAuth with no dependencies`));
|
|
184
|
+
if (!options.url && !options.uri) return Promise.reject(new Error('uri or url non existent'));
|
|
185
|
+
try { url = new URL(enteredUrl); }
|
|
186
|
+
catch (err) { return Promise.reject(new Error(`invalid url address: ${enteredUrl}: ${err.message}`)); }
|
|
152
187
|
const correlationId = (options.headers && options.headers['x-correlation-id']);
|
|
153
188
|
|
|
154
|
-
return getToken(type, correlationId)
|
|
189
|
+
return getToken(type, url.origin, options.token, correlationId)
|
|
155
190
|
.then((token) => {
|
|
156
191
|
const opts = options;
|
|
157
192
|
|
|
158
193
|
if (!opts.headers) opts.headers = {};
|
|
159
194
|
opts.headers.authorization = `Bearer ${token}`;
|
|
160
|
-
opts
|
|
161
|
-
return rp(opts)
|
|
195
|
+
return rpRetry(opts)
|
|
162
196
|
.catch((err) => {
|
|
163
197
|
if (err.statusCode === 403) {
|
|
164
198
|
logger.warn('got a unauthorized request, retrying', { error: err.message, type }, correlationId);
|
|
165
199
|
tokens[type].accessToken = null;
|
|
166
|
-
return getToken(type, correlationId)
|
|
200
|
+
return getToken(type, options.uri || options.url, options.token, correlationId)
|
|
167
201
|
.then((newToken) => {
|
|
168
|
-
if (!opts.headers) opts.headers = {};
|
|
169
202
|
opts.headers.authorization = `Bearer ${newToken}`;
|
|
170
|
-
return
|
|
203
|
+
return rpRetry(opts);
|
|
171
204
|
});
|
|
172
205
|
}
|
|
173
206
|
throw err;
|
|
@@ -175,6 +208,33 @@ module.exports = (config) => {
|
|
|
175
208
|
});
|
|
176
209
|
};
|
|
177
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Validate the request by checking the apiKey. To be used for `apikey`;
|
|
213
|
+
*
|
|
214
|
+
* @function apiKeySecurityHelper
|
|
215
|
+
* @category callback
|
|
216
|
+
* @param {object} request - The request with a token in the header.
|
|
217
|
+
* @param {object} definitions - Swagger security definitions to compare with.
|
|
218
|
+
* @param {string[]} apiKey - APIky extracted from the defined location.
|
|
219
|
+
* @param {requestCallback} next - The callback that handles the response.
|
|
220
|
+
*
|
|
221
|
+
* The validation adds the following properties to the request:
|
|
222
|
+
* - apiKey
|
|
223
|
+
* Only the header location is allowed.
|
|
224
|
+
*/
|
|
225
|
+
const apiKeySecurityHelper = (request, definitions, apiKey, next) => {
|
|
226
|
+
if (definitions.in !== HEADER) {
|
|
227
|
+
next(new Error(`invalid location: only ${HEADER} is accepted`));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (config.security.apiKeys.includes(apiKey)) {
|
|
231
|
+
request.apiKey = apiKey;
|
|
232
|
+
next(null);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
next(new Error('invalid API key'));
|
|
236
|
+
};
|
|
237
|
+
|
|
178
238
|
/**
|
|
179
239
|
* Validate the request by inspecting the token. To be used for `system` and `user` token.
|
|
180
240
|
*
|
|
@@ -186,11 +246,12 @@ module.exports = (config) => {
|
|
|
186
246
|
* @param {requestCallback} next - The callback that handles the response.
|
|
187
247
|
*
|
|
188
248
|
* The validation adds the following properties to the request:
|
|
189
|
-
* - in case of 'system' token: 'clientId', 'tokenType', 'customer', 'onBehlaf'.
|
|
249
|
+
* - in case of 'system' token: 'clientId', 'tokenType', 'customer', 'onBehlaf'. If the token is a cluster token the property 'cluster' is set to true.
|
|
190
250
|
* - in case of 'user' token: userId, appId, onBehalfId, 'onBehalf'.
|
|
191
251
|
*/
|
|
192
252
|
const apiTokenSecurityHelper = (request, definitions, scopes, next) => {
|
|
193
253
|
let authToken;
|
|
254
|
+
const { flow } = definitions;
|
|
194
255
|
|
|
195
256
|
try { authToken = checkHeaders(request.headers); }
|
|
196
257
|
catch (err) {
|
|
@@ -207,10 +268,19 @@ module.exports = (config) => {
|
|
|
207
268
|
next(new Error('invalid token: wrong type'));
|
|
208
269
|
return;
|
|
209
270
|
}
|
|
210
|
-
try { jwt.verify(authToken, keys(
|
|
271
|
+
try { jwt.verify(authToken, keys(flow), jwtOptions(flow)); }
|
|
211
272
|
catch (err) {
|
|
212
|
-
|
|
213
|
-
|
|
273
|
+
if (flow !== IMPLICIT && generic.previousKey) { // backward compatibility
|
|
274
|
+
try { jwt.verify(authToken, generic.previousKey, jwtOptions(flow)); }
|
|
275
|
+
catch (secondErr) {
|
|
276
|
+
next(secondErr);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
next(err);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
214
284
|
}
|
|
215
285
|
let onBehalfOption = false;
|
|
216
286
|
|
|
@@ -219,25 +289,24 @@ module.exports = (config) => {
|
|
|
219
289
|
next(errScopes);
|
|
220
290
|
return;
|
|
221
291
|
}
|
|
222
|
-
if (onBehalfOption) request.onBehalf = true;
|
|
292
|
+
if (onBehalfOption) request[TOKEN_PARAMS.onBehalf] = true;
|
|
223
293
|
if (definitions.flow === IMPLICIT) {
|
|
224
|
-
if (token.sub) request.userId = token.sub;
|
|
225
|
-
if (token.azp) request.appId = token.azp;
|
|
294
|
+
if (token.sub) request[TOKEN_PARAMS.userId] = token.sub;
|
|
295
|
+
if (token.azp) request[TOKEN_PARAMS.appId] = token.azp;
|
|
226
296
|
if (token.may_act && token.may_act.sub) {
|
|
227
|
-
request.onBehalfId = token.sub;
|
|
228
|
-
request.userId = token.may_act.sub;
|
|
297
|
+
request[TOKEN_PARAMS.onBehalfId] = token.sub;
|
|
298
|
+
request[TOKEN_PARAMS.userId] = token.may_act.sub;
|
|
229
299
|
}
|
|
300
|
+
request.tokenType = USER;
|
|
230
301
|
next(null);
|
|
231
302
|
return;
|
|
232
303
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
next(new Error(`flow ${definitions.flow} not implemented`));
|
|
304
|
+
// server to server or admin flow (application)
|
|
305
|
+
if (token.sub) request[TOKEN_PARAMS.clientId] = token.sub;
|
|
306
|
+
if (token.subType) request[TOKEN_PARAMS.tokenType] = token.subType;
|
|
307
|
+
if (token.cust) request[TOKEN_PARAMS.customer] = token.cust;
|
|
308
|
+
if (token.type === CLUSTER) request[TOKEN_PARAMS.cluster] = true;
|
|
309
|
+
next(null);
|
|
241
310
|
};
|
|
242
311
|
|
|
243
312
|
/**
|
|
@@ -254,6 +323,7 @@ module.exports = (config) => {
|
|
|
254
323
|
*/
|
|
255
324
|
const apiTokenAdminSecurityHelper = (request, definitions, scopes, next) => {
|
|
256
325
|
let authToken;
|
|
326
|
+
const { flow } = definitions;
|
|
257
327
|
|
|
258
328
|
try { authToken = checkHeaders(request.headers); }
|
|
259
329
|
catch (err) {
|
|
@@ -272,14 +342,20 @@ module.exports = (config) => {
|
|
|
272
342
|
return;
|
|
273
343
|
}
|
|
274
344
|
try {
|
|
275
|
-
jwt.verify(authToken,
|
|
276
|
-
audience: config.security.generic.audience,
|
|
277
|
-
issuer: config.security.server.issuer,
|
|
278
|
-
});
|
|
345
|
+
jwt.verify(authToken, keys(flow), jwtOptions(flow));
|
|
279
346
|
}
|
|
280
347
|
catch (err) {
|
|
281
|
-
|
|
282
|
-
|
|
348
|
+
if (generic.previousKey) { // backward compatibility
|
|
349
|
+
try { jwt.verify(authToken, generic.previousKey, jwtOptions(flow)); }
|
|
350
|
+
catch (secondErr) {
|
|
351
|
+
next(secondErr);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
next(err);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
283
359
|
}
|
|
284
360
|
try { checkScopes(token.scope, scopes); }
|
|
285
361
|
catch (errScopes) {
|
|
@@ -292,13 +368,13 @@ module.exports = (config) => {
|
|
|
292
368
|
return;
|
|
293
369
|
}
|
|
294
370
|
}
|
|
295
|
-
else if (token.sub !== `${config.security.admin.externalId}
|
|
371
|
+
else if (token.sub !== `${config.security.admin.externalId}${CLIENT}`) {
|
|
296
372
|
next(new Error(`jwt subject invalid: ${token.sub}`));
|
|
297
373
|
return;
|
|
298
374
|
}
|
|
299
|
-
if (token.subType) request.tokenType = token.subType;
|
|
300
|
-
if (token.sub) request.clientId = token.sub;
|
|
301
|
-
if (token.cust) request.customer = token.cust;
|
|
375
|
+
if (token.subType) request[TOKEN_PARAMS.tokenType] = token.subType;
|
|
376
|
+
if (token.sub) request[TOKEN_PARAMS.clientId] = token.sub;
|
|
377
|
+
if (token.cust) request[TOKEN_PARAMS.customer] = token.cust;
|
|
302
378
|
next(null);
|
|
303
379
|
};
|
|
304
380
|
|
|
@@ -313,25 +389,31 @@ module.exports = (config) => {
|
|
|
313
389
|
* @param {UUID<string>} correlationId - CorrelationId to associated with the request.
|
|
314
390
|
* @return {Promise}.
|
|
315
391
|
* @fulfil {object} The response of the request made to `mID`.
|
|
316
|
-
* @throws {Promise} Will throw
|
|
392
|
+
* @throws {Promise} Will throw a rich error is the request fails or an error if the id is not identified
|
|
317
393
|
*/
|
|
318
|
-
const authProfile = (method, id, correlationId) =>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
394
|
+
const authProfile = (method, id, correlationId) => {
|
|
395
|
+
if (!id) return Promise.reject(new Error('Id has to be defined'));
|
|
396
|
+
return rpAuth('mID', {
|
|
397
|
+
method,
|
|
398
|
+
uri: `${config.dependencies.mID.url}/users/${id}`,
|
|
399
|
+
headers: {
|
|
400
|
+
'x-correlation-id': correlationId,
|
|
401
|
+
},
|
|
402
|
+
json: true,
|
|
403
|
+
}).catch((err) => {
|
|
404
|
+
const error = getRichError(err.statusCode, 'could perform operation on identity server', { method, id }, err);
|
|
405
|
+
|
|
406
|
+
if (error.statusCode === 400 && method === 'DELETE') {
|
|
407
|
+
logger.warn('profile without authprofile', { id, error }, correlationId);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
throw error;
|
|
411
|
+
});
|
|
412
|
+
};
|
|
332
413
|
|
|
333
414
|
return {
|
|
334
415
|
rpAuth,
|
|
416
|
+
apiKeySecurityHelper,
|
|
335
417
|
apiTokenSecurityHelper,
|
|
336
418
|
apiTokenAdminSecurityHelper,
|
|
337
419
|
authProfile,
|