@vegait/accounts-sdk 0.1.1-beta.45452 → 0.1.1-beta.45596

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/dist/index.cjs CHANGED
@@ -190,10 +190,21 @@ function getAccessibleProducts(claims, opts) {
190
190
  }
191
191
 
192
192
  // src/identityModel.ts
193
+ function deriveIsUnitOwner(claims) {
194
+ if (!claims?.unit_owner_ids || !claims.active_unit_id) return false;
195
+ try {
196
+ const owned = JSON.parse(claims.unit_owner_ids);
197
+ return Array.isArray(owned) && owned.includes(claims.active_unit_id);
198
+ } catch {
199
+ return false;
200
+ }
201
+ }
193
202
  function buildIdentityModel(claims, cfg) {
194
203
  const persona = derivePersona(claims);
204
+ const isUnitOwner = deriveIsUnitOwner(claims);
195
205
  return {
196
206
  persona,
207
+ isUnitOwner,
197
208
  user: { name: claims?.name ?? "", email: claims?.email ?? "" },
198
209
  products: getAccessibleProducts(claims, { urls: cfg?.urls }),
199
210
  catalog: getCatalogWithAccess(claims, { urls: cfg?.urls }),
@@ -202,7 +213,9 @@ function buildIdentityModel(claims, cfg) {
202
213
  manageUsers: persona === "gestor" || persona === "vega",
203
214
  adminConsole: persona === "vega",
204
215
  switchContext: persona === "gestor" || persona === "vega",
205
- manageNetwork: persona === "gestor"
216
+ manageNetwork: persona === "gestor",
217
+ manageProducts: isUnitOwner,
218
+ appointAdmins: isUnitOwner
206
219
  }
207
220
  };
208
221
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/claims.ts","../src/persona.ts","../src/productCatalog.ts","../src/identityModel.ts"],"sourcesContent":["// Barrel do @vegait/accounts-sdk. Preenchido pelas tasks seguintes.\n\nexport type { AccountsClaims } from \"./claims\";\nexport {\n decodeJwtPayload,\n decodeAccessToken,\n parseJsonArrayClaim,\n normalizeClaimArray,\n} from \"./claims\";\n\nexport type { Persona } from \"./persona\";\nexport { derivePersona } from \"./persona\";\n\nexport type { ProductMeta, CatalogProduct } from \"./productCatalog\";\nexport {\n PRODUCT_CATALOG,\n resolveProduct,\n getAccessibleProducts,\n getCatalogWithAccess,\n} from \"./productCatalog\";\n\nexport type { IdentityModel } from \"./identityModel\";\nexport { buildIdentityModel } from \"./identityModel\";\n","// Decoder de payload JWT sem verificacao criptografica. Universal (browser +\n// Node). Usar APENAS para extrair claims de tokens ja confiaveis (recebidos do\n// Accounts via fluxo OIDC). NUNCA para validar tokens de origem externa.\n\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid JWT format\");\n }\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = payload + \"=\".repeat((4 - (payload.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n const json = new TextDecoder(\"utf-8\").decode(bytes);\n return JSON.parse(json) as T;\n}\n\n// Claims do AT emitido pelo Accounts via OpenIddict. Tudo opcional (nem todo\n// campo chega em todo contexto). unit_ids/products vem como string contendo\n// JSON array. platform_role aparece so para Time VEGA. Espelha o token; fonte\n// da verdade = VegaClaimsHandler no accounts.api.\nexport type AccountsClaims = {\n sub?: string;\n email?: string;\n name?: string;\n platform_role?: string;\n can_manage_scope_members?: string;\n org_id?: string | string[];\n unit_ids?: string;\n active_unit_id?: string;\n products?: string;\n exp?: number;\n iat?: number;\n iss?: string;\n aud?: string | string[];\n};\n\n// Normaliza claim que pode vir como string ou array (OpenIddict emite array\n// quando ha multiplos valores, scalar quando ha um so).\nexport function normalizeClaimArray(\n value: string | string[] | undefined,\n): string[] {\n if (!value) return [];\n return Array.isArray(value) ? value : [value];\n}\n\n// Parseia claim cujo value e string contendo JSON array (unit_ids, products).\n// Lista vazia em claim ausente ou JSON malformado.\nexport function parseJsonArrayClaim(value: string | undefined): string[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((v): v is string => typeof v === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\n// Wrapper safe que devolve null em vez de jogar em token invalido.\nexport function decodeAccessToken(\n token: string | null | undefined,\n): AccountsClaims | null {\n if (!token) return null;\n try {\n return decodeJwtPayload<AccountsClaims>(token);\n } catch {\n return null;\n }\n}\n","import type { AccountsClaims } from \"./claims\";\n\nexport type Persona = \"usuario\" | \"gestor\" | \"vega\";\n\n// Deriva a persona a partir das claims do AT. Fail-closed: sem claims ou sem\n// sinal de gestao, cai em 'usuario' (menor privilegio). platform_role (Time\n// VEGA) tem precedencia sobre a flag de gestor de cliente.\n// ATENCAO: presentation gating, NAO autorizacao (ver README).\nexport function derivePersona(claims: AccountsClaims | null): Persona {\n if (claims?.platform_role) return \"vega\";\n if (claims?.can_manage_scope_members === \"true\") return \"gestor\";\n return \"usuario\";\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { parseJsonArrayClaim } from \"./claims\";\nimport { derivePersona } from \"./persona\";\n\n// Catalogo cliente-side dos produtos VEGA. Mapeia o code (claim `products` do\n// AT) pra display name + URL default + accent. codes batem com o seed do\n// backend (SeedMediaHubInProductCatalog etc.).\n//\n// ssoEnabled = METADADO de COMO o produto abre (SSO unificado via Accounts vs.\n// login proprio). NAO e gate de visibilidade desde o deploy waffle-completo\n// (17/08). Um produto com ssoEnabled=false aparece normalmente; ao clicar, o\n// usuario cai no login proprio do produto — comportamento aceito pelo produto/PO.\n// Visibilidade = acesso (persona/claim) AND url truthy (pos-override de env).\n//\n// URLs aqui sao DEFAULTS de producao. O consumidor sobrescreve por ambiente via\n// o parametro `urls` (do seu proprio env) -- o SDK NAO le process.env.\n\nexport type ProductMeta = {\n code: string;\n name: string;\n description: string;\n url: string;\n accent: string;\n ssoEnabled: boolean;\n};\n\nexport const PRODUCT_CATALOG: Record<string, ProductMeta> = {\n accounts: {\n code: \"accounts\",\n name: \"Accounts\",\n description: \"Identidade unificada do ecossistema\",\n url: \"/\",\n accent: \"#1F2E52\",\n ssoEnabled: true, // e o proprio IdP; excluido da gaveta de qualquer forma\n },\n \"media-hub\": {\n code: \"media-hub\",\n name: \"Media Hub\",\n description: \"Sinalizacao digital nas unidades\",\n url: \"\", // sem frontend publico deployado; override local via NEXT_PUBLIC_MEDIA_HUB_URL\n accent: \"#EF802D\",\n ssoEnabled: true,\n },\n sig: {\n code: \"sig\",\n name: \"SIG\",\n description: \"Housekeeping e governanca das unidades\",\n url: \"https://sig.app.vegait.com/\",\n accent: \"#B0C925\",\n ssoEnabled: true,\n },\n gdi: {\n code: \"gdi\",\n name: \"GDI\",\n description: \"Gestao de dispositivos e internet\",\n url: \"\", // ainda nao esta em prod; override de HML via NEXT_PUBLIC_GDI_URL\n // quando ligar em prod, sera https://gdi.app.vegait.com/\n accent: \"#7B8B7E\",\n ssoEnabled: false,\n },\n sim: {\n code: \"sim\",\n name: \"SIM\",\n description: \"Service Issue Management\",\n url: \"https://sim.app.vegait.com/\",\n accent: \"#5B7DB1\",\n ssoEnabled: false,\n },\n hxp: {\n code: \"hxp\",\n name: \"HXP\",\n description: \"Hotel Experience Platform\",\n url: \"\", // nao ligado no Accounts\n accent: \"#9B6A4F\",\n ssoEnabled: false,\n },\n feedback: {\n code: \"feedback\",\n name: \"Feedback\",\n description: \"Pesquisas e NPS de hospedes\",\n url: \"\", // nao ligado no Accounts\n accent: \"#C26A8C\",\n ssoEnabled: false,\n },\n behavior: {\n code: \"behavior\",\n name: \"Behavior\",\n description: \"Inteligencia comportamental\",\n url: \"https://behavior.app.vegait.com/\",\n accent: \"#4A7C7E\",\n ssoEnabled: false,\n },\n};\n\n// Aliases de code legado -> canonico. Resolve dados gravados ANTES de uma\n// renomeacao (ex.: governanca -> sig) sem duplicar entrada no catalogo.\nconst LEGACY_CODE_ALIASES: Record<string, string> = {\n governanca: \"sig\",\n};\n\n// resolveProduct: retorna o meta cru do produto (com override aplicado se houver).\n// NAO filtra url falsy — o chamador decide o que fazer com produto sem URL.\n// getCatalogWithAccess e getAccessibleProducts filtram apos o override (Opcao A).\n// `urls` sobrescreve a URL default pelo code CANONICO. Code desconhecido => fallback ssoEnabled=false.\nexport function resolveProduct(\n code: string,\n urls?: Record<string, string>,\n): ProductMeta {\n const canonical = LEGACY_CODE_ALIASES[code] ?? code;\n const base =\n PRODUCT_CATALOG[canonical] ??\n ({\n code,\n name: code,\n description: \"\",\n url: \"#\",\n accent: \"#1F2E52\",\n ssoEnabled: false,\n } satisfies ProductMeta);\n const override = urls?.[canonical];\n return override ? { ...base, url: override } : base;\n}\n\nexport type CatalogProduct = ProductMeta & { accessible: boolean };\n\n// Catalogo COMPLETO pro app launcher (menos o Accounts, que e o proprio IdP),\n// com flag de acesso: Time VEGA acessa tudo; cliente acessa os codes da claim\n// `products` (aliases legados resolvidos). ssoEnabled NAO esconde produto aqui\n// — e metadado de COMO o produto abre, nao de quem ve. Sem claims: tudo\n// inacessivel (fail-closed). `urls` sobrescreve URLs por ambiente.\nexport function getCatalogWithAccess(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): CatalogProduct[] {\n const urls = opts?.urls;\n const isVega = claims != null && derivePersona(claims) === \"vega\";\n const accessibleCodes = new Set(\n parseJsonArrayClaim(claims?.products).map(\n (code) => LEGACY_CODE_ALIASES[code] ?? code,\n ),\n );\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => ({\n ...(urls?.[p.code] ? { ...p, url: urls[p.code] } : p),\n accessible: isVega || accessibleCodes.has(p.code),\n }))\n .filter((p) => p.url); // Opcao A: produto sem URL publica some do waffle/card/drawer\n}\n\n// Produtos do app launcher (gaveta/drawer). Visibilidade = acesso (persona/claim)\n// AND url truthy (pos-override de env). ssoEnabled NAO filtra — e apenas metadado\n// de como o produto abre. `urls` sobrescreve URLs por ambiente (override aplicado\n// ANTES do filtro de url, para que env possa ressuscitar produto sem URL default).\nexport function getAccessibleProducts(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): ProductMeta[] {\n if (!claims) return [];\n const urls = opts?.urls;\n if (derivePersona(claims) === \"vega\") {\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => (urls?.[p.code] ? { ...p, url: urls[p.code] } : p))\n .filter((p) => p.url); // Opcao A: sem URL publica = nao lancavel\n }\n return parseJsonArrayClaim(claims.products)\n .map((code) => resolveProduct(code, urls))\n .filter((p) => p.url); // Opcao A: idem; ssoEnabled nao filtra\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { derivePersona, type Persona } from \"./persona\";\nimport {\n getAccessibleProducts,\n getCatalogWithAccess,\n type CatalogProduct,\n type ProductMeta,\n} from \"./productCatalog\";\n\n// Modelo de dados da casca de identidade (SEM React). O host mapeia isto pros\n// props do <IdentityBar> do @vegait/lyra-ds-v2, adicionando icones/labels/\n// callbacks a partir de `capabilities`.\nexport type IdentityModel = {\n persona: Persona;\n user: { name: string; email: string };\n products: ProductMeta[];\n /** Catalogo completo (menos Accounts) com flag de acesso — pro waffle\n * mostrar tudo e desabilitar o que a persona nao acessa. `products`\n * permanece so-acessiveis por retrocompat (SIG consome hoje). */\n catalog: CatalogProduct[];\n currentProductCode?: string;\n capabilities: {\n manageUsers: boolean; // gestor/vega\n adminConsole: boolean; // vega\n switchContext: boolean; // gestor/vega\n manageNetwork: boolean; // gestor (rodape do launcher)\n };\n};\n\nexport function buildIdentityModel(\n claims: AccountsClaims | null,\n cfg?: { currentProductCode?: string; urls?: Record<string, string> },\n): IdentityModel {\n const persona = derivePersona(claims);\n return {\n persona,\n user: { name: claims?.name ?? \"\", email: claims?.email ?? \"\" },\n products: getAccessibleProducts(claims, { urls: cfg?.urls }),\n catalog: getCatalogWithAccess(claims, { urls: cfg?.urls }),\n currentProductCode: cfg?.currentProductCode,\n capabilities: {\n manageUsers: persona === \"gestor\" || persona === \"vega\",\n adminConsole: persona === \"vega\",\n switchContext: persona === \"gestor\" || persona === \"vega\",\n manageNetwork: persona === \"gestor\",\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/claims.ts","../src/persona.ts","../src/productCatalog.ts","../src/identityModel.ts"],"sourcesContent":["// Barrel do @vegait/accounts-sdk. Preenchido pelas tasks seguintes.\n\nexport type { AccountsClaims } from \"./claims\";\nexport {\n decodeJwtPayload,\n decodeAccessToken,\n parseJsonArrayClaim,\n normalizeClaimArray,\n} from \"./claims\";\n\nexport type { Persona } from \"./persona\";\nexport { derivePersona } from \"./persona\";\n\nexport type { ProductMeta, CatalogProduct } from \"./productCatalog\";\nexport {\n PRODUCT_CATALOG,\n resolveProduct,\n getAccessibleProducts,\n getCatalogWithAccess,\n} from \"./productCatalog\";\n\nexport type { IdentityModel } from \"./identityModel\";\nexport { buildIdentityModel } from \"./identityModel\";\n","// Decoder de payload JWT sem verificacao criptografica. Universal (browser +\n// Node). Usar APENAS para extrair claims de tokens ja confiaveis (recebidos do\n// Accounts via fluxo OIDC). NUNCA para validar tokens de origem externa.\n\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid JWT format\");\n }\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = payload + \"=\".repeat((4 - (payload.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n const json = new TextDecoder(\"utf-8\").decode(bytes);\n return JSON.parse(json) as T;\n}\n\n// Claims do AT emitido pelo Accounts via OpenIddict. Tudo opcional (nem todo\n// campo chega em todo contexto). unit_ids/products vem como string contendo\n// JSON array. platform_role aparece so para Time VEGA. Espelha o token; fonte\n// da verdade = VegaClaimsHandler no accounts.api.\nexport type AccountsClaims = {\n sub?: string;\n email?: string;\n name?: string;\n platform_role?: string;\n can_manage_scope_members?: string;\n org_id?: string | string[];\n unit_ids?: string;\n unit_owner_ids?: string;\n active_unit_id?: string;\n products?: string;\n exp?: number;\n iat?: number;\n iss?: string;\n aud?: string | string[];\n};\n\n// Normaliza claim que pode vir como string ou array (OpenIddict emite array\n// quando ha multiplos valores, scalar quando ha um so).\nexport function normalizeClaimArray(\n value: string | string[] | undefined,\n): string[] {\n if (!value) return [];\n return Array.isArray(value) ? value : [value];\n}\n\n// Parseia claim cujo value e string contendo JSON array (unit_ids, products).\n// Lista vazia em claim ausente ou JSON malformado.\nexport function parseJsonArrayClaim(value: string | undefined): string[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((v): v is string => typeof v === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\n// Wrapper safe que devolve null em vez de jogar em token invalido.\nexport function decodeAccessToken(\n token: string | null | undefined,\n): AccountsClaims | null {\n if (!token) return null;\n try {\n return decodeJwtPayload<AccountsClaims>(token);\n } catch {\n return null;\n }\n}\n","import type { AccountsClaims } from \"./claims\";\n\nexport type Persona = \"usuario\" | \"gestor\" | \"vega\";\n\n// Deriva a persona a partir das claims do AT. Fail-closed: sem claims ou sem\n// sinal de gestao, cai em 'usuario' (menor privilegio). platform_role (Time\n// VEGA) tem precedencia sobre a flag de gestor de cliente.\n// ATENCAO: presentation gating, NAO autorizacao (ver README).\nexport function derivePersona(claims: AccountsClaims | null): Persona {\n if (claims?.platform_role) return \"vega\";\n if (claims?.can_manage_scope_members === \"true\") return \"gestor\";\n return \"usuario\";\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { parseJsonArrayClaim } from \"./claims\";\nimport { derivePersona } from \"./persona\";\n\n// Catalogo cliente-side dos produtos VEGA. Mapeia o code (claim `products` do\n// AT) pra display name + URL default + accent. codes batem com o seed do\n// backend (SeedMediaHubInProductCatalog etc.).\n//\n// ssoEnabled = METADADO de COMO o produto abre (SSO unificado via Accounts vs.\n// login proprio). NAO e gate de visibilidade desde o deploy waffle-completo\n// (17/08). Um produto com ssoEnabled=false aparece normalmente; ao clicar, o\n// usuario cai no login proprio do produto — comportamento aceito pelo produto/PO.\n// Visibilidade = acesso (persona/claim) AND url truthy (pos-override de env).\n//\n// URLs aqui sao DEFAULTS de producao. O consumidor sobrescreve por ambiente via\n// o parametro `urls` (do seu proprio env) -- o SDK NAO le process.env.\n\nexport type ProductMeta = {\n code: string;\n name: string;\n description: string;\n url: string;\n accent: string;\n ssoEnabled: boolean;\n};\n\nexport const PRODUCT_CATALOG: Record<string, ProductMeta> = {\n accounts: {\n code: \"accounts\",\n name: \"Accounts\",\n description: \"Identidade unificada do ecossistema\",\n url: \"/\",\n accent: \"#1F2E52\",\n ssoEnabled: true, // e o proprio IdP; excluido da gaveta de qualquer forma\n },\n \"media-hub\": {\n code: \"media-hub\",\n name: \"Media Hub\",\n description: \"Sinalizacao digital nas unidades\",\n url: \"\", // sem frontend publico deployado; override local via NEXT_PUBLIC_MEDIA_HUB_URL\n accent: \"#EF802D\",\n ssoEnabled: true,\n },\n sig: {\n code: \"sig\",\n name: \"SIG\",\n description: \"Housekeeping e governanca das unidades\",\n url: \"https://sig.app.vegait.com/\",\n accent: \"#B0C925\",\n ssoEnabled: true,\n },\n gdi: {\n code: \"gdi\",\n name: \"GDI\",\n description: \"Gestao de dispositivos e internet\",\n url: \"\", // ainda nao esta em prod; override de HML via NEXT_PUBLIC_GDI_URL\n // quando ligar em prod, sera https://gdi.app.vegait.com/\n accent: \"#7B8B7E\",\n ssoEnabled: false,\n },\n sim: {\n code: \"sim\",\n name: \"SIM\",\n description: \"Service Issue Management\",\n url: \"https://sim.app.vegait.com/\",\n accent: \"#5B7DB1\",\n ssoEnabled: false,\n },\n hxp: {\n code: \"hxp\",\n name: \"HXP\",\n description: \"Hotel Experience Platform\",\n url: \"\", // nao ligado no Accounts\n accent: \"#9B6A4F\",\n ssoEnabled: false,\n },\n feedback: {\n code: \"feedback\",\n name: \"Feedback\",\n description: \"Pesquisas e NPS de hospedes\",\n url: \"\", // nao ligado no Accounts\n accent: \"#C26A8C\",\n ssoEnabled: false,\n },\n behavior: {\n code: \"behavior\",\n name: \"Behavior\",\n description: \"Inteligencia comportamental\",\n url: \"https://behavior.app.vegait.com/\",\n accent: \"#4A7C7E\",\n ssoEnabled: false,\n },\n};\n\n// Aliases de code legado -> canonico. Resolve dados gravados ANTES de uma\n// renomeacao (ex.: governanca -> sig) sem duplicar entrada no catalogo.\nconst LEGACY_CODE_ALIASES: Record<string, string> = {\n governanca: \"sig\",\n};\n\n// resolveProduct: retorna o meta cru do produto (com override aplicado se houver).\n// NAO filtra url falsy — o chamador decide o que fazer com produto sem URL.\n// getCatalogWithAccess e getAccessibleProducts filtram apos o override (Opcao A).\n// `urls` sobrescreve a URL default pelo code CANONICO. Code desconhecido => fallback ssoEnabled=false.\nexport function resolveProduct(\n code: string,\n urls?: Record<string, string>,\n): ProductMeta {\n const canonical = LEGACY_CODE_ALIASES[code] ?? code;\n const base =\n PRODUCT_CATALOG[canonical] ??\n ({\n code,\n name: code,\n description: \"\",\n url: \"#\",\n accent: \"#1F2E52\",\n ssoEnabled: false,\n } satisfies ProductMeta);\n const override = urls?.[canonical];\n return override ? { ...base, url: override } : base;\n}\n\nexport type CatalogProduct = ProductMeta & { accessible: boolean };\n\n// Catalogo COMPLETO pro app launcher (menos o Accounts, que e o proprio IdP),\n// com flag de acesso: Time VEGA acessa tudo; cliente acessa os codes da claim\n// `products` (aliases legados resolvidos). ssoEnabled NAO esconde produto aqui\n// — e metadado de COMO o produto abre, nao de quem ve. Sem claims: tudo\n// inacessivel (fail-closed). `urls` sobrescreve URLs por ambiente.\nexport function getCatalogWithAccess(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): CatalogProduct[] {\n const urls = opts?.urls;\n const isVega = claims != null && derivePersona(claims) === \"vega\";\n const accessibleCodes = new Set(\n parseJsonArrayClaim(claims?.products).map(\n (code) => LEGACY_CODE_ALIASES[code] ?? code,\n ),\n );\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => ({\n ...(urls?.[p.code] ? { ...p, url: urls[p.code] } : p),\n accessible: isVega || accessibleCodes.has(p.code),\n }))\n .filter((p) => p.url); // Opcao A: produto sem URL publica some do waffle/card/drawer\n}\n\n// Produtos do app launcher (gaveta/drawer). Visibilidade = acesso (persona/claim)\n// AND url truthy (pos-override de env). ssoEnabled NAO filtra — e apenas metadado\n// de como o produto abre. `urls` sobrescreve URLs por ambiente (override aplicado\n// ANTES do filtro de url, para que env possa ressuscitar produto sem URL default).\nexport function getAccessibleProducts(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): ProductMeta[] {\n if (!claims) return [];\n const urls = opts?.urls;\n if (derivePersona(claims) === \"vega\") {\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => (urls?.[p.code] ? { ...p, url: urls[p.code] } : p))\n .filter((p) => p.url); // Opcao A: sem URL publica = nao lancavel\n }\n return parseJsonArrayClaim(claims.products)\n .map((code) => resolveProduct(code, urls))\n .filter((p) => p.url); // Opcao A: idem; ssoEnabled nao filtra\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { derivePersona, type Persona } from \"./persona\";\nimport {\n getAccessibleProducts,\n getCatalogWithAccess,\n type CatalogProduct,\n type ProductMeta,\n} from \"./productCatalog\";\n\n// Modelo de dados da casca de identidade (SEM React). O host mapeia isto pros\n// props do <IdentityBar> do @vegait/lyra-ds-v2, adicionando icones/labels/\n// callbacks a partir de `capabilities`.\nexport type IdentityModel = {\n persona: Persona;\n isUnitOwner: boolean;\n user: { name: string; email: string };\n products: ProductMeta[];\n /** Catalogo completo (menos Accounts) com flag de acesso — pro waffle\n * mostrar tudo e desabilitar o que a persona nao acessa. `products`\n * permanece so-acessiveis por retrocompat (SIG consome hoje). */\n catalog: CatalogProduct[];\n currentProductCode?: string;\n capabilities: {\n manageUsers: boolean; // gestor/vega\n adminConsole: boolean; // vega\n switchContext: boolean; // gestor/vega\n manageNetwork: boolean; // gestor (rodape do launcher)\n manageProducts: boolean; // owner da unidade ativa\n appointAdmins: boolean; // owner delega admin por produto\n };\n};\n\nfunction deriveIsUnitOwner(claims: AccountsClaims | null): boolean {\n if (!claims?.unit_owner_ids || !claims.active_unit_id) return false;\n try {\n const owned = JSON.parse(claims.unit_owner_ids) as string[];\n return Array.isArray(owned) && owned.includes(claims.active_unit_id);\n } catch {\n return false;\n }\n}\n\nexport function buildIdentityModel(\n claims: AccountsClaims | null,\n cfg?: { currentProductCode?: string; urls?: Record<string, string> },\n): IdentityModel {\n const persona = derivePersona(claims);\n const isUnitOwner = deriveIsUnitOwner(claims);\n return {\n persona,\n isUnitOwner,\n user: { name: claims?.name ?? \"\", email: claims?.email ?? \"\" },\n products: getAccessibleProducts(claims, { urls: cfg?.urls }),\n catalog: getCatalogWithAccess(claims, { urls: cfg?.urls }),\n currentProductCode: cfg?.currentProductCode,\n capabilities: {\n manageUsers: persona === \"gestor\" || persona === \"vega\",\n adminConsole: persona === \"vega\",\n switchContext: persona === \"gestor\" || persona === \"vega\",\n manageNetwork: persona === \"gestor\",\n manageProducts: isUnitOwner,\n appointAdmins: isUnitOwner,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIO,SAAS,iBAA8C,OAAkB;AAC9E,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AACA,QAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,QAAM,SAAS,UAAU,IAAI,QAAQ,IAAK,QAAQ,SAAS,KAAM,CAAC;AAClE,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,QAAM,OAAO,IAAI,YAAY,OAAO,EAAE,OAAO,KAAK;AAClD,SAAO,KAAK,MAAM,IAAI;AACxB;AAyBO,SAAS,oBACd,OACU;AACV,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,SAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAC9C;AAIO,SAAS,oBAAoB,OAAqC;AACvE,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,WAAO,MAAM,QAAQ,MAAM,IACvB,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IACvD,CAAC;AAAA,EACP,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGO,SAAS,kBACd,OACuB;AACvB,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,WAAO,iBAAiC,KAAK;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AClEO,SAAS,cAAc,QAAwC;AACpE,MAAI,QAAQ,cAAe,QAAO;AAClC,MAAI,QAAQ,6BAA6B,OAAQ,QAAO;AACxD,SAAO;AACT;;;ACcO,IAAM,kBAA+C;AAAA,EAC1D,UAAU;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA;AAAA,EACd;AAAA,EACA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA;AAAA;AAAA,IAEL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AACF;AAIA,IAAM,sBAA8C;AAAA,EAClD,YAAY;AACd;AAMO,SAAS,eACd,MACA,MACa;AACb,QAAM,YAAY,oBAAoB,IAAI,KAAK;AAC/C,QAAM,OACJ,gBAAgB,SAAS,KACxB;AAAA,IACC;AAAA,IACA,MAAM;AAAA,IACN,aAAa;AAAA,IACb,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AACF,QAAM,WAAW,OAAO,SAAS;AACjC,SAAO,WAAW,EAAE,GAAG,MAAM,KAAK,SAAS,IAAI;AACjD;AASO,SAAS,qBACd,QACA,MACkB;AAClB,QAAM,OAAO,MAAM;AACnB,QAAM,SAAS,UAAU,QAAQ,cAAc,MAAM,MAAM;AAC3D,QAAM,kBAAkB,IAAI;AAAA,IAC1B,oBAAoB,QAAQ,QAAQ,EAAE;AAAA,MACpC,CAAC,SAAS,oBAAoB,IAAI,KAAK;AAAA,IACzC;AAAA,EACF;AACA,SAAO,OAAO,OAAO,eAAe,EACjC,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU,EACnC,IAAI,CAAC,OAAO;AAAA,IACX,GAAI,OAAO,EAAE,IAAI,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,EAAE,IAAI,EAAE,IAAI;AAAA,IACnD,YAAY,UAAU,gBAAgB,IAAI,EAAE,IAAI;AAAA,EAClD,EAAE,EACD,OAAO,CAAC,MAAM,EAAE,GAAG;AACxB;AAMO,SAAS,sBACd,QACA,MACe;AACf,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAM,OAAO,MAAM;AACnB,MAAI,cAAc,MAAM,MAAM,QAAQ;AACpC,WAAO,OAAO,OAAO,eAAe,EACjC,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU,EACnC,IAAI,CAAC,MAAO,OAAO,EAAE,IAAI,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,EAAE,IAAI,EAAE,IAAI,CAAE,EAC7D,OAAO,CAAC,MAAM,EAAE,GAAG;AAAA,EACxB;AACA,SAAO,oBAAoB,OAAO,QAAQ,EACvC,IAAI,CAAC,SAAS,eAAe,MAAM,IAAI,CAAC,EACxC,OAAO,CAAC,MAAM,EAAE,GAAG;AACxB;;;ACzIA,SAAS,kBAAkB,QAAwC;AACjE,MAAI,CAAC,QAAQ,kBAAkB,CAAC,OAAO,eAAgB,QAAO;AAC9D,MAAI;AACF,UAAM,QAAQ,KAAK,MAAM,OAAO,cAAc;AAC9C,WAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,OAAO,cAAc;AAAA,EACrE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBACd,QACA,KACe;AACf,QAAM,UAAU,cAAc,MAAM;AACpC,QAAM,cAAc,kBAAkB,MAAM;AAC5C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,EAAE,MAAM,QAAQ,QAAQ,IAAI,OAAO,QAAQ,SAAS,GAAG;AAAA,IAC7D,UAAU,sBAAsB,QAAQ,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,IAC3D,SAAS,qBAAqB,QAAQ,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,IACzD,oBAAoB,KAAK;AAAA,IACzB,cAAc;AAAA,MACZ,aAAa,YAAY,YAAY,YAAY;AAAA,MACjD,cAAc,YAAY;AAAA,MAC1B,eAAe,YAAY,YAAY,YAAY;AAAA,MACnD,eAAe,YAAY;AAAA,MAC3B,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AACF;","names":[]}
package/dist/index.d.cts CHANGED
@@ -7,6 +7,7 @@ type AccountsClaims = {
7
7
  can_manage_scope_members?: string;
8
8
  org_id?: string | string[];
9
9
  unit_ids?: string;
10
+ unit_owner_ids?: string;
10
11
  active_unit_id?: string;
11
12
  products?: string;
12
13
  exp?: number;
@@ -43,6 +44,7 @@ declare function getAccessibleProducts(claims: AccountsClaims | null, opts?: {
43
44
 
44
45
  type IdentityModel = {
45
46
  persona: Persona;
47
+ isUnitOwner: boolean;
46
48
  user: {
47
49
  name: string;
48
50
  email: string;
@@ -58,6 +60,8 @@ type IdentityModel = {
58
60
  adminConsole: boolean;
59
61
  switchContext: boolean;
60
62
  manageNetwork: boolean;
63
+ manageProducts: boolean;
64
+ appointAdmins: boolean;
61
65
  };
62
66
  };
63
67
  declare function buildIdentityModel(claims: AccountsClaims | null, cfg?: {
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ type AccountsClaims = {
7
7
  can_manage_scope_members?: string;
8
8
  org_id?: string | string[];
9
9
  unit_ids?: string;
10
+ unit_owner_ids?: string;
10
11
  active_unit_id?: string;
11
12
  products?: string;
12
13
  exp?: number;
@@ -43,6 +44,7 @@ declare function getAccessibleProducts(claims: AccountsClaims | null, opts?: {
43
44
 
44
45
  type IdentityModel = {
45
46
  persona: Persona;
47
+ isUnitOwner: boolean;
46
48
  user: {
47
49
  name: string;
48
50
  email: string;
@@ -58,6 +60,8 @@ type IdentityModel = {
58
60
  adminConsole: boolean;
59
61
  switchContext: boolean;
60
62
  manageNetwork: boolean;
63
+ manageProducts: boolean;
64
+ appointAdmins: boolean;
61
65
  };
62
66
  };
63
67
  declare function buildIdentityModel(claims: AccountsClaims | null, cfg?: {
package/dist/index.js CHANGED
@@ -155,10 +155,21 @@ function getAccessibleProducts(claims, opts) {
155
155
  }
156
156
 
157
157
  // src/identityModel.ts
158
+ function deriveIsUnitOwner(claims) {
159
+ if (!claims?.unit_owner_ids || !claims.active_unit_id) return false;
160
+ try {
161
+ const owned = JSON.parse(claims.unit_owner_ids);
162
+ return Array.isArray(owned) && owned.includes(claims.active_unit_id);
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
158
167
  function buildIdentityModel(claims, cfg) {
159
168
  const persona = derivePersona(claims);
169
+ const isUnitOwner = deriveIsUnitOwner(claims);
160
170
  return {
161
171
  persona,
172
+ isUnitOwner,
162
173
  user: { name: claims?.name ?? "", email: claims?.email ?? "" },
163
174
  products: getAccessibleProducts(claims, { urls: cfg?.urls }),
164
175
  catalog: getCatalogWithAccess(claims, { urls: cfg?.urls }),
@@ -167,7 +178,9 @@ function buildIdentityModel(claims, cfg) {
167
178
  manageUsers: persona === "gestor" || persona === "vega",
168
179
  adminConsole: persona === "vega",
169
180
  switchContext: persona === "gestor" || persona === "vega",
170
- manageNetwork: persona === "gestor"
181
+ manageNetwork: persona === "gestor",
182
+ manageProducts: isUnitOwner,
183
+ appointAdmins: isUnitOwner
171
184
  }
172
185
  };
173
186
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/claims.ts","../src/persona.ts","../src/productCatalog.ts","../src/identityModel.ts"],"sourcesContent":["// Decoder de payload JWT sem verificacao criptografica. Universal (browser +\n// Node). Usar APENAS para extrair claims de tokens ja confiaveis (recebidos do\n// Accounts via fluxo OIDC). NUNCA para validar tokens de origem externa.\n\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid JWT format\");\n }\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = payload + \"=\".repeat((4 - (payload.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n const json = new TextDecoder(\"utf-8\").decode(bytes);\n return JSON.parse(json) as T;\n}\n\n// Claims do AT emitido pelo Accounts via OpenIddict. Tudo opcional (nem todo\n// campo chega em todo contexto). unit_ids/products vem como string contendo\n// JSON array. platform_role aparece so para Time VEGA. Espelha o token; fonte\n// da verdade = VegaClaimsHandler no accounts.api.\nexport type AccountsClaims = {\n sub?: string;\n email?: string;\n name?: string;\n platform_role?: string;\n can_manage_scope_members?: string;\n org_id?: string | string[];\n unit_ids?: string;\n active_unit_id?: string;\n products?: string;\n exp?: number;\n iat?: number;\n iss?: string;\n aud?: string | string[];\n};\n\n// Normaliza claim que pode vir como string ou array (OpenIddict emite array\n// quando ha multiplos valores, scalar quando ha um so).\nexport function normalizeClaimArray(\n value: string | string[] | undefined,\n): string[] {\n if (!value) return [];\n return Array.isArray(value) ? value : [value];\n}\n\n// Parseia claim cujo value e string contendo JSON array (unit_ids, products).\n// Lista vazia em claim ausente ou JSON malformado.\nexport function parseJsonArrayClaim(value: string | undefined): string[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((v): v is string => typeof v === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\n// Wrapper safe que devolve null em vez de jogar em token invalido.\nexport function decodeAccessToken(\n token: string | null | undefined,\n): AccountsClaims | null {\n if (!token) return null;\n try {\n return decodeJwtPayload<AccountsClaims>(token);\n } catch {\n return null;\n }\n}\n","import type { AccountsClaims } from \"./claims\";\n\nexport type Persona = \"usuario\" | \"gestor\" | \"vega\";\n\n// Deriva a persona a partir das claims do AT. Fail-closed: sem claims ou sem\n// sinal de gestao, cai em 'usuario' (menor privilegio). platform_role (Time\n// VEGA) tem precedencia sobre a flag de gestor de cliente.\n// ATENCAO: presentation gating, NAO autorizacao (ver README).\nexport function derivePersona(claims: AccountsClaims | null): Persona {\n if (claims?.platform_role) return \"vega\";\n if (claims?.can_manage_scope_members === \"true\") return \"gestor\";\n return \"usuario\";\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { parseJsonArrayClaim } from \"./claims\";\nimport { derivePersona } from \"./persona\";\n\n// Catalogo cliente-side dos produtos VEGA. Mapeia o code (claim `products` do\n// AT) pra display name + URL default + accent. codes batem com o seed do\n// backend (SeedMediaHubInProductCatalog etc.).\n//\n// ssoEnabled = METADADO de COMO o produto abre (SSO unificado via Accounts vs.\n// login proprio). NAO e gate de visibilidade desde o deploy waffle-completo\n// (17/08). Um produto com ssoEnabled=false aparece normalmente; ao clicar, o\n// usuario cai no login proprio do produto — comportamento aceito pelo produto/PO.\n// Visibilidade = acesso (persona/claim) AND url truthy (pos-override de env).\n//\n// URLs aqui sao DEFAULTS de producao. O consumidor sobrescreve por ambiente via\n// o parametro `urls` (do seu proprio env) -- o SDK NAO le process.env.\n\nexport type ProductMeta = {\n code: string;\n name: string;\n description: string;\n url: string;\n accent: string;\n ssoEnabled: boolean;\n};\n\nexport const PRODUCT_CATALOG: Record<string, ProductMeta> = {\n accounts: {\n code: \"accounts\",\n name: \"Accounts\",\n description: \"Identidade unificada do ecossistema\",\n url: \"/\",\n accent: \"#1F2E52\",\n ssoEnabled: true, // e o proprio IdP; excluido da gaveta de qualquer forma\n },\n \"media-hub\": {\n code: \"media-hub\",\n name: \"Media Hub\",\n description: \"Sinalizacao digital nas unidades\",\n url: \"\", // sem frontend publico deployado; override local via NEXT_PUBLIC_MEDIA_HUB_URL\n accent: \"#EF802D\",\n ssoEnabled: true,\n },\n sig: {\n code: \"sig\",\n name: \"SIG\",\n description: \"Housekeeping e governanca das unidades\",\n url: \"https://sig.app.vegait.com/\",\n accent: \"#B0C925\",\n ssoEnabled: true,\n },\n gdi: {\n code: \"gdi\",\n name: \"GDI\",\n description: \"Gestao de dispositivos e internet\",\n url: \"\", // ainda nao esta em prod; override de HML via NEXT_PUBLIC_GDI_URL\n // quando ligar em prod, sera https://gdi.app.vegait.com/\n accent: \"#7B8B7E\",\n ssoEnabled: false,\n },\n sim: {\n code: \"sim\",\n name: \"SIM\",\n description: \"Service Issue Management\",\n url: \"https://sim.app.vegait.com/\",\n accent: \"#5B7DB1\",\n ssoEnabled: false,\n },\n hxp: {\n code: \"hxp\",\n name: \"HXP\",\n description: \"Hotel Experience Platform\",\n url: \"\", // nao ligado no Accounts\n accent: \"#9B6A4F\",\n ssoEnabled: false,\n },\n feedback: {\n code: \"feedback\",\n name: \"Feedback\",\n description: \"Pesquisas e NPS de hospedes\",\n url: \"\", // nao ligado no Accounts\n accent: \"#C26A8C\",\n ssoEnabled: false,\n },\n behavior: {\n code: \"behavior\",\n name: \"Behavior\",\n description: \"Inteligencia comportamental\",\n url: \"https://behavior.app.vegait.com/\",\n accent: \"#4A7C7E\",\n ssoEnabled: false,\n },\n};\n\n// Aliases de code legado -> canonico. Resolve dados gravados ANTES de uma\n// renomeacao (ex.: governanca -> sig) sem duplicar entrada no catalogo.\nconst LEGACY_CODE_ALIASES: Record<string, string> = {\n governanca: \"sig\",\n};\n\n// resolveProduct: retorna o meta cru do produto (com override aplicado se houver).\n// NAO filtra url falsy — o chamador decide o que fazer com produto sem URL.\n// getCatalogWithAccess e getAccessibleProducts filtram apos o override (Opcao A).\n// `urls` sobrescreve a URL default pelo code CANONICO. Code desconhecido => fallback ssoEnabled=false.\nexport function resolveProduct(\n code: string,\n urls?: Record<string, string>,\n): ProductMeta {\n const canonical = LEGACY_CODE_ALIASES[code] ?? code;\n const base =\n PRODUCT_CATALOG[canonical] ??\n ({\n code,\n name: code,\n description: \"\",\n url: \"#\",\n accent: \"#1F2E52\",\n ssoEnabled: false,\n } satisfies ProductMeta);\n const override = urls?.[canonical];\n return override ? { ...base, url: override } : base;\n}\n\nexport type CatalogProduct = ProductMeta & { accessible: boolean };\n\n// Catalogo COMPLETO pro app launcher (menos o Accounts, que e o proprio IdP),\n// com flag de acesso: Time VEGA acessa tudo; cliente acessa os codes da claim\n// `products` (aliases legados resolvidos). ssoEnabled NAO esconde produto aqui\n// — e metadado de COMO o produto abre, nao de quem ve. Sem claims: tudo\n// inacessivel (fail-closed). `urls` sobrescreve URLs por ambiente.\nexport function getCatalogWithAccess(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): CatalogProduct[] {\n const urls = opts?.urls;\n const isVega = claims != null && derivePersona(claims) === \"vega\";\n const accessibleCodes = new Set(\n parseJsonArrayClaim(claims?.products).map(\n (code) => LEGACY_CODE_ALIASES[code] ?? code,\n ),\n );\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => ({\n ...(urls?.[p.code] ? { ...p, url: urls[p.code] } : p),\n accessible: isVega || accessibleCodes.has(p.code),\n }))\n .filter((p) => p.url); // Opcao A: produto sem URL publica some do waffle/card/drawer\n}\n\n// Produtos do app launcher (gaveta/drawer). Visibilidade = acesso (persona/claim)\n// AND url truthy (pos-override de env). ssoEnabled NAO filtra — e apenas metadado\n// de como o produto abre. `urls` sobrescreve URLs por ambiente (override aplicado\n// ANTES do filtro de url, para que env possa ressuscitar produto sem URL default).\nexport function getAccessibleProducts(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): ProductMeta[] {\n if (!claims) return [];\n const urls = opts?.urls;\n if (derivePersona(claims) === \"vega\") {\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => (urls?.[p.code] ? { ...p, url: urls[p.code] } : p))\n .filter((p) => p.url); // Opcao A: sem URL publica = nao lancavel\n }\n return parseJsonArrayClaim(claims.products)\n .map((code) => resolveProduct(code, urls))\n .filter((p) => p.url); // Opcao A: idem; ssoEnabled nao filtra\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { derivePersona, type Persona } from \"./persona\";\nimport {\n getAccessibleProducts,\n getCatalogWithAccess,\n type CatalogProduct,\n type ProductMeta,\n} from \"./productCatalog\";\n\n// Modelo de dados da casca de identidade (SEM React). O host mapeia isto pros\n// props do <IdentityBar> do @vegait/lyra-ds-v2, adicionando icones/labels/\n// callbacks a partir de `capabilities`.\nexport type IdentityModel = {\n persona: Persona;\n user: { name: string; email: string };\n products: ProductMeta[];\n /** Catalogo completo (menos Accounts) com flag de acesso — pro waffle\n * mostrar tudo e desabilitar o que a persona nao acessa. `products`\n * permanece so-acessiveis por retrocompat (SIG consome hoje). */\n catalog: CatalogProduct[];\n currentProductCode?: string;\n capabilities: {\n manageUsers: boolean; // gestor/vega\n adminConsole: boolean; // vega\n switchContext: boolean; // gestor/vega\n manageNetwork: boolean; // gestor (rodape do launcher)\n };\n};\n\nexport function buildIdentityModel(\n claims: AccountsClaims | null,\n cfg?: { currentProductCode?: string; urls?: Record<string, string> },\n): IdentityModel {\n const persona = derivePersona(claims);\n return {\n persona,\n user: { name: claims?.name ?? \"\", email: claims?.email ?? \"\" },\n products: getAccessibleProducts(claims, { urls: cfg?.urls }),\n catalog: getCatalogWithAccess(claims, { urls: cfg?.urls }),\n currentProductCode: cfg?.currentProductCode,\n capabilities: {\n manageUsers: persona === \"gestor\" || persona === \"vega\",\n adminConsole: persona === \"vega\",\n switchContext: persona === \"gestor\" || persona === \"vega\",\n manageNetwork: persona === \"gestor\",\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/claims.ts","../src/persona.ts","../src/productCatalog.ts","../src/identityModel.ts"],"sourcesContent":["// Decoder de payload JWT sem verificacao criptografica. Universal (browser +\n// Node). Usar APENAS para extrair claims de tokens ja confiaveis (recebidos do\n// Accounts via fluxo OIDC). NUNCA para validar tokens de origem externa.\n\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid JWT format\");\n }\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = payload + \"=\".repeat((4 - (payload.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n const json = new TextDecoder(\"utf-8\").decode(bytes);\n return JSON.parse(json) as T;\n}\n\n// Claims do AT emitido pelo Accounts via OpenIddict. Tudo opcional (nem todo\n// campo chega em todo contexto). unit_ids/products vem como string contendo\n// JSON array. platform_role aparece so para Time VEGA. Espelha o token; fonte\n// da verdade = VegaClaimsHandler no accounts.api.\nexport type AccountsClaims = {\n sub?: string;\n email?: string;\n name?: string;\n platform_role?: string;\n can_manage_scope_members?: string;\n org_id?: string | string[];\n unit_ids?: string;\n unit_owner_ids?: string;\n active_unit_id?: string;\n products?: string;\n exp?: number;\n iat?: number;\n iss?: string;\n aud?: string | string[];\n};\n\n// Normaliza claim que pode vir como string ou array (OpenIddict emite array\n// quando ha multiplos valores, scalar quando ha um so).\nexport function normalizeClaimArray(\n value: string | string[] | undefined,\n): string[] {\n if (!value) return [];\n return Array.isArray(value) ? value : [value];\n}\n\n// Parseia claim cujo value e string contendo JSON array (unit_ids, products).\n// Lista vazia em claim ausente ou JSON malformado.\nexport function parseJsonArrayClaim(value: string | undefined): string[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((v): v is string => typeof v === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\n// Wrapper safe que devolve null em vez de jogar em token invalido.\nexport function decodeAccessToken(\n token: string | null | undefined,\n): AccountsClaims | null {\n if (!token) return null;\n try {\n return decodeJwtPayload<AccountsClaims>(token);\n } catch {\n return null;\n }\n}\n","import type { AccountsClaims } from \"./claims\";\n\nexport type Persona = \"usuario\" | \"gestor\" | \"vega\";\n\n// Deriva a persona a partir das claims do AT. Fail-closed: sem claims ou sem\n// sinal de gestao, cai em 'usuario' (menor privilegio). platform_role (Time\n// VEGA) tem precedencia sobre a flag de gestor de cliente.\n// ATENCAO: presentation gating, NAO autorizacao (ver README).\nexport function derivePersona(claims: AccountsClaims | null): Persona {\n if (claims?.platform_role) return \"vega\";\n if (claims?.can_manage_scope_members === \"true\") return \"gestor\";\n return \"usuario\";\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { parseJsonArrayClaim } from \"./claims\";\nimport { derivePersona } from \"./persona\";\n\n// Catalogo cliente-side dos produtos VEGA. Mapeia o code (claim `products` do\n// AT) pra display name + URL default + accent. codes batem com o seed do\n// backend (SeedMediaHubInProductCatalog etc.).\n//\n// ssoEnabled = METADADO de COMO o produto abre (SSO unificado via Accounts vs.\n// login proprio). NAO e gate de visibilidade desde o deploy waffle-completo\n// (17/08). Um produto com ssoEnabled=false aparece normalmente; ao clicar, o\n// usuario cai no login proprio do produto — comportamento aceito pelo produto/PO.\n// Visibilidade = acesso (persona/claim) AND url truthy (pos-override de env).\n//\n// URLs aqui sao DEFAULTS de producao. O consumidor sobrescreve por ambiente via\n// o parametro `urls` (do seu proprio env) -- o SDK NAO le process.env.\n\nexport type ProductMeta = {\n code: string;\n name: string;\n description: string;\n url: string;\n accent: string;\n ssoEnabled: boolean;\n};\n\nexport const PRODUCT_CATALOG: Record<string, ProductMeta> = {\n accounts: {\n code: \"accounts\",\n name: \"Accounts\",\n description: \"Identidade unificada do ecossistema\",\n url: \"/\",\n accent: \"#1F2E52\",\n ssoEnabled: true, // e o proprio IdP; excluido da gaveta de qualquer forma\n },\n \"media-hub\": {\n code: \"media-hub\",\n name: \"Media Hub\",\n description: \"Sinalizacao digital nas unidades\",\n url: \"\", // sem frontend publico deployado; override local via NEXT_PUBLIC_MEDIA_HUB_URL\n accent: \"#EF802D\",\n ssoEnabled: true,\n },\n sig: {\n code: \"sig\",\n name: \"SIG\",\n description: \"Housekeeping e governanca das unidades\",\n url: \"https://sig.app.vegait.com/\",\n accent: \"#B0C925\",\n ssoEnabled: true,\n },\n gdi: {\n code: \"gdi\",\n name: \"GDI\",\n description: \"Gestao de dispositivos e internet\",\n url: \"\", // ainda nao esta em prod; override de HML via NEXT_PUBLIC_GDI_URL\n // quando ligar em prod, sera https://gdi.app.vegait.com/\n accent: \"#7B8B7E\",\n ssoEnabled: false,\n },\n sim: {\n code: \"sim\",\n name: \"SIM\",\n description: \"Service Issue Management\",\n url: \"https://sim.app.vegait.com/\",\n accent: \"#5B7DB1\",\n ssoEnabled: false,\n },\n hxp: {\n code: \"hxp\",\n name: \"HXP\",\n description: \"Hotel Experience Platform\",\n url: \"\", // nao ligado no Accounts\n accent: \"#9B6A4F\",\n ssoEnabled: false,\n },\n feedback: {\n code: \"feedback\",\n name: \"Feedback\",\n description: \"Pesquisas e NPS de hospedes\",\n url: \"\", // nao ligado no Accounts\n accent: \"#C26A8C\",\n ssoEnabled: false,\n },\n behavior: {\n code: \"behavior\",\n name: \"Behavior\",\n description: \"Inteligencia comportamental\",\n url: \"https://behavior.app.vegait.com/\",\n accent: \"#4A7C7E\",\n ssoEnabled: false,\n },\n};\n\n// Aliases de code legado -> canonico. Resolve dados gravados ANTES de uma\n// renomeacao (ex.: governanca -> sig) sem duplicar entrada no catalogo.\nconst LEGACY_CODE_ALIASES: Record<string, string> = {\n governanca: \"sig\",\n};\n\n// resolveProduct: retorna o meta cru do produto (com override aplicado se houver).\n// NAO filtra url falsy — o chamador decide o que fazer com produto sem URL.\n// getCatalogWithAccess e getAccessibleProducts filtram apos o override (Opcao A).\n// `urls` sobrescreve a URL default pelo code CANONICO. Code desconhecido => fallback ssoEnabled=false.\nexport function resolveProduct(\n code: string,\n urls?: Record<string, string>,\n): ProductMeta {\n const canonical = LEGACY_CODE_ALIASES[code] ?? code;\n const base =\n PRODUCT_CATALOG[canonical] ??\n ({\n code,\n name: code,\n description: \"\",\n url: \"#\",\n accent: \"#1F2E52\",\n ssoEnabled: false,\n } satisfies ProductMeta);\n const override = urls?.[canonical];\n return override ? { ...base, url: override } : base;\n}\n\nexport type CatalogProduct = ProductMeta & { accessible: boolean };\n\n// Catalogo COMPLETO pro app launcher (menos o Accounts, que e o proprio IdP),\n// com flag de acesso: Time VEGA acessa tudo; cliente acessa os codes da claim\n// `products` (aliases legados resolvidos). ssoEnabled NAO esconde produto aqui\n// — e metadado de COMO o produto abre, nao de quem ve. Sem claims: tudo\n// inacessivel (fail-closed). `urls` sobrescreve URLs por ambiente.\nexport function getCatalogWithAccess(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): CatalogProduct[] {\n const urls = opts?.urls;\n const isVega = claims != null && derivePersona(claims) === \"vega\";\n const accessibleCodes = new Set(\n parseJsonArrayClaim(claims?.products).map(\n (code) => LEGACY_CODE_ALIASES[code] ?? code,\n ),\n );\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => ({\n ...(urls?.[p.code] ? { ...p, url: urls[p.code] } : p),\n accessible: isVega || accessibleCodes.has(p.code),\n }))\n .filter((p) => p.url); // Opcao A: produto sem URL publica some do waffle/card/drawer\n}\n\n// Produtos do app launcher (gaveta/drawer). Visibilidade = acesso (persona/claim)\n// AND url truthy (pos-override de env). ssoEnabled NAO filtra — e apenas metadado\n// de como o produto abre. `urls` sobrescreve URLs por ambiente (override aplicado\n// ANTES do filtro de url, para que env possa ressuscitar produto sem URL default).\nexport function getAccessibleProducts(\n claims: AccountsClaims | null,\n opts?: { urls?: Record<string, string> },\n): ProductMeta[] {\n if (!claims) return [];\n const urls = opts?.urls;\n if (derivePersona(claims) === \"vega\") {\n return Object.values(PRODUCT_CATALOG)\n .filter((p) => p.code !== \"accounts\")\n .map((p) => (urls?.[p.code] ? { ...p, url: urls[p.code] } : p))\n .filter((p) => p.url); // Opcao A: sem URL publica = nao lancavel\n }\n return parseJsonArrayClaim(claims.products)\n .map((code) => resolveProduct(code, urls))\n .filter((p) => p.url); // Opcao A: idem; ssoEnabled nao filtra\n}\n","import type { AccountsClaims } from \"./claims\";\nimport { derivePersona, type Persona } from \"./persona\";\nimport {\n getAccessibleProducts,\n getCatalogWithAccess,\n type CatalogProduct,\n type ProductMeta,\n} from \"./productCatalog\";\n\n// Modelo de dados da casca de identidade (SEM React). O host mapeia isto pros\n// props do <IdentityBar> do @vegait/lyra-ds-v2, adicionando icones/labels/\n// callbacks a partir de `capabilities`.\nexport type IdentityModel = {\n persona: Persona;\n isUnitOwner: boolean;\n user: { name: string; email: string };\n products: ProductMeta[];\n /** Catalogo completo (menos Accounts) com flag de acesso — pro waffle\n * mostrar tudo e desabilitar o que a persona nao acessa. `products`\n * permanece so-acessiveis por retrocompat (SIG consome hoje). */\n catalog: CatalogProduct[];\n currentProductCode?: string;\n capabilities: {\n manageUsers: boolean; // gestor/vega\n adminConsole: boolean; // vega\n switchContext: boolean; // gestor/vega\n manageNetwork: boolean; // gestor (rodape do launcher)\n manageProducts: boolean; // owner da unidade ativa\n appointAdmins: boolean; // owner delega admin por produto\n };\n};\n\nfunction deriveIsUnitOwner(claims: AccountsClaims | null): boolean {\n if (!claims?.unit_owner_ids || !claims.active_unit_id) return false;\n try {\n const owned = JSON.parse(claims.unit_owner_ids) as string[];\n return Array.isArray(owned) && owned.includes(claims.active_unit_id);\n } catch {\n return false;\n }\n}\n\nexport function buildIdentityModel(\n claims: AccountsClaims | null,\n cfg?: { currentProductCode?: string; urls?: Record<string, string> },\n): IdentityModel {\n const persona = derivePersona(claims);\n const isUnitOwner = deriveIsUnitOwner(claims);\n return {\n persona,\n isUnitOwner,\n user: { name: claims?.name ?? \"\", email: claims?.email ?? \"\" },\n products: getAccessibleProducts(claims, { urls: cfg?.urls }),\n catalog: getCatalogWithAccess(claims, { urls: cfg?.urls }),\n currentProductCode: cfg?.currentProductCode,\n capabilities: {\n manageUsers: persona === \"gestor\" || persona === \"vega\",\n adminConsole: persona === \"vega\",\n switchContext: persona === \"gestor\" || persona === \"vega\",\n manageNetwork: persona === \"gestor\",\n manageProducts: isUnitOwner,\n appointAdmins: isUnitOwner,\n },\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegait/accounts-sdk",
3
- "version": "0.1.1-beta.45452",
3
+ "version": "0.1.1-beta.45596",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "tag": "beta"