@neetru/sdk 3.1.2 → 3.1.6

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +64 -9
  3. package/dist/auth.cjs +290 -27
  4. package/dist/auth.cjs.map +1 -1
  5. package/dist/auth.d.cts +1 -1
  6. package/dist/auth.d.ts +1 -1
  7. package/dist/auth.mjs +290 -27
  8. package/dist/auth.mjs.map +1 -1
  9. package/dist/catalog.cjs.map +1 -1
  10. package/dist/catalog.d.cts +1 -1
  11. package/dist/catalog.d.ts +1 -1
  12. package/dist/catalog.mjs.map +1 -1
  13. package/dist/checkout.cjs +22 -7
  14. package/dist/checkout.cjs.map +1 -1
  15. package/dist/checkout.d.cts +1 -1
  16. package/dist/checkout.d.ts +1 -1
  17. package/dist/checkout.mjs +22 -7
  18. package/dist/checkout.mjs.map +1 -1
  19. package/dist/db-react.d.cts +1 -1
  20. package/dist/db-react.d.ts +1 -1
  21. package/dist/db.cjs +59 -3
  22. package/dist/db.cjs.map +1 -1
  23. package/dist/db.d.cts +1 -1
  24. package/dist/db.d.ts +1 -1
  25. package/dist/db.mjs +59 -3
  26. package/dist/db.mjs.map +1 -1
  27. package/dist/entitlements.cjs.map +1 -1
  28. package/dist/entitlements.d.cts +1 -1
  29. package/dist/entitlements.d.ts +1 -1
  30. package/dist/entitlements.mjs.map +1 -1
  31. package/dist/errors.cjs.map +1 -1
  32. package/dist/errors.d.cts +11 -6
  33. package/dist/errors.d.ts +11 -6
  34. package/dist/errors.mjs.map +1 -1
  35. package/dist/firestore-compat.d.cts +1 -1
  36. package/dist/firestore-compat.d.ts +1 -1
  37. package/dist/index.cjs +302 -31
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +3 -3
  40. package/dist/index.d.ts +3 -3
  41. package/dist/index.mjs +302 -31
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/mocks.cjs +51 -7
  44. package/dist/mocks.cjs.map +1 -1
  45. package/dist/mocks.d.cts +41 -7
  46. package/dist/mocks.d.ts +41 -7
  47. package/dist/mocks.mjs +51 -7
  48. package/dist/mocks.mjs.map +1 -1
  49. package/dist/notifications.cjs.map +1 -1
  50. package/dist/notifications.d.cts +1 -1
  51. package/dist/notifications.d.ts +1 -1
  52. package/dist/notifications.mjs.map +1 -1
  53. package/dist/react.d.cts +1 -1
  54. package/dist/react.d.ts +1 -1
  55. package/dist/support.cjs.map +1 -1
  56. package/dist/support.d.cts +1 -1
  57. package/dist/support.d.ts +1 -1
  58. package/dist/support.mjs.map +1 -1
  59. package/dist/telemetry.cjs +2 -1
  60. package/dist/telemetry.cjs.map +1 -1
  61. package/dist/telemetry.d.cts +1 -1
  62. package/dist/telemetry.d.ts +1 -1
  63. package/dist/telemetry.mjs +2 -1
  64. package/dist/telemetry.mjs.map +1 -1
  65. package/dist/{types-DePdSU3P.d.cts → types-BXvGHppn.d.cts} +113 -12
  66. package/dist/{types-B3XFHD4A.d.ts → types-CQVak2a3.d.ts} +113 -12
  67. package/dist/usage.cjs.map +1 -1
  68. package/dist/usage.d.cts +1 -1
  69. package/dist/usage.d.ts +1 -1
  70. package/dist/usage.mjs.map +1 -1
  71. package/dist/webhooks.cjs +11 -3
  72. package/dist/webhooks.cjs.map +1 -1
  73. package/dist/webhooks.d.cts +1 -1
  74. package/dist/webhooks.d.ts +1 -1
  75. package/dist/webhooks.mjs +11 -3
  76. package/dist/webhooks.mjs.map +1 -1
  77. package/package.json +1 -1
