@vario-software/vario-app-framework-backend 2025.44.0 → 2025.45.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/api/Api.js +6 -4
- package/api/ErpApi.js +7 -1
- package/api/modules/permittedToken.js +29 -0
- package/app.js +10 -1
- package/package.json +1 -1
- package/setup/appAuthentication.js +6 -0
- package/setup/context.js +1 -1
- package/utils/permission.js +18 -11
- package/utils/token.js +24 -19
package/api/Api.js
CHANGED
|
@@ -19,6 +19,7 @@ class Api
|
|
|
19
19
|
resolveOn = 'end',
|
|
20
20
|
timeout = 15 * 60 * 1000,
|
|
21
21
|
suppressLogs = false,
|
|
22
|
+
secretsToMask = ['value'],
|
|
22
23
|
followRedirects,
|
|
23
24
|
body,
|
|
24
25
|
inputStream,
|
|
@@ -41,6 +42,7 @@ class Api
|
|
|
41
42
|
this.restOptions = restOptions;
|
|
42
43
|
this.suppressLogs = suppressLogs;
|
|
43
44
|
this.followRedirects = followRedirects;
|
|
45
|
+
this.secretsToMask = secretsToMask;
|
|
44
46
|
|
|
45
47
|
this.app = getApp();
|
|
46
48
|
|
|
@@ -171,7 +173,7 @@ class Api
|
|
|
171
173
|
requestOptions: this.requestOptions,
|
|
172
174
|
body: this.body,
|
|
173
175
|
},
|
|
174
|
-
response: this.secret ? maskSpecificKey(response,
|
|
176
|
+
response: this.secret ? maskSpecificKey(response, this.secretsToMask) : response,
|
|
175
177
|
duration: `${(performance.now() - this.timer).toFixed(2)}ms`,
|
|
176
178
|
retryCount: this.retryCount,
|
|
177
179
|
};
|
|
@@ -422,7 +424,7 @@ Api.redirectRequest = redirectRequestFn;
|
|
|
422
424
|
|
|
423
425
|
module.exports = Api;
|
|
424
426
|
|
|
425
|
-
function maskSpecificKey(response,
|
|
427
|
+
function maskSpecificKey(response, secretsToMask = ['value'], mask = '[secret]')
|
|
426
428
|
{
|
|
427
429
|
if (!response || typeof response !== 'object')
|
|
428
430
|
{
|
|
@@ -431,13 +433,13 @@ function maskSpecificKey(response, keyToMask = 'value', mask = '[secret]')
|
|
|
431
433
|
|
|
432
434
|
return Object.keys(response).reduce((acc, key) =>
|
|
433
435
|
{
|
|
434
|
-
if (key
|
|
436
|
+
if (secretsToMask.includes(key))
|
|
435
437
|
{
|
|
436
438
|
acc[key] = mask;
|
|
437
439
|
}
|
|
438
440
|
else if (typeof response[key] === 'object' && response[key] !== null)
|
|
439
441
|
{
|
|
440
|
-
acc[key] = maskSpecificKey(response[key],
|
|
442
|
+
acc[key] = maskSpecificKey(response[key], secretsToMask, mask);
|
|
441
443
|
}
|
|
442
444
|
else
|
|
443
445
|
{
|
package/api/ErpApi.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const Api = require('#backend/api/Api.js');
|
|
2
|
-
const { getTenant } = require('#backend/utils/context.js');
|
|
2
|
+
const { getTenant, getAppToken } = require('#backend/utils/context.js');
|
|
3
3
|
|
|
4
4
|
const PromiseSingletonMap = require('#backend/utils/promiseSingletonMap.js');
|
|
5
5
|
const refreshAccessToken = require('#backend/utils/keycloak.js');
|
|
@@ -7,6 +7,7 @@ const Eav = require('#backend/api/modules/eav.js');
|
|
|
7
7
|
const Migration = require('#backend/api/modules/migration.js');
|
|
8
8
|
const TextEnum = require('#backend/api/modules/textEnum.js');
|
|
9
9
|
const Webhook = require('#backend/api/modules/webhook.js');
|
|
10
|
+
const PermittedToken = require('#backend/api/modules/permittedToken.js');
|
|
10
11
|
const { validateOfflineToken } = require('#backend/utils/token.js');
|
|
11
12
|
|
|
12
13
|
const singletonPromise = new PromiseSingletonMap();
|
|
@@ -16,6 +17,10 @@ class ErpApi extends Api
|
|
|
16
17
|
{
|
|
17
18
|
const { baseUrl, Authorization } = await this.getAuthorization();
|
|
18
19
|
|
|
20
|
+
this.setHeaders({
|
|
21
|
+
'X-Forwarded-App-Token': getAppToken(),
|
|
22
|
+
});
|
|
23
|
+
|
|
19
24
|
this.setAuthorization(Authorization);
|
|
20
25
|
|
|
21
26
|
if (this.useInternalApi)
|
|
@@ -90,5 +95,6 @@ ErpApi.migration = new Migration(ErpApi);
|
|
|
90
95
|
ErpApi.eav = new Eav(ErpApi);
|
|
91
96
|
ErpApi.textenum = new TextEnum(ErpApi);
|
|
92
97
|
ErpApi.webhook = new Webhook(ErpApi);
|
|
98
|
+
ErpApi.permittedToken = new PermittedToken(ErpApi);
|
|
93
99
|
|
|
94
100
|
module.exports = ErpApi;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { getApp } = require('#backend/utils/context.js');
|
|
2
|
+
|
|
3
|
+
const PermittedToken = class
|
|
4
|
+
{
|
|
5
|
+
constructor(ApiAdapter)
|
|
6
|
+
{
|
|
7
|
+
this.ApiAdapter = ApiAdapter;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
create = async function(expiresAt, permissions)
|
|
11
|
+
{
|
|
12
|
+
const app = getApp();
|
|
13
|
+
|
|
14
|
+
const { data } = await this.ApiAdapter.fetch(`/cmn/apps/${app.client.appIdentifier}/permitted-token`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
expiresAt,
|
|
18
|
+
permissions,
|
|
19
|
+
}),
|
|
20
|
+
secret: true,
|
|
21
|
+
secretsToMask: ['bearerToken'],
|
|
22
|
+
useInternalApi: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return data;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = PermittedToken;
|
package/app.js
CHANGED
|
@@ -111,13 +111,22 @@ function validateClient(client)
|
|
|
111
111
|
throw new Error('client config missing');
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const missingProps = ['clientId', 'clientSecret', 'appIdentifier']
|
|
114
|
+
const missingProps = ['clientId', 'clientSecret', 'appIdentifier', 'appJWK']
|
|
115
115
|
.filter(prop => !client[prop]);
|
|
116
116
|
|
|
117
117
|
if (missingProps.length)
|
|
118
118
|
{
|
|
119
119
|
throw new Error(`client config is missing: ${missingProps.join()}`);
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
try
|
|
123
|
+
{
|
|
124
|
+
JSON.parse(client.appJWK);
|
|
125
|
+
}
|
|
126
|
+
catch
|
|
127
|
+
{
|
|
128
|
+
throw new Error('appJWK is not a valid JSON');
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
module.exports = VarioCloudApp;
|
package/package.json
CHANGED
package/setup/context.js
CHANGED
|
@@ -21,7 +21,7 @@ function setupContext(app)
|
|
|
21
21
|
res.on('finish', async () =>
|
|
22
22
|
{
|
|
23
23
|
await app.log(
|
|
24
|
-
`${(performance.now() - specificContext.startTime).toFixed(2)}ms`,
|
|
24
|
+
`${(performance.now() - specificContext.startTime).toFixed(2)}ms (${res.statusCode} ${res.statusMessage})`,
|
|
25
25
|
'setup/context/finish',
|
|
26
26
|
'DEBUG',
|
|
27
27
|
);
|
package/utils/permission.js
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
const { getAccessToken } = require('#backend/utils/context.js');
|
|
2
2
|
const HttpError = require('#backend/utils/httpError.js');
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
function checkPermission(verb, strict = true)
|
|
5
5
|
{
|
|
6
|
-
const { isSuperUser, permissions } =
|
|
6
|
+
const { isSuperUser, permissions } = getAccessToken();
|
|
7
7
|
|
|
8
8
|
if (!(isSuperUser || permissions?.includes(verb)))
|
|
9
9
|
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
if (strict)
|
|
11
|
+
{
|
|
12
|
+
throw new HttpError(
|
|
13
|
+
'APP_AUTHORIZATION_FAILED',
|
|
14
|
+
403,
|
|
15
|
+
'utils/permission',
|
|
16
|
+
{ missingVerb: verb },
|
|
17
|
+
null,
|
|
18
|
+
'ERROR',
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return false;
|
|
18
23
|
}
|
|
24
|
+
|
|
25
|
+
return true;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
function checkPermissionMiddleware(verb)
|
|
22
29
|
{
|
|
23
30
|
return async (req, res, next) =>
|
|
24
31
|
{
|
|
25
|
-
|
|
32
|
+
checkPermission(verb);
|
|
26
33
|
|
|
27
34
|
next();
|
|
28
35
|
};
|
package/utils/token.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { jwtVerify, decodeJwt } = require('jose');
|
|
1
|
+
const { jwtVerify, decodeJwt, importJWK } = require('jose');
|
|
2
2
|
const { getApp } = require('#backend/utils/context.js');
|
|
3
3
|
|
|
4
4
|
function validateOfflineToken(offlineToken)
|
|
@@ -48,30 +48,35 @@ function validateAppToken(appToken)
|
|
|
48
48
|
|
|
49
49
|
const app = getApp();
|
|
50
50
|
|
|
51
|
-
const {
|
|
51
|
+
const { appIdentifier, appJWK } = app.client;
|
|
52
52
|
|
|
53
|
-
const
|
|
53
|
+
const jwk = JSON.parse(appJWK).keys[0];
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
.then(
|
|
55
|
+
importJWK(jwk, 'ES256')
|
|
56
|
+
.then(key =>
|
|
57
57
|
{
|
|
58
|
-
|
|
58
|
+
jwtVerify(appToken, key)
|
|
59
|
+
.then(({ payload }) =>
|
|
60
|
+
{
|
|
61
|
+
const { aud, exp } = payload;
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if (!aud || aud !== appIdentifier)
|
|
64
|
+
{
|
|
65
|
+
console.log('First cond', !aud, aud !== appIdentifier);
|
|
66
|
+
reject();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if (!exp || exp < (Date.now() / 1000))
|
|
71
|
+
{
|
|
72
|
+
console.log('Second cond', !exp, exp < (Date.now() / 1000));
|
|
73
|
+
reject();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
resolve(payload);
|
|
78
|
+
})
|
|
79
|
+
.catch(reject);
|
|
75
80
|
})
|
|
76
81
|
.catch(reject);
|
|
77
82
|
});
|