@oondemand/oon-core-back 0.4.20 → 0.4.22

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.20",
3
+ "version": "0.4.22",
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,
@@ -398,11 +398,13 @@ function decrypt(instance) {
398
398
 
399
399
  function instanceHeaders(instance) {
400
400
  if (instance.deploymentId) {
401
+ const tenantAware =
402
+ (ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel) !== "none";
401
403
  return {
402
404
  "x-oon-deployment-id": instance.deploymentId,
403
405
  "x-oon-deployment-token": decrypt(instance),
404
406
  "x-app-code": getAppCode(),
405
- ...(instance.tenantId ? { "x-oon-tenant-id": instance.tenantId } : {}),
407
+ ...(tenantAware && instance.tenantId ? { "x-oon-tenant-id": instance.tenantId } : {}),
406
408
  };
407
409
  }
408
410
  return {
@@ -426,11 +428,23 @@ async function operationalRequestHeaders(options = {}, dependencies = {}) {
426
428
  code: "OPERATIONAL_CREDENTIAL_REQUIRED",
427
429
  });
428
430
  }
429
- return {
431
+ const tenancyModel = ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel;
432
+ if (tenancyModel === "none" && options.tenantId) {
433
+ throw new GenericError("Apps globais devem usar targetTenantId, não tenantId.", {
434
+ statusCode: 400,
435
+ code: "TENANT_CONTEXT_NOT_ALLOWED",
436
+ });
437
+ }
438
+ const headers = {
430
439
  ...buildHeaders(instance),
431
440
  ...(options.tenantId ? { "x-oon-tenant-id": String(options.tenantId) } : {}),
441
+ ...(options.targetTenantId
442
+ ? { "x-oon-target-tenant-id": String(options.targetTenantId) }
443
+ : {}),
432
444
  ...(options.correlationId ? { "x-correlation-id": String(options.correlationId) } : {}),
433
445
  };
446
+ if (tenancyModel === "none") delete headers["x-oon-tenant-id"];
447
+ return headers;
434
448
  }
435
449
 
436
450
  function validateUrl(value) {
@@ -14,7 +14,7 @@ const SUPPORTED_MODULES = Object.freeze([
14
14
  ]);
15
15
  const SUPPORTED_TENANCY_MODELS = Object.freeze(["none", "single_tenant", "multi_tenant"]);
16
16
  const SUPPORTED_AVAILABILITIES = Object.freeze(["system", "common", "licensed"]);
17
- const SUPPORTED_ACCESS_MODES = Object.freeze(["tenant_members", "explicit_users"]);
17
+ const SUPPORTED_ACCESS_MODES = Object.freeze(["tenant_members", "explicit_users", "authenticated_users"]);
18
18
  const SUPPORTED_AUTHORIZATION_MODES = Object.freeze(["none", "rbac"]);
19
19
  // Compatibilidade de leitura para manifestos anteriores à separação das duas dimensões.
20
20
  const SUPPORTED_ACCESS_POLICIES = Object.freeze(["tenant_members", "explicit_users", "rbac"]);
@@ -264,6 +264,23 @@ function validateAppManifest(input, { coreVersion = corePackage.version } = {})
264
264
  if (dimensions.tenancyModel === "none" && dimensions.accessMode === "tenant_members") {
265
265
  addIssue(issues, "accessMode", "tenant_members exige uma aplicação tenant-aware.");
266
266
  }
267
+ if (dimensions.accessMode === "authenticated_users") {
268
+ if (dimensions.tenancyModel !== "none") {
269
+ addIssue(issues, "tenancyModel", "authenticated_users exige tenancyModel=none.");
270
+ }
271
+ if (dimensions.availability !== "common") {
272
+ addIssue(issues, "availability", "authenticated_users exige availability=common.");
273
+ }
274
+ if (dimensions.authorizationMode !== "rbac") {
275
+ addIssue(issues, "authorizationMode", "authenticated_users exige authorizationMode=rbac.");
276
+ }
277
+ if (dimensions.deploymentMode !== "singleton") {
278
+ addIssue(issues, "deploymentMode", "authenticated_users exige deploymentMode=singleton.");
279
+ }
280
+ }
281
+ if (dimensions.tenancyModel === "single_tenant" && dimensions.deploymentMode === "shared") {
282
+ addIssue(issues, "deploymentMode", "single_tenant não pode usar deploymentMode=shared; use dedicated.");
283
+ }
267
284
  if (!isPlainObject(input.modules)) {
268
285
  addIssue(issues, "modules", "deve ser um objeto.");
269
286
  } else {