package/dist/auth.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as NeetruClientConfig, z as NeetruClient } from './types-DePdSU3P.cjs';
1
+ import { G as NeetruClientConfig, B as NeetruClient } from './types-BXvGHppn.cjs';
2
2
  import 'drizzle-orm/node-postgres';
3
3
  import './errors.cjs';
4
4
 
package/dist/auth.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as NeetruClientConfig, z as NeetruClient } from './types-B3XFHD4A.js';
1
+ import { G as NeetruClientConfig, B as NeetruClient } from './types-CQVak2a3.js';
2
2
  import 'drizzle-orm/node-postgres';
3
3
  import './errors.js';
4
4
 
package/dist/auth.mjs CHANGED
@@ -1093,7 +1093,8 @@ function createTelemetryNamespace(config) {
1093
1093
  message: input.message
1094
1094
  };
1095
1095
  if (input.metadata) body.metadata = input.metadata;
1096
- if (input.productSlug) body.productSlug = input.productSlug;
1096
+ const productSlug = input.productSlug ?? config.productId;
1097
+ if (productSlug) body.productSlug = productSlug;
1097
1098
  let cid = input.correlationId;
1098
1099
  if (!cid) {
1099
1100
  try {
@@ -4601,13 +4602,69 @@ function createRestSyncTransport(config) {
4601
4602
  if (!config) {
4602
4603
  return { docs: [], newWatermark: Date.now(), resyncRequired: false };
4603
4604
  }
4604
- return { docs: [], newWatermark: Date.now(), resyncRequired: false };
4605
+ return { docs: [], newWatermark: Date.now(), resyncRequired: true };
4605
4606
  },
4606
- async fullResync(_collections) {
4607
+ async fullResync(collections) {
4607
4608
  if (!config) {
4608
4609
  return { docs: [], newWatermark: Date.now() };
4609
4610
  }
4610
- return { docs: [], newWatermark: Date.now() };
4611
+ const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
4612
+ const tenantScopeId = config.tenantId ?? config.productId;
4613
+ const tenantHeaders = tenantScopeId ? { "x-neetru-tenant": tenantScopeId } : void 0;
4614
+ const now = Date.now();
4615
+ const docs = [];
4616
+ for (const collection of collections) {
4617
+ let raw;
4618
+ try {
4619
+ raw = await httpRequest2(config, {
4620
+ method: "GET",
4621
+ path: DATASTORE_COLLECTION_ENDPOINT(collection),
4622
+ requireAuth: true,
4623
+ retries: 2,
4624
+ headers: tenantHeaders,
4625
+ // Teto do Core é 200. Coleções > 200 docs truncam silenciosamente
4626
+ // aqui (paginação cursor é tarefa Core separada) — a detecção de
4627
+ // deleção-por-ausência do SyncEngine NÃO deve rodar sobre truncamento,
4628
+ // então mantemos o limite no máximo do servidor por ora.
4629
+ query: { limit: 200 }
4630
+ });
4631
+ } catch (err) {
4632
+ const msg = err instanceof Error ? err.message : String(err);
4633
+ throw new NeetruDbError(
4634
+ "db_unavailable",
4635
+ `[RestSyncTransport] fullResync falhou na cole\xE7\xE3o "${collection}": ${msg}`
4636
+ );
4637
+ }
4638
+ const resp = raw;
4639
+ const items = Array.isArray(resp?.items) ? resp.items : [];
4640
+ for (const item of items) {
4641
+ if (!item || typeof item !== "object") continue;
4642
+ const record = item;
4643
+ const id = typeof record["id"] === "string" ? record["id"] : null;
4644
+ if (!id) continue;
4645
+ const { id: _inlinedId, ...data } = record;
4646
+ let serverTimestamp = now;
4647
+ const updatedAt = data["_updatedAt"];
4648
+ if (typeof updatedAt === "number") {
4649
+ serverTimestamp = updatedAt;
4650
+ } else if (updatedAt !== null && typeof updatedAt === "object" && typeof updatedAt["_seconds"] === "number") {
4651
+ serverTimestamp = updatedAt["_seconds"] * 1e3;
4652
+ } else if (typeof updatedAt === "string") {
4653
+ const parsed = Date.parse(updatedAt);
4654
+ if (!Number.isNaN(parsed)) serverTimestamp = parsed;
4655
+ }
4656
+ const serverVersion = `rest_${id}_${serverTimestamp}`;
4657
+ docs.push({
4658
+ collection,
4659
+ id,
4660
+ data,
4661
+ serverVersion,
4662
+ serverTimestamp,
4663
+ deleted: false
4664
+ });
4665
+ }
4666
+ }
4667
+ return { docs, newWatermark: now };
4611
4668
  }
4612
4669
  };
4613
4670
  }
@@ -4847,6 +4904,15 @@ function createLazyCollectionRef(name, getDocsPromise) {
4847
4904
  // src/checkout.ts
4848
4905
  init_errors();
4849
4906
  init_http();
4907
+ function checkoutAuth(accessToken, method) {
4908
+ if (!accessToken || typeof accessToken !== "string") {
4909
+ throw new NeetruError(
4910
+ "validation_failed",
4911
+ `checkout.${method} requer o access_token OIDC do usu\xE1rio final (passe \`accessToken\`). A chave de API do SDK (nrt_*) N\xC3O \xE9 aceita pelo endpoint de checkout \u2014 ele autentica a identidade do cliente que est\xE1 assinando (Bearer access_token OIDC ou cookie de sess\xE3o).`
4912
+ );
4913
+ }
4914
+ return { requireAuth: false, headers: { authorization: `Bearer ${accessToken}` } };
4915
+ }
4850
4916
  function parseStartResponse(raw) {
4851
4917
  if (!raw || typeof raw !== "object") {
4852
4918
  throw new NeetruError("invalid_response", "checkout.start response is not an object");
@@ -4924,11 +4990,13 @@ function createHttpCheckoutNamespace(config) {
4924
4990
  };
4925
4991
  if (input.tenantType) body.targetTenantType = input.tenantType;
4926
4992
  if (input.tenantId) body.targetTenantId = input.tenantId;
4993
+ const { requireAuth, headers } = checkoutAuth(input.accessToken, "start");
4927
4994
  const raw = await httpRequest(config, {
4928
4995
  method: "POST",
4929
4996
  path: "/api/v1/checkout/intents",
4930
4997
  body,
4931
- requireAuth: true,
4998
+ requireAuth,
4999
+ headers,
4932
5000
  idempotencyKey: true
4933
5001
  });
4934
5002
  const result = parseStartResponse(raw);
@@ -4938,25 +5006,29 @@ function createHttpCheckoutNamespace(config) {
4938
5006
  }
4939
5007
  return result;
4940
5008
  },
4941
- async get(intentId) {
5009
+ async get(intentId, opts) {
4942
5010
  if (!intentId || typeof intentId !== "string") {
4943
5011
  throw new NeetruError("validation_failed", "checkout.get: intentId is required");
4944
5012
  }
5013
+ const { requireAuth, headers } = checkoutAuth(opts?.accessToken, "get");
4945
5014
  const raw = await httpRequest(config, {
4946
5015
  method: "GET",
4947
5016
  path: `/api/v1/checkout/intents/${encodeURIComponent(intentId)}`,
4948
- requireAuth: true
5017
+ requireAuth,
5018
+ headers
4949
5019
  });
4950
5020
  return parseGetResponse(raw);
4951
5021
  },
4952
- async cancel(intentId) {
5022
+ async cancel(intentId, opts) {
4953
5023
  if (!intentId || typeof intentId !== "string") {
4954
5024
  throw new NeetruError("validation_failed", "checkout.cancel: intentId is required");
4955
5025
  }
5026
+ const { requireAuth, headers } = checkoutAuth(opts?.accessToken, "cancel");
4956
5027
  const raw = await httpRequest(config, {
4957
5028
  method: "DELETE",
4958
5029
  path: `/api/v1/checkout/intents/${encodeURIComponent(intentId)}`,
4959
- requireAuth: true
5030
+ requireAuth,
5031
+ headers
4960
5032
  });
4961
5033
  return {
4962
5034
  ok: true,
@@ -5000,14 +5072,14 @@ var MockCheckout = class {
5000
5072
  requiresKyc: false
5001
5073
  };
5002
5074
  }
5003
- async get(intentId) {
5075
+ async get(intentId, _opts) {
5004
5076
  const found = this.intents.get(intentId);
5005
5077
  if (!found) {
5006
5078
  throw new NeetruError("not_found", `Mock intent ${intentId} not found`);
5007
5079
  }
5008
5080
  return { ...found };
5009
5081
  }
5010
- async cancel(intentId) {
5082
+ async cancel(intentId, _opts) {
5011
5083
  const found = this.intents.get(intentId);
5012
5084
  if (!found) {
5013
5085
  throw new NeetruError("not_found", `Mock intent ${intentId} not found`);
@@ -5395,9 +5467,19 @@ var DEV_FIXTURE_USER = Object.freeze({
5395
5467
  var MockAuth = class {
5396
5468
  _user;
5397
5469
  _listeners;
5398
- constructor(initialUser = null) {
5470
+ _allowUnsignedDecode;
5471
+ constructor(initialUser = null, options) {
5399
5472
  this._user = initialUser;
5400
5473
  this._listeners = /* @__PURE__ */ new Set();
5474
+ this._allowUnsignedDecode = options?.allowUnsignedDecode ?? false;
5475
+ if (this._allowUnsignedDecode) {
5476
+ const isTestEnv = typeof process !== "undefined" && (process.env?.NODE_ENV === "test" || process.env?.VITEST === "true" || process.env?.VITEST != null);
5477
+ if (!isTestEnv && typeof console !== "undefined") {
5478
+ console.warn(
5479
+ "[neetru-sdk] MockAuth.verifyToken est\xE1 em modo allowUnsignedDecode=true: tokens N\xC3O t\xEAm assinatura verificada. Este modo \xE9 exclusivo para testes \u2014 NUNCA use como security boundary em c\xF3digo deployado."
5480
+ );
5481
+ }
5482
+ }
5401
5483
  }
5402
5484
  async signIn(_options) {
5403
5485
  if (!this._user) this._user = { ...DEV_FIXTURE_USER };
@@ -5422,18 +5504,27 @@ var MockAuth = class {
5422
5504
  };
5423
5505
  }
5424
5506
  /**
5425
- * Mock de `verifyToken` — decodifica o JWT sem verificar assinatura (pra testes).
5426
- * Em dev, retorna o user atual do mock se já estiver logado. Aceita qualquer token
5427
- * não-vazio sem chamar rede.
5507
+ * Mock de `verifyToken` — por default lança `unauthorized` pra prevenir uso
5508
+ * acidental como security boundary em código deployado.
5428
5509
  *
5429
- * Para simular falhas em testes, use `MockAuth.__mockVerifyToken(fn)`.
5510
+ * Para decodificar JWTs sem verificar assinatura em testes, crie a instância
5511
+ * com `new MockAuth(user, { allowUnsignedDecode: true })`. Isso é um opt-in
5512
+ * explícito — nunca acontece silenciosamente.
5513
+ *
5514
+ * Para simular falhas ou resultados customizados, use `__mockVerifyToken(fn)`.
5430
5515
  */
5431
5516
  async verifyToken(token) {
5432
5517
  if (this._verifyTokenOverride) {
5433
5518
  return this._verifyTokenOverride(token);
5434
5519
  }
5435
5520
  if (!token) {
5436
- throw Object.assign(new Error("Token inv\xE1lido"), { code: "token_invalid_signature" });
5521
+ throw new NeetruError("token_invalid_signature", "Token inv\xE1lido: string vazia");
5522
+ }
5523
+ if (!this._allowUnsignedDecode) {
5524
+ throw new NeetruError(
5525
+ "unauthorized",
5526
+ "MockAuth.verifyToken n\xE3o verifica assinaturas JWT e N\xC3O deve ser usado como security boundary em c\xF3digo deployado. Em testes, use new MockAuth(user, { allowUnsignedDecode: true }) para habilitar decodifica\xE7\xE3o sem assinatura, ou __mockVerifyToken(fn) para injetar resultado customizado."
5527
+ );
5437
5528
  }
5438
5529
  try {
5439
5530
  const parts = token.split(".");
@@ -5460,7 +5551,32 @@ var MockAuth = class {
5460
5551
  } catch {
5461
5552
  }
5462
5553
  if (this._user) return this._user;
5463
- throw Object.assign(new Error("Nenhum user mock dispon\xEDvel"), { code: "token_invalid_signature" });
5554
+ throw new NeetruError("token_invalid_signature", "Nenhum user mock dispon\xEDvel e JWT sem sub v\xE1lido");
5555
+ }
5556
+ /**
5557
+ * Mock de `handleRedirectCallback` (Bug 2 / 3.1.3) — não há redirect real no
5558
+ * mock, então retorna `null` por default. Use `__setPendingCallback(result)`
5559
+ * em testes pra simular um retorno do IdP.
5560
+ */
5561
+ async handleRedirectCallback(_currentUrl) {
5562
+ return this._pendingCallback;
5563
+ }
5564
+ /**
5565
+ * Mock de `getIdToken` (Bug 2 / 3.1.3). Retorna `null` por default; use
5566
+ * `__setIdToken(token)` pra injetar um token cru em testes.
5567
+ */
5568
+ getIdToken() {
5569
+ return this._idToken;
5570
+ }
5571
+ _pendingCallback = null;
5572
+ _idToken = null;
5573
+ /** Test helper — simula o material devolvido por um callback OIDC. */
5574
+ __setPendingCallback(result) {
5575
+ this._pendingCallback = result;
5576
+ }
5577
+ /** Test helper — injeta o id_token cru retornado por `getIdToken`. */
5578
+ __setIdToken(token) {
5579
+ this._idToken = token;
5464
5580
  }
5465
5581
  _verifyTokenOverride = null;
5466
5582
  /**
@@ -5680,6 +5796,49 @@ function _jwtPayloadToUser(payload) {
5680
5796
  ...payload
5681
5797
  };
5682
5798
  }
5799
+ var OIDC_TX_KEY = "neetru_oauth_tx";
5800
+ function resolveIdpOrigin(baseUrl) {
5801
+ const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
5802
+ return overrideAuthUrl ?? baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
5803
+ }
5804
+ function getWebCrypto() {
5805
+ const c = globalThis.crypto;
5806
+ if (!c || typeof c.getRandomValues !== "function" || !c.subtle) {
5807
+ throw new NeetruError(
5808
+ "runtime_unsupported",
5809
+ "auth.signIn requer WebCrypto (crypto.subtle) \u2014 indispon\xEDvel neste runtime. Use HTTPS (ou http://localhost) num browser moderno."
5810
+ );
5811
+ }
5812
+ return c;
5813
+ }
5814
+ function base64UrlFromBytes(bytes) {
5815
+ let str = "";
5816
+ for (const b of bytes) str += String.fromCharCode(b);
5817
+ const b64 = typeof btoa === "function" ? btoa(str) : Buffer.from(str, "binary").toString("base64");
5818
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
5819
+ }
5820
+ function randomUrlSafe(crypto2, byteLen) {
5821
+ const bytes = new Uint8Array(byteLen);
5822
+ crypto2.getRandomValues(bytes);
5823
+ return base64UrlFromBytes(bytes);
5824
+ }
5825
+ async function pkceChallengeS256(crypto2, verifier) {
5826
+ const data = new TextEncoder().encode(verifier);
5827
+ const digest = await crypto2.subtle.digest("SHA-256", data);
5828
+ return base64UrlFromBytes(new Uint8Array(digest));
5829
+ }
5830
+ function cleanCallbackUrl(parsed) {
5831
+ try {
5832
+ const history = globalThis.history;
5833
+ if (!history || typeof history.replaceState !== "function") return;
5834
+ const clean = new URL(parsed.toString());
5835
+ for (const p of ["code", "state", "error", "error_description", "iss", "session_state"]) {
5836
+ clean.searchParams.delete(p);
5837
+ }
5838
+ history.replaceState(history.state ?? null, "", clean.pathname + clean.search + clean.hash);
5839
+ } catch {
5840
+ }
5841
+ }
5683
5842
  function createOidcAuthNamespace(config) {
5684
5843
  const STORAGE_KEY = "neetru_id_token";
5685
5844
  const listeners = /* @__PURE__ */ new Set();
@@ -5692,6 +5851,13 @@ function createOidcAuthNamespace(config) {
5692
5851
  return null;
5693
5852
  }
5694
5853
  }
5854
+ function getSessionStorage() {
5855
+ try {
5856
+ return typeof globalThis.sessionStorage !== "undefined" ? globalThis.sessionStorage : null;
5857
+ } catch {
5858
+ return null;
5859
+ }
5860
+ }
5695
5861
  function decodeJwtPayload(token) {
5696
5862
  const parts = token.split(".");
5697
5863
  if (parts.length !== 3) return null;
@@ -5775,20 +5941,48 @@ function createOidcAuthNamespace(config) {
5775
5941
  return {
5776
5942
  async signIn(options) {
5777
5943
  if (typeof globalThis.location !== "undefined" && typeof globalThis.location.assign === "function") {
5944
+ const crypto2 = getWebCrypto();
5778
5945
  const redirectUri = options?.redirectUri ?? globalThis.location.origin;
5779
5946
  const scope = options?.scope ?? "openid profile email";
5780
- const state = options?.postLoginRedirect ?? globalThis.location.href;
5781
- const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
5782
- const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
5947
+ let clientId = config.oidcClientId;
5948
+ if (!clientId && config.apiKey) {
5949
+ clientId = parseApiKey(config.apiKey).keyId;
5950
+ }
5951
+ if (!clientId) {
5952
+ throw new NeetruError(
5953
+ "invalid_config",
5954
+ "auth.signIn requer `oidcClientId` (ou `apiKey`) configurado para montar o client_id."
5955
+ );
5956
+ }
5957
+ const state = randomUrlSafe(crypto2, 16);
5958
+ const nonce = randomUrlSafe(crypto2, 16);
5959
+ const codeVerifier = randomUrlSafe(crypto2, 32);
5960
+ const codeChallenge = await pkceChallengeS256(crypto2, codeVerifier);
5961
+ const session = getSessionStorage();
5962
+ if (!session) {
5963
+ throw new NeetruError(
5964
+ "runtime_unsupported",
5965
+ "auth.signIn requer sessionStorage para guardar a transa\xE7\xE3o PKCE entre o redirect e o callback."
5966
+ );
5967
+ }
5968
+ const tx = {
5969
+ state,
5970
+ nonce,
5971
+ codeVerifier,
5972
+ redirectUri,
5973
+ postLoginRedirect: options?.postLoginRedirect
5974
+ };
5975
+ session.setItem(OIDC_TX_KEY, JSON.stringify(tx));
5976
+ const idpOrigin = resolveIdpOrigin(config.baseUrl);
5783
5977
  const url = new URL("/api/v1/oauth/authorize", idpOrigin);
5784
5978
  url.searchParams.set("response_type", "code");
5979
+ url.searchParams.set("client_id", clientId);
5785
5980
  url.searchParams.set("redirect_uri", redirectUri);
5786
5981
  url.searchParams.set("scope", scope);
5787
5982
  url.searchParams.set("state", state);
5788
- if (config.apiKey) {
5789
- const { keyId } = parseApiKey(config.apiKey);
5790
- url.searchParams.set("client_id", keyId);
5791
- }
5983
+ url.searchParams.set("nonce", nonce);
5984
+ url.searchParams.set("code_challenge", codeChallenge);
5985
+ url.searchParams.set("code_challenge_method", "S256");
5792
5986
  globalThis.location.assign(url.toString());
5793
5987
  return;
5794
5988
  }
@@ -5797,6 +5991,67 @@ function createOidcAuthNamespace(config) {
5797
5991
  "auth.signIn requires a browser context or mocks. Use NEETRU_ENV=dev or pass mocks.auth."
5798
5992
  );
5799
5993
  },
5994
+ async handleRedirectCallback(currentUrl) {
5995
+ const href = currentUrl ?? (typeof globalThis.location !== "undefined" ? globalThis.location.href : void 0);
5996
+ if (!href) return null;
5997
+ let parsed;
5998
+ try {
5999
+ parsed = new URL(href);
6000
+ } catch {
6001
+ return null;
6002
+ }
6003
+ const params = parsed.searchParams;
6004
+ const idpError = params.get("error");
6005
+ if (idpError) {
6006
+ const desc = params.get("error_description") ?? idpError;
6007
+ getSessionStorage()?.removeItem(OIDC_TX_KEY);
6008
+ cleanCallbackUrl(parsed);
6009
+ throw new NeetruError(idpError, `OIDC authorize falhou: ${desc}`);
6010
+ }
6011
+ const code = params.get("code");
6012
+ if (!code) return null;
6013
+ const returnedState = params.get("state");
6014
+ const session = getSessionStorage();
6015
+ const txRaw = session?.getItem(OIDC_TX_KEY) ?? null;
6016
+ let tx = null;
6017
+ if (txRaw) {
6018
+ try {
6019
+ tx = JSON.parse(txRaw);
6020
+ } catch {
6021
+ tx = null;
6022
+ }
6023
+ }
6024
+ if (!tx) {
6025
+ throw new NeetruError(
6026
+ "oidc_state_mismatch",
6027
+ "Callback OIDC sem transa\xE7\xE3o pendente \u2014 `signIn` n\xE3o foi iniciado nesta sess\xE3o (ou j\xE1 consumido)."
6028
+ );
6029
+ }
6030
+ if (!returnedState || returnedState !== tx.state) {
6031
+ session?.removeItem(OIDC_TX_KEY);
6032
+ cleanCallbackUrl(parsed);
6033
+ throw new NeetruError(
6034
+ "oidc_state_mismatch",
6035
+ "state do callback OIDC n\xE3o confere com o da transa\xE7\xE3o (poss\xEDvel CSRF)."
6036
+ );
6037
+ }
6038
+ session?.removeItem(OIDC_TX_KEY);
6039
+ cleanCallbackUrl(parsed);
6040
+ return {
6041
+ code,
6042
+ codeVerifier: tx.codeVerifier,
6043
+ redirectUri: tx.redirectUri,
6044
+ nonce: tx.nonce,
6045
+ state: tx.state,
6046
+ postLoginRedirect: tx.postLoginRedirect
6047
+ };
6048
+ },
6049
+ getIdToken() {
6050
+ const storage = getStorage();
6051
+ const token = storage?.getItem(STORAGE_KEY) ?? null;
6052
+ if (!token) return null;
6053
+ return tokenToUser(token) ? token : null;
6054
+ },
5800
6055
  async signOut() {
5801
6056
  const storage = getStorage();
5802
6057
  const storedToken = storage?.getItem(STORAGE_KEY) ?? null;
@@ -5856,13 +6111,20 @@ function createOidcAuthNamespace(config) {
5856
6111
  const issuer = idpOrigin;
5857
6112
  const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
5858
6113
  const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
6114
+ const audienceFromOptions = options?.audience;
5859
6115
  const parsedKey = tryParseApiKey(config.apiKey);
5860
- const audience = parsedKey?.keyId;
6116
+ const audience = audienceFromOptions ?? parsedKey?.keyId;
6117
+ if (!audience) {
6118
+ throw new NeetruError(
6119
+ "invalid_config",
6120
+ "verifyToken requires an audience: provide apiKey on the client or pass options.audience. Calling jwtVerify without an audience allows tokens issued for other OIDC clients to pass."
6121
+ );
6122
+ }
5861
6123
  const jwks = _getJwks(jwksUrl, cacheTtlMs);
5862
6124
  try {
5863
6125
  const { payload } = await jwtVerify(token, jwks, {
5864
6126
  issuer,
5865
- ...audience ? { audience } : {},
6127
+ audience,
5866
6128
  algorithms: ["RS256"]
5867
6129
  });
5868
6130
  return _jwtPayloadToUser(payload);
@@ -5903,6 +6165,7 @@ function createNeetruClient(config = {}) {
5903
6165
  const env = resolveEnv(config.env);
5904
6166
  const resolved = Object.freeze({
5905
6167
  apiKey,
6168
+ oidcClientId: config.oidcClientId,
5906
6169
  baseUrl,
5907
6170
  fetch: fetchImpl.bind(globalThis),
5908
6171
  env,