@neetru/sdk 2.1.1 → 2.2.0
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 +42 -1
- package/dist/auth.cjs +127 -0
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +4 -2
- package/dist/auth.d.ts +4 -2
- package/dist/auth.mjs +127 -1
- 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.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs.map +1 -1
- 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.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 +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +123 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +123 -0
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +55 -0
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +21 -1
- package/dist/mocks.d.ts +21 -1
- package/dist/mocks.mjs +55 -0
- 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.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-V1EfjR1p.d.ts → types-CvTje138.d.ts} +45 -1
- package/dist/{types-Cfb-qeDg.d.cts → types-DlDxttiG.d.cts} +45 -1
- 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.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,9 +5,50 @@ All notable changes to `@neetru/sdk` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.2.0] - 2026-05-25 — **auth.verifyToken — validação server-side de id_token via JWKS [feature]**
|
|
9
|
+
|
|
10
|
+
Fix do bug `bug_a7ef1702680044e3994b74bdccecb2ab` — resolve P0-1 audit security pdv-agiliza.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`client.auth.verifyToken(token, options?): Promise<NeetruUser>`** — valida um id_token JWT
|
|
15
|
+
emitido por `auth.neetru.com` server-side, sem round-trip de rede por request. Usa JWKS local
|
|
16
|
+
cacheado (default TTL 1h, configurável via `options.jwksCacheTtlMs`).
|
|
17
|
+
|
|
18
|
+
Erros tipados novos (`NeetruErrorCode`):
|
|
19
|
+
- `token_expired` — `exp` no passado
|
|
20
|
+
- `token_invalid_signature` — assinatura não verificada pela JWKS
|
|
21
|
+
- `token_invalid_audience` — `aud` não bate com o `clientId` extraído do `apiKey`
|
|
22
|
+
- `token_invalid_issuer` — `iss` não é `https://auth.neetru.com`
|
|
23
|
+
|
|
24
|
+
Dependência nova: `jose` (Vercel/Cloudflare-friendly, sem Node nativo).
|
|
25
|
+
|
|
26
|
+
- **`MockAuth.verifyToken(token)`** — decodifica JWT sem verificar assinatura (útil em testes).
|
|
27
|
+
Aceita `__mockVerifyToken(fn)` pra injetar falhas determinísticas.
|
|
28
|
+
|
|
29
|
+
- **`VerifyTokenOptions`** — exportado em `index.ts` pra consumers tiparem as options.
|
|
30
|
+
|
|
31
|
+
### Usage
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// Next.js route handler
|
|
35
|
+
import { createNeetruClient } from '@neetru/sdk';
|
|
36
|
+
const client = createNeetruClient({ apiKey: 'nrt_a1b2c3_...', env: 'prod' });
|
|
37
|
+
|
|
38
|
+
export async function POST(req: Request) {
|
|
39
|
+
const token = req.cookies.get('neetru-session')?.value ?? '';
|
|
40
|
+
try {
|
|
41
|
+
const user = await client.auth.verifyToken(token);
|
|
42
|
+
// user.uid, user.email, user.isStaff, user.tenantId disponíveis
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return new Response('Unauthorized', { status: 401 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
8
49
|
## [Unreleased]
|
|
9
50
|
|
|
10
|
-
### Planned (post-2.
|
|
51
|
+
### Planned (post-2.2)
|
|
11
52
|
- size-limit budget no CI (<12KB gz core com db namespace).
|
|
12
53
|
- CDN distribution (`cdn.neetru.com/sdk/v2/`).
|
|
13
54
|
- Endpoint batch `/api/v1/sdk/telemetry/batch` (substitui drenagem N×1 do `track()` por 1 request).
|
package/dist/auth.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var jose = require('jose');
|
|
3
4
|
var idb = require('idb');
|
|
4
5
|
|
|
5
6
|
var __defProp = Object.defineProperty;
|
|
@@ -4845,6 +4846,61 @@ var MockAuth = class {
|
|
|
4845
4846
|
this._listeners.delete(listener);
|
|
4846
4847
|
};
|
|
4847
4848
|
}
|
|
4849
|
+
/**
|
|
4850
|
+
* Mock de `verifyToken` — decodifica o JWT sem verificar assinatura (pra testes).
|
|
4851
|
+
* Em dev, retorna o user atual do mock se já estiver logado. Aceita qualquer token
|
|
4852
|
+
* não-vazio sem chamar rede.
|
|
4853
|
+
*
|
|
4854
|
+
* Para simular falhas em testes, use `MockAuth.__mockVerifyToken(fn)`.
|
|
4855
|
+
*/
|
|
4856
|
+
async verifyToken(token) {
|
|
4857
|
+
if (this._verifyTokenOverride) {
|
|
4858
|
+
return this._verifyTokenOverride(token);
|
|
4859
|
+
}
|
|
4860
|
+
if (!token) {
|
|
4861
|
+
throw Object.assign(new Error("Token inv\xE1lido"), { code: "token_invalid_signature" });
|
|
4862
|
+
}
|
|
4863
|
+
try {
|
|
4864
|
+
const parts = token.split(".");
|
|
4865
|
+
if (parts.length === 3) {
|
|
4866
|
+
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
4867
|
+
const pad = "=".repeat((4 - b64.length % 4) % 4);
|
|
4868
|
+
const decoded = JSON.parse(
|
|
4869
|
+
typeof atob === "function" ? atob(b64 + pad) : Buffer.from(b64 + pad, "base64").toString("utf-8")
|
|
4870
|
+
);
|
|
4871
|
+
if (typeof decoded.sub === "string") {
|
|
4872
|
+
return {
|
|
4873
|
+
uid: decoded.sub,
|
|
4874
|
+
email: typeof decoded.email === "string" ? decoded.email : "",
|
|
4875
|
+
emailVerified: typeof decoded.email_verified === "boolean" ? decoded.email_verified : void 0,
|
|
4876
|
+
displayName: typeof decoded.name === "string" ? decoded.name : void 0,
|
|
4877
|
+
photoURL: typeof decoded.picture === "string" ? decoded.picture : void 0,
|
|
4878
|
+
isStaff: decoded.is_staff === true,
|
|
4879
|
+
isCustomer: decoded.is_customer === true,
|
|
4880
|
+
tenantId: typeof decoded.tenant_id === "string" ? decoded.tenant_id : void 0,
|
|
4881
|
+
...decoded
|
|
4882
|
+
};
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
} catch {
|
|
4886
|
+
}
|
|
4887
|
+
if (this._user) return this._user;
|
|
4888
|
+
throw Object.assign(new Error("Nenhum user mock dispon\xEDvel"), { code: "token_invalid_signature" });
|
|
4889
|
+
}
|
|
4890
|
+
_verifyTokenOverride = null;
|
|
4891
|
+
/**
|
|
4892
|
+
* Injeta override pra `verifyToken` em testes — permite simular expiração,
|
|
4893
|
+
* assinatura inválida etc sem precisar de JWT real.
|
|
4894
|
+
*
|
|
4895
|
+
* @example
|
|
4896
|
+
* ```ts
|
|
4897
|
+
* const mock = new MockAuth(null);
|
|
4898
|
+
* mock.__mockVerifyToken(async () => { throw new NeetruError('token_expired', '...'); });
|
|
4899
|
+
* ```
|
|
4900
|
+
*/
|
|
4901
|
+
__mockVerifyToken(fn) {
|
|
4902
|
+
this._verifyTokenOverride = fn;
|
|
4903
|
+
}
|
|
4848
4904
|
/** Helper de tests — força um user state arbitrário. */
|
|
4849
4905
|
__setUser(user) {
|
|
4850
4906
|
this._user = user;
|
|
@@ -5018,6 +5074,37 @@ function resolveEnv(configEnv) {
|
|
|
5018
5074
|
}
|
|
5019
5075
|
return "prod";
|
|
5020
5076
|
}
|
|
5077
|
+
var _jwksCache = /* @__PURE__ */ new Map();
|
|
5078
|
+
var DEFAULT_JWKS_TTL_MS = 60 * 60 * 1e3;
|
|
5079
|
+
function _getJwks(jwksUrl, cacheTtlMs) {
|
|
5080
|
+
const now = Date.now();
|
|
5081
|
+
const cached = _jwksCache.get(jwksUrl);
|
|
5082
|
+
if (cached && now - cached.createdAt < cacheTtlMs) {
|
|
5083
|
+
return cached.jwks;
|
|
5084
|
+
}
|
|
5085
|
+
const jwks = jose.createRemoteJWKSet(new URL(jwksUrl));
|
|
5086
|
+
_jwksCache.set(jwksUrl, { jwks, createdAt: now });
|
|
5087
|
+
return jwks;
|
|
5088
|
+
}
|
|
5089
|
+
function __resetJwksCacheForTests() {
|
|
5090
|
+
_jwksCache.clear();
|
|
5091
|
+
}
|
|
5092
|
+
function _jwtPayloadToUser(payload) {
|
|
5093
|
+
const sub = payload.sub ?? "";
|
|
5094
|
+
const email = typeof payload.email === "string" ? payload.email : "";
|
|
5095
|
+
return {
|
|
5096
|
+
uid: sub,
|
|
5097
|
+
email,
|
|
5098
|
+
emailVerified: typeof payload.email_verified === "boolean" ? payload.email_verified : void 0,
|
|
5099
|
+
displayName: typeof payload.name === "string" ? payload.name : void 0,
|
|
5100
|
+
photoURL: typeof payload.picture === "string" ? payload.picture : void 0,
|
|
5101
|
+
isStaff: payload.is_staff === true,
|
|
5102
|
+
isCustomer: payload.is_customer === true,
|
|
5103
|
+
tenantId: typeof payload.tenant_id === "string" ? payload.tenant_id : void 0,
|
|
5104
|
+
// Spreads todos os claims originais (aud, iss, iat, exp, etc).
|
|
5105
|
+
...payload
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5021
5108
|
function createOidcAuthNamespace(config) {
|
|
5022
5109
|
const STORAGE_KEY = "neetru_id_token";
|
|
5023
5110
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -5125,6 +5212,45 @@ function createOidcAuthNamespace(config) {
|
|
|
5125
5212
|
return () => {
|
|
5126
5213
|
listeners.delete(listener);
|
|
5127
5214
|
};
|
|
5215
|
+
},
|
|
5216
|
+
async verifyToken(token, options) {
|
|
5217
|
+
if (!token || typeof token !== "string") {
|
|
5218
|
+
throw new NeetruError("token_invalid_signature", "token must be a non-empty string");
|
|
5219
|
+
}
|
|
5220
|
+
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
5221
|
+
const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
5222
|
+
const issuer = idpOrigin;
|
|
5223
|
+
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
5224
|
+
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
5225
|
+
const audience = config.apiKey ? config.apiKey.split("_")[1] ?? void 0 : void 0;
|
|
5226
|
+
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
5227
|
+
try {
|
|
5228
|
+
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
5229
|
+
issuer,
|
|
5230
|
+
...audience ? { audience } : {},
|
|
5231
|
+
algorithms: ["RS256"]
|
|
5232
|
+
});
|
|
5233
|
+
return _jwtPayloadToUser(payload);
|
|
5234
|
+
} catch (err) {
|
|
5235
|
+
const errName = err?.code ?? err?.name ?? "";
|
|
5236
|
+
const errMsg = err?.message ?? String(err);
|
|
5237
|
+
if (errName === "ERR_JWT_EXPIRED") {
|
|
5238
|
+
throw new NeetruError("token_expired", `Token expired: ${errMsg}`);
|
|
5239
|
+
}
|
|
5240
|
+
if (errName === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
5241
|
+
throw new NeetruError("token_invalid_signature", `Token signature invalid: ${errMsg}`);
|
|
5242
|
+
}
|
|
5243
|
+
if (errName === "ERR_JWT_CLAIM_VALIDATION_FAILED") {
|
|
5244
|
+
if (errMsg.includes('"aud"') || errMsg.includes("audience")) {
|
|
5245
|
+
throw new NeetruError("token_invalid_audience", `Token audience mismatch: ${errMsg}`);
|
|
5246
|
+
}
|
|
5247
|
+
if (errMsg.includes('"iss"') || errMsg.includes("issuer")) {
|
|
5248
|
+
throw new NeetruError("token_invalid_issuer", `Token issuer mismatch: ${errMsg}`);
|
|
5249
|
+
}
|
|
5250
|
+
throw new NeetruError("token_invalid_signature", `Token claim validation failed: ${errMsg}`);
|
|
5251
|
+
}
|
|
5252
|
+
throw new NeetruError("token_invalid_signature", `Token verification failed: ${errMsg}`);
|
|
5253
|
+
}
|
|
5128
5254
|
}
|
|
5129
5255
|
};
|
|
5130
5256
|
}
|
|
@@ -5171,6 +5297,7 @@ function createNeetruClient(config = {}) {
|
|
|
5171
5297
|
return client;
|
|
5172
5298
|
}
|
|
5173
5299
|
|
|
5300
|
+
exports.__resetJwksCacheForTests = __resetJwksCacheForTests;
|
|
5174
5301
|
exports.createNeetruClient = createNeetruClient;
|
|
5175
5302
|
//# sourceMappingURL=auth.cjs.map
|
|
5176
5303
|
//# sourceMappingURL=auth.cjs.map
|