@oondemand/oon-core-back 0.4.0 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/oon-core-back",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -18,6 +18,7 @@ const {
18
18
  validateRootCredentials,
19
19
  encrypt,
20
20
  instanceHeaders,
21
+ operationalIdentity,
21
22
  validateParams,
22
23
  providerPost,
23
24
  providerPayload,
@@ -83,13 +84,11 @@ function safeContext(data) {
83
84
 
84
85
  function requireRootBootstrapResult(result) {
85
86
  const usuarioId = result?.usuario?.id || result?.usuario?._id;
86
- const tenantId = result?.tenant?.id || result?.tenant?._id;
87
87
  const appId = result?.app?.id || result?.app?._id;
88
- const licencaId = result?.licenca?.id || result?.licenca?._id;
89
88
 
90
- if (!usuarioId || !tenantId || !appId || !licencaId) {
89
+ if (!usuarioId || !appId) {
91
90
  throw new GenericError(
92
- "O bootstrap root não retornou a estrutura mínima do ecossistema.",
91
+ "O bootstrap root não retornou Usuario e App de sistema.",
93
92
  { statusCode: 500 },
94
93
  );
95
94
  }
@@ -106,9 +105,8 @@ function requireRootBootstrapResult(result) {
106
105
 
107
106
  return {
108
107
  usuarioId: String(usuarioId),
109
- tenantId: String(tenantId),
110
108
  appId: String(appId),
111
- licencaId: String(licencaId),
109
+ systemAccessId: idOf(result?.systemAccess) ? String(idOf(result.systemAccess)) : undefined,
112
110
  };
113
111
  }
114
112
 
@@ -141,11 +139,17 @@ function idOf(value) {
141
139
  return value?.id || value?._id || value;
142
140
  }
143
141
 
144
- function summarizeDevInstance(data, appCode) {
142
+ function summarizeOperationalIdentity(data, appCode, { allowSynthetic = false } = {}) {
145
143
  const appId = idOf(data?.app);
144
+ const deploymentId = idOf(data?.deployment);
145
+ const instanceId = idOf(data?.instancia || data?.instance);
146
146
  return {
147
- instanceId: `dev-local:${appId || appCode}`,
148
- instanceCode: "dev-local",
147
+ instanceId: instanceId ? String(instanceId) : (allowSynthetic ? `dev-local:${appId || appCode}` : undefined),
148
+ instanceCode: data?.instancia?.codigo || data?.instance?.codigo || (allowSynthetic ? "dev-local" : undefined),
149
+ deploymentId: deploymentId ? String(deploymentId) : undefined,
150
+ bindingId: idOf(data?.binding) ? String(idOf(data.binding)) : undefined,
151
+ entitlementId: idOf(data?.entitlement) ? String(idOf(data.entitlement)) : undefined,
152
+ credentialType: deploymentId ? "deployment" : "instance",
149
153
  appId: appId ? String(appId) : undefined,
150
154
  tenantId: idOf(data?.tenant) ? String(idOf(data.tenant)) : undefined,
151
155
  licencaId: idOf(data?.licenca) ? String(idOf(data.licenca)) : undefined,
@@ -171,7 +175,7 @@ router.get(
171
175
  const canActivateMember =
172
176
  instance.ecosystemRole === "member" &&
173
177
  ["nao_ativada", "erro"].includes(instance.status) &&
174
- !instance.instanceId;
178
+ !operationalIdentity(instance);
175
179
 
176
180
  sendResponse({
177
181
  res,
@@ -182,7 +186,7 @@ router.get(
182
186
  podeAtivar: canActivateRoot || canActivateMember,
183
187
  podeTentarNovamente:
184
188
  instance.ecosystemRole === "member" &&
185
- Boolean(instance.instanceId) &&
189
+ Boolean(operationalIdentity(instance)) &&
186
190
  ["configurando", "erro"].includes(instance.status),
187
191
  motivoSuspensao: instance.motivoSuspensao,
188
192
  motivoRevogacao: instance.motivoRevogacao,
@@ -284,14 +288,14 @@ router.post(
284
288
  instanceCode: result.instanceCode || "root",
285
289
  appCode: getAppCode(),
286
290
  appId: ids.appId,
287
- tenantId: ids.tenantId,
288
- licencaId: ids.licencaId,
291
+ tenantId: undefined,
292
+ licencaId: undefined,
289
293
  ambiente: ambiente(),
290
294
  nomeInstancia: ctx.config.name || ctx.config.slug || getAppCode(),
291
295
  publicUrl: publicUrl(req),
292
- tenant: result.tenant,
293
296
  app: result.app,
294
- licenca: result.licenca,
297
+ tenant: undefined,
298
+ licenca: undefined,
295
299
  parametrosGlobais: {},
296
300
  parametrosAplicacao: {},
297
301
  configurationVersion: config.configurationVersion,
@@ -389,6 +393,13 @@ router.post(
389
393
  branch: req.body?.branch,
390
394
  nomeUsuario: req.body?.nomeUsuario,
391
395
  nomeTenant: req.body?.nomeTenant,
396
+ ecosystemRole: ctx.config.ecosystem?.role,
397
+ tenancyModel: ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel,
398
+ availability: ctx.config.availability || ctx.config.ecosystem?.availability,
399
+ accessMode: ctx.config.accessMode || ctx.config.ecosystem?.accessMode,
400
+ authorizationMode:
401
+ ctx.config.authorizationMode || ctx.config.ecosystem?.authorizationMode,
402
+ deploymentMode: ctx.config.deploymentMode || ctx.config.ecosystem?.deploymentMode,
392
403
  },
393
404
  { Authorization: authorization },
394
405
  );
@@ -399,7 +410,8 @@ router.post(
399
410
  const resolvedAppCode = String(
400
411
  data.app?.codigo || data.appCode || appCode,
401
412
  ).toLowerCase();
402
- const ids = summarizeDevInstance(data, resolvedAppCode);
413
+ const ids = summarizeOperationalIdentity(data, resolvedAppCode, { allowSynthetic: true });
414
+ const operationalToken = data?.credencial?.token || data?.deploymentCredential?.token;
403
415
  const now = new Date();
404
416
 
405
417
  Object.assign(instance, {
@@ -407,6 +419,10 @@ router.post(
407
419
  ecosystemRole: "member",
408
420
  instanceId: ids.instanceId,
409
421
  instanceCode: ids.instanceCode,
422
+ deploymentId: ids.deploymentId,
423
+ bindingId: ids.bindingId,
424
+ entitlementId: ids.entitlementId,
425
+ credentialType: ids.credentialType,
410
426
  appCode: resolvedAppCode,
411
427
  appId: ids.appId,
412
428
  tenantId: ids.tenantId,
@@ -417,13 +433,22 @@ router.post(
417
433
  publicUrl: urlPublica,
418
434
  coreVersion: process.env.npm_package_version || "0.0.0",
419
435
  appVersion: process.env.SERVICE_VERSION || "0.1.0",
420
- credentialEncrypted: undefined,
421
- credentialIv: undefined,
422
- credentialAuthTag: undefined,
423
- credentialVersion: undefined,
436
+ ...(operationalToken ? encrypt(operationalToken) : {
437
+ credentialEncrypted: undefined,
438
+ credentialIv: undefined,
439
+ credentialAuthTag: undefined,
440
+ }),
441
+ credentialVersion:
442
+ data?.credencial?.versao ||
443
+ data?.credencial?.version ||
444
+ data?.deploymentCredential?.versao ||
445
+ data?.deploymentCredential?.version,
424
446
  tenant: data.tenant,
425
447
  app: data.app,
426
448
  licenca: data.licenca,
449
+ deployment: data.deployment,
450
+ binding: data.binding,
451
+ entitlement: data.entitlement,
427
452
  parametrosGlobais: {
428
453
  ...globalDefaults,
429
454
  nomeInstancia: nomeCentral,
@@ -455,6 +480,7 @@ router.post(
455
480
  sucesso: true,
456
481
  status: "ativa",
457
482
  instanceId: instance.instanceId,
483
+ deploymentId: instance.deploymentId,
458
484
  });
459
485
  } catch (error) {
460
486
  instance.status = "erro";
@@ -550,7 +576,7 @@ router.post(
550
576
  const instance = await ensureInstance();
551
577
 
552
578
  if (
553
- instance.instanceId &&
579
+ operationalIdentity(instance) &&
554
580
  ["configurando", "erro"].includes(instance.status)
555
581
  ) {
556
582
  return res.redirect(307, "/ativacao/tentar-novamente");
@@ -589,8 +615,9 @@ router.post(
589
615
  );
590
616
  assertApp(activated.data);
591
617
 
592
- const token = activated.data?.credencial?.token;
593
- if (!activated.data?.instancia?.id || !token) {
618
+ const token = activated.data?.credencial?.token || activated.data?.deploymentCredential?.token;
619
+ const ids = summarizeOperationalIdentity(activated.data, getAppCode());
620
+ if (!(ids.deploymentId || ids.instanceId) || !token) {
594
621
  throw new GenericError("Resposta de ativação inválida.", {
595
622
  statusCode: 502,
596
623
  });
@@ -598,9 +625,17 @@ router.post(
598
625
 
599
626
  Object.assign(instance, encrypt(token), {
600
627
  status: "configurando",
601
- instanceId: activated.data.instancia.id,
602
- instanceCode: activated.data.instancia.codigo,
603
- credentialVersion: activated.data.credencial.versao,
628
+ instanceId: ids.instanceId,
629
+ instanceCode: ids.instanceCode,
630
+ deploymentId: ids.deploymentId,
631
+ bindingId: ids.bindingId,
632
+ entitlementId: ids.entitlementId,
633
+ credentialType: ids.credentialType,
634
+ credentialVersion:
635
+ activated.data.credencial?.versao ||
636
+ activated.data.credencial?.version ||
637
+ activated.data.deploymentCredential?.versao ||
638
+ activated.data.deploymentCredential?.version,
604
639
  appCode: getAppCode(),
605
640
  appId: activated.data.app?.id || preview.data?.app?.id,
606
641
  tenantId: activated.data.tenant?.id || preview.data?.tenant?.id,
@@ -611,6 +646,9 @@ router.post(
611
646
  tenant: activated.data.tenant || preview.data?.tenant,
612
647
  app: activated.data.app || preview.data?.app,
613
648
  licenca: activated.data.licenca || preview.data?.licenca,
649
+ deployment: activated.data.deployment || preview.data?.deployment,
650
+ binding: activated.data.binding || preview.data?.binding,
651
+ entitlement: activated.data.entitlement || preview.data?.entitlement,
614
652
  parametrosGlobais,
615
653
  parametrosAplicacao,
616
654
  configurationVersion: config.configurationVersion,
@@ -629,6 +667,7 @@ router.post(
629
667
  sucesso: true,
630
668
  status: "ativa",
631
669
  instanceId: instance.instanceId,
670
+ deploymentId: instance.deploymentId,
632
671
  });
633
672
  }),
634
673
  );
@@ -687,7 +726,7 @@ router.post(
687
726
  const config = activationConfig();
688
727
 
689
728
  if (
690
- !instance.instanceId ||
729
+ !operationalIdentity(instance) ||
691
730
  !["configurando", "erro"].includes(instance.status)
692
731
  ) {
693
732
  throw new GenericError(
@@ -15,6 +15,7 @@ const {
15
15
  } = require("../core/utils/activationProvider");
16
16
 
17
17
  const DEV_ACTIVATION_PATH = "/integracao/dev-local/ativar-central";
18
+ const TENANT_HEADER = "x-oon-tenant-id";
18
19
  const router = express.Router();
19
20
 
20
21
  function readBasicAuthorization(req) {
@@ -41,6 +42,15 @@ function translateProviderError(error) {
41
42
  return new GenericError(message, { statusCode });
42
43
  }
43
44
 
45
+ function tenantForLogin(req, instance) {
46
+ const tenancyModel =
47
+ ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel;
48
+ if (tenancyModel === "multi_tenant") {
49
+ return req.headers[TENANT_HEADER] || instance.tenantId;
50
+ }
51
+ return instance.tenantId;
52
+ }
53
+
44
54
  async function authenticateRoot(req, res, authorization, instance) {
45
55
  const authenticate = ctx.config.auth?.authenticate;
46
56
  if (instance.ecosystemRole === "root" && typeof authenticate === "function") {
@@ -62,6 +72,7 @@ router.post(
62
72
  if (await authenticateRoot(req, res, authorization, instance)) return;
63
73
  const baseUrl = getBaseUrl(req);
64
74
  const appCode = getAppCode();
75
+ const tenantId = tenantForLogin(req, instance);
65
76
  try {
66
77
  const response = await axios.post(
67
78
  `${baseUrl}${LOGIN_PATH}`,
@@ -73,6 +84,7 @@ router.post(
73
84
  Authorization: authorization,
74
85
  "Content-Type": "application/json",
75
86
  "x-app-code": appCode,
87
+ ...(tenantId ? { [TENANT_HEADER]: tenantId } : {}),
76
88
  },
77
89
  },
78
90
  );
@@ -104,6 +116,13 @@ router.post(
104
116
  branch: req.body?.branch,
105
117
  nomeUsuario: req.body?.nomeUsuario,
106
118
  nomeTenant: req.body?.nomeTenant,
119
+ ecosystemRole: ctx.config.ecosystem?.role,
120
+ tenancyModel: ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel,
121
+ availability: ctx.config.availability || ctx.config.ecosystem?.availability,
122
+ accessMode: ctx.config.accessMode || ctx.config.ecosystem?.accessMode,
123
+ authorizationMode:
124
+ ctx.config.authorizationMode || ctx.config.ecosystem?.authorizationMode,
125
+ deploymentMode: ctx.config.deploymentMode || ctx.config.ecosystem?.deploymentMode,
107
126
  },
108
127
  {
109
128
  timeout: getTimeout(),
@@ -10,6 +10,10 @@ const schema = new mongoose.Schema(
10
10
  ecosystemRole: { type: String, enum: roles, default: "member", index: true },
11
11
  instanceId: { type: String, index: true },
12
12
  instanceCode: String,
13
+ deploymentId: { type: String, index: true },
14
+ bindingId: String,
15
+ entitlementId: String,
16
+ credentialType: { type: String, enum: ["instance", "deployment"] },
13
17
  appCode: { type: String, index: true },
14
18
  appId: String,
15
19
  tenantId: String,
@@ -30,6 +34,9 @@ const schema = new mongoose.Schema(
30
34
  parametrosAplicacao: { type: mongoose.Schema.Types.Mixed, default: {} },
31
35
  tenant: { type: mongoose.Schema.Types.Mixed, default: {} },
32
36
  app: { type: mongoose.Schema.Types.Mixed, default: {} },
37
+ deployment: { type: mongoose.Schema.Types.Mixed, default: {} },
38
+ binding: { type: mongoose.Schema.Types.Mixed, default: {} },
39
+ entitlement: { type: mongoose.Schema.Types.Mixed, default: {} },
33
40
  licenca: { type: mongoose.Schema.Types.Mixed, default: {} },
34
41
  ativadaEm: Date,
35
42
  concluidaEm: Date,
@@ -5,6 +5,7 @@ const GenericError = require("../errors/GenericError");
5
5
  const {
6
6
  ensureInstance,
7
7
  instanceHeaders,
8
+ operationalIdentity,
8
9
  providerPost,
9
10
  } = require("../utils/instanceActivation");
10
11
 
@@ -15,10 +16,10 @@ const IDENTITY_PATHS = Object.freeze({
15
16
 
16
17
  async function defaultProviderCall(req, path, payload) {
17
18
  const instance = await ensureInstance();
18
- if (!instance?.instanceId || !instance?.credentialEncrypted) {
19
- throw new GenericError("A instância precisa estar ativa para gerenciar identidades.", {
19
+ if (!operationalIdentity(instance) || !instance?.credentialEncrypted) {
20
+ throw new GenericError("O deployment precisa estar ativo para gerenciar identidades.", {
20
21
  statusCode: 423,
21
- code: "RBAC_INSTANCE_CREDENTIAL_REQUIRED",
22
+ code: "RBAC_OPERATIONAL_CREDENTIAL_REQUIRED",
22
23
  });
23
24
  }
24
25
 
@@ -43,6 +44,7 @@ async function provisionInvitation({ req, invitation, password }) {
43
44
  return defaultProviderCall(req, IDENTITY_PATHS.provisionInvitation, {
44
45
  email: invitation.email,
45
46
  nome: invitation.name,
47
+ roleCode: invitation.roleCode,
46
48
  password,
47
49
  invitationFingerprint: invitation.tokenFingerprint,
48
50
  });
@@ -217,6 +217,10 @@ async function domainHasActiveRootApp() {
217
217
  function clearInstanceIdentity(instance) {
218
218
  instance.instanceId = undefined;
219
219
  instance.instanceCode = undefined;
220
+ instance.deploymentId = undefined;
221
+ instance.bindingId = undefined;
222
+ instance.entitlementId = undefined;
223
+ instance.credentialType = undefined;
220
224
  instance.appId = undefined;
221
225
  instance.tenantId = undefined;
222
226
  instance.licencaId = undefined;
@@ -393,6 +397,14 @@ function decrypt(instance) {
393
397
  }
394
398
 
395
399
  function instanceHeaders(instance) {
400
+ if (instance.deploymentId) {
401
+ return {
402
+ "x-oon-deployment-id": instance.deploymentId,
403
+ "x-oon-deployment-token": decrypt(instance),
404
+ "x-app-code": getAppCode(),
405
+ ...(instance.tenantId ? { "x-oon-tenant-id": instance.tenantId } : {}),
406
+ };
407
+ }
396
408
  return {
397
409
  "x-oon-instance-id": instance.instanceId,
398
410
  "x-oon-instance-token": decrypt(instance),
@@ -400,6 +412,10 @@ function instanceHeaders(instance) {
400
412
  };
401
413
  }
402
414
 
415
+ function operationalIdentity(instance) {
416
+ return instance?.deploymentId || instance?.instanceId;
417
+ }
418
+
403
419
  function validateUrl(value) {
404
420
  const url = new URL(value);
405
421
  if (!["http:", "https:"].includes(url.protocol)) throw new Error();
@@ -468,6 +484,11 @@ function providerPayload(req, codigoAtivacao, extra = {}) {
468
484
  coreVersion: version(),
469
485
  appVersion: version(),
470
486
  fingerprint: fingerprint(),
487
+ tenancyModel: ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel,
488
+ availability: ctx.config.availability || ctx.config.ecosystem?.availability,
489
+ accessMode: ctx.config.accessMode || ctx.config.ecosystem?.accessMode,
490
+ authorizationMode: ctx.config.authorizationMode || ctx.config.ecosystem?.authorizationMode,
491
+ deploymentMode: ctx.config.deploymentMode || ctx.config.ecosystem?.deploymentMode,
471
492
  ...extra,
472
493
  };
473
494
  }
@@ -491,6 +512,7 @@ module.exports = {
491
512
  encrypt,
492
513
  decrypt,
493
514
  instanceHeaders,
515
+ operationalIdentity,
494
516
  validateParams,
495
517
  providerPost,
496
518
  providerPayload,
@@ -12,10 +12,40 @@ const SUPPORTED_APP_KINDS = Object.freeze(["root-central", "member-central", "po
12
12
  const SUPPORTED_MODULES = Object.freeze([
13
13
  "collections", "documents", "pipelines", "integrations", "omie", "assistants", "currencies",
14
14
  ]);
15
+ const SUPPORTED_TENANCY_MODELS = Object.freeze(["none", "single_tenant", "multi_tenant"]);
16
+ const SUPPORTED_AVAILABILITIES = Object.freeze(["system", "common", "licensed"]);
17
+ const SUPPORTED_ACCESS_MODES = Object.freeze(["tenant_members", "explicit_users"]);
18
+ const SUPPORTED_AUTHORIZATION_MODES = Object.freeze(["none", "rbac"]);
19
+ // Compatibilidade de leitura para manifestos anteriores à separação das duas dimensões.
20
+ const SUPPORTED_ACCESS_POLICIES = Object.freeze(["tenant_members", "explicit_users", "rbac"]);
21
+ const SUPPORTED_DEPLOYMENT_MODES = Object.freeze(["singleton", "shared", "dedicated", "hybrid"]);
15
22
  const APP_KIND_ROLE = Object.freeze({
16
23
  "root-central": "root",
17
24
  "member-central": "member",
18
- "portal-cockpit": "portal",
25
+ "portal-cockpit": "member",
26
+ });
27
+ const APP_KIND_DIMENSIONS = Object.freeze({
28
+ "root-central": Object.freeze({
29
+ tenancyModel: "none",
30
+ availability: "system",
31
+ accessMode: "explicit_users",
32
+ authorizationMode: "rbac",
33
+ deploymentMode: "singleton",
34
+ }),
35
+ "member-central": Object.freeze({
36
+ tenancyModel: "single_tenant",
37
+ availability: "licensed",
38
+ accessMode: "explicit_users",
39
+ authorizationMode: "rbac",
40
+ deploymentMode: "dedicated",
41
+ }),
42
+ "portal-cockpit": Object.freeze({
43
+ tenancyModel: "multi_tenant",
44
+ availability: "common",
45
+ accessMode: "tenant_members",
46
+ authorizationMode: "none",
47
+ deploymentMode: "shared",
48
+ }),
19
49
  });
20
50
 
21
51
  class AppManifestError extends Error {
@@ -105,14 +135,42 @@ function validateActivation(activation, issues) {
105
135
  }
106
136
  }
107
137
 
138
+ function validateDimension(value, field, supported, issues) {
139
+ if (value === undefined) return;
140
+ if (!supported.includes(value)) {
141
+ addIssue(issues, field, `deve ser um de: ${supported.join(", ")}.`);
142
+ }
143
+ }
144
+
145
+ function legacyAccessDimensions(accessPolicy) {
146
+ return {
147
+ tenant_members: { accessMode: "tenant_members", authorizationMode: "none" },
148
+ explicit_users: { accessMode: "explicit_users", authorizationMode: "none" },
149
+ rbac: { accessMode: "explicit_users", authorizationMode: "rbac" },
150
+ }[accessPolicy];
151
+ }
152
+
153
+ function dimensionsFor(manifest) {
154
+ const defaults = APP_KIND_DIMENSIONS[manifest.appKind] || APP_KIND_DIMENSIONS["member-central"];
155
+ const legacy = legacyAccessDimensions(manifest.accessPolicy) || {};
156
+ return {
157
+ tenancyModel: manifest.tenancyModel || defaults.tenancyModel,
158
+ availability: manifest.availability || defaults.availability,
159
+ accessMode: manifest.accessMode || legacy.accessMode || defaults.accessMode,
160
+ authorizationMode: manifest.authorizationMode || legacy.authorizationMode || defaults.authorizationMode,
161
+ deploymentMode: manifest.deploymentMode || defaults.deploymentMode,
162
+ };
163
+ }
164
+
108
165
  function validateAppManifest(input, { coreVersion = corePackage.version } = {}) {
109
166
  const issues = [];
110
167
  if (!isPlainObject(input)) {
111
168
  throw new AppManifestError("central.app.json inválido.", [{ path: "$", message: "deve conter um objeto JSON." }]);
112
169
  }
113
170
  const allowedTopLevel = new Set([
114
- "$schema", "schemaVersion", "id", "name", "slug", "appKind", "modules", "capabilities",
115
- "compatibility", "activation", "rbac",
171
+ "$schema", "schemaVersion", "id", "name", "slug", "appKind",
172
+ "tenancyModel", "availability", "accessMode", "authorizationMode", "accessPolicy", "deploymentMode",
173
+ "modules", "capabilities", "compatibility", "activation", "rbac",
116
174
  ]);
117
175
  for (const key of Object.keys(input)) {
118
176
  if (!allowedTopLevel.has(key)) addIssue(issues, key, "propriedade não suportada.");
@@ -130,6 +188,32 @@ function validateAppManifest(input, { coreVersion = corePackage.version } = {})
130
188
  if (!SUPPORTED_APP_KINDS.includes(input.appKind)) {
131
189
  addIssue(issues, "appKind", `deve ser um de: ${SUPPORTED_APP_KINDS.join(", ")}.`);
132
190
  }
191
+ validateDimension(input.tenancyModel, "tenancyModel", SUPPORTED_TENANCY_MODELS, issues);
192
+ validateDimension(input.availability, "availability", SUPPORTED_AVAILABILITIES, issues);
193
+ validateDimension(input.accessMode, "accessMode", SUPPORTED_ACCESS_MODES, issues);
194
+ validateDimension(input.authorizationMode, "authorizationMode", SUPPORTED_AUTHORIZATION_MODES, issues);
195
+ validateDimension(input.accessPolicy, "accessPolicy", SUPPORTED_ACCESS_POLICIES, issues);
196
+ validateDimension(input.deploymentMode, "deploymentMode", SUPPORTED_DEPLOYMENT_MODES, issues);
197
+ if (input.accessPolicy !== undefined && (input.accessMode !== undefined || input.authorizationMode !== undefined)) {
198
+ addIssue(issues, "accessPolicy", "é legado e não pode ser combinado com accessMode/authorizationMode.");
199
+ }
200
+ const dimensions = dimensionsFor(input);
201
+ const role = APP_KIND_ROLE[input.appKind];
202
+ if (role === "root" && (
203
+ dimensions.tenancyModel !== "none" ||
204
+ dimensions.availability !== "system" ||
205
+ dimensions.accessMode !== "explicit_users" ||
206
+ dimensions.authorizationMode !== "rbac" ||
207
+ dimensions.deploymentMode !== "singleton"
208
+ )) {
209
+ addIssue(issues, "appKind", "root-central exige none/system/explicit_users/rbac/singleton.");
210
+ }
211
+ if (role === "member" && dimensions.availability === "system") {
212
+ addIssue(issues, "availability", "Apps member não podem usar availability=system.");
213
+ }
214
+ if (dimensions.tenancyModel === "none" && dimensions.accessMode === "tenant_members") {
215
+ addIssue(issues, "accessMode", "tenant_members exige uma aplicação tenant-aware.");
216
+ }
133
217
  if (!isPlainObject(input.modules)) {
134
218
  addIssue(issues, "modules", "deve ser um objeto.");
135
219
  } else {
@@ -165,13 +249,18 @@ function resolveAppManifestPath(cwd = process.cwd()) {
165
249
  }
166
250
 
167
251
  function appManifestToConfig(manifest, manifestPath) {
252
+ const dimensions = dimensionsFor(manifest);
168
253
  return {
169
254
  serviceName: manifest.slug,
170
255
  name: manifest.name,
171
256
  slug: manifest.slug,
172
257
  appCode: manifest.id,
173
258
  appKind: manifest.appKind,
174
- ecosystem: { role: APP_KIND_ROLE[manifest.appKind] },
259
+ ...dimensions,
260
+ ecosystem: {
261
+ role: APP_KIND_ROLE[manifest.appKind],
262
+ ...dimensions,
263
+ },
175
264
  activation: manifest.activation || {
176
265
  enabled: manifest.appKind !== "portal-cockpit",
177
266
  configurationVersion: 1,
@@ -208,9 +297,18 @@ module.exports = {
208
297
  APP_MANIFEST_SCHEMA_VERSION,
209
298
  SUPPORTED_APP_KINDS,
210
299
  SUPPORTED_MODULES,
300
+ SUPPORTED_TENANCY_MODELS,
301
+ SUPPORTED_AVAILABILITIES,
302
+ SUPPORTED_ACCESS_MODES,
303
+ SUPPORTED_AUTHORIZATION_MODES,
304
+ SUPPORTED_ACCESS_POLICIES,
305
+ SUPPORTED_DEPLOYMENT_MODES,
306
+ APP_KIND_DIMENSIONS,
211
307
  AppManifestError,
212
308
  parseVersion,
213
309
  compareVersions,
310
+ legacyAccessDimensions,
311
+ dimensionsFor,
214
312
  validateAppManifest,
215
313
  resolveAppManifestPath,
216
314
  appManifestToConfig,