@vario-software/vario-app-framework-backend 2025.43.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/migrator.js +14 -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/migrator.js
CHANGED
|
@@ -124,7 +124,7 @@ const Migrator = class
|
|
|
124
124
|
await this.methods.log(`Webhook for destination "${destinationQueue}" deregistered\n`);
|
|
125
125
|
},
|
|
126
126
|
|
|
127
|
-
createSalesChannelBackend: async label =>
|
|
127
|
+
createSalesChannelBackend: async (label, validChannelTypes) =>
|
|
128
128
|
{
|
|
129
129
|
const { data: salesChannelBackend } = await this.ApiAdapter.fetch('/erp/sales-channels/backend', {
|
|
130
130
|
method: 'POST',
|
|
@@ -132,6 +132,7 @@ const Migrator = class
|
|
|
132
132
|
appId: this.app.client.appIdentifier,
|
|
133
133
|
label,
|
|
134
134
|
type: 'APP',
|
|
135
|
+
validChannelTypes,
|
|
135
136
|
active: true,
|
|
136
137
|
}),
|
|
137
138
|
});
|
|
@@ -141,6 +142,18 @@ const Migrator = class
|
|
|
141
142
|
return salesChannelBackend;
|
|
142
143
|
},
|
|
143
144
|
|
|
145
|
+
changeSalesChannelBackend: async body =>
|
|
146
|
+
{
|
|
147
|
+
const { data: salesChannelBackend } = await this.ApiAdapter.fetch(`/erp/sales-channels/backend/${body.id}`, {
|
|
148
|
+
method: 'PUT',
|
|
149
|
+
body: JSON.stringify(body),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await this.methods.log(`Sales-Channel-Backend with id "${salesChannelBackend.id}" successfully updated\n`);
|
|
153
|
+
|
|
154
|
+
return salesChannelBackend;
|
|
155
|
+
},
|
|
156
|
+
|
|
144
157
|
createSalesChannel: async (salesChannelBackend, label, description, channelType = 'ECOMMERCE') =>
|
|
145
158
|
{
|
|
146
159
|
const { data: salesChannel } = await this.ApiAdapter.fetch('/erp/sales-channels', {
|
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
|
});
|