@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.
Files changed (205) hide show
  1. package/CHANGELOG.md +290 -0
  2. package/LICENSE +21 -0
  3. package/README.md +481 -2
  4. package/dist/admin/index.cjs +4 -0
  5. package/dist/admin/index.d.cts +2 -0
  6. package/dist/admin/index.d.ts +2 -0
  7. package/dist/admin/index.js +1 -0
  8. package/dist/admin-DjvuvuI8.js +408 -0
  9. package/dist/admin-DjvuvuI8.js.map +1 -0
  10. package/dist/admin-Q6lbWqLM.cjs +419 -0
  11. package/dist/admin-Q6lbWqLM.cjs.map +1 -0
  12. package/dist/api-path-2Ulruz7R.cjs +34 -0
  13. package/dist/api-path-2Ulruz7R.cjs.map +1 -0
  14. package/dist/api-path-B-vSkL2H.js +29 -0
  15. package/dist/api-path-B-vSkL2H.js.map +1 -0
  16. package/dist/auth-19r-Q6X2.js +84 -0
  17. package/dist/auth-19r-Q6X2.js.map +1 -0
  18. package/dist/auth-DX1N5JA4.cjs +131 -0
  19. package/dist/auth-DX1N5JA4.cjs.map +1 -0
  20. package/dist/auth-handlers-B3iJaPln.js +343 -0
  21. package/dist/auth-handlers-B3iJaPln.js.map +1 -0
  22. package/dist/auth-handlers-BTbXKJms.d.ts +15 -0
  23. package/dist/auth-handlers-BTbXKJms.d.ts.map +1 -0
  24. package/dist/auth-handlers-CDzGFDTu.cjs +404 -0
  25. package/dist/auth-handlers-CDzGFDTu.cjs.map +1 -0
  26. package/dist/auth-handlers-DRUUTEtW.d.cts +15 -0
  27. package/dist/auth-handlers-DRUUTEtW.d.cts.map +1 -0
  28. package/dist/catalog-CHPqldcj.cjs +172 -0
  29. package/dist/catalog-CHPqldcj.cjs.map +1 -0
  30. package/dist/catalog-CIqpzgsM.d.cts +75 -0
  31. package/dist/catalog-CIqpzgsM.d.cts.map +1 -0
  32. package/dist/catalog-CIqpzgsM.d.ts +75 -0
  33. package/dist/catalog-CIqpzgsM.d.ts.map +1 -0
  34. package/dist/catalog-DqPhtCKZ.js +95 -0
  35. package/dist/catalog-DqPhtCKZ.js.map +1 -0
  36. package/dist/config-D6Tr7ev6.d.cts +95 -0
  37. package/dist/config-D6Tr7ev6.d.cts.map +1 -0
  38. package/dist/config-rl4_-VAq.d.ts +95 -0
  39. package/dist/config-rl4_-VAq.d.ts.map +1 -0
  40. package/dist/constants-BE1_9Tod.cjs +52 -0
  41. package/dist/constants-BE1_9Tod.cjs.map +1 -0
  42. package/dist/constants-BY7QZokc.js +29 -0
  43. package/dist/constants-BY7QZokc.js.map +1 -0
  44. package/dist/constants-CcbhOaAP.cjs +153 -0
  45. package/dist/constants-CcbhOaAP.cjs.map +1 -0
  46. package/dist/constants-rNuD5qJn.js +94 -0
  47. package/dist/constants-rNuD5qJn.js.map +1 -0
  48. package/dist/contracts/index.cjs +1006 -0
  49. package/dist/contracts/index.cjs.map +1 -0
  50. package/dist/contracts/index.d.cts +488 -0
  51. package/dist/contracts/index.d.cts.map +1 -0
  52. package/dist/contracts/index.d.ts +488 -0
  53. package/dist/contracts/index.d.ts.map +1 -0
  54. package/dist/contracts/index.js +817 -0
  55. package/dist/contracts/index.js.map +1 -0
  56. package/dist/cookies-DCIcr7uy.cjs +94 -0
  57. package/dist/cookies-DCIcr7uy.cjs.map +1 -0
  58. package/dist/cookies-DKEC7AWv.js +71 -0
  59. package/dist/cookies-DKEC7AWv.js.map +1 -0
  60. package/dist/delegated-token-CVNbMNfy.js +426 -0
  61. package/dist/delegated-token-CVNbMNfy.js.map +1 -0
  62. package/dist/delegated-token-DHSK4rHI.cjs +461 -0
  63. package/dist/delegated-token-DHSK4rHI.cjs.map +1 -0
  64. package/dist/error-helpers-BenWu-_u.d.ts +105 -0
  65. package/dist/error-helpers-BenWu-_u.d.ts.map +1 -0
  66. package/dist/error-helpers-KBHUbkOM.d.cts +105 -0
  67. package/dist/error-helpers-KBHUbkOM.d.cts.map +1 -0
  68. package/dist/error-helpers-XCh4ZJ-g.cjs +282 -0
  69. package/dist/error-helpers-XCh4ZJ-g.cjs.map +1 -0
  70. package/dist/error-helpers-lnwgNOw9.js +145 -0
  71. package/dist/error-helpers-lnwgNOw9.js.map +1 -0
  72. package/dist/error-messages-CGpW1fsg.d.cts +48 -0
  73. package/dist/error-messages-CGpW1fsg.d.cts.map +1 -0
  74. package/dist/error-messages-CGpW1fsg.d.ts +48 -0
  75. package/dist/error-messages-CGpW1fsg.d.ts.map +1 -0
  76. package/dist/event-codes-BBJHon-I.cjs +48 -0
  77. package/dist/event-codes-BBJHon-I.cjs.map +1 -0
  78. package/dist/event-codes-BgcJWsov.d.cts +42 -0
  79. package/dist/event-codes-BgcJWsov.d.cts.map +1 -0
  80. package/dist/event-codes-BgcJWsov.d.ts +42 -0
  81. package/dist/event-codes-BgcJWsov.d.ts.map +1 -0
  82. package/dist/event-codes-DR5_VlaR.js +43 -0
  83. package/dist/event-codes-DR5_VlaR.js.map +1 -0
  84. package/dist/generic/index.cjs +91 -0
  85. package/dist/generic/index.cjs.map +1 -0
  86. package/dist/generic/index.d.cts +27 -0
  87. package/dist/generic/index.d.cts.map +1 -0
  88. package/dist/generic/index.d.ts +27 -0
  89. package/dist/generic/index.d.ts.map +1 -0
  90. package/dist/generic/index.js +80 -0
  91. package/dist/generic/index.js.map +1 -0
  92. package/dist/hono/index.cjs +309 -0
  93. package/dist/hono/index.cjs.map +1 -0
  94. package/dist/hono/index.d.cts +272 -0
  95. package/dist/hono/index.d.cts.map +1 -0
  96. package/dist/hono/index.d.ts +272 -0
  97. package/dist/hono/index.d.ts.map +1 -0
  98. package/dist/hono/index.js +290 -0
  99. package/dist/hono/index.js.map +1 -0
  100. package/dist/hono-openapi/index.cjs +326 -0
  101. package/dist/hono-openapi/index.cjs.map +1 -0
  102. package/dist/hono-openapi/index.d.cts +14 -0
  103. package/dist/hono-openapi/index.d.cts.map +1 -0
  104. package/dist/hono-openapi/index.d.ts +14 -0
  105. package/dist/hono-openapi/index.d.ts.map +1 -0
  106. package/dist/hono-openapi/index.js +307 -0
  107. package/dist/hono-openapi/index.js.map +1 -0
  108. package/dist/http-client-BVF3iktR.cjs +211 -0
  109. package/dist/http-client-BVF3iktR.cjs.map +1 -0
  110. package/dist/http-client-Da0-OMfc.js +206 -0
  111. package/dist/http-client-Da0-OMfc.js.map +1 -0
  112. package/dist/index-BYzDaMR9.d.ts +312 -0
  113. package/dist/index-BYzDaMR9.d.ts.map +1 -0
  114. package/dist/index-CNNMCrdl.d.ts +66 -0
  115. package/dist/index-CNNMCrdl.d.ts.map +1 -0
  116. package/dist/index-DBZoLU4E.d.ts +240 -0
  117. package/dist/index-DBZoLU4E.d.ts.map +1 -0
  118. package/dist/index-DcvQWByo.d.cts +66 -0
  119. package/dist/index-DcvQWByo.d.cts.map +1 -0
  120. package/dist/index-Dold-Ycb.d.cts +1711 -0
  121. package/dist/index-Dold-Ycb.d.cts.map +1 -0
  122. package/dist/index-Dold-Ycb.d.ts +1711 -0
  123. package/dist/index-Dold-Ycb.d.ts.map +1 -0
  124. package/dist/index-jAO62-OD.d.cts +240 -0
  125. package/dist/index-jAO62-OD.d.cts.map +1 -0
  126. package/dist/index-o7nXcl7z.d.cts +312 -0
  127. package/dist/index-o7nXcl7z.d.cts.map +1 -0
  128. package/dist/index.cjs +89 -0
  129. package/dist/index.cjs.map +1 -0
  130. package/dist/index.d.cts +114 -0
  131. package/dist/index.d.cts.map +1 -0
  132. package/dist/index.d.ts +114 -0
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +28 -0
  135. package/dist/index.js.map +1 -0
  136. package/dist/jwt-BBNqk0TV.cjs +155 -0
  137. package/dist/jwt-BBNqk0TV.cjs.map +1 -0
  138. package/dist/jwt-DhqgH94B.js +150 -0
  139. package/dist/jwt-DhqgH94B.js.map +1 -0
  140. package/dist/key-resolver-CESqj4-P.cjs +45 -0
  141. package/dist/key-resolver-CESqj4-P.cjs.map +1 -0
  142. package/dist/key-resolver-D56I2Zpz.js +34 -0
  143. package/dist/key-resolver-D56I2Zpz.js.map +1 -0
  144. package/dist/parse-error-response-6qOQS5y9.cjs +316 -0
  145. package/dist/parse-error-response-6qOQS5y9.cjs.map +1 -0
  146. package/dist/parse-error-response-BM9-kjs0.js +239 -0
  147. package/dist/parse-error-response-BM9-kjs0.js.map +1 -0
  148. package/dist/path-segment-C25i6aaH.cjs +29 -0
  149. package/dist/path-segment-C25i6aaH.cjs.map +1 -0
  150. package/dist/path-segment-CyqnaFrP.js +24 -0
  151. package/dist/path-segment-CyqnaFrP.js.map +1 -0
  152. package/dist/react/index.cjs +336 -0
  153. package/dist/react/index.cjs.map +1 -0
  154. package/dist/react/index.d.cts +151 -0
  155. package/dist/react/index.d.cts.map +1 -0
  156. package/dist/react/index.d.ts +151 -0
  157. package/dist/react/index.d.ts.map +1 -0
  158. package/dist/react/index.js +300 -0
  159. package/dist/react/index.js.map +1 -0
  160. package/dist/refresh-dedup-CKaHHe7m.js +14 -0
  161. package/dist/refresh-dedup-CKaHHe7m.js.map +1 -0
  162. package/dist/refresh-dedup-CLdACQXR.cjs +19 -0
  163. package/dist/refresh-dedup-CLdACQXR.cjs.map +1 -0
  164. package/dist/rolldown-runtime-CmQyd0Gu.cjs +27 -0
  165. package/dist/routes-DpAzgY1V.cjs +424 -0
  166. package/dist/routes-DpAzgY1V.cjs.map +1 -0
  167. package/dist/routes-F1g5aVqP.js +347 -0
  168. package/dist/routes-F1g5aVqP.js.map +1 -0
  169. package/dist/s2s/index.cjs +24 -0
  170. package/dist/s2s/index.d.cts +5 -0
  171. package/dist/s2s/index.d.ts +5 -0
  172. package/dist/s2s/index.js +5 -0
  173. package/dist/s2s-6wbotr78.cjs +926 -0
  174. package/dist/s2s-6wbotr78.cjs.map +1 -0
  175. package/dist/s2s-B_0SIQzn.js +897 -0
  176. package/dist/s2s-B_0SIQzn.js.map +1 -0
  177. package/dist/sign-C7y6xdvT.cjs +324 -0
  178. package/dist/sign-C7y6xdvT.cjs.map +1 -0
  179. package/dist/sign-HFtcYwa-.js +259 -0
  180. package/dist/sign-HFtcYwa-.js.map +1 -0
  181. package/dist/tokens-C1nzkn21.cjs +232 -0
  182. package/dist/tokens-C1nzkn21.cjs.map +1 -0
  183. package/dist/tokens-C3Z40qd7.js +107 -0
  184. package/dist/tokens-C3Z40qd7.js.map +1 -0
  185. package/dist/types-B5sf7kKE.d.cts +52 -0
  186. package/dist/types-B5sf7kKE.d.cts.map +1 -0
  187. package/dist/types-CLOIHzRu.d.ts +96 -0
  188. package/dist/types-CLOIHzRu.d.ts.map +1 -0
  189. package/dist/types-CQTZdZIF.d.ts +52 -0
  190. package/dist/types-CQTZdZIF.d.ts.map +1 -0
  191. package/dist/types-DGeQ8j4k.d.cts +538 -0
  192. package/dist/types-DGeQ8j4k.d.cts.map +1 -0
  193. package/dist/types-DIUWza9-.d.ts +538 -0
  194. package/dist/types-DIUWza9-.d.ts.map +1 -0
  195. package/dist/types-qh6ANBCV.d.cts +96 -0
  196. package/dist/types-qh6ANBCV.d.cts.map +1 -0
  197. package/dist/webhooks/index.cjs +8 -0
  198. package/dist/webhooks/index.d.cts +5 -0
  199. package/dist/webhooks/index.d.ts +5 -0
  200. package/dist/webhooks/index.js +4 -0
  201. package/dist/webhooks-B5sAWC9W.js +74 -0
  202. package/dist/webhooks-B5sAWC9W.js.map +1 -0
  203. package/dist/webhooks-Cx11psg1.cjs +79 -0
  204. package/dist/webhooks-Cx11psg1.cjs.map +1 -0
  205. package/package.json +192 -3
