@oondemand/oon-core-back 0.4.21 → 0.4.23

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.21",
3
+ "version": "0.4.23",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -60,6 +60,7 @@ function configuredSessionScope() {
60
60
  function tenantForLogin(req, instance) {
61
61
  const tenancyModel =
62
62
  ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel;
63
+ if (tenancyModel === "none") return undefined;
63
64
  if (tenancyModel === "multi_tenant") {
64
65
  // Uma instância compartilhada não pertence ao tenant que a ativou
65
66
  // originalmente. Sem seleção explícita, a Central de Ativações resolve o
@@ -47,8 +47,10 @@ function addDataScopeFields(schema, definition) {
47
47
  if (!["tenant", "user"].includes(scope)) return schema;
48
48
 
49
49
  const fields = {};
50
+ const tenantScoped = scope === "tenant"
51
+ || (scope === "user" && (ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel) !== "none");
50
52
  const scopedTenantField = tenantField(definition);
51
- if (!schema.path(scopedTenantField)) {
53
+ if (tenantScoped && !schema.path(scopedTenantField)) {
52
54
  fields[scopedTenantField] = {
53
55
  type: String,
54
56
  required: true,
@@ -17,7 +17,10 @@ const {
17
17
  async function defaultVerifyToken(token, { req } = {}) {
18
18
  const baseUrl = getBaseUrl(req);
19
19
  const appCode = getAppCode();
20
- const requestedTenantId = readRequestedTenantId(req);
20
+ const requestedTenantId =
21
+ (ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel) === "none"
22
+ ? undefined
23
+ : readRequestedTenantId(req);
21
24
 
22
25
  let response;
23
26
  try {
@@ -178,6 +178,20 @@ async function resolveUserAccess(user, { touch = true } = {}) {
178
178
  }
179
179
 
180
180
  let access = await findAccessForUser(user);
181
+ const authenticatedAdmission =
182
+ (ctx.config.accessMode || ctx.config.ecosystem?.accessMode) === "authenticated_users";
183
+ if (!access && authenticatedAdmission) {
184
+ const globalPermissions = Array.isArray(user?.globalPermissions)
185
+ ? [...new Set(user.globalPermissions.map(String))]
186
+ : [];
187
+ return {
188
+ mode: "authenticated_users",
189
+ status: "active",
190
+ roleCode: "authenticated_user",
191
+ permissions: globalPermissions,
192
+ mustChangePassword: Boolean(user?.mustChangePassword || user?.firstAccessPending),
193
+ };
194
+ }
181
195
  if (!access) access = await migrateLegacyAccess(user);
182
196
  if (!access) {
183
197
  throw accessError("Usuário autenticado sem perfil local nesta Central.", "RBAC_LOCAL_ACCESS_REQUIRED");
@@ -1,6 +1,7 @@
1
1
  const GenericError = require("./errors/GenericError");
2
2
 
3
3
  const TENANT_HEADER = "x-oon-tenant-id";
4
+ const TARGET_TENANT_HEADER = "x-oon-target-tenant-id";
4
5
  const TENANCY_MODELS = Object.freeze(["none", "single_tenant", "multi_tenant"]);
5
6
  const DATA_SCOPES = Object.freeze(["legacy", "system", "tenant", "user"]);
6
7
 
@@ -21,6 +22,21 @@ function readRequestedTenantId(req) {
21
22
  return stringId(req?.headers?.[TENANT_HEADER]);
22
23
  }
23
24
 
25
+ function readTargetTenantId(req) {
26
+ return stringId(req?.headers?.[TARGET_TENANT_HEADER]);
27
+ }
28
+
29
+ function requireTargetTenantId(req) {
30
+ const targetTenantId = readTargetTenantId(req);
31
+ if (!targetTenantId) {
32
+ throw new GenericError("Informe o tenant-alvo da operação.", {
33
+ statusCode: 400,
34
+ code: "TARGET_TENANT_REQUIRED",
35
+ });
36
+ }
37
+ return targetTenantId;
38
+ }
39
+
24
40
  function inferVerifiedTenantId(usuario) {
25
41
  return stringId(
26
42
  usuario?.tenantId ||
@@ -124,6 +140,16 @@ function scopeFilter(entryOrDefinition, accessContext) {
124
140
  const scope = dataScope(entryOrDefinition);
125
141
  if (scope === "legacy" || scope === "system") return {};
126
142
 
143
+ if (scope === "user" && accessContext?.tenancyModel === "none") {
144
+ return {
145
+ [userField(entryOrDefinition)]: requireContextValue(
146
+ accessContext?.userId,
147
+ "USER_CONTEXT_REQUIRED",
148
+ "Contexto de usuário obrigatório para este recurso.",
149
+ ),
150
+ };
151
+ }
152
+
127
153
  const tenantId = requireContextValue(
128
154
  accessContext?.tenantId,
129
155
  "TENANT_CONTEXT_REQUIRED",
@@ -144,6 +170,9 @@ function scopeFilter(entryOrDefinition, accessContext) {
144
170
 
145
171
  function mergeScopedFilter(entryOrDefinition, filter = {}, accessContext) {
146
172
  const clean = { ...(filter || {}) };
173
+ if (dataScope(entryOrDefinition) === "user" && accessContext?.tenancyModel === "none") {
174
+ delete clean[tenantField(entryOrDefinition)];
175
+ }
147
176
  const enforced = scopeFilter(entryOrDefinition, accessContext);
148
177
 
149
178
  // Remove tentativas de substituir escopo via query/body. O valor confiável
@@ -162,6 +191,9 @@ function scopeMutation(entryOrDefinition, data = {}, accessContext) {
162
191
 
163
192
  const scope = dataScope(entryOrDefinition);
164
193
  if (scope === "legacy" || scope === "system") return result;
194
+ if (scope === "user" && accessContext?.tenancyModel === "none") {
195
+ delete result[tenantField(entryOrDefinition)];
196
+ }
165
197
 
166
198
  const enforced = scopeFilter(entryOrDefinition, accessContext);
167
199
  Object.assign(result, enforced);
@@ -182,10 +214,13 @@ function assertScopedEntry(entryOrDefinition) {
182
214
 
183
215
  module.exports = {
184
216
  TENANT_HEADER,
217
+ TARGET_TENANT_HEADER,
185
218
  TENANCY_MODELS,
186
219
  DATA_SCOPES,
187
220
  stringId,
188
221
  readRequestedTenantId,
222
+ readTargetTenantId,
223
+ requireTargetTenantId,
189
224
  inferVerifiedTenantId,
190
225
  inferTenancyModel,
191
226
  createAccessContext,
@@ -114,6 +114,27 @@ function role() {
114
114
  return ctx.config.ecosystem?.role === "root" ? "root" : "member";
115
115
  }
116
116
 
117
+ function deploymentBootstrap() {
118
+ const deploymentId = String(process.env.OON_DEPLOYMENT_ID || "").trim();
119
+ const token = String(process.env.OON_DEPLOYMENT_TOKEN || "").trim();
120
+ if (!deploymentId && !token) return null;
121
+ if (!deploymentId || !token) {
122
+ throw new GenericError(
123
+ "OON_DEPLOYMENT_ID e OON_DEPLOYMENT_TOKEN devem ser configurados juntos.",
124
+ { statusCode: 500, code: "DEPLOYMENT_BOOTSTRAP_INCOMPLETE" },
125
+ );
126
+ }
127
+ const tenancyModel =
128
+ ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel;
129
+ if (tenancyModel !== "none") {
130
+ throw new GenericError(
131
+ "Bootstrap direto de deployment é permitido somente para Apps globais.",
132
+ { statusCode: 500, code: "DEPLOYMENT_BOOTSTRAP_TENANCY_INVALID" },
133
+ );
134
+ }
135
+ return { deploymentId, token };
136
+ }
137
+
117
138
  function ambiente() {
118
139
  const value =
119
140
  process.env.APP_ENVIRONMENT || process.env.NODE_ENV || "desenvolvimento";
@@ -264,6 +285,38 @@ async function ensureInstance() {
264
285
  await instance.save();
265
286
  }
266
287
 
288
+ const bootstrap = deploymentBootstrap();
289
+ if (bootstrap) {
290
+ let currentToken = null;
291
+ try {
292
+ currentToken = decrypt(instance);
293
+ } catch {
294
+ currentToken = null;
295
+ }
296
+ if (
297
+ instance.deploymentId !== bootstrap.deploymentId ||
298
+ instance.credentialType !== "deployment" ||
299
+ currentToken !== bootstrap.token ||
300
+ instance.status !== "ativa"
301
+ ) {
302
+ clearInstanceIdentity(instance);
303
+ instance.deploymentId = bootstrap.deploymentId;
304
+ instance.credentialType = "deployment";
305
+ instance.status = "ativa";
306
+ instance.appCode = getAppCode();
307
+ instance.ambiente = ambiente();
308
+ instance.ecosystemUrl = process.env.CENTRAL_ATIVACAO_URL;
309
+ instance.publicUrl = process.env.PUBLIC_APP_URL;
310
+ instance.coreVersion = version();
311
+ instance.appVersion = version();
312
+ instance.ativadaEm = new Date();
313
+ instance.concluidaEm = new Date();
314
+ instance.credentialVersion = Number(instance.credentialVersion || 0) + 1;
315
+ Object.assign(instance, encrypt(bootstrap.token));
316
+ await instance.save();
317
+ }
318
+ }
319
+
267
320
  // Versões anteriores do Core marcavam a aplicação root como ativa sem criar
268
321
  // o App root e o grafo mínimo do ecossistema. Quando o domínio disponibiliza
269
322
  // o verificador, o App cadastrado passa a ser a fonte de verdade.
@@ -398,11 +451,13 @@ function decrypt(instance) {
398
451
 
399
452
  function instanceHeaders(instance) {
400
453
  if (instance.deploymentId) {
454
+ const tenantAware =
455
+ (ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel) !== "none";
401
456
  return {
402
457
  "x-oon-deployment-id": instance.deploymentId,
403
458
  "x-oon-deployment-token": decrypt(instance),
404
459
  "x-app-code": getAppCode(),
405
- ...(instance.tenantId ? { "x-oon-tenant-id": instance.tenantId } : {}),
460
+ ...(tenantAware && instance.tenantId ? { "x-oon-tenant-id": instance.tenantId } : {}),
406
461
  };
407
462
  }
408
463
  return {
@@ -426,11 +481,23 @@ async function operationalRequestHeaders(options = {}, dependencies = {}) {
426
481
  code: "OPERATIONAL_CREDENTIAL_REQUIRED",
427
482
  });
428
483
  }
429
- return {
484
+ const tenancyModel = ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel;
485
+ if (tenancyModel === "none" && options.tenantId) {
486
+ throw new GenericError("Apps globais devem usar targetTenantId, não tenantId.", {
487
+ statusCode: 400,
488
+ code: "TENANT_CONTEXT_NOT_ALLOWED",
489
+ });
490
+ }
491
+ const headers = {
430
492
  ...buildHeaders(instance),
431
493
  ...(options.tenantId ? { "x-oon-tenant-id": String(options.tenantId) } : {}),
494
+ ...(options.targetTenantId
495
+ ? { "x-oon-target-tenant-id": String(options.targetTenantId) }
496
+ : {}),
432
497
  ...(options.correlationId ? { "x-correlation-id": String(options.correlationId) } : {}),
433
498
  };
499
+ if (tenancyModel === "none") delete headers["x-oon-tenant-id"];
500
+ return headers;
434
501
  }
435
502
 
436
503
  function validateUrl(value) {
@@ -516,6 +583,7 @@ module.exports = {
516
583
  globalFields,
517
584
  rootFields,
518
585
  role,
586
+ deploymentBootstrap,
519
587
  ambiente,
520
588
  publicUrl,
521
589
  version,