@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 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, 'value') : 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, keyToMask = 'value', mask = '[secret]')
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 === keyToMask)
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], keyToMask, mask);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vario-software/vario-app-framework-backend",
3
- "version": "2025.44.0",
3
+ "version": "2025.45.0",
4
4
  "repository": "https://github.com/vario-software/vario-app-framework",
5
5
  "author": "VARIO Software AG",
6
6
  "homepage": "https://www.vario.ag",
@@ -26,6 +26,12 @@ function appAuthentication(req, res, next)
26
26
  context.appToken = token;
27
27
  context.accessToken = accessToken;
28
28
 
29
+ if (accessToken.tokenType === 'PERMITTED_TOKEN')
30
+ {
31
+ res.status(401).end();
32
+ return;
33
+ }
34
+
29
35
  next();
30
36
  })
31
37
  .catch(error =>
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
  );
@@ -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
- async function checkPermission(verb)
4
+ function checkPermission(verb, strict = true)
5
5
  {
6
- const { isSuperUser, permissions } = await getAccessToken();
6
+ const { isSuperUser, permissions } = getAccessToken();
7
7
 
8
8
  if (!(isSuperUser || permissions?.includes(verb)))
9
9
  {
10
- throw new HttpError(
11
- 'APP_AUTHORIZATION_FAILED',
12
- 403,
13
- 'utils/permission',
14
- { missingVerb: verb },
15
- null,
16
- 'ERROR',
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
- await checkPermission(verb);
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 { clientSecret, appIdentifier } = app.client;
51
+ const { appIdentifier, appJWK } = app.client;
52
52
 
53
- const key = new TextEncoder().encode(clientSecret);
53
+ const jwk = JSON.parse(appJWK).keys[0];
54
54
 
55
- jwtVerify(appToken, key)
56
- .then(({ payload }) =>
55
+ importJWK(jwk, 'ES256')
56
+ .then(key =>
57
57
  {
58
- const { aud, exp } = payload;
58
+ jwtVerify(appToken, key)
59
+ .then(({ payload }) =>
60
+ {
61
+ const { aud, exp } = payload;
59
62
 
60
- if (!aud || aud !== appIdentifier)
61
- {
62
- console.log('First cond', !aud, aud !== appIdentifier);
63
- reject();
64
- return;
65
- }
63
+ if (!aud || aud !== appIdentifier)
64
+ {
65
+ console.log('First cond', !aud, aud !== appIdentifier);
66
+ reject();
67
+ return;
68
+ }
66
69
 
67
- if (!exp || exp < (Date.now() / 1000))
68
- {
69
- console.log('Second cond', !exp, exp < (Date.now() / 1000));
70
- reject();
71
- return;
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
- resolve(payload);
77
+ resolve(payload);
78
+ })
79
+ .catch(reject);
75
80
  })
76
81
  .catch(reject);
77
82
  });