@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.
- package/CHANGELOG.md +9 -0
- package/README.md +64 -9
- package/dist/auth.cjs +290 -27
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +290 -27
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +22 -7
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +22 -7
- package/dist/checkout.mjs.map +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.cjs +59 -3
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +59 -3
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +11 -6
- package/dist/errors.d.ts +11 -6
- package/dist/errors.mjs.map +1 -1
- package/dist/firestore-compat.d.cts +1 -1
- package/dist/firestore-compat.d.ts +1 -1
- package/dist/index.cjs +302 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +302 -31
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +51 -7
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +41 -7
- package/dist/mocks.d.ts +41 -7
- package/dist/mocks.mjs +51 -7
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +2 -1
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +2 -1
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-DePdSU3P.d.cts → types-BXvGHppn.d.cts} +113 -12
- package/dist/{types-B3XFHD4A.d.ts → types-CQVak2a3.d.ts} +113 -12
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +11 -3
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +11 -3
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +1 -1
package/dist/auth.d.cts
CHANGED
package/dist/auth.d.ts
CHANGED
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
|
-
|
|
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:
|
|
4605
|
+
return { docs: [], newWatermark: Date.now(), resyncRequired: true };
|
|
4605
4606
|
},
|
|
4606
|
-
async fullResync(
|
|
4607
|
+
async fullResync(collections) {
|
|
4607
4608
|
if (!config) {
|
|
4608
4609
|
return { docs: [], newWatermark: Date.now() };
|
|
4609
4610
|
}
|
|
4610
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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` —
|
|
5426
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
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
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
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
|
-
|
|
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,
|