@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/index.cjs
CHANGED
|
@@ -1095,7 +1095,8 @@ function createTelemetryNamespace(config) {
|
|
|
1095
1095
|
message: input.message
|
|
1096
1096
|
};
|
|
1097
1097
|
if (input.metadata) body.metadata = input.metadata;
|
|
1098
|
-
|
|
1098
|
+
const productSlug = input.productSlug ?? config.productId;
|
|
1099
|
+
if (productSlug) body.productSlug = productSlug;
|
|
1099
1100
|
let cid = input.correlationId;
|
|
1100
1101
|
if (!cid) {
|
|
1101
1102
|
try {
|
|
@@ -4614,13 +4615,69 @@ function createRestSyncTransport(config) {
|
|
|
4614
4615
|
if (!config) {
|
|
4615
4616
|
return { docs: [], newWatermark: Date.now(), resyncRequired: false };
|
|
4616
4617
|
}
|
|
4617
|
-
return { docs: [], newWatermark: Date.now(), resyncRequired:
|
|
4618
|
+
return { docs: [], newWatermark: Date.now(), resyncRequired: true };
|
|
4618
4619
|
},
|
|
4619
|
-
async fullResync(
|
|
4620
|
+
async fullResync(collections) {
|
|
4620
4621
|
if (!config) {
|
|
4621
4622
|
return { docs: [], newWatermark: Date.now() };
|
|
4622
4623
|
}
|
|
4623
|
-
|
|
4624
|
+
const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
4625
|
+
const tenantScopeId = config.tenantId ?? config.productId;
|
|
4626
|
+
const tenantHeaders = tenantScopeId ? { "x-neetru-tenant": tenantScopeId } : void 0;
|
|
4627
|
+
const now = Date.now();
|
|
4628
|
+
const docs = [];
|
|
4629
|
+
for (const collection of collections) {
|
|
4630
|
+
let raw;
|
|
4631
|
+
try {
|
|
4632
|
+
raw = await httpRequest2(config, {
|
|
4633
|
+
method: "GET",
|
|
4634
|
+
path: DATASTORE_COLLECTION_ENDPOINT(collection),
|
|
4635
|
+
requireAuth: true,
|
|
4636
|
+
retries: 2,
|
|
4637
|
+
headers: tenantHeaders,
|
|
4638
|
+
// Teto do Core é 200. Coleções > 200 docs truncam silenciosamente
|
|
4639
|
+
// aqui (paginação cursor é tarefa Core separada) — a detecção de
|
|
4640
|
+
// deleção-por-ausência do SyncEngine NÃO deve rodar sobre truncamento,
|
|
4641
|
+
// então mantemos o limite no máximo do servidor por ora.
|
|
4642
|
+
query: { limit: 200 }
|
|
4643
|
+
});
|
|
4644
|
+
} catch (err) {
|
|
4645
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4646
|
+
throw new exports.NeetruDbError(
|
|
4647
|
+
"db_unavailable",
|
|
4648
|
+
`[RestSyncTransport] fullResync falhou na cole\xE7\xE3o "${collection}": ${msg}`
|
|
4649
|
+
);
|
|
4650
|
+
}
|
|
4651
|
+
const resp = raw;
|
|
4652
|
+
const items = Array.isArray(resp?.items) ? resp.items : [];
|
|
4653
|
+
for (const item of items) {
|
|
4654
|
+
if (!item || typeof item !== "object") continue;
|
|
4655
|
+
const record = item;
|
|
4656
|
+
const id = typeof record["id"] === "string" ? record["id"] : null;
|
|
4657
|
+
if (!id) continue;
|
|
4658
|
+
const { id: _inlinedId, ...data } = record;
|
|
4659
|
+
let serverTimestamp2 = now;
|
|
4660
|
+
const updatedAt = data["_updatedAt"];
|
|
4661
|
+
if (typeof updatedAt === "number") {
|
|
4662
|
+
serverTimestamp2 = updatedAt;
|
|
4663
|
+
} else if (updatedAt !== null && typeof updatedAt === "object" && typeof updatedAt["_seconds"] === "number") {
|
|
4664
|
+
serverTimestamp2 = updatedAt["_seconds"] * 1e3;
|
|
4665
|
+
} else if (typeof updatedAt === "string") {
|
|
4666
|
+
const parsed = Date.parse(updatedAt);
|
|
4667
|
+
if (!Number.isNaN(parsed)) serverTimestamp2 = parsed;
|
|
4668
|
+
}
|
|
4669
|
+
const serverVersion = `rest_${id}_${serverTimestamp2}`;
|
|
4670
|
+
docs.push({
|
|
4671
|
+
collection,
|
|
4672
|
+
id,
|
|
4673
|
+
data,
|
|
4674
|
+
serverVersion,
|
|
4675
|
+
serverTimestamp: serverTimestamp2,
|
|
4676
|
+
deleted: false
|
|
4677
|
+
});
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
return { docs, newWatermark: now };
|
|
4624
4681
|
}
|
|
4625
4682
|
};
|
|
4626
4683
|
}
|
|
@@ -4860,6 +4917,15 @@ function createLazyCollectionRef(name, getDocsPromise) {
|
|
|
4860
4917
|
// src/checkout.ts
|
|
4861
4918
|
init_errors();
|
|
4862
4919
|
init_http();
|
|
4920
|
+
function checkoutAuth(accessToken, method) {
|
|
4921
|
+
if (!accessToken || typeof accessToken !== "string") {
|
|
4922
|
+
throw new exports.NeetruError(
|
|
4923
|
+
"validation_failed",
|
|
4924
|
+
`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).`
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
return { requireAuth: false, headers: { authorization: `Bearer ${accessToken}` } };
|
|
4928
|
+
}
|
|
4863
4929
|
function parseStartResponse(raw) {
|
|
4864
4930
|
if (!raw || typeof raw !== "object") {
|
|
4865
4931
|
throw new exports.NeetruError("invalid_response", "checkout.start response is not an object");
|
|
@@ -4937,11 +5003,13 @@ function createHttpCheckoutNamespace(config) {
|
|
|
4937
5003
|
};
|
|
4938
5004
|
if (input.tenantType) body.targetTenantType = input.tenantType;
|
|
4939
5005
|
if (input.tenantId) body.targetTenantId = input.tenantId;
|
|
5006
|
+
const { requireAuth, headers } = checkoutAuth(input.accessToken, "start");
|
|
4940
5007
|
const raw = await httpRequest(config, {
|
|
4941
5008
|
method: "POST",
|
|
4942
5009
|
path: "/api/v1/checkout/intents",
|
|
4943
5010
|
body,
|
|
4944
|
-
requireAuth
|
|
5011
|
+
requireAuth,
|
|
5012
|
+
headers,
|
|
4945
5013
|
idempotencyKey: true
|
|
4946
5014
|
});
|
|
4947
5015
|
const result = parseStartResponse(raw);
|
|
@@ -4951,25 +5019,29 @@ function createHttpCheckoutNamespace(config) {
|
|
|
4951
5019
|
}
|
|
4952
5020
|
return result;
|
|
4953
5021
|
},
|
|
4954
|
-
async get(intentId) {
|
|
5022
|
+
async get(intentId, opts) {
|
|
4955
5023
|
if (!intentId || typeof intentId !== "string") {
|
|
4956
5024
|
throw new exports.NeetruError("validation_failed", "checkout.get: intentId is required");
|
|
4957
5025
|
}
|
|
5026
|
+
const { requireAuth, headers } = checkoutAuth(opts?.accessToken, "get");
|
|
4958
5027
|
const raw = await httpRequest(config, {
|
|
4959
5028
|
method: "GET",
|
|
4960
5029
|
path: `/api/v1/checkout/intents/${encodeURIComponent(intentId)}`,
|
|
4961
|
-
requireAuth
|
|
5030
|
+
requireAuth,
|
|
5031
|
+
headers
|
|
4962
5032
|
});
|
|
4963
5033
|
return parseGetResponse(raw);
|
|
4964
5034
|
},
|
|
4965
|
-
async cancel(intentId) {
|
|
5035
|
+
async cancel(intentId, opts) {
|
|
4966
5036
|
if (!intentId || typeof intentId !== "string") {
|
|
4967
5037
|
throw new exports.NeetruError("validation_failed", "checkout.cancel: intentId is required");
|
|
4968
5038
|
}
|
|
5039
|
+
const { requireAuth, headers } = checkoutAuth(opts?.accessToken, "cancel");
|
|
4969
5040
|
const raw = await httpRequest(config, {
|
|
4970
5041
|
method: "DELETE",
|
|
4971
5042
|
path: `/api/v1/checkout/intents/${encodeURIComponent(intentId)}`,
|
|
4972
|
-
requireAuth
|
|
5043
|
+
requireAuth,
|
|
5044
|
+
headers
|
|
4973
5045
|
});
|
|
4974
5046
|
return {
|
|
4975
5047
|
ok: true,
|
|
@@ -5013,14 +5085,14 @@ var MockCheckout = class {
|
|
|
5013
5085
|
requiresKyc: false
|
|
5014
5086
|
};
|
|
5015
5087
|
}
|
|
5016
|
-
async get(intentId) {
|
|
5088
|
+
async get(intentId, _opts) {
|
|
5017
5089
|
const found = this.intents.get(intentId);
|
|
5018
5090
|
if (!found) {
|
|
5019
5091
|
throw new exports.NeetruError("not_found", `Mock intent ${intentId} not found`);
|
|
5020
5092
|
}
|
|
5021
5093
|
return { ...found };
|
|
5022
5094
|
}
|
|
5023
|
-
async cancel(intentId) {
|
|
5095
|
+
async cancel(intentId, _opts) {
|
|
5024
5096
|
const found = this.intents.get(intentId);
|
|
5025
5097
|
if (!found) {
|
|
5026
5098
|
throw new exports.NeetruError("not_found", `Mock intent ${intentId} not found`);
|
|
@@ -5038,7 +5110,7 @@ function createCheckoutNamespace(config) {
|
|
|
5038
5110
|
// src/webhooks.ts
|
|
5039
5111
|
init_errors();
|
|
5040
5112
|
init_http();
|
|
5041
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
5113
|
+
async function verifyWebhookSignature(payload, signature, secret, timestamp) {
|
|
5042
5114
|
if (!signature || !secret) return false;
|
|
5043
5115
|
const sigHex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
5044
5116
|
if (!/^[0-9a-f]+$/i.test(sigHex)) return false;
|
|
@@ -5051,7 +5123,15 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
5051
5123
|
}
|
|
5052
5124
|
const encoder = new TextEncoder();
|
|
5053
5125
|
const keyBytes = encoder.encode(secret);
|
|
5054
|
-
const
|
|
5126
|
+
const rawBytes = typeof payload === "string" ? encoder.encode(payload) : payload;
|
|
5127
|
+
let payloadBytes = rawBytes;
|
|
5128
|
+
if (timestamp !== void 0) {
|
|
5129
|
+
const prefix = encoder.encode(`${timestamp}.`);
|
|
5130
|
+
const merged = new Uint8Array(prefix.length + rawBytes.length);
|
|
5131
|
+
merged.set(prefix, 0);
|
|
5132
|
+
merged.set(rawBytes, prefix.length);
|
|
5133
|
+
payloadBytes = merged;
|
|
5134
|
+
}
|
|
5055
5135
|
const key = await subtle.importKey(
|
|
5056
5136
|
"raw",
|
|
5057
5137
|
keyBytes,
|
|
@@ -5099,7 +5179,7 @@ async function verifyWebhookRequest(payload, headers, secret, options = {}) {
|
|
|
5099
5179
|
return { ok: false, reason: "timestamp_expired" };
|
|
5100
5180
|
}
|
|
5101
5181
|
const sig = getHeader("x-neetru-signature");
|
|
5102
|
-
const valid = await verifyWebhookSignature(payload, sig, secret);
|
|
5182
|
+
const valid = await verifyWebhookSignature(payload, sig, secret, tsHeader);
|
|
5103
5183
|
if (!valid) {
|
|
5104
5184
|
return { ok: false, reason: "invalid_signature" };
|
|
5105
5185
|
}
|
|
@@ -5487,9 +5567,19 @@ var DEV_FIXTURE_USER = Object.freeze({
|
|
|
5487
5567
|
var MockAuth = class {
|
|
5488
5568
|
_user;
|
|
5489
5569
|
_listeners;
|
|
5490
|
-
|
|
5570
|
+
_allowUnsignedDecode;
|
|
5571
|
+
constructor(initialUser = null, options) {
|
|
5491
5572
|
this._user = initialUser;
|
|
5492
5573
|
this._listeners = /* @__PURE__ */ new Set();
|
|
5574
|
+
this._allowUnsignedDecode = options?.allowUnsignedDecode ?? false;
|
|
5575
|
+
if (this._allowUnsignedDecode) {
|
|
5576
|
+
const isTestEnv = typeof process !== "undefined" && (process.env?.NODE_ENV === "test" || process.env?.VITEST === "true" || process.env?.VITEST != null);
|
|
5577
|
+
if (!isTestEnv && typeof console !== "undefined") {
|
|
5578
|
+
console.warn(
|
|
5579
|
+
"[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."
|
|
5580
|
+
);
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5493
5583
|
}
|
|
5494
5584
|
async signIn(_options) {
|
|
5495
5585
|
if (!this._user) this._user = { ...DEV_FIXTURE_USER };
|
|
@@ -5514,18 +5604,27 @@ var MockAuth = class {
|
|
|
5514
5604
|
};
|
|
5515
5605
|
}
|
|
5516
5606
|
/**
|
|
5517
|
-
* Mock de `verifyToken` —
|
|
5518
|
-
*
|
|
5519
|
-
* não-vazio sem chamar rede.
|
|
5607
|
+
* Mock de `verifyToken` — por default lança `unauthorized` pra prevenir uso
|
|
5608
|
+
* acidental como security boundary em código deployado.
|
|
5520
5609
|
*
|
|
5521
|
-
* Para
|
|
5610
|
+
* Para decodificar JWTs sem verificar assinatura em testes, crie a instância
|
|
5611
|
+
* com `new MockAuth(user, { allowUnsignedDecode: true })`. Isso é um opt-in
|
|
5612
|
+
* explícito — nunca acontece silenciosamente.
|
|
5613
|
+
*
|
|
5614
|
+
* Para simular falhas ou resultados customizados, use `__mockVerifyToken(fn)`.
|
|
5522
5615
|
*/
|
|
5523
5616
|
async verifyToken(token) {
|
|
5524
5617
|
if (this._verifyTokenOverride) {
|
|
5525
5618
|
return this._verifyTokenOverride(token);
|
|
5526
5619
|
}
|
|
5527
5620
|
if (!token) {
|
|
5528
|
-
throw
|
|
5621
|
+
throw new exports.NeetruError("token_invalid_signature", "Token inv\xE1lido: string vazia");
|
|
5622
|
+
}
|
|
5623
|
+
if (!this._allowUnsignedDecode) {
|
|
5624
|
+
throw new exports.NeetruError(
|
|
5625
|
+
"unauthorized",
|
|
5626
|
+
"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."
|
|
5627
|
+
);
|
|
5529
5628
|
}
|
|
5530
5629
|
try {
|
|
5531
5630
|
const parts = token.split(".");
|
|
@@ -5552,7 +5651,32 @@ var MockAuth = class {
|
|
|
5552
5651
|
} catch {
|
|
5553
5652
|
}
|
|
5554
5653
|
if (this._user) return this._user;
|
|
5555
|
-
throw
|
|
5654
|
+
throw new exports.NeetruError("token_invalid_signature", "Nenhum user mock dispon\xEDvel e JWT sem sub v\xE1lido");
|
|
5655
|
+
}
|
|
5656
|
+
/**
|
|
5657
|
+
* Mock de `handleRedirectCallback` (Bug 2 / 3.1.3) — não há redirect real no
|
|
5658
|
+
* mock, então retorna `null` por default. Use `__setPendingCallback(result)`
|
|
5659
|
+
* em testes pra simular um retorno do IdP.
|
|
5660
|
+
*/
|
|
5661
|
+
async handleRedirectCallback(_currentUrl) {
|
|
5662
|
+
return this._pendingCallback;
|
|
5663
|
+
}
|
|
5664
|
+
/**
|
|
5665
|
+
* Mock de `getIdToken` (Bug 2 / 3.1.3). Retorna `null` por default; use
|
|
5666
|
+
* `__setIdToken(token)` pra injetar um token cru em testes.
|
|
5667
|
+
*/
|
|
5668
|
+
getIdToken() {
|
|
5669
|
+
return this._idToken;
|
|
5670
|
+
}
|
|
5671
|
+
_pendingCallback = null;
|
|
5672
|
+
_idToken = null;
|
|
5673
|
+
/** Test helper — simula o material devolvido por um callback OIDC. */
|
|
5674
|
+
__setPendingCallback(result) {
|
|
5675
|
+
this._pendingCallback = result;
|
|
5676
|
+
}
|
|
5677
|
+
/** Test helper — injeta o id_token cru retornado por `getIdToken`. */
|
|
5678
|
+
__setIdToken(token) {
|
|
5679
|
+
this._idToken = token;
|
|
5556
5680
|
}
|
|
5557
5681
|
_verifyTokenOverride = null;
|
|
5558
5682
|
/**
|
|
@@ -6083,6 +6207,49 @@ function _jwtPayloadToUser(payload) {
|
|
|
6083
6207
|
...payload
|
|
6084
6208
|
};
|
|
6085
6209
|
}
|
|
6210
|
+
var OIDC_TX_KEY = "neetru_oauth_tx";
|
|
6211
|
+
function resolveIdpOrigin(baseUrl) {
|
|
6212
|
+
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
6213
|
+
return overrideAuthUrl ?? baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
6214
|
+
}
|
|
6215
|
+
function getWebCrypto() {
|
|
6216
|
+
const c = globalThis.crypto;
|
|
6217
|
+
if (!c || typeof c.getRandomValues !== "function" || !c.subtle) {
|
|
6218
|
+
throw new exports.NeetruError(
|
|
6219
|
+
"runtime_unsupported",
|
|
6220
|
+
"auth.signIn requer WebCrypto (crypto.subtle) \u2014 indispon\xEDvel neste runtime. Use HTTPS (ou http://localhost) num browser moderno."
|
|
6221
|
+
);
|
|
6222
|
+
}
|
|
6223
|
+
return c;
|
|
6224
|
+
}
|
|
6225
|
+
function base64UrlFromBytes(bytes) {
|
|
6226
|
+
let str = "";
|
|
6227
|
+
for (const b of bytes) str += String.fromCharCode(b);
|
|
6228
|
+
const b64 = typeof btoa === "function" ? btoa(str) : Buffer.from(str, "binary").toString("base64");
|
|
6229
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
6230
|
+
}
|
|
6231
|
+
function randomUrlSafe(crypto2, byteLen) {
|
|
6232
|
+
const bytes = new Uint8Array(byteLen);
|
|
6233
|
+
crypto2.getRandomValues(bytes);
|
|
6234
|
+
return base64UrlFromBytes(bytes);
|
|
6235
|
+
}
|
|
6236
|
+
async function pkceChallengeS256(crypto2, verifier) {
|
|
6237
|
+
const data = new TextEncoder().encode(verifier);
|
|
6238
|
+
const digest = await crypto2.subtle.digest("SHA-256", data);
|
|
6239
|
+
return base64UrlFromBytes(new Uint8Array(digest));
|
|
6240
|
+
}
|
|
6241
|
+
function cleanCallbackUrl(parsed) {
|
|
6242
|
+
try {
|
|
6243
|
+
const history = globalThis.history;
|
|
6244
|
+
if (!history || typeof history.replaceState !== "function") return;
|
|
6245
|
+
const clean = new URL(parsed.toString());
|
|
6246
|
+
for (const p of ["code", "state", "error", "error_description", "iss", "session_state"]) {
|
|
6247
|
+
clean.searchParams.delete(p);
|
|
6248
|
+
}
|
|
6249
|
+
history.replaceState(history.state ?? null, "", clean.pathname + clean.search + clean.hash);
|
|
6250
|
+
} catch {
|
|
6251
|
+
}
|
|
6252
|
+
}
|
|
6086
6253
|
function createOidcAuthNamespace(config) {
|
|
6087
6254
|
const STORAGE_KEY = "neetru_id_token";
|
|
6088
6255
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -6095,6 +6262,13 @@ function createOidcAuthNamespace(config) {
|
|
|
6095
6262
|
return null;
|
|
6096
6263
|
}
|
|
6097
6264
|
}
|
|
6265
|
+
function getSessionStorage() {
|
|
6266
|
+
try {
|
|
6267
|
+
return typeof globalThis.sessionStorage !== "undefined" ? globalThis.sessionStorage : null;
|
|
6268
|
+
} catch {
|
|
6269
|
+
return null;
|
|
6270
|
+
}
|
|
6271
|
+
}
|
|
6098
6272
|
function decodeJwtPayload(token) {
|
|
6099
6273
|
const parts = token.split(".");
|
|
6100
6274
|
if (parts.length !== 3) return null;
|
|
@@ -6178,20 +6352,48 @@ function createOidcAuthNamespace(config) {
|
|
|
6178
6352
|
return {
|
|
6179
6353
|
async signIn(options) {
|
|
6180
6354
|
if (typeof globalThis.location !== "undefined" && typeof globalThis.location.assign === "function") {
|
|
6355
|
+
const crypto2 = getWebCrypto();
|
|
6181
6356
|
const redirectUri = options?.redirectUri ?? globalThis.location.origin;
|
|
6182
6357
|
const scope = options?.scope ?? "openid profile email";
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6358
|
+
let clientId = config.oidcClientId;
|
|
6359
|
+
if (!clientId && config.apiKey) {
|
|
6360
|
+
clientId = parseApiKey(config.apiKey).keyId;
|
|
6361
|
+
}
|
|
6362
|
+
if (!clientId) {
|
|
6363
|
+
throw new exports.NeetruError(
|
|
6364
|
+
"invalid_config",
|
|
6365
|
+
"auth.signIn requer `oidcClientId` (ou `apiKey`) configurado para montar o client_id."
|
|
6366
|
+
);
|
|
6367
|
+
}
|
|
6368
|
+
const state = randomUrlSafe(crypto2, 16);
|
|
6369
|
+
const nonce = randomUrlSafe(crypto2, 16);
|
|
6370
|
+
const codeVerifier = randomUrlSafe(crypto2, 32);
|
|
6371
|
+
const codeChallenge = await pkceChallengeS256(crypto2, codeVerifier);
|
|
6372
|
+
const session = getSessionStorage();
|
|
6373
|
+
if (!session) {
|
|
6374
|
+
throw new exports.NeetruError(
|
|
6375
|
+
"runtime_unsupported",
|
|
6376
|
+
"auth.signIn requer sessionStorage para guardar a transa\xE7\xE3o PKCE entre o redirect e o callback."
|
|
6377
|
+
);
|
|
6378
|
+
}
|
|
6379
|
+
const tx = {
|
|
6380
|
+
state,
|
|
6381
|
+
nonce,
|
|
6382
|
+
codeVerifier,
|
|
6383
|
+
redirectUri,
|
|
6384
|
+
postLoginRedirect: options?.postLoginRedirect
|
|
6385
|
+
};
|
|
6386
|
+
session.setItem(OIDC_TX_KEY, JSON.stringify(tx));
|
|
6387
|
+
const idpOrigin = resolveIdpOrigin(config.baseUrl);
|
|
6186
6388
|
const url = new URL("/api/v1/oauth/authorize", idpOrigin);
|
|
6187
6389
|
url.searchParams.set("response_type", "code");
|
|
6390
|
+
url.searchParams.set("client_id", clientId);
|
|
6188
6391
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
6189
6392
|
url.searchParams.set("scope", scope);
|
|
6190
6393
|
url.searchParams.set("state", state);
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
}
|
|
6394
|
+
url.searchParams.set("nonce", nonce);
|
|
6395
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
6396
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
6195
6397
|
globalThis.location.assign(url.toString());
|
|
6196
6398
|
return;
|
|
6197
6399
|
}
|
|
@@ -6200,6 +6402,67 @@ function createOidcAuthNamespace(config) {
|
|
|
6200
6402
|
"auth.signIn requires a browser context or mocks. Use NEETRU_ENV=dev or pass mocks.auth."
|
|
6201
6403
|
);
|
|
6202
6404
|
},
|
|
6405
|
+
async handleRedirectCallback(currentUrl) {
|
|
6406
|
+
const href = currentUrl ?? (typeof globalThis.location !== "undefined" ? globalThis.location.href : void 0);
|
|
6407
|
+
if (!href) return null;
|
|
6408
|
+
let parsed;
|
|
6409
|
+
try {
|
|
6410
|
+
parsed = new URL(href);
|
|
6411
|
+
} catch {
|
|
6412
|
+
return null;
|
|
6413
|
+
}
|
|
6414
|
+
const params = parsed.searchParams;
|
|
6415
|
+
const idpError = params.get("error");
|
|
6416
|
+
if (idpError) {
|
|
6417
|
+
const desc = params.get("error_description") ?? idpError;
|
|
6418
|
+
getSessionStorage()?.removeItem(OIDC_TX_KEY);
|
|
6419
|
+
cleanCallbackUrl(parsed);
|
|
6420
|
+
throw new exports.NeetruError(idpError, `OIDC authorize falhou: ${desc}`);
|
|
6421
|
+
}
|
|
6422
|
+
const code = params.get("code");
|
|
6423
|
+
if (!code) return null;
|
|
6424
|
+
const returnedState = params.get("state");
|
|
6425
|
+
const session = getSessionStorage();
|
|
6426
|
+
const txRaw = session?.getItem(OIDC_TX_KEY) ?? null;
|
|
6427
|
+
let tx = null;
|
|
6428
|
+
if (txRaw) {
|
|
6429
|
+
try {
|
|
6430
|
+
tx = JSON.parse(txRaw);
|
|
6431
|
+
} catch {
|
|
6432
|
+
tx = null;
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
if (!tx) {
|
|
6436
|
+
throw new exports.NeetruError(
|
|
6437
|
+
"oidc_state_mismatch",
|
|
6438
|
+
"Callback OIDC sem transa\xE7\xE3o pendente \u2014 `signIn` n\xE3o foi iniciado nesta sess\xE3o (ou j\xE1 consumido)."
|
|
6439
|
+
);
|
|
6440
|
+
}
|
|
6441
|
+
if (!returnedState || returnedState !== tx.state) {
|
|
6442
|
+
session?.removeItem(OIDC_TX_KEY);
|
|
6443
|
+
cleanCallbackUrl(parsed);
|
|
6444
|
+
throw new exports.NeetruError(
|
|
6445
|
+
"oidc_state_mismatch",
|
|
6446
|
+
"state do callback OIDC n\xE3o confere com o da transa\xE7\xE3o (poss\xEDvel CSRF)."
|
|
6447
|
+
);
|
|
6448
|
+
}
|
|
6449
|
+
session?.removeItem(OIDC_TX_KEY);
|
|
6450
|
+
cleanCallbackUrl(parsed);
|
|
6451
|
+
return {
|
|
6452
|
+
code,
|
|
6453
|
+
codeVerifier: tx.codeVerifier,
|
|
6454
|
+
redirectUri: tx.redirectUri,
|
|
6455
|
+
nonce: tx.nonce,
|
|
6456
|
+
state: tx.state,
|
|
6457
|
+
postLoginRedirect: tx.postLoginRedirect
|
|
6458
|
+
};
|
|
6459
|
+
},
|
|
6460
|
+
getIdToken() {
|
|
6461
|
+
const storage = getStorage();
|
|
6462
|
+
const token = storage?.getItem(STORAGE_KEY) ?? null;
|
|
6463
|
+
if (!token) return null;
|
|
6464
|
+
return tokenToUser(token) ? token : null;
|
|
6465
|
+
},
|
|
6203
6466
|
async signOut() {
|
|
6204
6467
|
const storage = getStorage();
|
|
6205
6468
|
const storedToken = storage?.getItem(STORAGE_KEY) ?? null;
|
|
@@ -6259,13 +6522,20 @@ function createOidcAuthNamespace(config) {
|
|
|
6259
6522
|
const issuer = idpOrigin;
|
|
6260
6523
|
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
6261
6524
|
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
6525
|
+
const audienceFromOptions = options?.audience;
|
|
6262
6526
|
const parsedKey = tryParseApiKey(config.apiKey);
|
|
6263
|
-
const audience = parsedKey?.keyId;
|
|
6527
|
+
const audience = audienceFromOptions ?? parsedKey?.keyId;
|
|
6528
|
+
if (!audience) {
|
|
6529
|
+
throw new exports.NeetruError(
|
|
6530
|
+
"invalid_config",
|
|
6531
|
+
"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."
|
|
6532
|
+
);
|
|
6533
|
+
}
|
|
6264
6534
|
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
6265
6535
|
try {
|
|
6266
6536
|
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
6267
6537
|
issuer,
|
|
6268
|
-
|
|
6538
|
+
audience,
|
|
6269
6539
|
algorithms: ["RS256"]
|
|
6270
6540
|
});
|
|
6271
6541
|
return _jwtPayloadToUser(payload);
|
|
@@ -6306,6 +6576,7 @@ function createNeetruClient(config = {}) {
|
|
|
6306
6576
|
const env = resolveEnv(config.env);
|
|
6307
6577
|
const resolved = Object.freeze({
|
|
6308
6578
|
apiKey,
|
|
6579
|
+
oidcClientId: config.oidcClientId,
|
|
6309
6580
|
baseUrl,
|
|
6310
6581
|
fetch: fetchImpl.bind(globalThis),
|
|
6311
6582
|
env,
|
|
@@ -6340,7 +6611,7 @@ function createNeetruClient(config = {}) {
|
|
|
6340
6611
|
init_errors();
|
|
6341
6612
|
init_db_errors();
|
|
6342
6613
|
init_http();
|
|
6343
|
-
var VERSION = "3.1.
|
|
6614
|
+
var VERSION = "3.1.6";
|
|
6344
6615
|
|
|
6345
6616
|
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
6346
6617
|
exports.DEV_FIXTURE_USER = DEV_FIXTURE_USER;
|