@eva_solutions/auth-sdk 0.0.0-stage → 3.0.0-dev-20261107051125
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 +290 -0
- package/LICENSE +21 -0
- package/README.md +481 -2
- package/dist/admin/index.cjs +4 -0
- package/dist/admin/index.d.cts +2 -0
- package/dist/admin/index.d.ts +2 -0
- package/dist/admin/index.js +1 -0
- package/dist/admin-DjvuvuI8.js +408 -0
- package/dist/admin-DjvuvuI8.js.map +1 -0
- package/dist/admin-Q6lbWqLM.cjs +419 -0
- package/dist/admin-Q6lbWqLM.cjs.map +1 -0
- package/dist/api-path-2Ulruz7R.cjs +34 -0
- package/dist/api-path-2Ulruz7R.cjs.map +1 -0
- package/dist/api-path-B-vSkL2H.js +29 -0
- package/dist/api-path-B-vSkL2H.js.map +1 -0
- package/dist/auth-19r-Q6X2.js +84 -0
- package/dist/auth-19r-Q6X2.js.map +1 -0
- package/dist/auth-DX1N5JA4.cjs +131 -0
- package/dist/auth-DX1N5JA4.cjs.map +1 -0
- package/dist/auth-handlers-B3iJaPln.js +343 -0
- package/dist/auth-handlers-B3iJaPln.js.map +1 -0
- package/dist/auth-handlers-BTbXKJms.d.ts +15 -0
- package/dist/auth-handlers-BTbXKJms.d.ts.map +1 -0
- package/dist/auth-handlers-CDzGFDTu.cjs +404 -0
- package/dist/auth-handlers-CDzGFDTu.cjs.map +1 -0
- package/dist/auth-handlers-DRUUTEtW.d.cts +15 -0
- package/dist/auth-handlers-DRUUTEtW.d.cts.map +1 -0
- package/dist/catalog-CHPqldcj.cjs +172 -0
- package/dist/catalog-CHPqldcj.cjs.map +1 -0
- package/dist/catalog-CIqpzgsM.d.cts +75 -0
- package/dist/catalog-CIqpzgsM.d.cts.map +1 -0
- package/dist/catalog-CIqpzgsM.d.ts +75 -0
- package/dist/catalog-CIqpzgsM.d.ts.map +1 -0
- package/dist/catalog-DqPhtCKZ.js +95 -0
- package/dist/catalog-DqPhtCKZ.js.map +1 -0
- package/dist/config-D6Tr7ev6.d.cts +95 -0
- package/dist/config-D6Tr7ev6.d.cts.map +1 -0
- package/dist/config-rl4_-VAq.d.ts +95 -0
- package/dist/config-rl4_-VAq.d.ts.map +1 -0
- package/dist/constants-BE1_9Tod.cjs +52 -0
- package/dist/constants-BE1_9Tod.cjs.map +1 -0
- package/dist/constants-BY7QZokc.js +29 -0
- package/dist/constants-BY7QZokc.js.map +1 -0
- package/dist/constants-CcbhOaAP.cjs +153 -0
- package/dist/constants-CcbhOaAP.cjs.map +1 -0
- package/dist/constants-rNuD5qJn.js +94 -0
- package/dist/constants-rNuD5qJn.js.map +1 -0
- package/dist/contracts/index.cjs +1006 -0
- package/dist/contracts/index.cjs.map +1 -0
- package/dist/contracts/index.d.cts +488 -0
- package/dist/contracts/index.d.cts.map +1 -0
- package/dist/contracts/index.d.ts +488 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +817 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/cookies-DCIcr7uy.cjs +94 -0
- package/dist/cookies-DCIcr7uy.cjs.map +1 -0
- package/dist/cookies-DKEC7AWv.js +71 -0
- package/dist/cookies-DKEC7AWv.js.map +1 -0
- package/dist/delegated-token-CVNbMNfy.js +426 -0
- package/dist/delegated-token-CVNbMNfy.js.map +1 -0
- package/dist/delegated-token-DHSK4rHI.cjs +461 -0
- package/dist/delegated-token-DHSK4rHI.cjs.map +1 -0
- package/dist/error-helpers-BenWu-_u.d.ts +105 -0
- package/dist/error-helpers-BenWu-_u.d.ts.map +1 -0
- package/dist/error-helpers-KBHUbkOM.d.cts +105 -0
- package/dist/error-helpers-KBHUbkOM.d.cts.map +1 -0
- package/dist/error-helpers-XCh4ZJ-g.cjs +282 -0
- package/dist/error-helpers-XCh4ZJ-g.cjs.map +1 -0
- package/dist/error-helpers-lnwgNOw9.js +145 -0
- package/dist/error-helpers-lnwgNOw9.js.map +1 -0
- package/dist/error-messages-CGpW1fsg.d.cts +48 -0
- package/dist/error-messages-CGpW1fsg.d.cts.map +1 -0
- package/dist/error-messages-CGpW1fsg.d.ts +48 -0
- package/dist/error-messages-CGpW1fsg.d.ts.map +1 -0
- package/dist/event-codes-BBJHon-I.cjs +48 -0
- package/dist/event-codes-BBJHon-I.cjs.map +1 -0
- package/dist/event-codes-BgcJWsov.d.cts +42 -0
- package/dist/event-codes-BgcJWsov.d.cts.map +1 -0
- package/dist/event-codes-BgcJWsov.d.ts +42 -0
- package/dist/event-codes-BgcJWsov.d.ts.map +1 -0
- package/dist/event-codes-DR5_VlaR.js +43 -0
- package/dist/event-codes-DR5_VlaR.js.map +1 -0
- package/dist/generic/index.cjs +91 -0
- package/dist/generic/index.cjs.map +1 -0
- package/dist/generic/index.d.cts +27 -0
- package/dist/generic/index.d.cts.map +1 -0
- package/dist/generic/index.d.ts +27 -0
- package/dist/generic/index.d.ts.map +1 -0
- package/dist/generic/index.js +80 -0
- package/dist/generic/index.js.map +1 -0
- package/dist/hono/index.cjs +309 -0
- package/dist/hono/index.cjs.map +1 -0
- package/dist/hono/index.d.cts +272 -0
- package/dist/hono/index.d.cts.map +1 -0
- package/dist/hono/index.d.ts +272 -0
- package/dist/hono/index.d.ts.map +1 -0
- package/dist/hono/index.js +290 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/hono-openapi/index.cjs +326 -0
- package/dist/hono-openapi/index.cjs.map +1 -0
- package/dist/hono-openapi/index.d.cts +14 -0
- package/dist/hono-openapi/index.d.cts.map +1 -0
- package/dist/hono-openapi/index.d.ts +14 -0
- package/dist/hono-openapi/index.d.ts.map +1 -0
- package/dist/hono-openapi/index.js +307 -0
- package/dist/hono-openapi/index.js.map +1 -0
- package/dist/http-client-BVF3iktR.cjs +211 -0
- package/dist/http-client-BVF3iktR.cjs.map +1 -0
- package/dist/http-client-Da0-OMfc.js +206 -0
- package/dist/http-client-Da0-OMfc.js.map +1 -0
- package/dist/index-BYzDaMR9.d.ts +312 -0
- package/dist/index-BYzDaMR9.d.ts.map +1 -0
- package/dist/index-CNNMCrdl.d.ts +66 -0
- package/dist/index-CNNMCrdl.d.ts.map +1 -0
- package/dist/index-DBZoLU4E.d.ts +240 -0
- package/dist/index-DBZoLU4E.d.ts.map +1 -0
- package/dist/index-DcvQWByo.d.cts +66 -0
- package/dist/index-DcvQWByo.d.cts.map +1 -0
- package/dist/index-Dold-Ycb.d.cts +1711 -0
- package/dist/index-Dold-Ycb.d.cts.map +1 -0
- package/dist/index-Dold-Ycb.d.ts +1711 -0
- package/dist/index-Dold-Ycb.d.ts.map +1 -0
- package/dist/index-jAO62-OD.d.cts +240 -0
- package/dist/index-jAO62-OD.d.cts.map +1 -0
- package/dist/index-o7nXcl7z.d.cts +312 -0
- package/dist/index-o7nXcl7z.d.cts.map +1 -0
- package/dist/index.cjs +89 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +114 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt-BBNqk0TV.cjs +155 -0
- package/dist/jwt-BBNqk0TV.cjs.map +1 -0
- package/dist/jwt-DhqgH94B.js +150 -0
- package/dist/jwt-DhqgH94B.js.map +1 -0
- package/dist/key-resolver-CESqj4-P.cjs +45 -0
- package/dist/key-resolver-CESqj4-P.cjs.map +1 -0
- package/dist/key-resolver-D56I2Zpz.js +34 -0
- package/dist/key-resolver-D56I2Zpz.js.map +1 -0
- package/dist/parse-error-response-6qOQS5y9.cjs +316 -0
- package/dist/parse-error-response-6qOQS5y9.cjs.map +1 -0
- package/dist/parse-error-response-BM9-kjs0.js +239 -0
- package/dist/parse-error-response-BM9-kjs0.js.map +1 -0
- package/dist/path-segment-C25i6aaH.cjs +29 -0
- package/dist/path-segment-C25i6aaH.cjs.map +1 -0
- package/dist/path-segment-CyqnaFrP.js +24 -0
- package/dist/path-segment-CyqnaFrP.js.map +1 -0
- package/dist/react/index.cjs +336 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +151 -0
- package/dist/react/index.d.cts.map +1 -0
- package/dist/react/index.d.ts +151 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +300 -0
- package/dist/react/index.js.map +1 -0
- package/dist/refresh-dedup-CKaHHe7m.js +14 -0
- package/dist/refresh-dedup-CKaHHe7m.js.map +1 -0
- package/dist/refresh-dedup-CLdACQXR.cjs +19 -0
- package/dist/refresh-dedup-CLdACQXR.cjs.map +1 -0
- package/dist/rolldown-runtime-CmQyd0Gu.cjs +27 -0
- package/dist/routes-DpAzgY1V.cjs +424 -0
- package/dist/routes-DpAzgY1V.cjs.map +1 -0
- package/dist/routes-F1g5aVqP.js +347 -0
- package/dist/routes-F1g5aVqP.js.map +1 -0
- package/dist/s2s/index.cjs +24 -0
- package/dist/s2s/index.d.cts +5 -0
- package/dist/s2s/index.d.ts +5 -0
- package/dist/s2s/index.js +5 -0
- package/dist/s2s-6wbotr78.cjs +926 -0
- package/dist/s2s-6wbotr78.cjs.map +1 -0
- package/dist/s2s-B_0SIQzn.js +897 -0
- package/dist/s2s-B_0SIQzn.js.map +1 -0
- package/dist/sign-C7y6xdvT.cjs +324 -0
- package/dist/sign-C7y6xdvT.cjs.map +1 -0
- package/dist/sign-HFtcYwa-.js +259 -0
- package/dist/sign-HFtcYwa-.js.map +1 -0
- package/dist/tokens-C1nzkn21.cjs +232 -0
- package/dist/tokens-C1nzkn21.cjs.map +1 -0
- package/dist/tokens-C3Z40qd7.js +107 -0
- package/dist/tokens-C3Z40qd7.js.map +1 -0
- package/dist/types-B5sf7kKE.d.cts +52 -0
- package/dist/types-B5sf7kKE.d.cts.map +1 -0
- package/dist/types-CLOIHzRu.d.ts +96 -0
- package/dist/types-CLOIHzRu.d.ts.map +1 -0
- package/dist/types-CQTZdZIF.d.ts +52 -0
- package/dist/types-CQTZdZIF.d.ts.map +1 -0
- package/dist/types-DGeQ8j4k.d.cts +538 -0
- package/dist/types-DGeQ8j4k.d.cts.map +1 -0
- package/dist/types-DIUWza9-.d.ts +538 -0
- package/dist/types-DIUWza9-.d.ts.map +1 -0
- package/dist/types-qh6ANBCV.d.cts +96 -0
- package/dist/types-qh6ANBCV.d.cts.map +1 -0
- package/dist/webhooks/index.cjs +8 -0
- package/dist/webhooks/index.d.cts +5 -0
- package/dist/webhooks/index.d.ts +5 -0
- package/dist/webhooks/index.js +4 -0
- package/dist/webhooks-B5sAWC9W.js +74 -0
- package/dist/webhooks-B5sAWC9W.js.map +1 -0
- package/dist/webhooks-Cx11psg1.cjs +79 -0
- package/dist/webhooks-Cx11psg1.cjs.map +1 -0
- package/package.json +192 -3
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { a as HEADERS } from "./constants-rNuD5qJn.js";
|
|
2
|
+
import { d as SESSION_ROUTES, f as USER_ROUTES, i as DIRECTORIO_ROUTES, r as AUTH_ROUTES } from "./routes-F1g5aVqP.js";
|
|
3
|
+
import { t as apiPath } from "./api-path-B-vSkL2H.js";
|
|
4
|
+
import { c as isEvaAuthConfigError, s as getProduct, t as parseErrorResponse } from "./parse-error-response-BM9-kjs0.js";
|
|
5
|
+
import { r as getAuthServiceUrl } from "./constants-BY7QZokc.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
// src/http-client.ts
|
|
8
|
+
const AuthServiceResponseSchema = z.object({ data: z.unknown() });
|
|
9
|
+
const AUTH_SERVICE_TIMEOUT = 1e4;
|
|
10
|
+
const extractTokens = (headers) => {
|
|
11
|
+
const accessToken = headers.get(HEADERS.NEW_ACCESS_TOKEN);
|
|
12
|
+
const refreshToken = headers.get(HEADERS.NEW_REFRESH_TOKEN);
|
|
13
|
+
if (!accessToken || !refreshToken) return void 0;
|
|
14
|
+
return {
|
|
15
|
+
accessToken,
|
|
16
|
+
refreshToken
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const authFetch = async (path, options = {}) => {
|
|
20
|
+
const { method = "GET", accessToken, refreshToken, body } = options;
|
|
21
|
+
const headers = { "content-type": "application/json" };
|
|
22
|
+
if (accessToken) headers[HEADERS.AUTHORIZATION] = `Bearer ${accessToken}`;
|
|
23
|
+
if (refreshToken) headers[HEADERS.REFRESH_TOKEN] = refreshToken;
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`${getAuthServiceUrl()}${path}`, {
|
|
26
|
+
method,
|
|
27
|
+
headers,
|
|
28
|
+
signal: AbortSignal.timeout(AUTH_SERVICE_TIMEOUT),
|
|
29
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = await res.json().catch(() => null);
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: parseErrorResponse(res.status, body)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const json = await res.json();
|
|
39
|
+
const parsed = AuthServiceResponseSchema.safeParse(json);
|
|
40
|
+
if (!parsed.success) return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: {
|
|
43
|
+
kind: "sdk",
|
|
44
|
+
reason: "malformed",
|
|
45
|
+
message: "Estructura de respuesta inválida",
|
|
46
|
+
status: 502
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const data = parsed.data.data;
|
|
50
|
+
if (Array.isArray(data)) return {
|
|
51
|
+
ok: true,
|
|
52
|
+
data
|
|
53
|
+
};
|
|
54
|
+
if (options.reissuesAccessTokenOnly) {
|
|
55
|
+
const accessToken = res.headers.get(HEADERS.NEW_ACCESS_TOKEN);
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
data: {
|
|
59
|
+
...data,
|
|
60
|
+
...accessToken ? { accessToken } : {}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const tokens = extractTokens(res.headers);
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
data: {
|
|
68
|
+
...data,
|
|
69
|
+
...tokens ? { tokens } : {}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (isEvaAuthConfigError(err)) throw err;
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
error: {
|
|
77
|
+
kind: "sdk",
|
|
78
|
+
reason: "network",
|
|
79
|
+
message: err instanceof Error ? err.message : "Error de red",
|
|
80
|
+
status: 0
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Solicita un código OTP. En éxito devuelve `{ message, challengeId }`:
|
|
87
|
+
* `challengeId` es opaco, el caller debe conservarlo (en memoria) y enviarlo a
|
|
88
|
+
* `login`; cada `getCode` nuevo invalida el `challengeId` anterior.
|
|
89
|
+
* El tipo es un cast: la respuesta del Auth Service no se parsea en runtime.
|
|
90
|
+
*/
|
|
91
|
+
const getCode = (params) => authFetch(apiPath(AUTH_ROUTES.getCode), {
|
|
92
|
+
method: "POST",
|
|
93
|
+
body: { phone: params.phone }
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* Verifica el código OTP. `challengeId` es obligatorio: debe ser el que
|
|
97
|
+
* devolvió el `getCode` que originó el código; sin el desafío vigente el Auth
|
|
98
|
+
* Service responde 401 sin consumir intentos.
|
|
99
|
+
*
|
|
100
|
+
* El `producto` del login sale SIEMPRE de `configureEvaAuth({ product })`, nunca del llamador: el
|
|
101
|
+
* access token queda emitido para ese producto. `empresaId` (opcional) deja esa empresa activa en
|
|
102
|
+
* la sesión si la persona pertenece a ella.
|
|
103
|
+
*
|
|
104
|
+
* @throws Error de configuración (`EvaAuthConfigError`) si falta `configureEvaAuth({ product })`.
|
|
105
|
+
*/
|
|
106
|
+
const login = (params) => {
|
|
107
|
+
const { phone, code, challengeId, empresaId, deviceType, os, browser, userAgent } = params;
|
|
108
|
+
return authFetch(apiPath(AUTH_ROUTES.login), {
|
|
109
|
+
method: "POST",
|
|
110
|
+
body: {
|
|
111
|
+
phone,
|
|
112
|
+
code,
|
|
113
|
+
challengeId,
|
|
114
|
+
producto: getProduct(),
|
|
115
|
+
...empresaId !== void 0 && { empresaId },
|
|
116
|
+
deviceType,
|
|
117
|
+
os,
|
|
118
|
+
browser,
|
|
119
|
+
userAgent
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Cambia la empresa activa de la sesión (`null` la deja sin empresa). Auth reemite SOLO el access
|
|
125
|
+
* token (`x-eva-new-access-token`; el refresh no rota): llega en `accessToken`. Auth valida la
|
|
126
|
+
* pertenencia; si la persona no pertenece, responde error.
|
|
127
|
+
*/
|
|
128
|
+
const setActiveEmpresa = (params) => authFetch(apiPath(AUTH_ROUTES.setActiveEmpresa), {
|
|
129
|
+
method: "POST",
|
|
130
|
+
accessToken: params.accessToken,
|
|
131
|
+
body: { empresaId: params.empresaId },
|
|
132
|
+
reissuesAccessTokenOnly: true
|
|
133
|
+
});
|
|
134
|
+
const refresh = (params) => authFetch(apiPath(AUTH_ROUTES.refresh), {
|
|
135
|
+
method: "POST",
|
|
136
|
+
refreshToken: params.refreshToken
|
|
137
|
+
});
|
|
138
|
+
const logout = (params) => authFetch(apiPath(AUTH_ROUTES.logout), {
|
|
139
|
+
method: "POST",
|
|
140
|
+
refreshToken: params.refreshToken
|
|
141
|
+
});
|
|
142
|
+
const getUser = (params) => authFetch(apiPath(USER_ROUTES.get), { accessToken: params.accessToken });
|
|
143
|
+
const updateUser = (params) => authFetch(apiPath(USER_ROUTES.update), {
|
|
144
|
+
method: "PATCH",
|
|
145
|
+
accessToken: params.accessToken,
|
|
146
|
+
body: params.data
|
|
147
|
+
});
|
|
148
|
+
const deleteUser = (params) => authFetch(apiPath(USER_ROUTES.deactivate), {
|
|
149
|
+
method: "DELETE",
|
|
150
|
+
accessToken: params.accessToken
|
|
151
|
+
});
|
|
152
|
+
const getUserEmpresas = (params) => authFetch(apiPath(USER_ROUTES.listCompanies), { accessToken: params.accessToken });
|
|
153
|
+
/**
|
|
154
|
+
* Busca en el directorio de empresas (solo las activas que optaron por aparecer; solo campos de
|
|
155
|
+
* perfil público; 50 por página). Basta el access token de usuario de CUALQUIER producto habilitado:
|
|
156
|
+
* Auth limita los pedidos por usuario. La siguiente página se pide con el `nextCursor` recibido.
|
|
157
|
+
*/
|
|
158
|
+
const getDirectorio = (params) => {
|
|
159
|
+
const { accessToken, ...filters } = params;
|
|
160
|
+
const query = new URLSearchParams();
|
|
161
|
+
for (const [key, value] of Object.entries(filters)) if (value !== void 0) query.set(key, String(value));
|
|
162
|
+
const qs = query.toString();
|
|
163
|
+
return authFetch(`${apiPath(DIRECTORIO_ROUTES.search)}${qs === "" ? "" : `?${qs}`}`, { accessToken });
|
|
164
|
+
};
|
|
165
|
+
const getSessions = (params) => authFetch(apiPath(SESSION_ROUTES.list), { accessToken: params.accessToken });
|
|
166
|
+
const deleteSession = (params) => {
|
|
167
|
+
if (!/^[\w-]+$/.test(params.sessionId)) return Promise.resolve({
|
|
168
|
+
ok: false,
|
|
169
|
+
error: {
|
|
170
|
+
kind: "sdk",
|
|
171
|
+
reason: "malformed",
|
|
172
|
+
message: "ID de sesión inválido",
|
|
173
|
+
status: 400
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return authFetch(apiPath(SESSION_ROUTES.deleteOne, { id: params.sessionId }), {
|
|
177
|
+
method: "DELETE",
|
|
178
|
+
accessToken: params.accessToken
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
const deleteAllSessions = (params) => authFetch(apiPath(SESSION_ROUTES.deleteAll), {
|
|
182
|
+
method: "DELETE",
|
|
183
|
+
refreshToken: params.refreshToken
|
|
184
|
+
});
|
|
185
|
+
const health = () => authFetch("/health");
|
|
186
|
+
function createHttpClient() {
|
|
187
|
+
return {
|
|
188
|
+
getCode,
|
|
189
|
+
login,
|
|
190
|
+
setActiveEmpresa,
|
|
191
|
+
refresh,
|
|
192
|
+
logout,
|
|
193
|
+
getUser,
|
|
194
|
+
updateUser,
|
|
195
|
+
deleteUser,
|
|
196
|
+
getUserEmpresas,
|
|
197
|
+
getDirectorio,
|
|
198
|
+
getSessions,
|
|
199
|
+
deleteSession,
|
|
200
|
+
deleteAllSessions,
|
|
201
|
+
health
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
export { createHttpClient as t };
|
|
205
|
+
|
|
206
|
+
//# sourceMappingURL=http-client-Da0-OMfc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client-Da0-OMfc.js","names":[],"sources":["../src/http-client.ts"],"sourcesContent":["import { z } from 'zod'\nimport {\n AUTH_ROUTES,\n DIRECTORIO_ROUTES,\n SESSION_ROUTES,\n USER_ROUTES,\n} from '@eva_solutions/auth-contracts'\nimport type {\n DirectorioQuery,\n DirectorioResponse,\n} from '@eva_solutions/auth-contracts'\nimport { apiPath } from './api-path'\nimport { getProduct, isEvaAuthConfigError } from './config'\nimport { HEADERS, getAuthServiceUrl } from './constants'\nimport { parseErrorResponse } from './parse-error-response'\nimport type {\n EvaUser,\n EvaSession,\n EvaEmpresa,\n Result,\n TokenPair,\n DeviceInfo,\n} from './types'\n\nconst AuthServiceResponseSchema = z.object({\n data: z.unknown(),\n})\n\nconst AUTH_SERVICE_TIMEOUT = 10_000\n\ntype FetchOptions = {\n method?: string\n accessToken?: string\n refreshToken?: string\n body?: unknown\n /** La respuesta reemite solo el access token (`x-eva-new-access-token`); el refresh no rota. */\n reissuesAccessTokenOnly?: boolean\n}\n\nconst extractTokens = (headers: Headers): TokenPair | undefined => {\n const accessToken = headers.get(HEADERS.NEW_ACCESS_TOKEN)\n const refreshToken = headers.get(HEADERS.NEW_REFRESH_TOKEN)\n if (!accessToken || !refreshToken) return undefined\n return { accessToken, refreshToken }\n}\n\nconst authFetch = async <T>(\n path: string,\n options: FetchOptions = {},\n): Promise<Result<T & { tokens?: TokenPair }>> => {\n const { method = 'GET', accessToken, refreshToken, body } = options\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n }\n\n if (accessToken) headers[HEADERS.AUTHORIZATION] = `Bearer ${accessToken}`\n if (refreshToken) headers[HEADERS.REFRESH_TOKEN] = refreshToken\n\n try {\n const res = await fetch(`${getAuthServiceUrl()}${path}`, {\n method,\n headers,\n signal: AbortSignal.timeout(AUTH_SERVICE_TIMEOUT),\n ...(body ? { body: JSON.stringify(body) } : {}),\n })\n\n if (!res.ok) {\n const body = await res.json().catch(() => null)\n return { ok: false, error: parseErrorResponse(res.status, body) }\n }\n\n const json: unknown = await res.json()\n const parsed = AuthServiceResponseSchema.safeParse(json)\n if (!parsed.success) {\n return {\n ok: false,\n error: {\n kind: 'sdk',\n reason: 'malformed',\n message: 'Estructura de respuesta inválida',\n status: 502,\n },\n }\n }\n const data = parsed.data.data as T\n // Un array (`/sessions`, `/user/empresas`) se devuelve intacto: esparcirlo en un objeto lo\n // convertiria en `{ \"0\": …, \"1\": … }`. Esos endpoints se piden solo con access token, asi que\n // no hay tokens nuevos que adjuntar.\n if (Array.isArray(data))\n return { ok: true, data: data as T & { tokens?: TokenPair } }\n\n if (options.reissuesAccessTokenOnly) {\n const accessToken = res.headers.get(HEADERS.NEW_ACCESS_TOKEN)\n return {\n ok: true,\n data: { ...data, ...(accessToken ? { accessToken } : {}) },\n }\n }\n\n const tokens = extractTokens(res.headers)\n\n return { ok: true, data: { ...data, ...(tokens ? { tokens } : {}) } }\n } catch (err) {\n // Falta de configuración (authUrl): error del consumidor, no una falla de red.\n if (isEvaAuthConfigError(err)) throw err\n const message = err instanceof Error ? err.message : 'Error de red'\n return {\n ok: false,\n error: { kind: 'sdk', reason: 'network', message, status: 0 },\n }\n }\n}\n\n/**\n * Solicita un código OTP. En éxito devuelve `{ message, challengeId }`:\n * `challengeId` es opaco, el caller debe conservarlo (en memoria) y enviarlo a\n * `login`; cada `getCode` nuevo invalida el `challengeId` anterior.\n * El tipo es un cast: la respuesta del Auth Service no se parsea en runtime.\n */\nconst getCode = (params: {\n phone: string\n}): Promise<Result<{ message: string; challengeId: string }>> =>\n authFetch(apiPath(AUTH_ROUTES.getCode), {\n method: 'POST',\n body: { phone: params.phone },\n })\n\n/**\n * Verifica el código OTP. `challengeId` es obligatorio: debe ser el que\n * devolvió el `getCode` que originó el código; sin el desafío vigente el Auth\n * Service responde 401 sin consumir intentos.\n *\n * El `producto` del login sale SIEMPRE de `configureEvaAuth({ product })`, nunca del llamador: el\n * access token queda emitido para ese producto. `empresaId` (opcional) deja esa empresa activa en\n * la sesión si la persona pertenece a ella.\n *\n * @throws Error de configuración (`EvaAuthConfigError`) si falta `configureEvaAuth({ product })`.\n */\nconst login = (\n params: {\n phone: string\n code: string\n challengeId: string\n empresaId?: string\n } & DeviceInfo,\n): Promise<Result<{ user: { id: string }; tokens?: TokenPair }>> => {\n const {\n phone,\n code,\n challengeId,\n empresaId,\n deviceType,\n os,\n browser,\n userAgent,\n } = params\n return authFetch(apiPath(AUTH_ROUTES.login), {\n method: 'POST',\n body: {\n phone,\n code,\n challengeId,\n producto: getProduct(),\n ...(empresaId !== undefined && { empresaId }),\n deviceType,\n os,\n browser,\n userAgent,\n },\n })\n}\n\n/**\n * Cambia la empresa activa de la sesión (`null` la deja sin empresa). Auth reemite SOLO el access\n * token (`x-eva-new-access-token`; el refresh no rota): llega en `accessToken`. Auth valida la\n * pertenencia; si la persona no pertenece, responde error.\n */\nconst setActiveEmpresa = (params: {\n accessToken: string\n empresaId: string | null\n}): Promise<Result<{ empresaId: string | null; accessToken?: string }>> =>\n authFetch(apiPath(AUTH_ROUTES.setActiveEmpresa), {\n method: 'POST',\n accessToken: params.accessToken,\n body: { empresaId: params.empresaId },\n reissuesAccessTokenOnly: true,\n })\n\nconst refresh = (params: {\n refreshToken: string\n}): Promise<Result<{ user: { id: string }; tokens?: TokenPair }>> =>\n authFetch(apiPath(AUTH_ROUTES.refresh), {\n method: 'POST',\n refreshToken: params.refreshToken,\n })\n\nconst logout = (params: {\n refreshToken: string\n}): Promise<Result<{ message: string }>> =>\n authFetch(apiPath(AUTH_ROUTES.logout), {\n method: 'POST',\n refreshToken: params.refreshToken,\n })\n\nconst getUser = (params: { accessToken: string }): Promise<Result<EvaUser>> =>\n authFetch(apiPath(USER_ROUTES.get), { accessToken: params.accessToken })\n\nconst updateUser = (params: {\n accessToken: string\n data: Record<string, unknown>\n}): Promise<Result<EvaUser>> =>\n authFetch(apiPath(USER_ROUTES.update), {\n method: 'PATCH',\n accessToken: params.accessToken,\n body: params.data,\n })\n\nconst deleteUser = (params: {\n accessToken: string\n}): Promise<Result<{ message: string }>> =>\n authFetch(apiPath(USER_ROUTES.deactivate), {\n method: 'DELETE',\n accessToken: params.accessToken,\n })\n\nconst getUserEmpresas = (params: {\n accessToken: string\n}): Promise<Result<EvaEmpresa[]>> =>\n authFetch(apiPath(USER_ROUTES.listCompanies), {\n accessToken: params.accessToken,\n })\n\n/**\n * Busca en el directorio de empresas (solo las activas que optaron por aparecer; solo campos de\n * perfil público; 50 por página). Basta el access token de usuario de CUALQUIER producto habilitado:\n * Auth limita los pedidos por usuario. La siguiente página se pide con el `nextCursor` recibido.\n */\nconst getDirectorio = (\n params: { accessToken: string } & DirectorioQuery,\n): Promise<Result<DirectorioResponse['data']>> => {\n const { accessToken, ...filters } = params\n const query = new URLSearchParams()\n for (const [key, value] of Object.entries(filters)) {\n if (value !== undefined) query.set(key, String(value))\n }\n const qs = query.toString()\n return authFetch(\n `${apiPath(DIRECTORIO_ROUTES.search)}${qs === '' ? '' : `?${qs}`}`,\n { accessToken },\n )\n}\n\nconst getSessions = (params: {\n accessToken: string\n}): Promise<Result<EvaSession[]>> =>\n authFetch(apiPath(SESSION_ROUTES.list), { accessToken: params.accessToken })\n\nconst deleteSession = (params: {\n accessToken: string\n sessionId: string\n}): Promise<Result<{ message: string }>> => {\n if (!/^[\\w-]+$/.test(params.sessionId)) {\n return Promise.resolve({\n ok: false,\n error: {\n kind: 'sdk',\n reason: 'malformed',\n message: 'ID de sesión inválido',\n status: 400,\n },\n })\n }\n return authFetch(\n apiPath(SESSION_ROUTES.deleteOne, { id: params.sessionId }),\n {\n method: 'DELETE',\n accessToken: params.accessToken,\n },\n )\n}\n\nconst deleteAllSessions = (params: {\n refreshToken: string\n}): Promise<Result<{ message: string; count: number }>> =>\n authFetch(apiPath(SESSION_ROUTES.deleteAll), {\n method: 'DELETE',\n refreshToken: params.refreshToken,\n })\n\n// Every other route is built with `apiPath` (`API_PREFIX + path`); health is an operational probe\n// that the API serves at the root, outside the versioned prefix.\nconst health = (): Promise<Result<{ status: string }>> => authFetch('/health')\n\nexport function createHttpClient() {\n return {\n getCode,\n login,\n setActiveEmpresa,\n refresh,\n logout,\n getUser,\n updateUser,\n deleteUser,\n getUserEmpresas,\n getDirectorio,\n getSessions,\n deleteSession,\n deleteAllSessions,\n health,\n }\n}\n\nexport type EvaHttpClient = ReturnType<typeof createHttpClient>\n"],"mappings":";;;;;;;AAwBA,MAAM,4BAA4B,EAAE,OAAO,EACzC,MAAM,EAAE,QAAQ,EAClB,CAAC;AAED,MAAM,uBAAuB;AAW7B,MAAM,iBAAiB,YAA4C;CACjE,MAAM,cAAc,QAAQ,IAAI,QAAQ,gBAAgB;CACxD,MAAM,eAAe,QAAQ,IAAI,QAAQ,iBAAiB;CAC1D,IAAI,CAAC,eAAe,CAAC,cAAc,OAAO,KAAA;CAC1C,OAAO;EAAE;EAAa;CAAa;AACrC;AAEA,MAAM,YAAY,OAChB,MACA,UAAwB,CAAC,MACuB;CAChD,MAAM,EAAE,SAAS,OAAO,aAAa,cAAc,SAAS;CAE5D,MAAM,UAAkC,EACtC,gBAAgB,mBAClB;CAEA,IAAI,aAAa,QAAQ,QAAQ,iBAAiB,UAAU;CAC5D,IAAI,cAAc,QAAQ,QAAQ,iBAAiB;CAEnD,IAAI;EACF,MAAM,MAAM,MAAM,MAAM,GAAG,kBAAkB,IAAI,QAAQ;GACvD;GACA;GACA,QAAQ,YAAY,QAAQ,oBAAoB;GAChD,GAAI,OAAO,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;EAC/C,CAAC;EAED,IAAI,CAAC,IAAI,IAAI;GACX,MAAM,OAAO,MAAM,IAAI,KAAK,CAAC,CAAC,YAAY,IAAI;GAC9C,OAAO;IAAE,IAAI;IAAO,OAAO,mBAAmB,IAAI,QAAQ,IAAI;GAAE;EAClE;EAEA,MAAM,OAAgB,MAAM,IAAI,KAAK;EACrC,MAAM,SAAS,0BAA0B,UAAU,IAAI;EACvD,IAAI,CAAC,OAAO,SACV,OAAO;GACL,IAAI;GACJ,OAAO;IACL,MAAM;IACN,QAAQ;IACR,SAAS;IACT,QAAQ;GACV;EACF;EAEF,MAAM,OAAO,OAAO,KAAK;EAIzB,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO;GAAE,IAAI;GAAY;EAAmC;EAE9D,IAAI,QAAQ,yBAAyB;GACnC,MAAM,cAAc,IAAI,QAAQ,IAAI,QAAQ,gBAAgB;GAC5D,OAAO;IACL,IAAI;IACJ,MAAM;KAAE,GAAG;KAAM,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;IAAG;GAC3D;EACF;EAEA,MAAM,SAAS,cAAc,IAAI,OAAO;EAExC,OAAO;GAAE,IAAI;GAAM,MAAM;IAAE,GAAG;IAAM,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAAG;EAAE;CACtE,SAAS,KAAK;EAEZ,IAAI,qBAAqB,GAAG,GAAG,MAAM;EAErC,OAAO;GACL,IAAI;GACJ,OAAO;IAAE,MAAM;IAAO,QAAQ;IAAW,SAH3B,eAAe,QAAQ,IAAI,UAAU;IAGD,QAAQ;GAAE;EAC9D;CACF;AACF;;;;;;;AAQA,MAAM,WAAW,WAGf,UAAU,QAAQ,YAAY,OAAO,GAAG;CACtC,QAAQ;CACR,MAAM,EAAE,OAAO,OAAO,MAAM;AAC9B,CAAC;;;;;;;;;;;;AAaH,MAAM,SACJ,WAMkE;CAClE,MAAM,EACJ,OACA,MACA,aACA,WACA,YACA,IACA,SACA,cACE;CACJ,OAAO,UAAU,QAAQ,YAAY,KAAK,GAAG;EAC3C,QAAQ;EACR,MAAM;GACJ;GACA;GACA;GACA,UAAU,WAAW;GACrB,GAAI,cAAc,KAAA,KAAa,EAAE,UAAU;GAC3C;GACA;GACA;GACA;EACF;CACF,CAAC;AACH;;;;;;AAOA,MAAM,oBAAoB,WAIxB,UAAU,QAAQ,YAAY,gBAAgB,GAAG;CAC/C,QAAQ;CACR,aAAa,OAAO;CACpB,MAAM,EAAE,WAAW,OAAO,UAAU;CACpC,yBAAyB;AAC3B,CAAC;AAEH,MAAM,WAAW,WAGf,UAAU,QAAQ,YAAY,OAAO,GAAG;CACtC,QAAQ;CACR,cAAc,OAAO;AACvB,CAAC;AAEH,MAAM,UAAU,WAGd,UAAU,QAAQ,YAAY,MAAM,GAAG;CACrC,QAAQ;CACR,cAAc,OAAO;AACvB,CAAC;AAEH,MAAM,WAAW,WACf,UAAU,QAAQ,YAAY,GAAG,GAAG,EAAE,aAAa,OAAO,YAAY,CAAC;AAEzE,MAAM,cAAc,WAIlB,UAAU,QAAQ,YAAY,MAAM,GAAG;CACrC,QAAQ;CACR,aAAa,OAAO;CACpB,MAAM,OAAO;AACf,CAAC;AAEH,MAAM,cAAc,WAGlB,UAAU,QAAQ,YAAY,UAAU,GAAG;CACzC,QAAQ;CACR,aAAa,OAAO;AACtB,CAAC;AAEH,MAAM,mBAAmB,WAGvB,UAAU,QAAQ,YAAY,aAAa,GAAG,EAC5C,aAAa,OAAO,YACtB,CAAC;;;;;;AAOH,MAAM,iBACJ,WACgD;CAChD,MAAM,EAAE,aAAa,GAAG,YAAY;CACpC,MAAM,QAAQ,IAAI,gBAAgB;CAClC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,KAAK,OAAO,KAAK,CAAC;CAEvD,MAAM,KAAK,MAAM,SAAS;CAC1B,OAAO,UACL,GAAG,QAAQ,kBAAkB,MAAM,IAAI,OAAO,KAAK,KAAK,IAAI,QAC5D,EAAE,YAAY,CAChB;AACF;AAEA,MAAM,eAAe,WAGnB,UAAU,QAAQ,eAAe,IAAI,GAAG,EAAE,aAAa,OAAO,YAAY,CAAC;AAE7E,MAAM,iBAAiB,WAGqB;CAC1C,IAAI,CAAC,WAAW,KAAK,OAAO,SAAS,GACnC,OAAO,QAAQ,QAAQ;EACrB,IAAI;EACJ,OAAO;GACL,MAAM;GACN,QAAQ;GACR,SAAS;GACT,QAAQ;EACV;CACF,CAAC;CAEH,OAAO,UACL,QAAQ,eAAe,WAAW,EAAE,IAAI,OAAO,UAAU,CAAC,GAC1D;EACE,QAAQ;EACR,aAAa,OAAO;CACtB,CACF;AACF;AAEA,MAAM,qBAAqB,WAGzB,UAAU,QAAQ,eAAe,SAAS,GAAG;CAC3C,QAAQ;CACR,cAAc,OAAO;AACvB,CAAC;AAIH,MAAM,eAAoD,UAAU,SAAS;AAE7E,SAAgB,mBAAmB;CACjC,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { d as Result } from "./types-CLOIHzRu.js";
|
|
2
|
+
import { A as RenameGrupoInput, B as SearchDirectorioInput, C as ListAutorizacionesInput, D as ListWebhookDeliveriesQuery, E as ListEmpresasResult, F as S2SGetUserResult, G as UpdateEmpresaMemberInput, H as SubjectTokenInput, K as UpdateSucursalInput, L as S2SVerifyOptions, M as S2SBatchUsersResult, N as S2SCanonicalParts, O as RemoveEmpresaMemberInput, P as S2SClientConfig, S as GrupoResult, T as ListEmpresasInput, U as SucursalResult, V as SetContratacionInput, W as UpdateEmpresaInput, _ as EmpresaEstadoResult, a as CheckAutorizacionResult, b as EmpresaResult, c as CreateAutorizacionInput, d as CreateSucursalInput, f as CreateWebhookSubscriptionInput, h as DirectorioResult, i as CheckAutorizacionInput, j as S2SBatchUsersInput, k as RemoveGrupoEmpresaInput, l as CreateEmpresaInput, m as DeleteAutorizacionInput, n as AddGrupoEmpresaInput, o as ContratacionResult, p as DeactivateSucursalInput, q as UpdateWebhookSubscriptionInput, r as AutorizacionResult, s as ContratadoResult, t as AddEmpresaMemberInput, u as CreateGrupoInput, v as EmpresaMemberResult, w as ListAutorizacionesResult, x as GetEmpresaResult, z as S2SVerifyResult } from "./types-DIUWza9-.js";
|
|
3
|
+
import { a as WebhookSubscriptionWithSecret, i as WebhookSubscription, n as WebhookDelivery, t as RotateWebhookSecretResult } from "./types-CQTZdZIF.js";
|
|
4
|
+
import "./index-Dold-Ycb.js";
|
|
5
|
+
/**
|
|
6
|
+
* Tipo del cliente S2S retornado por `createS2SClient`.
|
|
7
|
+
*/
|
|
8
|
+
type S2SClient = {
|
|
9
|
+
getUser(userId: string): Promise<Result<S2SGetUserResult>>;
|
|
10
|
+
batchUsers(input: S2SBatchUsersInput): Promise<Result<S2SBatchUsersResult>>;
|
|
11
|
+
createWebhookSubscription(input: CreateWebhookSubscriptionInput): Promise<Result<WebhookSubscriptionWithSecret>>;
|
|
12
|
+
listWebhookSubscriptions(): Promise<Result<{
|
|
13
|
+
subscriptions: WebhookSubscription[];
|
|
14
|
+
}>>;
|
|
15
|
+
getWebhookSubscription(id: string): Promise<Result<WebhookSubscription>>;
|
|
16
|
+
updateWebhookSubscription(id: string, input: UpdateWebhookSubscriptionInput): Promise<Result<WebhookSubscription>>;
|
|
17
|
+
deleteWebhookSubscription(id: string): Promise<Result<void>>;
|
|
18
|
+
rotateWebhookSubscriptionSecret(id: string): Promise<Result<RotateWebhookSecretResult>>;
|
|
19
|
+
listWebhookDeliveries(query?: ListWebhookDeliveriesQuery): Promise<Result<{
|
|
20
|
+
deliveries: WebhookDelivery[];
|
|
21
|
+
}>>;
|
|
22
|
+
/**
|
|
23
|
+
* Crea una empresa (`empresas:escribir`, el actor necesita `producto`). La persona del
|
|
24
|
+
* `subjectToken` queda como titular y el producto del actor en `prueba`. 409 si el RUC, la razón
|
|
25
|
+
* social o el slug ya existen; 429 si la persona superó el límite de altas.
|
|
26
|
+
*/
|
|
27
|
+
createEmpresa(input: CreateEmpresaInput): Promise<Result<EmpresaResult>>;
|
|
28
|
+
/** Edita datos maestros, perfil y visibilidad en el directorio (administrador o titular; `empresas:escribir`). */
|
|
29
|
+
updateEmpresa(empresaId: string, input: UpdateEmpresaInput): Promise<Result<EmpresaResult>>;
|
|
30
|
+
/** Empresas que contrataron el producto del actor, paginadas con `cursor` (`empresas:leer`). */
|
|
31
|
+
listEmpresas(query?: ListEmpresasInput): Promise<Result<ListEmpresasResult>>;
|
|
32
|
+
/**
|
|
33
|
+
* Una empresa (`empresas:leer`): vista completa (`vista: 'completa'`) si el actor tiene producto y
|
|
34
|
+
* la empresa lo contrató; recortada (`vista: 'recortada'`, sin contacto ni miembros) si el actor
|
|
35
|
+
* no tiene producto (servicio compartido). 404 si no existe o no la contrató.
|
|
36
|
+
*/
|
|
37
|
+
getEmpresa(empresaId: string): Promise<Result<GetEmpresaResult>>;
|
|
38
|
+
/** Da de baja una empresa (solo facturación, permiso `empresas:baja`; no lleva persona). */
|
|
39
|
+
bajaEmpresa(empresaId: string): Promise<Result<EmpresaEstadoResult>>;
|
|
40
|
+
/** Reactiva una empresa de baja (solo facturación, permiso `empresas:baja`). */
|
|
41
|
+
reactivarEmpresa(empresaId: string): Promise<Result<EmpresaEstadoResult>>;
|
|
42
|
+
/** Agrega un miembro como `miembro`; si ya lo es, 409 `conflict` (administrador o titular; `empresas:escribir`). */
|
|
43
|
+
addEmpresaMember(empresaId: string, input: AddEmpresaMemberInput): Promise<Result<EmpresaMemberResult>>;
|
|
44
|
+
/** Cambia el rol de un miembro (solo titular; `empresas:escribir`). */
|
|
45
|
+
setEmpresaMemberRole(empresaId: string, userId: string, input: UpdateEmpresaMemberInput): Promise<Result<EmpresaMemberResult>>;
|
|
46
|
+
/**
|
|
47
|
+
* Quita un miembro (`empresas:escribir`); si no lo es, 404 `not_found`. Con `userId` igual a la
|
|
48
|
+
* persona del token es salir por cuenta propia (cualquier rol, salvo el último titular).
|
|
49
|
+
*/
|
|
50
|
+
removeEmpresaMember(empresaId: string, userId: string, input: RemoveEmpresaMemberInput): Promise<Result<void>>;
|
|
51
|
+
/** Crea una sucursal de la empresa (administrador o titular; `empresas:escribir`). */
|
|
52
|
+
createSucursal(empresaId: string, input: CreateSucursalInput): Promise<Result<SucursalResult>>;
|
|
53
|
+
/** Edita una sucursal (administrador o titular; `empresas:escribir`). */
|
|
54
|
+
updateSucursal(empresaId: string, sucursalId: string, input: UpdateSucursalInput): Promise<Result<SucursalResult>>;
|
|
55
|
+
/** Baja lógica de una sucursal, `activa = false` (administrador o titular; `empresas:escribir`). */
|
|
56
|
+
deactivateSucursal(empresaId: string, sucursalId: string, input: DeactivateSucursalInput): Promise<Result<void>>;
|
|
57
|
+
/**
|
|
58
|
+
* Pide la contratación del PROPIO producto del actor para una empresa existente
|
|
59
|
+
* (`pendiente_pago`; `contrataciones:pedir`). La persona del `subjectToken` debe ser titular:
|
|
60
|
+
* 404 igual si la empresa no existe o no es titular. Solo si no hay fila o está `cancelado`.
|
|
61
|
+
*/
|
|
62
|
+
requestContratacion(empresaId: string, input: SubjectTokenInput): Promise<Result<ContratacionResult>>;
|
|
63
|
+
/** Cancela la contratación del propio producto (`cancelado`, desde cualquier estado; solo titular). */
|
|
64
|
+
cancelContratacion(empresaId: string, input: SubjectTokenInput): Promise<Result<void>>;
|
|
65
|
+
/**
|
|
66
|
+
* ¿La empresa tiene contratado ese producto? (`contrataciones:leer`). Para servicios compartidos
|
|
67
|
+
* que atienden llamadas de servicio donde no hay token delegado.
|
|
68
|
+
*/
|
|
69
|
+
getContratacion(empresaId: string, producto: string): Promise<Result<ContratadoResult>>;
|
|
70
|
+
/** Facturación: escribe el estado y el vencimiento de cualquier fila (`contrataciones:escribir`, actor sin producto). */
|
|
71
|
+
setContratacion(empresaId: string, producto: string, input: SetContratacionInput): Promise<Result<ContratacionResult>>;
|
|
72
|
+
/** Crea un grupo con una empresa de la persona. */
|
|
73
|
+
createGrupo(input: CreateGrupoInput): Promise<Result<GrupoResult>>;
|
|
74
|
+
/** Renombra el grupo (titular de alguna de sus empresas). */
|
|
75
|
+
renameGrupo(grupoId: string, input: RenameGrupoInput): Promise<Result<GrupoResult>>;
|
|
76
|
+
/** Agrega una empresa de la que la persona es titular. */
|
|
77
|
+
addGrupoEmpresa(grupoId: string, input: AddGrupoEmpresaInput): Promise<Result<GrupoResult>>;
|
|
78
|
+
/** Quita una empresa del grupo; un grupo que queda vacío se borra solo. */
|
|
79
|
+
removeGrupoEmpresa(grupoId: string, empresaId: string, input: RemoveGrupoEmpresaInput): Promise<Result<void>>;
|
|
80
|
+
/** Busca en el directorio de empresas visibles (`directorio:leer`). Solo campos de perfil público; 50 por página. */
|
|
81
|
+
searchDirectorio(query?: SearchDirectorioInput): Promise<Result<DirectorioResult>>;
|
|
82
|
+
/** Concede un permiso del catálogo entre empresas a otra empresa (administrador o titular de la otorgante; `autorizaciones:escribir`). 409 si ya está vigente. */
|
|
83
|
+
createAutorizacion(otorganteId: string, input: CreateAutorizacionInput): Promise<Result<AutorizacionResult>>;
|
|
84
|
+
/** Revoca (otorgante) o renuncia a (beneficiaria) una autorización; administrador o titular (`autorizaciones:escribir`). */
|
|
85
|
+
deleteAutorizacion(empresaId: string, autorizacionId: string, input: DeleteAutorizacionInput): Promise<Result<void>>;
|
|
86
|
+
/** Autorizaciones vigentes de la empresa, `otorgadas` o `recibidas` (administrador o titular; `autorizaciones:leer`). La persona viaja en el header `x-eva-subject-token`. */
|
|
87
|
+
listAutorizaciones(empresaId: string, input: ListAutorizacionesInput): Promise<Result<ListAutorizacionesResult>>;
|
|
88
|
+
/**
|
|
89
|
+
* ¿La otorgante autorizó ese permiso a la beneficiaria? (`autorizaciones:leer`, solo actores cuya
|
|
90
|
+
* audiencia es dueña del permiso; puede tener además `producto`). Vale solo si las dos empresas están activas.
|
|
91
|
+
*
|
|
92
|
+
* Guarda el resultado 30 s (`AUTORIZACION_CACHE_TTL_MS`) por cliente: una revocación o una baja
|
|
93
|
+
* tardan como máximo eso en notarse. Los errores no se guardan.
|
|
94
|
+
*/
|
|
95
|
+
checkAutorizacion(input: CheckAutorizacionInput): Promise<Result<CheckAutorizacionResult>>;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Crea un cliente S2S autenticado para comunicarse con la API interna del Auth Service.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const tokenProvider = createServiceTokenProvider({
|
|
103
|
+
* authUrl: 'https://auth.miapp.com',
|
|
104
|
+
* clientId: 'mi-servicio',
|
|
105
|
+
* secretHex: env.AUTH_CLIENT_SECRET_HEX,
|
|
106
|
+
* audience: 'auth-api',
|
|
107
|
+
* })
|
|
108
|
+
* const s2s = createS2SClient({ baseUrl: 'https://auth.miapp.com', tokenProvider })
|
|
109
|
+
* const result = await s2s.getUser(userId)
|
|
110
|
+
* if (!result.ok) return handleError(result.error)
|
|
111
|
+
* return result.data // perfil del usuario
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
declare function createS2SClient(config: S2SClientConfig): S2SClient;
|
|
115
|
+
/**
|
|
116
|
+
* SHA-256 hex de string vacío (precomputado).
|
|
117
|
+
* Constante usada cuando el body es vacío — evita hash innecesario.
|
|
118
|
+
* Exportado para uso externo (REQ-S2S-02 LOCKED).
|
|
119
|
+
*/
|
|
120
|
+
declare const EMPTY_BODY_SHA256_HEX = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
121
|
+
/**
|
|
122
|
+
* Genera un nonce S2S: 16 bytes de `crypto.getRandomValues` como 32 hex en minúsculas.
|
|
123
|
+
* Es el valor del header `x-eva-nonce`; cada request debe llevar uno nuevo.
|
|
124
|
+
*/
|
|
125
|
+
declare function generateS2SNonce(): string;
|
|
126
|
+
/**
|
|
127
|
+
* Construye el canonical string de 7 líneas (SigV4-style).
|
|
128
|
+
*
|
|
129
|
+
* Formato EXACTO:
|
|
130
|
+
* {METHOD}\n{PATH}\n{CANONICAL_QUERY}\n{TIMESTAMP}\n{NONCE}\n{CLIENT_ID}\n{BODY_HASH}
|
|
131
|
+
*
|
|
132
|
+
* @param parts - Componentes del request a firmar.
|
|
133
|
+
*/
|
|
134
|
+
declare function buildS2SCanonicalString(parts: S2SCanonicalParts): Promise<string>;
|
|
135
|
+
/**
|
|
136
|
+
* Genera la firma HMAC-SHA-256 del canonical string.
|
|
137
|
+
*
|
|
138
|
+
* @returns String con formato `"sha256=${hexLowercase}"`.
|
|
139
|
+
* @throws Si `secretHex` tiene formato inválido (longitud impar o chars no-hex).
|
|
140
|
+
* @throws Si `parts.nonce` no es un nonce canónico (32 hex en minúsculas).
|
|
141
|
+
*/
|
|
142
|
+
declare function signS2SRequest({ parts, secretHex }: {
|
|
143
|
+
parts: S2SCanonicalParts;
|
|
144
|
+
secretHex: string;
|
|
145
|
+
}): Promise<string>;
|
|
146
|
+
/**
|
|
147
|
+
* Genera los 4 headers de autenticación S2S para un request: client id, timestamp (segundos),
|
|
148
|
+
* nonce y firma. Es el único punto que genera timestamp + nonce y firma; `createS2SClient` lo
|
|
149
|
+
* usa y los firmantes manuales deben usarlo en vez de armar los headers a mano.
|
|
150
|
+
*
|
|
151
|
+
* El nonce es nuevo en cada llamada: no reutilices los headers devueltos en otro request.
|
|
152
|
+
*
|
|
153
|
+
* @param rawQuery - Query string crudo sin `?`. Default `''`.
|
|
154
|
+
* @param bodyBytes - Bytes EXACTOS que se enviarán como body. Default: vacío.
|
|
155
|
+
* @throws Si `secretHex` tiene formato inválido.
|
|
156
|
+
*/
|
|
157
|
+
declare function signS2SHeaders({ method, path, rawQuery, bodyBytes, clientId, secretHex }: {
|
|
158
|
+
method: string;
|
|
159
|
+
path: string;
|
|
160
|
+
rawQuery?: string;
|
|
161
|
+
bodyBytes?: Uint8Array;
|
|
162
|
+
clientId: string;
|
|
163
|
+
secretHex: string;
|
|
164
|
+
}): Promise<Record<string, string>>;
|
|
165
|
+
/**
|
|
166
|
+
* Constantes del módulo S2S (service-to-service).
|
|
167
|
+
*
|
|
168
|
+
* REQ-S2S-05 LOCKED: la ventana de tiempo para el timestamp S2S es de 120 segundos.
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* Ventana máxima (en segundos) para aceptar un request S2S.
|
|
172
|
+
* Si |serverTime - timestamp| > S2S_TIMESTAMP_WINDOW_SECONDS → firma rechazada.
|
|
173
|
+
*/
|
|
174
|
+
declare const S2S_TIMESTAMP_WINDOW_SECONDS = 120;
|
|
175
|
+
/**
|
|
176
|
+
* Permisos de la audiencia `auth-api`, tal como los concede el Auth Service a un service client.
|
|
177
|
+
*
|
|
178
|
+
* Se mantiene como objeto del SDK (`S2S_SCOPES.USERS_READ`) pero sus valores deben ser miembros del
|
|
179
|
+
* catálogo `SERVICE_AUDIENCE_SCOPES['auth-api']` del contrato: `satisfies` lo comprueba en
|
|
180
|
+
* compilación. `admin` no está: la superficie `/admin` de Auth se firma, no usa tokens.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { S2S_SCOPES } from '@eva_solutions/auth-sdk/s2s'
|
|
185
|
+
*
|
|
186
|
+
* const scope = [S2S_SCOPES.USERS_READ, S2S_SCOPES.EMPRESAS_ESCRIBIR].join(' ')
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare const S2S_SCOPES: {
|
|
190
|
+
readonly USERS_READ: "users:read";
|
|
191
|
+
readonly WEBHOOKS_READ: "webhooks:read";
|
|
192
|
+
readonly WEBHOOKS_WRITE: "webhooks:write";
|
|
193
|
+
readonly EMPRESAS_LEER: "empresas:leer";
|
|
194
|
+
readonly EMPRESAS_ESCRIBIR: "empresas:escribir";
|
|
195
|
+
readonly TOKENS_EXCHANGE: "tokens:exchange";
|
|
196
|
+
readonly CONTRATACIONES_PEDIR: "contrataciones:pedir";
|
|
197
|
+
readonly CONTRATACIONES_ESCRIBIR: "contrataciones:escribir";
|
|
198
|
+
readonly CONTRATACIONES_LEER: "contrataciones:leer";
|
|
199
|
+
readonly EMPRESAS_BAJA: "empresas:baja";
|
|
200
|
+
readonly AUTORIZACIONES_ESCRIBIR: "autorizaciones:escribir";
|
|
201
|
+
readonly AUTORIZACIONES_LEER: "autorizaciones:leer";
|
|
202
|
+
readonly DIRECTORIO_LEER: "directorio:leer";
|
|
203
|
+
};
|
|
204
|
+
/** Unión de literales de todos los permisos de la audiencia `auth-api`. */
|
|
205
|
+
type S2SScope = (typeof S2S_SCOPES)[keyof typeof S2S_SCOPES];
|
|
206
|
+
/**
|
|
207
|
+
* Header de respuesta que el Auth Service incluye en cada respuesta S2S
|
|
208
|
+
* con la hora del servidor (Unix timestamp en segundos como string).
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { S2S_RESPONSE_HEADERS } from '@eva_solutions/auth-sdk/s2s'
|
|
213
|
+
*
|
|
214
|
+
* const serverTime = response.headers.get(S2S_RESPONSE_HEADERS.SERVER_TIME)
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
declare const S2S_RESPONSE_HEADERS: {
|
|
218
|
+
readonly SERVER_TIME: "x-eva-server-time";
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Vida en milisegundos del caché de `checkAutorizacion` en el cliente S2S (30 s). Una revocación o
|
|
222
|
+
* la baja de una empresa tardan como máximo esto en notarse en un servicio que verifica; así
|
|
223
|
+
* está declarado en el diseño.
|
|
224
|
+
*/
|
|
225
|
+
declare const AUTORIZACION_CACHE_TTL_MS = 30000;
|
|
226
|
+
/**
|
|
227
|
+
* Códigos de error específicos del módulo S2S.
|
|
228
|
+
*
|
|
229
|
+
* Complementan a `ERROR_CODES` (core) para los errores propios de los endpoints
|
|
230
|
+
* `/api/v1/internal/*` que consume `createS2SClient`: usuarios y subscriptions de webhooks.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* import { S2S_ERROR_CODES } from '@eva_solutions/auth-sdk/s2s'
|
|
235
|
+
*
|
|
236
|
+
* const r = await s2s.rotateWebhookSubscriptionSecret(id)
|
|
237
|
+
* if (!r.ok && r.error.kind === 'api' && r.error.code === S2S_ERROR_CODES.webhook_secret_rotation_conflict) {
|
|
238
|
+
* // otra rotación concurrente ganó; esta llamada no recibió clave. Reintenta.
|
|
239
|
+
* }
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
declare const S2S_ERROR_CODES: {
|
|
243
|
+
/** `GET /internal/users/{id}`: el usuario no existe o está soft-deleted (404). */
|
|
244
|
+
readonly user_not_found: "user_not_found";
|
|
245
|
+
/** Ya existe una subscription del mismo service client con la misma URL y los mismos eventCodes (409). */
|
|
246
|
+
readonly webhook_subscription_duplicate: "webhook_subscription_duplicate";
|
|
247
|
+
/** La subscription no existe o pertenece a otro service client (404). */
|
|
248
|
+
readonly webhook_subscription_not_found: "webhook_subscription_not_found";
|
|
249
|
+
/**
|
|
250
|
+
* `rotate-secret` perdió contra otra rotación concurrente de la misma subscription (409).
|
|
251
|
+
* El perdedor no recibe clave; es reintentable.
|
|
252
|
+
*/
|
|
253
|
+
readonly webhook_secret_rotation_conflict: "webhook_secret_rotation_conflict";
|
|
254
|
+
/** URL de webhook inválida: reglas estáticas, IP privada o host sin direcciones (400). */
|
|
255
|
+
readonly webhook_url_invalid: "webhook_url_invalid";
|
|
256
|
+
/** `eventCodes` vacío al crear o actualizar (400). */
|
|
257
|
+
readonly webhook_event_codes_empty: "webhook_event_codes_empty";
|
|
258
|
+
};
|
|
259
|
+
/** Unión de literales de los error codes específicos del módulo S2S. */
|
|
260
|
+
type S2SErrorCode = (typeof S2S_ERROR_CODES)[keyof typeof S2S_ERROR_CODES];
|
|
261
|
+
/**
|
|
262
|
+
* Verify an inbound S2S request signed with HMAC-SHA-256.
|
|
263
|
+
*
|
|
264
|
+
* The function reads the body into memory exactly once (via `request.arrayBuffer()`).
|
|
265
|
+
* Callers that need to read the body again after calling this function MUST clone the
|
|
266
|
+
* request before passing it here (e.g., Hono's `s2sAuth` uses `cloneRawRequest`).
|
|
267
|
+
*
|
|
268
|
+
* @param request - Web API `Request` to verify. Body is read and buffered internally.
|
|
269
|
+
* @param opts.secretStore - Callback returning the plaintext secret (64 hex chars) for
|
|
270
|
+
* the given clientId, or null if unknown. Return null — do NOT throw — for unknown clients.
|
|
271
|
+
* @param opts.timestampWindowSeconds - Override timestamp window (default: 120 s).
|
|
272
|
+
*
|
|
273
|
+
* @throws If `secretStore` returns a non-null value that is not valid hex (empty, odd length
|
|
274
|
+
* or non-hex chars). This is a server misconfiguration, so it is NOT mapped to a 401; the
|
|
275
|
+
* error message never contains the secret.
|
|
276
|
+
*
|
|
277
|
+
* @remarks A query string with malformed percent-encoding (e.g. `?a=%ZZ`) cannot be
|
|
278
|
+
* canonicalized; it fails closed with `signature_invalid`, following the same path as an
|
|
279
|
+
* invalid signature (secret lookup and HMAC included, `replayCache` not touched).
|
|
280
|
+
*
|
|
281
|
+
* @returns `S2SVerifyResult<{ clientId: string }>` — discriminated union.
|
|
282
|
+
* `ok: true` → `data.clientId` is the authenticated service client slug.
|
|
283
|
+
* `ok: false` → `error.reason` narrows the failure; `error.message` is human-readable.
|
|
284
|
+
*
|
|
285
|
+
* @security Timing-safe: whether `secretStore` returns null (client_unknown path) or a
|
|
286
|
+
* valid secret with an invalid signature (signature_invalid path), this function
|
|
287
|
+
* ALWAYS reaches `crypto.subtle.verify` with the same execution path — using
|
|
288
|
+
* `DUMMY_HMAC_KEY_HEX` when the secret is null. The verify result is discarded in
|
|
289
|
+
* the client_unknown path, but the operation always executes. See AC-10.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const result = await verifyS2SRequest(request, {
|
|
294
|
+
* secretStore: async (clientId) => {
|
|
295
|
+
* const client = await db.serviceClients.findById(clientId)
|
|
296
|
+
* return client?.secret ?? null
|
|
297
|
+
* },
|
|
298
|
+
* })
|
|
299
|
+
* if (!result.ok) {
|
|
300
|
+
* return new Response(
|
|
301
|
+
* JSON.stringify({ error: result.error }),
|
|
302
|
+
* { status: 401, headers: { 'Content-Type': 'application/json' } },
|
|
303
|
+
* )
|
|
304
|
+
* }
|
|
305
|
+
* const { clientId } = result.data
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
declare function verifyS2SRequest(request: Request, opts: S2SVerifyOptions): Promise<S2SVerifyResult<{
|
|
309
|
+
clientId: string;
|
|
310
|
+
}>>;
|
|
311
|
+
export { S2SScope as a, S2S_TIMESTAMP_WINDOW_SECONDS as c, generateS2SNonce as d, signS2SHeaders as f, createS2SClient as h, AUTORIZACION_CACHE_TTL_MS as i, EMPTY_BODY_SHA256_HEX as l, S2SClient as m, S2SErrorCode as n, S2S_RESPONSE_HEADERS as o, signS2SRequest as p, S2S_ERROR_CODES as r, S2S_SCOPES as s, verifyS2SRequest as t, buildS2SCanonicalString as u };
|
|
312
|
+
//# sourceMappingURL=index-BYzDaMR9.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BYzDaMR9.d.ts","names":[],"sources":["../src/s2s/client.ts","../src/s2s/sign.ts","../src/s2s/constants.ts","../src/s2s/error-codes.ts","../src/s2s/verify.ts"],"mappings":";;;;;;;KAoGY;EACV,QAAQ,iBAAiB,QAAQ,OAAO;EACxC,WAAW,OAAO,qBAAqB,QAAQ,OAAO;EACtD,0BACE,OAAO,iCACN,QAAQ,OAAO;EAClB,4BAA4B,QAC1B;IAAS,eAAe;;EAE1B,uBAAuB,aAAa,QAAQ,OAAO;EACnD,0BACE,YACA,OAAO,iCACN,QAAQ,OAAO;EAClB,0BAA0B,aAAa,QAAQ;EAC/C,gCACE,aACC,QAAQ,OAAO;EAClB,sBACE,QAAQ,6BACP,QAAQ;IAAS,YAAY;;;;;;;EAQhC,cAAc,OAAO,qBAAqB,QAAQ,OAAO;;EAEzD,cACE,mBACA,OAAO,qBACN,QAAQ,OAAO;;EAElB,aAAa,QAAQ,oBAAoB,QAAQ,OAAO;;;;;;EAMxD,WAAW,oBAAoB,QAAQ,OAAO;;EAE9C,YAAY,oBAAoB,QAAQ,OAAO;;EAE/C,iBAAiB,oBAAoB,QAAQ,OAAO;;EAEpD,iBACE,mBACA,OAAO,wBACN,QAAQ,OAAO;;EAElB,qBACE,mBACA,gBACA,OAAO,2BACN,QAAQ,OAAO;;;;;EAKlB,oBACE,mBACA,gBACA,OAAO,2BACN,QAAQ;;EAEX,eACE,mBACA,OAAO,sBACN,QAAQ,OAAO;;EAElB,eACE,mBACA,oBACA,OAAO,sBACN,QAAQ,OAAO;;EAElB,mBACE,mBACA,oBACA,OAAO,0BACN,QAAQ;;;;;;EASX,oBACE,mBACA,OAAO,oBACN,QAAQ,OAAO;;EAElB,mBACE,mBACA,OAAO,oBACN,QAAQ;;;;;EAKX,gBACE,mBACA,mBACC,QAAQ,OAAO;;EAElB,gBACE,mBACA,kBACA,OAAO,uBACN,QAAQ,OAAO;;EAKlB,YAAY,OAAO,mBAAmB,QAAQ,OAAO;;EAErD,YACE,iBACA,OAAO,mBACN,QAAQ,OAAO;;EAElB,gBACE,iBACA,OAAO,uBACN,QAAQ,OAAO;;EAElB,mBACE,iBACA,mBACA,OAAO,0BACN,QAAQ;;EAKX,iBACE,QAAQ,wBACP,QAAQ,OAAO;;EAKlB,mBACE,qBACA,OAAO,0BACN,QAAQ,OAAO;;EAElB,mBACE,mBACA,wBACA,OAAO,0BACN,QAAQ;;EAEX,mBACE,mBACA,OAAO,0BACN,QAAQ,OAAO;;;;;;;;EAQlB,kBACE,OAAO,yBACN,QAAQ,OAAO;;;;;;;;;;;;;;;;;;;iBAsLJ,gBAAgB,QAAQ,kBAAkB;;;;;;cCjV7C;;;;;iBAOG;;;;;;;;;iBAaM,wBACpB,OAAO,oBACN;;;;;;;;iBAwCmB,iBACpB,OACA;EAEA,OAAO;EACP;IACE;;;;;;;;;;;;iBAgDkB,iBACpB,QACA,MACA,UACA,WACA,UACA;EAEA;EACA;EACA;EACA,YAAY;EACZ;EACA;IACE,QAAQ;;;;;;;;;;cCxOC;;;;;;;;;;;;;;;cAmCA;;;;;;;;;;;;;;;;KAiBD,mBAAmB,yBAAyB;;;;;;;;;;;;cAa3C;;;;;;;;cASA;;;;;;;;;;;;;;;;;cCvEA;;;;;;;;;;;;;;;;;;KAmBD,uBACF,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCoHlB,iBACpB,SAAS,SACT,MAAM,mBACL,QAAQ;EAAkB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import "./index-Dold-Ycb.js";
|
|
2
|
+
import "./types-CQTZdZIF.js";
|
|
3
|
+
/**
|
|
4
|
+
* Verificación de firma HMAC-SHA-256 de webhooks entrantes del Auth Service.
|
|
5
|
+
*
|
|
6
|
+
* REQ-WH-03, REQ-WH-04, REQ-WH-05, REQ-WH-06 LOCKED.
|
|
7
|
+
*
|
|
8
|
+
* Algoritmo (espejo del API `firmarWebhook` en webhook-dispatcher.ts):
|
|
9
|
+
* canonical = `${timestamp}\n${eventId}\n${sha256Hex(rawBody)}`
|
|
10
|
+
* expected = HMAC-SHA-256(signingKey, canonical)
|
|
11
|
+
*
|
|
12
|
+
* Usa Web Crypto API (`crypto.subtle`) — isomorfo (browser + worker + Node 20+).
|
|
13
|
+
*
|
|
14
|
+
* D-12 LOCKED: hexToBytes y sha256Hex se duplican aquí (NO se extraen a shared)
|
|
15
|
+
* para que el entry point ./webhooks sea completamente tree-shakeable.
|
|
16
|
+
*/
|
|
17
|
+
type VerifyWebhookSignatureParams = {
|
|
18
|
+
/** Body raw del request (string, antes de parsear JSON). */
|
|
19
|
+
rawBody: string;
|
|
20
|
+
/**
|
|
21
|
+
* Signing key del webhook — hex 64 chars (32 bytes).
|
|
22
|
+
* Es el `signingKey` que devolvió el API al crear/rotar la subscription.
|
|
23
|
+
* NOTA H3 LOCKED: NO re-hashear — usarlo tal como viene.
|
|
24
|
+
*/
|
|
25
|
+
signingKey: string;
|
|
26
|
+
/**
|
|
27
|
+
* Valor completo del header `X-Eva-Webhook-Signature`.
|
|
28
|
+
* Debe comenzar con `"sha256="`.
|
|
29
|
+
*/
|
|
30
|
+
signature: string;
|
|
31
|
+
/** Valor del header `X-Eva-Webhook-Id` (UUID del evento). */
|
|
32
|
+
eventId: string;
|
|
33
|
+
/**
|
|
34
|
+
* Valor del header `X-Eva-Webhook-Timestamp` parseado a number
|
|
35
|
+
* (unix seconds).
|
|
36
|
+
*/
|
|
37
|
+
timestamp: number;
|
|
38
|
+
/**
|
|
39
|
+
* Override del tiempo actual (unix seconds) para tests.
|
|
40
|
+
* Por defecto: `Math.floor(Date.now() / 1000)`.
|
|
41
|
+
*/
|
|
42
|
+
now?: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Verifica la firma HMAC-SHA-256 de un webhook entrante del Auth Service.
|
|
46
|
+
*
|
|
47
|
+
* @returns `true` si la firma es válida y está dentro de la ventana de tiempo;
|
|
48
|
+
* `false` en cualquier otro caso (firma inválida, expirada, formato incorrecto).
|
|
49
|
+
*
|
|
50
|
+
* La comparación es constant-time via `crypto.subtle.verify` (por spec).
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const valid = await verifyWebhookSignature({
|
|
55
|
+
* rawBody: await req.text(),
|
|
56
|
+
* signingKey: process.env.WEBHOOK_SIGNING_KEY,
|
|
57
|
+
* signature: req.headers.get('x-eva-webhook-signature') ?? '',
|
|
58
|
+
* eventId: req.headers.get('x-eva-webhook-id') ?? '',
|
|
59
|
+
* timestamp: Number(req.headers.get('x-eva-webhook-timestamp')),
|
|
60
|
+
* })
|
|
61
|
+
* if (!valid) return new Response('Unauthorized', { status: 401 })
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function verifyWebhookSignature({ rawBody, signingKey, signature, eventId, timestamp, now }: VerifyWebhookSignatureParams): Promise<boolean>;
|
|
65
|
+
export { verifyWebhookSignature as n, VerifyWebhookSignatureParams as t };
|
|
66
|
+
//# sourceMappingURL=index-CNNMCrdl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-CNNMCrdl.d.ts","names":[],"sources":["../src/webhooks/verify-signature.ts"],"mappings":";;;;;;;;;;;;;;;;KAqDY;;EAEV;;;;;;EAMA;;;;;EAKA;;EAEA;;;;;EAKA;;;;;EAKA;;;;;;;;;;;;;;;;;;;;;;iBAuBoB,yBACpB,SACA,YACA,WACA,SACA,WACA,OACC,+BAA+B"}
|