@@ -0,0 +1,897 @@
1
+ import { a as INTERNAL_AUTORIZACION_ROUTES, c as INTERNAL_TOKEN_EXCHANGE_ROUTES, i as DIRECTORIO_ROUTES, l as INTERNAL_TOKEN_ROUTES, o as INTERNAL_EMPRESA_ROUTES, p as WEBHOOK_ROUTES, s as INTERNAL_GRUPO_ROUTES, u as INTERNAL_USER_ROUTES } from "./routes-F1g5aVqP.js";
2
+ import { _ as TOKEN_EXCHANGE_GRANT_TYPE, f as SUBJECT_TOKEN_HEADER, p as SUBJECT_TOKEN_INVALID_CODE, t as ACCESS_TOKEN_TYPE } from "./tokens-C3Z40qd7.js";
3
+ import "./constants-rNuD5qJn.js";
4
+ import { t as apiPath } from "./api-path-B-vSkL2H.js";
5
+ import { t as parseErrorResponse, u as validateHttpsUrl } from "./parse-error-response-BM9-kjs0.js";
6
+ import { t as encodePathSegment } from "./path-segment-CyqnaFrP.js";
7
+ import { i as signS2SHeaders, s as AUTORIZACION_CACHE_TTL_MS } from "./sign-HFtcYwa-.js";
8
+ import { f as isScopeOfAudience, l as isDelegableAudience, p as isServiceAudience, u as isDelegableScope } from "./catalog-DqPhtCKZ.js";
9
+ // src/s2s/token-provider.ts
10
+ /**
11
+ * Proveedor de tokens de servicio (client credentials) para un servicio que llama a otro.
12
+ *
13
+ * Pide el token a `POST /api/v1/internal/token` autenticándose con la firma HMAC del service client
14
+ * (la única ruta servicio -> servicio que la conserva), lo guarda en memoria y lo entrega a cada
15
+ * llamada como `Authorization: Bearer`. El caché vive en la instancia: crea UN proveedor por
16
+ * (audiencia, scope) y compártelo.
17
+ */
18
+ /** Cuánto antes del vencimiento se renueva el token, en milisegundos. */
19
+ const SERVICE_TOKEN_RENEW_MARGIN_MS = 3e4;
20
+ const REQUEST_TIMEOUT_MS = 1e4;
21
+ const encoder = new TextEncoder();
22
+ /** El Auth Service rechazó el pedido de token. `code` es el `error` de RFC 6749 (`invalid_client`, …). */
23
+ var ServiceTokenRequestError = class extends Error {
24
+ code;
25
+ status;
26
+ /** Segundos de espera que pidió Auth con `Retry-After` (429 / 503), si los mandó. */
27
+ retryAfterSeconds;
28
+ constructor(code, status, description, retryAfterSeconds) {
29
+ super(description ?? code);
30
+ this.name = "ServiceTokenRequestError";
31
+ this.code = code;
32
+ this.status = status;
33
+ if (retryAfterSeconds !== void 0) this.retryAfterSeconds = retryAfterSeconds;
34
+ }
35
+ };
36
+ async function requestToken(config, url, body) {
37
+ const requestedAtMs = Date.now();
38
+ const headers = await signS2SHeaders({
39
+ method: "POST",
40
+ path: url.pathname,
41
+ rawQuery: "",
42
+ bodyBytes: encoder.encode(body),
43
+ clientId: config.clientId,
44
+ secretHex: config.secretHex
45
+ });
46
+ const res = await fetch(url, {
47
+ method: "POST",
48
+ headers: {
49
+ "content-type": "application/json",
50
+ ...headers
51
+ },
52
+ body,
53
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
54
+ });
55
+ const json = await res.json().catch(() => null);
56
+ if (!res.ok) {
57
+ const code = typeof json?.error === "string" ? json.error : res.status === 429 ? "temporarily_unavailable" : "server_error";
58
+ const retryAfterHeader = res.headers?.get("retry-after")?.trim();
59
+ const retryAfter = retryAfterHeader !== void 0 && /^\d+$/.test(retryAfterHeader) ? Number(retryAfterHeader) : void 0;
60
+ const description = typeof json?.error_description === "string" ? json.error_description : void 0;
61
+ throw new ServiceTokenRequestError(code, res.status, description, retryAfter);
62
+ }
63
+ const token = json?.access_token;
64
+ const expiresIn = json?.expires_in;
65
+ if (typeof token !== "string" || token === "" || typeof expiresIn !== "number" || !(expiresIn > 0)) throw new ServiceTokenRequestError("server_error", res.status, "Respuesta de token inválida");
66
+ return {
67
+ token,
68
+ renewAtMs: requestedAtMs + expiresIn * 1e3 - SERVICE_TOKEN_RENEW_MARGIN_MS
69
+ };
70
+ }
71
+ /** Entries the exchange cache keeps per provider before it drops expired ones and then the oldest. */
72
+ const EXCHANGE_CACHE_MAX_ENTRIES = 500;
73
+ const malformed = (message) => ({
74
+ ok: false,
75
+ error: {
76
+ kind: "sdk",
77
+ reason: "malformed",
78
+ message,
79
+ status: 0
80
+ }
81
+ });
82
+ /** SHA-256 of the subject token as hex: the cache never keys on claims nobody verified here. */
83
+ async function fingerprint(value) {
84
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
85
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
86
+ }
87
+ /** Validates the exchange input locally (a bad audience or scope is a sure `invalid_target` / `invalid_scope`). */
88
+ function validateExchangeInput(input) {
89
+ if (typeof input.subjectToken !== "string" || input.subjectToken === "") return malformed("exchangeToken: subjectToken requerido");
90
+ if (!isDelegableAudience(input.audience)) return malformed(`exchangeToken: "${String(input.audience)}" no es una audiencia delegable del catálogo (auth-api nunca lo es)`);
91
+ for (const scope of input.scope?.split(" ").filter(Boolean) ?? []) if (!isDelegableScope(input.audience, scope)) return malformed(`exchangeToken: "${scope}" no es un permiso delegable de la audiencia "${input.audience}"`);
92
+ return null;
93
+ }
94
+ function postExchange(url, bearer, input) {
95
+ return fetch(url, {
96
+ method: "POST",
97
+ headers: {
98
+ "content-type": "application/json",
99
+ authorization: `Bearer ${bearer}`
100
+ },
101
+ body: JSON.stringify({
102
+ grant_type: TOKEN_EXCHANGE_GRANT_TYPE,
103
+ subject_token: input.subjectToken,
104
+ subject_token_type: ACCESS_TOKEN_TYPE,
105
+ audience: input.audience,
106
+ ...input.scope !== void 0 && { scope: input.scope }
107
+ }),
108
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
109
+ });
110
+ }
111
+ /**
112
+ * Crea un proveedor de tokens de servicio.
113
+ *
114
+ * Deduplica los pedidos simultáneos dentro del mismo proceso (isolate): N llamadas concurrentes a
115
+ * `getToken()` con el caché vacío o vencido hacen UN pedido al Auth Service. Un pedido fallido no
116
+ * se guarda: la siguiente llamada reintenta.
117
+ *
118
+ * Valida su configuración al crearse (audiencia del catálogo, permisos de esa audiencia, clave hex).
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const provider = createServiceTokenProvider({
123
+ * authUrl: env.AUTH_URL,
124
+ * clientId: env.AUTH_CLIENT_ID,
125
+ * secretHex: env.AUTH_CLIENT_SECRET_HEX,
126
+ * audience: 'auth-api',
127
+ * })
128
+ * const s2s = createS2SClient({ baseUrl: env.AUTH_URL, tokenProvider: provider })
129
+ * ```
130
+ */
131
+ function createServiceTokenProvider(config) {
132
+ validateHttpsUrl(config.authUrl, "ServiceTokenProviderConfig.authUrl");
133
+ if (!isServiceAudience(config.audience)) throw new TypeError(`[eva-auth-sdk] createServiceTokenProvider: audience "${config.audience}" no es una audiencia de servicio del catálogo`);
134
+ for (const scope of config.scope?.split(" ").filter(Boolean) ?? []) if (!isScopeOfAudience(config.audience, scope)) throw new TypeError(`[eva-auth-sdk] createServiceTokenProvider: "${scope}" no es un permiso de la audiencia "${config.audience}"`);
135
+ if (!/^(?:[0-9a-fA-F]{2})+$/.test(config.secretHex)) throw new TypeError("[eva-auth-sdk] createServiceTokenProvider: secretHex inválido (formato hex requerido)");
136
+ const url = new URL(`${config.authUrl.replace(/\/$/, "")}${apiPath(INTERNAL_TOKEN_ROUTES.issue)}`);
137
+ const body = JSON.stringify({
138
+ audience: config.audience,
139
+ ...config.scope !== void 0 && { scope: config.scope }
140
+ });
141
+ let cached;
142
+ let inFlight;
143
+ const exchangeUrl = new URL(`${config.authUrl.replace(/\/$/, "")}${apiPath(INTERNAL_TOKEN_EXCHANGE_ROUTES.exchange)}`);
144
+ let exchangeBearer;
145
+ const exchangeCache = /* @__PURE__ */ new Map();
146
+ const rememberGrant = (key, entry) => {
147
+ if (exchangeCache.size >= EXCHANGE_CACHE_MAX_ENTRIES) {
148
+ const now = Date.now();
149
+ for (const [k, v] of exchangeCache) if (now >= v.renewAtMs) exchangeCache.delete(k);
150
+ if (exchangeCache.size >= EXCHANGE_CACHE_MAX_ENTRIES) {
151
+ const oldest = exchangeCache.keys().next().value;
152
+ if (oldest !== void 0) exchangeCache.delete(oldest);
153
+ }
154
+ }
155
+ exchangeCache.set(key, entry);
156
+ };
157
+ const exchangeInFlight = /* @__PURE__ */ new Map();
158
+ const requestExchange = async (input, key) => {
159
+ try {
160
+ exchangeBearer ??= createServiceTokenProvider({
161
+ authUrl: config.authUrl,
162
+ clientId: config.clientId,
163
+ secretHex: config.secretHex,
164
+ audience: "auth-api",
165
+ scope: "tokens:exchange"
166
+ });
167
+ const requestedAtMs = Date.now();
168
+ let bearer = await exchangeBearer.getToken();
169
+ let res = await postExchange(exchangeUrl, bearer, input);
170
+ if (res.status === 401) {
171
+ exchangeBearer.invalidate(bearer);
172
+ bearer = await exchangeBearer.getToken();
173
+ res = await postExchange(exchangeUrl, bearer, input);
174
+ }
175
+ const json = await res.json().catch(() => null);
176
+ if (!res.ok) {
177
+ const code = typeof json?.error === "string" ? json.error : res.status === 429 ? "temporarily_unavailable" : "server_error";
178
+ return {
179
+ ok: false,
180
+ error: {
181
+ kind: "api",
182
+ code,
183
+ message: typeof json?.error_description === "string" ? json.error_description : code,
184
+ status: res.status
185
+ }
186
+ };
187
+ }
188
+ const accessToken = json?.access_token;
189
+ const expiresIn = json?.expires_in;
190
+ const scope = json?.scope;
191
+ if (typeof accessToken !== "string" || accessToken === "" || typeof expiresIn !== "number" || !(expiresIn > 0) || typeof scope !== "string" || json?.issued_token_type !== "urn:ietf:params:oauth:token-type:access_token" || json?.token_type !== "Bearer") return {
192
+ ok: false,
193
+ error: {
194
+ kind: "api",
195
+ code: "server_error",
196
+ message: "Respuesta de token exchange inválida",
197
+ status: res.status
198
+ }
199
+ };
200
+ const grant = {
201
+ accessToken,
202
+ scope
203
+ };
204
+ rememberGrant(key, {
205
+ grant,
206
+ renewAtMs: requestedAtMs + expiresIn * 1e3 - SERVICE_TOKEN_RENEW_MARGIN_MS
207
+ });
208
+ return {
209
+ ok: true,
210
+ data: grant
211
+ };
212
+ } catch (err) {
213
+ if (err instanceof ServiceTokenRequestError) return {
214
+ ok: false,
215
+ error: {
216
+ kind: "api",
217
+ code: err.code,
218
+ message: err.message,
219
+ status: err.status
220
+ }
221
+ };
222
+ return {
223
+ ok: false,
224
+ error: {
225
+ kind: "sdk",
226
+ reason: "network",
227
+ message: err instanceof Error ? err.message : "Network error",
228
+ status: 0
229
+ }
230
+ };
231
+ }
232
+ };
233
+ const exchangeToken = async (input) => {
234
+ const invalid = validateExchangeInput(input);
235
+ if (invalid !== null) return invalid;
236
+ const scopeKey = (input.scope ?? "").split(" ").filter(Boolean).sort().join(" ");
237
+ const key = `${await fingerprint(input.subjectToken)}|${input.audience}|${scopeKey}`;
238
+ const hit = exchangeCache.get(key);
239
+ if (hit !== void 0) {
240
+ if (Date.now() < hit.renewAtMs) return {
241
+ ok: true,
242
+ data: hit.grant
243
+ };
244
+ exchangeCache.delete(key);
245
+ }
246
+ const pending = exchangeInFlight.get(key);
247
+ if (pending !== void 0) return new Promise((resolve, reject) => {
248
+ const timer = setTimeout(() => {
249
+ if (exchangeInFlight.get(key) === pending) exchangeInFlight.delete(key);
250
+ exchangeToken(input).then(resolve, reject);
251
+ }, 11e3);
252
+ pending.then((result) => {
253
+ clearTimeout(timer);
254
+ resolve(result);
255
+ }, (err) => {
256
+ clearTimeout(timer);
257
+ reject(err);
258
+ });
259
+ });
260
+ const started = requestExchange(input, key).finally(() => {
261
+ if (exchangeInFlight.get(key) === started) exchangeInFlight.delete(key);
262
+ });
263
+ exchangeInFlight.set(key, started);
264
+ return started;
265
+ };
266
+ const joinWithDeadline = (pending, retry) => new Promise((resolve, reject) => {
267
+ const timer = setTimeout(() => {
268
+ if (inFlight === pending) inFlight = void 0;
269
+ retry().then(resolve, reject);
270
+ }, 11e3);
271
+ pending.then((token) => {
272
+ clearTimeout(timer);
273
+ resolve(token);
274
+ }, (err) => {
275
+ clearTimeout(timer);
276
+ reject(err);
277
+ });
278
+ });
279
+ const provider = {
280
+ getToken() {
281
+ if (cached !== void 0 && Date.now() < cached.renewAtMs) return Promise.resolve(cached.token);
282
+ if (inFlight !== void 0) return joinWithDeadline(inFlight, () => provider.getToken());
283
+ const pending = requestToken(config, url, body).then((fresh) => {
284
+ cached = fresh;
285
+ return fresh.token;
286
+ }).finally(() => {
287
+ if (inFlight === pending) inFlight = void 0;
288
+ });
289
+ inFlight = pending;
290
+ return pending;
291
+ },
292
+ invalidate(rejectedToken) {
293
+ if (rejectedToken === void 0 || cached?.token === rejectedToken) cached = void 0;
294
+ },
295
+ exchangeToken
296
+ };
297
+ return provider;
298
+ }
299
+ // src/s2s/client.ts
300
+ /**
301
+ * Cliente S2S (service-to-service) para llamar a la API interna del Auth Service desde servicios
302
+ * backend. Autentica cada llamada con `Authorization: Bearer <token de servicio>` (audiencia
303
+ * `auth-api`) que entrega un `ServiceTokenProvider`; ante un 401 invalida el token y reintenta UNA vez.
304
+ *
305
+ * REQ-S2S-01, REQ-S2S-04, REQ-INT-01, REQ-INT-02 LOCKED.
306
+ *
307
+ * Endpoints disponibles:
308
+ * GET /internal/users/:id
309
+ * POST /internal/users/batch
310
+ * POST /internal/webhooks/subscriptions
311
+ * GET /internal/webhooks/subscriptions
312
+ * GET /internal/webhooks/subscriptions/:id
313
+ * PATCH /internal/webhooks/subscriptions/:id
314
+ * DELETE /internal/webhooks/subscriptions/:id
315
+ * POST /internal/webhooks/subscriptions/:id/rotate-secret
316
+ * GET /internal/webhooks/deliveries
317
+ * POST /internal/empresas
318
+ * GET /internal/empresas/:id
319
+ * POST /internal/empresas/:id/miembros
320
+ * DELETE /internal/empresas/:id/miembros/:userId
321
+ * POST /internal/empresas/:id/sucursales
322
+ * (fase 2) PATCH /internal/empresas/:id, GET /internal/empresas, POST .../baja|reactivar,
323
+ * PATCH .../miembros/:userId, PATCH|DELETE .../sucursales/:sucursalId, contratación
324
+ * (.../productos), grupos (/internal/grupos), directorio (/directorio) y autorizaciones.
325
+ *
326
+ * Los paths salen de `INTERNAL_USER_ROUTES`, `WEBHOOK_ROUTES` e `INTERNAL_EMPRESA_ROUTES`
327
+ * (contrato compartido) vía `apiPath`.
328
+ */
329
+ /**
330
+ * Construye el query string a partir de un objeto de parámetros.
331
+ * Omite valores undefined.
332
+ */
333
+ function buildQueryString(params) {
334
+ const parts = [];
335
+ for (const [key, value] of Object.entries(params)) if (value !== void 0) parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
336
+ return parts.join("&");
337
+ }
338
+ function invalidIdResult(method) {
339
+ return {
340
+ ok: false,
341
+ error: {
342
+ kind: "sdk",
343
+ reason: "malformed",
344
+ message: `${method}: id inválido (no puede ser vacío, "." ni "..")`,
345
+ status: 0
346
+ }
347
+ };
348
+ }
349
+ /**
350
+ * Codifica cada id como UN segmento del path (`encodePathSegment`). Devuelve los segmentos listos
351
+ * para `apiPath` o, si alguno es vacío, `.` o `..`, el `Result` de error (sin hacer la request).
352
+ */
353
+ function encodeSegments(method, values) {
354
+ const out = {};
355
+ for (const key of Object.keys(values)) {
356
+ const segment = encodePathSegment(values[key]);
357
+ if (segment === null) return invalidIdResult(method);
358
+ out[key] = segment;
359
+ }
360
+ return out;
361
+ }
362
+ /** ¿El cuerpo de error es el fallo del token de la persona (`subject_token_invalid`)? */
363
+ const isSubjectTokenFailure = (body) => body?.error?.code === SUBJECT_TOKEN_INVALID_CODE;
364
+ const isInvalid = (value) => "ok" in value;
365
+ /** Entradas que guarda el caché de `checkAutorizacion` antes de soltar las vencidas y luego la más vieja. */
366
+ const AUTORIZACION_CACHE_MAX_ENTRIES = 1e3;
367
+ /**
368
+ * Realiza un fetch autenticado con el token de servicio del proveedor. Un 401 invalida el token y
369
+ * reintenta una sola vez con uno nuevo. Retorna `Result<T>` con la propiedad `data` del body.
370
+ */
371
+ async function s2sFetch({ config, method, pathname, rawQuery = "", body, headers: extraHeaders }) {
372
+ try {
373
+ const bodyText = body !== void 0 ? JSON.stringify(body) : void 0;
374
+ const url = rawQuery ? `${config.baseUrl}${pathname}?${rawQuery}` : `${config.baseUrl}${pathname}`;
375
+ let usedToken = "";
376
+ const send = async () => {
377
+ const token = await config.tokenProvider.getToken();
378
+ usedToken = token;
379
+ return fetch(url, {
380
+ method,
381
+ headers: {
382
+ "content-type": "application/json",
383
+ ...extraHeaders,
384
+ authorization: `Bearer ${token}`
385
+ },
386
+ body: bodyText,
387
+ signal: AbortSignal.timeout(1e4)
388
+ });
389
+ };
390
+ let res = await send();
391
+ if (res.status === 401) {
392
+ const body = await res.json().catch(() => null);
393
+ if (isSubjectTokenFailure(body)) return {
394
+ ok: false,
395
+ error: parseErrorResponse(res.status, body)
396
+ };
397
+ config.tokenProvider.invalidate(usedToken);
398
+ res = await send();
399
+ }
400
+ if (!res.ok) {
401
+ const responseBody = await res.json().catch(() => null);
402
+ return {
403
+ ok: false,
404
+ error: parseErrorResponse(res.status, responseBody)
405
+ };
406
+ }
407
+ if (res.status === 204) return {
408
+ ok: true,
409
+ data: void 0
410
+ };
411
+ return {
412
+ ok: true,
413
+ data: (await res.json()).data
414
+ };
415
+ } catch (err) {
416
+ if (err instanceof ServiceTokenRequestError) return {
417
+ ok: false,
418
+ error: {
419
+ kind: "api",
420
+ code: err.code,
421
+ message: err.message,
422
+ status: err.status
423
+ }
424
+ };
425
+ return {
426
+ ok: false,
427
+ error: {
428
+ kind: "sdk",
429
+ reason: "network",
430
+ message: err instanceof Error ? err.message : "Network error",
431
+ status: 0
432
+ }
433
+ };
434
+ }
435
+ }
436
+ /**
437
+ * Crea un cliente S2S autenticado para comunicarse con la API interna del Auth Service.
438
+ *
439
+ * @example
440
+ * ```ts
441
+ * const tokenProvider = createServiceTokenProvider({
442
+ * authUrl: 'https://auth.miapp.com',
443
+ * clientId: 'mi-servicio',
444
+ * secretHex: env.AUTH_CLIENT_SECRET_HEX,
445
+ * audience: 'auth-api',
446
+ * })
447
+ * const s2s = createS2SClient({ baseUrl: 'https://auth.miapp.com', tokenProvider })
448
+ * const result = await s2s.getUser(userId)
449
+ * if (!result.ok) return handleError(result.error)
450
+ * return result.data // perfil del usuario
451
+ * ```
452
+ */
453
+ function createS2SClient(config) {
454
+ validateHttpsUrl(config.baseUrl, "S2SClientConfig.baseUrl");
455
+ const autorizacionCache = /* @__PURE__ */ new Map();
456
+ return {
457
+ async getUser(userId) {
458
+ const segment = encodePathSegment(userId);
459
+ if (segment === null) return invalidIdResult("getUser");
460
+ return s2sFetch({
461
+ config,
462
+ method: "GET",
463
+ pathname: apiPath(INTERNAL_USER_ROUTES.get, { id: segment })
464
+ });
465
+ },
466
+ /**
467
+ * Consulta múltiples usuarios por ID en una sola llamada.
468
+ *
469
+ * @param input - Input con la lista de IDs a consultar.
470
+ * @param input.ids - Máximo `USERS_BATCH_MAX_IDS` (100, constante del contrato compartido)
471
+ * IDs por llamada. El schema del request no lleva ese tope: lo aplica este cliente.
472
+ * Si se superan, retorna error sin hacer fetch al API.
473
+ */
474
+ async batchUsers(input) {
475
+ if (input.ids.length > 100) return {
476
+ ok: false,
477
+ error: {
478
+ kind: "sdk",
479
+ reason: "malformed",
480
+ message: `batchUsers acepta máximo 100 IDs por llamada (recibidos: ${input.ids.length})`,
481
+ status: 0
482
+ }
483
+ };
484
+ return s2sFetch({
485
+ config,
486
+ method: "POST",
487
+ pathname: apiPath(INTERNAL_USER_ROUTES.batch),
488
+ body: input
489
+ });
490
+ },
491
+ async createWebhookSubscription(input) {
492
+ return s2sFetch({
493
+ config,
494
+ method: "POST",
495
+ pathname: apiPath(WEBHOOK_ROUTES.createSubscription),
496
+ body: input
497
+ });
498
+ },
499
+ async listWebhookSubscriptions() {
500
+ return s2sFetch({
501
+ config,
502
+ method: "GET",
503
+ pathname: apiPath(WEBHOOK_ROUTES.listSubscriptions)
504
+ });
505
+ },
506
+ async getWebhookSubscription(id) {
507
+ const segment = encodePathSegment(id);
508
+ if (segment === null) return invalidIdResult("getWebhookSubscription");
509
+ return s2sFetch({
510
+ config,
511
+ method: "GET",
512
+ pathname: apiPath(WEBHOOK_ROUTES.getSubscription, { id: segment })
513
+ });
514
+ },
515
+ async updateWebhookSubscription(id, input) {
516
+ const segment = encodePathSegment(id);
517
+ if (segment === null) return invalidIdResult("updateWebhookSubscription");
518
+ return s2sFetch({
519
+ config,
520
+ method: "PATCH",
521
+ pathname: apiPath(WEBHOOK_ROUTES.updateSubscription, { id: segment }),
522
+ body: input
523
+ });
524
+ },
525
+ async deleteWebhookSubscription(id) {
526
+ const segment = encodePathSegment(id);
527
+ if (segment === null) return invalidIdResult("deleteWebhookSubscription");
528
+ return s2sFetch({
529
+ config,
530
+ method: "DELETE",
531
+ pathname: apiPath(WEBHOOK_ROUTES.deleteSubscription, { id: segment })
532
+ });
533
+ },
534
+ async rotateWebhookSubscriptionSecret(id) {
535
+ const segment = encodePathSegment(id);
536
+ if (segment === null) return invalidIdResult("rotateWebhookSubscriptionSecret");
537
+ return s2sFetch({
538
+ config,
539
+ method: "POST",
540
+ pathname: apiPath(WEBHOOK_ROUTES.rotateSubscriptionSecret, { id: segment })
541
+ });
542
+ },
543
+ async listWebhookDeliveries(query) {
544
+ if (query?.limit !== void 0 && (query.limit < 1 || query.limit > 100)) return {
545
+ ok: false,
546
+ error: {
547
+ kind: "sdk",
548
+ reason: "malformed",
549
+ message: `listWebhookDeliveries: limit debe estar entre 1 y 100 (recibido: ${query.limit})`,
550
+ status: 0
551
+ }
552
+ };
553
+ if (query?.offset !== void 0 && query.offset < 0) return {
554
+ ok: false,
555
+ error: {
556
+ kind: "sdk",
557
+ reason: "malformed",
558
+ message: `listWebhookDeliveries: offset no puede ser negativo (recibido: ${query.offset})`,
559
+ status: 0
560
+ }
561
+ };
562
+ const rawQuery = query ? buildQueryString(query) : "";
563
+ return s2sFetch({
564
+ config,
565
+ method: "GET",
566
+ pathname: apiPath(WEBHOOK_ROUTES.listDeliveries),
567
+ rawQuery
568
+ });
569
+ },
570
+ async createEmpresa(input) {
571
+ return s2sFetch({
572
+ config,
573
+ method: "POST",
574
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.create),
575
+ body: input
576
+ });
577
+ },
578
+ async updateEmpresa(empresaId, input) {
579
+ const seg = encodeSegments("updateEmpresa", { id: empresaId });
580
+ if (isInvalid(seg)) return seg;
581
+ return s2sFetch({
582
+ config,
583
+ method: "PATCH",
584
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.update, seg),
585
+ body: input
586
+ });
587
+ },
588
+ async listEmpresas(query) {
589
+ return s2sFetch({
590
+ config,
591
+ method: "GET",
592
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.list),
593
+ rawQuery: query ? buildQueryString({ ...query }) : ""
594
+ });
595
+ },
596
+ async getEmpresa(empresaId) {
597
+ const seg = encodeSegments("getEmpresa", { id: empresaId });
598
+ if (isInvalid(seg)) return seg;
599
+ return s2sFetch({
600
+ config,
601
+ method: "GET",
602
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.get, seg)
603
+ });
604
+ },
605
+ async bajaEmpresa(empresaId) {
606
+ const seg = encodeSegments("bajaEmpresa", { id: empresaId });
607
+ if (isInvalid(seg)) return seg;
608
+ return s2sFetch({
609
+ config,
610
+ method: "POST",
611
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.baja, seg)
612
+ });
613
+ },
614
+ async reactivarEmpresa(empresaId) {
615
+ const seg = encodeSegments("reactivarEmpresa", { id: empresaId });
616
+ if (isInvalid(seg)) return seg;
617
+ return s2sFetch({
618
+ config,
619
+ method: "POST",
620
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.reactivar, seg)
621
+ });
622
+ },
623
+ async addEmpresaMember(empresaId, input) {
624
+ const seg = encodeSegments("addEmpresaMember", { id: empresaId });
625
+ if (isInvalid(seg)) return seg;
626
+ return s2sFetch({
627
+ config,
628
+ method: "POST",
629
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.addMember, seg),
630
+ body: input
631
+ });
632
+ },
633
+ async setEmpresaMemberRole(empresaId, userId, input) {
634
+ const seg = encodeSegments("setEmpresaMemberRole", {
635
+ id: empresaId,
636
+ userId
637
+ });
638
+ if (isInvalid(seg)) return seg;
639
+ return s2sFetch({
640
+ config,
641
+ method: "PATCH",
642
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.updateMember, seg),
643
+ body: input
644
+ });
645
+ },
646
+ async removeEmpresaMember(empresaId, userId, input) {
647
+ const seg = encodeSegments("removeEmpresaMember", {
648
+ id: empresaId,
649
+ userId
650
+ });
651
+ if (isInvalid(seg)) return seg;
652
+ return s2sFetch({
653
+ config,
654
+ method: "DELETE",
655
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.removeMember, seg),
656
+ body: input
657
+ });
658
+ },
659
+ async createSucursal(empresaId, input) {
660
+ const seg = encodeSegments("createSucursal", { id: empresaId });
661
+ if (isInvalid(seg)) return seg;
662
+ return s2sFetch({
663
+ config,
664
+ method: "POST",
665
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.createBranch, seg),
666
+ body: input
667
+ });
668
+ },
669
+ async updateSucursal(empresaId, sucursalId, input) {
670
+ const seg = encodeSegments("updateSucursal", {
671
+ id: empresaId,
672
+ sucursalId
673
+ });
674
+ if (isInvalid(seg)) return seg;
675
+ return s2sFetch({
676
+ config,
677
+ method: "PATCH",
678
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.updateBranch, seg),
679
+ body: input
680
+ });
681
+ },
682
+ async deactivateSucursal(empresaId, sucursalId, input) {
683
+ const seg = encodeSegments("deactivateSucursal", {
684
+ id: empresaId,
685
+ sucursalId
686
+ });
687
+ if (isInvalid(seg)) return seg;
688
+ return s2sFetch({
689
+ config,
690
+ method: "DELETE",
691
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.deactivateBranch, seg),
692
+ body: input
693
+ });
694
+ },
695
+ async requestContratacion(empresaId, input) {
696
+ const seg = encodeSegments("requestContratacion", { id: empresaId });
697
+ if (isInvalid(seg)) return seg;
698
+ return s2sFetch({
699
+ config,
700
+ method: "POST",
701
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.requestContratacion, seg),
702
+ body: input
703
+ });
704
+ },
705
+ async cancelContratacion(empresaId, input) {
706
+ const seg = encodeSegments("cancelContratacion", { id: empresaId });
707
+ if (isInvalid(seg)) return seg;
708
+ return s2sFetch({
709
+ config,
710
+ method: "DELETE",
711
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.cancelContratacion, seg),
712
+ body: input
713
+ });
714
+ },
715
+ async getContratacion(empresaId, producto) {
716
+ const seg = encodeSegments("getContratacion", {
717
+ id: empresaId,
718
+ producto
719
+ });
720
+ if (isInvalid(seg)) return seg;
721
+ return s2sFetch({
722
+ config,
723
+ method: "GET",
724
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.getContratacion, seg)
725
+ });
726
+ },
727
+ async setContratacion(empresaId, producto, input) {
728
+ const seg = encodeSegments("setContratacion", {
729
+ id: empresaId,
730
+ producto
731
+ });
732
+ if (isInvalid(seg)) return seg;
733
+ return s2sFetch({
734
+ config,
735
+ method: "PUT",
736
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.setContratacion, seg),
737
+ body: input
738
+ });
739
+ },
740
+ async createGrupo(input) {
741
+ return s2sFetch({
742
+ config,
743
+ method: "POST",
744
+ pathname: apiPath(INTERNAL_GRUPO_ROUTES.create),
745
+ body: input
746
+ });
747
+ },
748
+ async renameGrupo(grupoId, input) {
749
+ const seg = encodeSegments("renameGrupo", { id: grupoId });
750
+ if (isInvalid(seg)) return seg;
751
+ return s2sFetch({
752
+ config,
753
+ method: "PATCH",
754
+ pathname: apiPath(INTERNAL_GRUPO_ROUTES.rename, seg),
755
+ body: input
756
+ });
757
+ },
758
+ async addGrupoEmpresa(grupoId, input) {
759
+ const seg = encodeSegments("addGrupoEmpresa", { id: grupoId });
760
+ if (isInvalid(seg)) return seg;
761
+ return s2sFetch({
762
+ config,
763
+ method: "POST",
764
+ pathname: apiPath(INTERNAL_GRUPO_ROUTES.addEmpresa, seg),
765
+ body: input
766
+ });
767
+ },
768
+ async removeGrupoEmpresa(grupoId, empresaId, input) {
769
+ const seg = encodeSegments("removeGrupoEmpresa", {
770
+ id: grupoId,
771
+ empresaId
772
+ });
773
+ if (isInvalid(seg)) return seg;
774
+ return s2sFetch({
775
+ config,
776
+ method: "DELETE",
777
+ pathname: apiPath(INTERNAL_GRUPO_ROUTES.removeEmpresa, seg),
778
+ body: input
779
+ });
780
+ },
781
+ async searchDirectorio(query) {
782
+ return s2sFetch({
783
+ config,
784
+ method: "GET",
785
+ pathname: apiPath(DIRECTORIO_ROUTES.search),
786
+ rawQuery: query ? buildQueryString({ ...query }) : ""
787
+ });
788
+ },
789
+ async createAutorizacion(otorganteId, input) {
790
+ const seg = encodeSegments("createAutorizacion", { id: otorganteId });
791
+ if (isInvalid(seg)) return seg;
792
+ return s2sFetch({
793
+ config,
794
+ method: "POST",
795
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.createAutorizacion, seg),
796
+ body: input
797
+ });
798
+ },
799
+ async deleteAutorizacion(empresaId, autorizacionId, input) {
800
+ const seg = encodeSegments("deleteAutorizacion", {
801
+ id: empresaId,
802
+ autorizacionId
803
+ });
804
+ if (isInvalid(seg)) return seg;
805
+ return s2sFetch({
806
+ config,
807
+ method: "DELETE",
808
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.deleteAutorizacion, seg),
809
+ body: input
810
+ });
811
+ },
812
+ async listAutorizaciones(empresaId, input) {
813
+ const seg = encodeSegments("listAutorizaciones", { id: empresaId });
814
+ if (isInvalid(seg)) return seg;
815
+ return s2sFetch({
816
+ config,
817
+ method: "GET",
818
+ pathname: apiPath(INTERNAL_EMPRESA_ROUTES.listAutorizaciones, seg),
819
+ rawQuery: buildQueryString({ direccion: input.direccion }),
820
+ headers: { [SUBJECT_TOKEN_HEADER]: input.subjectToken }
821
+ });
822
+ },
823
+ async checkAutorizacion(input) {
824
+ const key = `${input.otorgante}|${input.beneficiaria}|${input.permiso}`;
825
+ const hit = autorizacionCache.get(key);
826
+ if (hit !== void 0) {
827
+ if (Date.now() < hit.expiresAtMs) return {
828
+ ok: true,
829
+ data: { autorizado: hit.autorizado }
830
+ };
831
+ autorizacionCache.delete(key);
832
+ }
833
+ const result = await s2sFetch({
834
+ config,
835
+ method: "GET",
836
+ pathname: apiPath(INTERNAL_AUTORIZACION_ROUTES.verificar),
837
+ rawQuery: buildQueryString({
838
+ otorgante: input.otorgante,
839
+ beneficiaria: input.beneficiaria,
840
+ permiso: input.permiso
841
+ })
842
+ });
843
+ if (result.ok) {
844
+ if (autorizacionCache.size >= AUTORIZACION_CACHE_MAX_ENTRIES) {
845
+ const now = Date.now();
846
+ for (const [k, v] of autorizacionCache) if (now >= v.expiresAtMs) autorizacionCache.delete(k);
847
+ if (autorizacionCache.size >= AUTORIZACION_CACHE_MAX_ENTRIES) {
848
+ const oldest = autorizacionCache.keys().next().value;
849
+ if (oldest !== void 0) autorizacionCache.delete(oldest);
850
+ }
851
+ }
852
+ autorizacionCache.set(key, {
853
+ autorizado: result.data.autorizado,
854
+ expiresAtMs: Date.now() + AUTORIZACION_CACHE_TTL_MS
855
+ });
856
+ }
857
+ return result;
858
+ }
859
+ };
860
+ }
861
+ // src/s2s/error-codes.ts
862
+ /**
863
+ * Códigos de error específicos del módulo S2S.
864
+ *
865
+ * Complementan a `ERROR_CODES` (core) para los errores propios de los endpoints
866
+ * `/api/v1/internal/*` que consume `createS2SClient`: usuarios y subscriptions de webhooks.
867
+ *
868
+ * @example
869
+ * ```ts
870
+ * import { S2S_ERROR_CODES } from '@eva_solutions/auth-sdk/s2s'
871
+ *
872
+ * const r = await s2s.rotateWebhookSubscriptionSecret(id)
873
+ * if (!r.ok && r.error.kind === 'api' && r.error.code === S2S_ERROR_CODES.webhook_secret_rotation_conflict) {
874
+ * // otra rotación concurrente ganó; esta llamada no recibió clave. Reintenta.
875
+ * }
876
+ * ```
877
+ */
878
+ const S2S_ERROR_CODES = {
879
+ /** `GET /internal/users/{id}`: el usuario no existe o está soft-deleted (404). */
880
+ user_not_found: "user_not_found",
881
+ /** Ya existe una subscription del mismo service client con la misma URL y los mismos eventCodes (409). */
882
+ webhook_subscription_duplicate: "webhook_subscription_duplicate",
883
+ /** La subscription no existe o pertenece a otro service client (404). */
884
+ webhook_subscription_not_found: "webhook_subscription_not_found",
885
+ /**
886
+ * `rotate-secret` perdió contra otra rotación concurrente de la misma subscription (409).
887
+ * El perdedor no recibe clave; es reintentable.
888
+ */
889
+ webhook_secret_rotation_conflict: "webhook_secret_rotation_conflict",
890
+ /** URL de webhook inválida: reglas estáticas, IP privada o host sin direcciones (400). */
891
+ webhook_url_invalid: "webhook_url_invalid",
892
+ /** `eventCodes` vacío al crear o actualizar (400). */
893
+ webhook_event_codes_empty: "webhook_event_codes_empty"
894
+ };
895
+ export { createServiceTokenProvider as a, ServiceTokenRequestError as i, createS2SClient as n, SERVICE_TOKEN_RENEW_MARGIN_MS as r, S2S_ERROR_CODES as t };
896
+
897
+ //# sourceMappingURL=s2s-B_0SIQzn.js.map