@vario-software/vario-app-framework-backend 2025.44.0 → 2025.46.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/eav.js +19 -5
- 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 +34 -0
- 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;
|
package/api/modules/eav.js
CHANGED
|
@@ -35,9 +35,7 @@ const Eav = class
|
|
|
35
35
|
|
|
36
36
|
changeGroup = async function (groupKey, callback)
|
|
37
37
|
{
|
|
38
|
-
let
|
|
39
|
-
method: 'GET',
|
|
40
|
-
});
|
|
38
|
+
let eavGroup = await this.getGroup(groupKey);
|
|
41
39
|
|
|
42
40
|
eavGroup = callback(eavGroup);
|
|
43
41
|
|
|
@@ -51,7 +49,7 @@ const Eav = class
|
|
|
51
49
|
|
|
52
50
|
deleteGroup = async function (groupKey)
|
|
53
51
|
{
|
|
54
|
-
const
|
|
52
|
+
const eavGroup = await this.getGroup(groupKey);
|
|
55
53
|
|
|
56
54
|
await this.ApiAdapter.fetch(`/cmn/eav-groups/${eavGroup.id}/remove-data`, {
|
|
57
55
|
method: 'POST',
|
|
@@ -65,11 +63,27 @@ const Eav = class
|
|
|
65
63
|
return true;
|
|
66
64
|
};
|
|
67
65
|
|
|
66
|
+
removeDataFromGroup = async function (groupKey, attributeKeys = [])
|
|
67
|
+
{
|
|
68
|
+
const eavGroup = await this.getGroup(groupKey);
|
|
69
|
+
|
|
70
|
+
const attributeId = attributeKeys
|
|
71
|
+
.map(attrKey => eavGroup.attributes?.find(({ key }) => key === attrKey)?.id)
|
|
72
|
+
.filter(id => id !== null || id !== undefined);
|
|
73
|
+
|
|
74
|
+
await this.ApiAdapter.fetch(`/cmn/eav-groups/${eavGroup.id}/remove-data`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
body: JSON.stringify({ entities: eavGroup.entities, attributeId }),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
|
|
68
82
|
getGroupIdByKey = async function(groupKey)
|
|
69
83
|
{
|
|
70
84
|
try
|
|
71
85
|
{
|
|
72
|
-
const
|
|
86
|
+
const eavGroup = await this.getGroup(groupKey);
|
|
73
87
|
|
|
74
88
|
return eavGroup.id;
|
|
75
89
|
}
|
|
@@ -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
|
@@ -53,6 +53,26 @@ const Migrator = class
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
always = async function (key, callback)
|
|
57
|
+
{
|
|
58
|
+
const migration = `${this.key}.${key}`;
|
|
59
|
+
|
|
60
|
+
const context = getContext();
|
|
61
|
+
|
|
62
|
+
context.migration = migration;
|
|
63
|
+
|
|
64
|
+
try
|
|
65
|
+
{
|
|
66
|
+
await callback(this.methods, this.migrationResults);
|
|
67
|
+
}
|
|
68
|
+
catch (error)
|
|
69
|
+
{
|
|
70
|
+
await this.app.onMigrationError(error);
|
|
71
|
+
|
|
72
|
+
await this.methods.log(`Migration "${migration}" failed\n\n${error.message}`, 'ERROR', error.message);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
56
76
|
methods = {
|
|
57
77
|
log: async (message, level = 'INFO') =>
|
|
58
78
|
{
|
|
@@ -101,6 +121,20 @@ const Migrator = class
|
|
|
101
121
|
return eavGroup;
|
|
102
122
|
},
|
|
103
123
|
|
|
124
|
+
removeDataFromEavGroup: async (groupKey, attributeKeys) =>
|
|
125
|
+
{
|
|
126
|
+
const eavGroup = await this.ApiAdapter.eav.removeDataFromGroup(groupKey, attributeKeys);
|
|
127
|
+
|
|
128
|
+
const hasAttributeKeys = Array.isArray(attributeKeys) && attributeKeys.length > 0;
|
|
129
|
+
const logMessage = hasAttributeKeys
|
|
130
|
+
? `Data for the specified attributes (${attributeKeys.join(', ')}) in EAV group "${groupKey}" was successfully removed.\n`
|
|
131
|
+
: `All data from EAV group "${groupKey}" was successfully removed.\n`;
|
|
132
|
+
|
|
133
|
+
await this.methods.log(logMessage);
|
|
134
|
+
|
|
135
|
+
return eavGroup;
|
|
136
|
+
},
|
|
137
|
+
|
|
104
138
|
createTextEnumGroup: async textEnumGroup =>
|
|
105
139
|
{
|
|
106
140
|
textEnumGroup = await this.ApiAdapter.textenum.setGroup(textEnumGroup);
|
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
|
});
|