@eva_solutions/auth-sdk 0.0.0-stage → 3.0.0-dev-20261109133200

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-3avoUa9e.d.cts +95 -0
  37. package/dist/config-3avoUa9e.d.cts.map +1 -0
  38. package/dist/config-COX7rGAP.d.ts +95 -0
  39. package/dist/config-COX7rGAP.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-0woh13Wd.d.cts +105 -0
  65. package/dist/error-helpers-0woh13Wd.d.cts.map +1 -0
  66. package/dist/error-helpers-Ct3AUK1I.d.ts +105 -0
  67. package/dist/error-helpers-Ct3AUK1I.d.ts.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-CIH_2t7Y.d.ts +240 -0
  113. package/dist/index-CIH_2t7Y.d.ts.map +1 -0
  114. package/dist/index-CZru7c5T.d.cts +1711 -0
  115. package/dist/index-CZru7c5T.d.cts.map +1 -0
  116. package/dist/index-CZru7c5T.d.ts +1711 -0
  117. package/dist/index-CZru7c5T.d.ts.map +1 -0
  118. package/dist/index-DMVe5IJA.d.cts +240 -0
  119. package/dist/index-DMVe5IJA.d.cts.map +1 -0
  120. package/dist/index-DRWKobSp.d.ts +312 -0
  121. package/dist/index-DRWKobSp.d.ts.map +1 -0
  122. package/dist/index-DU9wJ-P2.d.cts +312 -0
  123. package/dist/index-DU9wJ-P2.d.cts.map +1 -0
  124. package/dist/index-DbiXllNy.d.cts +66 -0
  125. package/dist/index-DbiXllNy.d.cts.map +1 -0
  126. package/dist/index-z9r98qlk.d.ts +66 -0
  127. package/dist/index-z9r98qlk.d.ts.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-Ai3iIFlB.d.cts +538 -0
  186. package/dist/types-Ai3iIFlB.d.cts.map +1 -0
  187. package/dist/types-Bxg4hCMc.d.cts +52 -0
  188. package/dist/types-Bxg4hCMc.d.cts.map +1 -0
  189. package/dist/types-CEABh1Zf.d.ts +96 -0
  190. package/dist/types-CEABh1Zf.d.ts.map +1 -0
  191. package/dist/types-Ci0Jq0xB.d.cts +96 -0
  192. package/dist/types-Ci0Jq0xB.d.cts.map +1 -0
  193. package/dist/types-DXg8BvEh.d.ts +52 -0
  194. package/dist/types-DXg8BvEh.d.ts.map +1 -0
  195. package/dist/types-_zY7QbM5.d.ts +538 -0
  196. package/dist/types-_zY7QbM5.d.ts.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
package/README.md CHANGED
@@ -1,3 +1,482 @@
1
- # Temporary Holding Version
1
+ # @eva_solutions/auth-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Official TypeScript client for the EVA Auth Service — secure cookie-based auth with JWT ES256, JWKS rotation, S2S signing, and webhooks.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@eva_solutions/auth-sdk)](https://www.npmjs.com/package/@eva_solutions/auth-sdk)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@eva_solutions/auth-sdk)](https://www.npmjs.com/package/@eva_solutions/auth-sdk)
7
+ [![license MIT](https://img.shields.io/npm/l/@eva_solutions/auth-sdk)](./LICENSE)
8
+ [![CI status](https://github.com/eva-solutions-org/auth-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/eva-solutions-org/auth-sdk/actions/workflows/ci.yml)
9
+ [![Provenance](https://img.shields.io/badge/Provenance-%E2%9C%93-blue)](https://docs.npmjs.com/generating-provenance-statements)
10
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@eva_solutions/auth-sdk)](https://bundlephobia.com/package/@eva_solutions/auth-sdk)
11
+
12
+ ---
13
+
14
+ `@eva_solutions/auth-sdk` is the official TypeScript client for the EVA Auth Service. It handles the full authentication lifecycle — OTP login, secure cookie-based session management, automatic token refresh, and JWT verification with ES256 + JWKS rotation — so application code stays clean.
15
+
16
+ The SDK bridges two transport layers: HttpOnly cookies between the React frontend and the Hono backend, and custom headers between the backend and the EVA Auth Service. Neither layer is visible to consumers.
17
+
18
+ It ships as tree-shakeable ESM + CJS with TypeScript declarations, sourcemaps, and zero runtime dependencies on heavy frameworks. Pick only the entry points you need.
19
+
20
+ ## Features
21
+
22
+ - JWT verification with ES256 + automatic JWKS rotation (10 min cache, 30 s refetch cooldown)
23
+ - Secure HttpOnly cookie session management — no tokens exposed to JavaScript
24
+ - React hooks for client-side auth state (`useAuth`, `useUser`, `useSessions`, `useEmpresas`)
25
+ - Hono middleware and pre-built auth routes for server-side route protection
26
+ - S2S (service-to-service) client with automatic HMAC canonical signing
27
+ - Webhook event verification (HMAC-SHA256 with timestamp window)
28
+ - Built-in error handling with `Result<T>` pattern — no thrown exceptions for business flows
29
+ - Tree-shakeable ESM + CJS + `.d.ts` with declaration maps and sourcemaps
30
+ - 9 independent entry points — import only what your runtime needs
31
+ - Typed extra JWT claims via optional Zod schema (`extraClaimsSchema`)
32
+ - One npm package for every environment — the Auth Service URL is set at boot with `configureEvaAuth({ authUrl })`
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pnpm add @eva_solutions/auth-sdk
40
+ # or
41
+ npm install @eva_solutions/auth-sdk
42
+ # or
43
+ yarn add @eva_solutions/auth-sdk
44
+ ```
45
+
46
+ **Optional peer dependencies** — install only the peers you need:
47
+
48
+ ```bash
49
+ pnpm add hono # required for @eva_solutions/auth-sdk/hono
50
+ pnpm add react # required for @eva_solutions/auth-sdk/react
51
+ pnpm add @hono/zod-openapi # required for @eva_solutions/auth-sdk/hono-openapi
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Quick Start
57
+
58
+ ### Server — Hono
59
+
60
+ ```ts
61
+ import { Hono } from 'hono';
62
+ import { configureEvaAuth } from '@eva_solutions/auth-sdk';
63
+ import { evaAuth, evaAuthRoutes } from '@eva_solutions/auth-sdk/hono';
64
+
65
+ configureEvaAuth({ authUrl: process.env.AUTH_URL }); // once at boot, before the first request
66
+
67
+ const app = new Hono();
68
+
69
+ app.route('/auth', evaAuthRoutes()); // pre-built login / logout / me routes
70
+ app.use('/api/*', evaAuth()); // protect routes — verifies JWT, auto-refreshes
71
+
72
+ app.get('/api/me', (c) => {
73
+ const { id, sessionId } = c.var.evaPayload;
74
+ return c.json({ id, sessionId });
75
+ });
76
+ ```
77
+
78
+ > `authUrl` is required before the first request that needs the Auth Service. If it is missing, those calls throw a configuration error (HTTP 500, never 401). See [Configuration](#configuration) below.
79
+
80
+ Typed extra JWT claims:
81
+
82
+ ```ts
83
+ import { z } from 'zod';
84
+ import { evaAuth } from '@eva_solutions/auth-sdk/hono';
85
+
86
+ const auth = evaAuth({
87
+ extraClaimsSchema: z.object({
88
+ phone: z.string(),
89
+ empresaId: z.string().uuid(),
90
+ }),
91
+ });
92
+
93
+ app.use('/api/*', auth);
94
+ ```
95
+
96
+ ### Client — React
97
+
98
+ ```tsx
99
+ import { useState } from 'react';
100
+ import { EvaAuthProvider, useAuth, useUser } from '@eva_solutions/auth-sdk/react';
101
+
102
+ function App() {
103
+ return (
104
+ <EvaAuthProvider basePath="/auth">
105
+ <Profile />
106
+ </EvaAuthProvider>
107
+ );
108
+ }
109
+
110
+ function Profile() {
111
+ const { isAuthenticated, isLoading, login, logout } = useAuth();
112
+ const { data: user } = useUser();
113
+ // Two-step OTP login: keep the challengeId in memory between the steps.
114
+ const [challengeId, setChallengeId] = useState<string>();
115
+
116
+ if (isLoading) return <p>Loading...</p>;
117
+ if (!isAuthenticated) {
118
+ const phone = '+1555000000';
119
+ return challengeId ? (
120
+ <button onClick={() => login.verify({ phone, code: '123456', challengeId })}>
121
+ Verify code
122
+ </button>
123
+ ) : (
124
+ <button
125
+ onClick={async () => {
126
+ const res = await login.getCode({ phone });
127
+ if (res.ok) setChallengeId(res.data.challengeId);
128
+ }}
129
+ >
130
+ Sign in
131
+ </button>
132
+ );
133
+ }
134
+
135
+ return (
136
+ <div>
137
+ <p>Hello, {user?.name}</p>
138
+ <button onClick={logout}>Sign out</button>
139
+ </div>
140
+ );
141
+ }
142
+ ```
143
+
144
+ > **OTP login needs a `challengeId`.** `get-code` returns `{ data: { message, challengeId } }` and `/login` (and `login.verify`) requires that `challengeId` together with the code. Keep it in memory between the two calls; a new `get-code` invalidates the previous one. A login without it fails with `400`. Coming from 2.x? See the [migration guide](../../docs/migration.md#8-login-otp-requiere-challengeid).
145
+
146
+ ### Generic Runtime (non-Hono backends)
147
+
148
+ ```ts
149
+ import { verifyRequest } from '@eva_solutions/auth-sdk/generic';
150
+
151
+ const result = await verifyRequest(request); // standard Web API Request
152
+
153
+ if (!result.ok) {
154
+ return new Response('Unauthorized', { status: 401 });
155
+ }
156
+
157
+ const { payload, newCookies } = result.data;
158
+ // payload.id, payload.sessionId
159
+ // newCookies: string[] | undefined — forward to Set-Cookie if present
160
+ ```
161
+
162
+ ### Service-to-Service (service tokens)
163
+
164
+ ```ts
165
+ import { createS2SClient, createServiceTokenProvider } from '@eva_solutions/auth-sdk/s2s';
166
+
167
+ // The provider asks Auth for a service token (HMAC-signed request), caches it,
168
+ // renews it 30 s early and de-duplicates concurrent requests.
169
+ const tokenProvider = createServiceTokenProvider({
170
+ authUrl: 'https://auth.example.com',
171
+ clientId: 'my-service',
172
+ secretHex: process.env.AUTH_CLIENT_SECRET_HEX!,
173
+ audience: 'auth-api',
174
+ });
175
+
176
+ // Every call goes out as `Authorization: Bearer <token>`; after a 401 the token is
177
+ // invalidated and the call is retried once.
178
+ const s2s = createS2SClient({ baseUrl: 'https://auth.example.com', tokenProvider });
179
+ const user = await s2s.getUser('user-id-123');
180
+ ```
181
+
182
+ Verifying a service token you receive (no `product` needed):
183
+
184
+ ```ts
185
+ import { serviceTokenAuth } from '@eva_solutions/auth-sdk/hono';
186
+
187
+ app.use('/v1/internal/*', serviceTokenAuth({ audience: 'whatsapp-api', requiredScope: 'messages:send:text' }));
188
+ // c.var.serviceClientId (sub), c.var.serviceClientSlug (client_id), c.var.serviceScopes
189
+ ```
190
+
191
+ User sessions are per product: call `configureEvaAuth({ authUrl, product: 'whatsapp-panel' })` at boot. Cookies become `eva_<product>_access_token` / `eva_<product>_refresh_token`.
192
+
193
+ Low-level signing (when you need manual control). `signS2SHeaders` returns the four
194
+ required headers (`x-eva-client-id`, `x-eva-timestamp`, `x-eva-nonce`, `x-eva-signature`);
195
+ each call generates a fresh nonce:
196
+
197
+ ```ts
198
+ import { signS2SHeaders } from '@eva_solutions/auth-sdk/s2s';
199
+
200
+ const bodyBytes = new TextEncoder().encode(JSON.stringify({ ids: ['user-id-123'] }));
201
+ const headers = await signS2SHeaders({
202
+ method: 'POST',
203
+ path: '/api/v1/internal/users/batch',
204
+ bodyBytes,
205
+ clientId: 'my-service',
206
+ secretHex: process.env.S2S_SECRET!,
207
+ });
208
+
209
+ await fetch('https://auth.example.com/api/v1/internal/users/batch', {
210
+ method: 'POST',
211
+ headers: { 'content-type': 'application/json', ...headers },
212
+ body: bodyBytes,
213
+ });
214
+ ```
215
+
216
+ ### Verifying inbound S2S requests
217
+
218
+ If your service receives requests signed via `signS2SHeaders` or `signS2SRequest` (only the Auth token endpoint and `/admin` still use HMAC),
219
+ use the server-side verification primitives to authenticate them.
220
+
221
+ #### Hono middleware (recommended for Hono apps)
222
+
223
+ ```ts
224
+ import { Hono } from 'hono'
225
+ import { s2sAuth, requireScope } from '@eva_solutions/auth-sdk/hono'
226
+
227
+ const app = new Hono()
228
+
229
+ app.use('*', s2sAuth({
230
+ secretStore: async (clientId) => {
231
+ const client = await db.serviceClients.findBySlug(clientId)
232
+ return client?.secret ?? null
233
+ },
234
+ scopesOf: async (clientId) => {
235
+ const client = await db.serviceClients.findBySlug(clientId)
236
+ return client?.scopes ?? []
237
+ },
238
+ onAuth: (clientId, scopes) => {
239
+ logger.info({ clientId, scopes }, 's2s authenticated')
240
+ },
241
+ }))
242
+
243
+ app.use('/admin/*', requireScope('admin'))
244
+
245
+ app.post('/internal/users', async (c) => {
246
+ const clientId = c.get('s2sClientId')
247
+ // ... handle request
248
+ })
249
+ ```
250
+
251
+ On verification failure: responds with HTTP 401 and body `{ error: { code, message } }`.
252
+ On scope failure: responds with HTTP 403 and body `{ error: { code: 'insufficient_scope', required: '<scope>' } }`.
253
+
254
+ **Multi-scope (v1.1.0+)**:
255
+
256
+ ```ts
257
+ // AND — all scopes must be present
258
+ app.get('/admin/audit', s2sAuth(opts), requireScope({ all: ['admin', 'audit'] }), handler)
259
+ // 403 if missing any: { error: { code: 'insufficient_scope', requiredAll: [...], missing: [...] } }
260
+
261
+ // OR — at least one scope must be present
262
+ app.get('/data', s2sAuth(opts), requireScope({ any: ['data:read', 'data:write'] }), handler)
263
+ // 403 if none: { error: { code: 'insufficient_scope', requiredAny: [...] } }
264
+ ```
265
+
266
+ Setup-time validation: empty arrays, empty objects, and mixed `{ all, any }` throw `TypeError` immediately when constructing the middleware.
267
+
268
+ #### Framework-agnostic (Web API Request)
269
+
270
+ ```ts
271
+ import { verifyS2SRequest } from '@eva_solutions/auth-sdk/s2s'
272
+
273
+ const result = await verifyS2SRequest(request, {
274
+ secretStore: async (clientId) => lookupSecret(clientId),
275
+ })
276
+
277
+ if (!result.ok) {
278
+ return new Response(JSON.stringify({ error: result.error }), {
279
+ status: 401,
280
+ headers: { 'content-type': 'application/json' },
281
+ })
282
+ }
283
+
284
+ const { clientId } = result.data
285
+ ```
286
+
287
+ #### Replay protection (built-in)
288
+
289
+ Provide a `replayCache` callback to `verifyS2SRequest` (or `s2sAuth`) for atomic
290
+ nonce-based replay protection (every S2S request carries a unique `x-eva-nonce`; the SDK passes the key `${clientId}:${nonce}` and a TTL of `2 * timestampWindowSeconds + 1`, 241 s by default):
291
+
292
+ ```ts
293
+ import { Redis } from 'ioredis'
294
+ const redis = new Redis(/* ... */)
295
+
296
+ const replayCache = {
297
+ async setIfAbsent(key: string, ttlSeconds: number): Promise<boolean> {
298
+ const result = await redis.set(key, '1', 'EX', ttlSeconds, 'NX')
299
+ return result === 'OK' // null when key existed (replay detected)
300
+ },
301
+ }
302
+
303
+ const result = await verifyS2SRequest(request, { secretStore, replayCache })
304
+ if (!result.ok) return /* handle */
305
+ ```
306
+
307
+ The `replayCache.setIfAbsent` callback is **store-agnostic** — wrap Redis, Memcached, an
308
+ in-memory `Map`, or any backend that can atomically check-and-set with TTL.
309
+
310
+ > **Note**: the `replayCache` store should be **shared across all instances of your
311
+ > service** for the same `clientId`/secret pair to prevent cross-instance replay.
312
+
313
+ If `setIfAbsent` rejects (e.g. store unreachable), the error propagates — the SDK
314
+ treats store failure as **fail-closed** (no degraded mode).
315
+
316
+ #### Performance benchmarks (optional)
317
+
318
+ For empirical timing data of `verifyS2SRequest`, run the on-demand benchmark:
319
+
320
+ ```bash
321
+ pnpm exec vitest bench tests/perf/s2s-timing.bench.ts
322
+ ```
323
+
324
+ This benchmark is not part of CI by default — use it locally to capture
325
+ baseline timing and detect performance regressions when modifying
326
+ `verifyS2SRequest` or `buildS2SCanonicalString`.
327
+
328
+ ---
329
+
330
+ ### Webhook Verification
331
+
332
+ ```ts
333
+ import { verifyWebhookSignature } from '@eva_solutions/auth-sdk/webhooks';
334
+
335
+ const isValid = await verifyWebhookSignature({
336
+ payload: req.body, // raw string body
337
+ signature: req.headers['x-eva-signature']!,
338
+ secret: process.env.WEBHOOK_SECRET!,
339
+ timestamp: req.headers['x-eva-timestamp']!,
340
+ });
341
+
342
+ if (!isValid) return new Response('Forbidden', { status: 403 });
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Configuration
348
+
349
+ ```ts
350
+ import { configureEvaAuth } from '@eva_solutions/auth-sdk';
351
+
352
+ // Call once at application boot, before the first request
353
+ configureEvaAuth({
354
+ authUrl: 'https://auth.example.com', // required — Auth Service URL (see below)
355
+ cookieDomain: '.example.com', // share cookies across subdomains
356
+ errorWire: 'api', // 'api' (default) | 'string' (legacy 0.x compat)
357
+ errorMessages: { // optional — override SDK error messages
358
+ authRequired: 'Authentication required',
359
+ },
360
+ });
361
+ ```
362
+
363
+ | Option | Type | Default | Description |
364
+ |--------|------|---------|-------------|
365
+ | `authUrl` | `string` | — (required) | Base URL of the Auth Service. Immutable once set. |
366
+ | `cookieDomain` | `string` | `undefined` | Cookie `Domain` attribute — use `'.example.com'` for subdomain sharing. |
367
+ | `errorWire` | `'api' \| 'string'` | `'api'` | Wire shape for 4xx responses. `'api'` emits `{ error: { code, message } }`. `'string'` emits `{ error: string }` (legacy). |
368
+ | `errorMessages` | `Partial<EvaErrorMessages>` | built-in defaults | Granular override of SDK error messages (16 keys). Unknown keys throw. |
369
+
370
+ The Auth Service URL is configured at runtime with `authUrl`, once at boot, before the first request that needs the Auth Service. One package serves every environment.
371
+
372
+ - Accepted: `https://host` (a path is allowed), or `http` only on loopback (`localhost`, `127.0.0.1`, `[::1]`).
373
+ - Rejected (throws, without echoing the value): remote `http`, malformed values, userinfo, query and fragment.
374
+ - Immutable: the same value again is a no-op, a different value throws.
375
+ - If it is missing, the calls that need it throw a configuration error (`EvaAuthConfigError`): answer **500**, never 401.
376
+ - Auth cookies are always `Secure`. Safari drops them on `http://localhost`, so use https in local development.
377
+
378
+ For full configuration details see [docs/configuration.md](../../docs/configuration.md).
379
+
380
+ ---
381
+
382
+ ## Entry Points
383
+
384
+ The SDK exports 9 independent entry points. Import only what your runtime needs.
385
+
386
+ | Import | Description | Required peer |
387
+ |--------|-------------|---------------|
388
+ | `@eva_solutions/auth-sdk` | Core: client factory, JWT verify, JWKS, error system, types, constants | — |
389
+ | `@eva_solutions/auth-sdk/hono` | `evaAuth()` middleware, `evaAuthRoutes()`, helpers, cookies | `hono >=4.12.19` |
390
+ | `@eva_solutions/auth-sdk/react` | `EvaAuthProvider`, `useAuth`, `useUser`, `useSessions`, `useEmpresas`, `authFetch` | `react >=19.2.6` |
391
+ | `@eva_solutions/auth-sdk/generic` | `verifyRequest()`, `setTokenCookies()`, `clearTokenCookies()` — Web API `Request` | — |
392
+ | `@eva_solutions/auth-sdk/hono-openapi` | `evaAuthOpenAPIRoutes()` — OpenAPI 3.1 variant with `/doc` | `hono >=4.12.19`, `@hono/zod-openapi >=1.4.0` |
393
+ | `@eva_solutions/auth-sdk/webhooks` | `verifyWebhookSignature()`, `EVENT_CODES`, webhook types | — |
394
+ | `@eva_solutions/auth-sdk/admin` | `createAdminClient()` — service clients and user management | — |
395
+ | `@eva_solutions/auth-sdk/s2s` | `createS2SClient()`, `signS2SHeaders()`, `signS2SRequest()` — HMAC S2S signing | — |
396
+ | `@eva_solutions/auth-sdk/contracts` | Shared wire contract of the Auth API: error and login schemas, `ERROR_CODES`, header names, `AUTH_ROUTES`. Used by the Auth API; the consumer must resolve a single `zod` copy. See [docs/api.md](../../docs/api.md#eva_solutionsauth-sdkcontracts) | — |
397
+
398
+ ---
399
+
400
+ ## JWT Configuration
401
+
402
+ The SDK verifies tokens issued by the EVA Auth Service. The JWT `iss` claim is fixed to the value emitted by the official service (`iss: 'auth-service'`), and the user access token `aud` must equal the product you set with `configureEvaAuth({ product })`. Service tokens must carry `typ: 'svc-at+jwt'`, a `svc-` key id and the `aud` of the receiving service. These values reflect the Auth Service contract and are intentional.
403
+
404
+ ---
405
+
406
+ ## Error Handling
407
+
408
+ All SDK operations return `Result<T>` — never throw for business flows:
409
+
410
+ ```ts
411
+ import { isNotFound, isUnauthorized, getMessage } from '@eva_solutions/auth-sdk';
412
+
413
+ const result = await client.getUser(id);
414
+
415
+ if (!result.ok) {
416
+ if (isNotFound(result.error)) return c.notFound();
417
+ if (isUnauthorized(result.error)) return c.json({ error: 'Unauthorized' }, 401);
418
+ return c.json({ error: getMessage(result.error) }, 500);
419
+ }
420
+
421
+ return c.json(result.data);
422
+ ```
423
+
424
+ Available error helpers (re-exported from core):
425
+
426
+ ```ts
427
+ // Type guards
428
+ isApiError(err), isSdkError(err)
429
+
430
+ // Generic matchers
431
+ matchError(err, code), matchSdkReason(err, reason)
432
+
433
+ // API shortcuts (12)
434
+ isNotFound, isUnauthorized, isForbidden, isConflict,
435
+ isValidationError, isRateLimited, isGone, isUnprocessableEntity,
436
+ isBadRequest, isInternalError, isServiceUnavailable, isAccountStateLocked
437
+
438
+ // SDK shortcuts (6)
439
+ isNetworkError, isTokenInvalid, isAuthRequired,
440
+ isRefreshNoTokens, isVerifyFailed, isMalformed
441
+ ```
442
+
443
+ ---
444
+
445
+ ## Documentation
446
+
447
+ Full API reference, architecture diagrams, and configuration guide are available at:
448
+
449
+ **[https://eva-solutions-org.github.io/auth-sdk](https://eva-solutions-org.github.io/auth-sdk)**
450
+
451
+ Generated with TypeDoc + DMT theme. Source in `docs/`.
452
+
453
+ | Document | Contents |
454
+ |----------|----------|
455
+ | [docs/api.md](../../docs/api.md) | Complete API reference per entry point |
456
+ | [docs/architecture.md](../../docs/architecture.md) | Two-layer model, full auth flow, module structure |
457
+ | [docs/configuration.md](../../docs/configuration.md) | configureEvaAuth options (authUrl, cookieDomain, errorWire), build and distribution |
458
+ | [docs/security.md](../../docs/security.md) | Cookies, JWT, JWKS cache, deduplication |
459
+ | [docs/integrations.md](../../docs/integrations.md) | How the SDK fits the EVA system: consumers, cross-repo contracts, runtime Auth URL, releasing |
460
+ | [docs/migration.md](../../docs/migration.md) | Migration guides 2.x → 3.0.0 and 0.x → 1.0.0 |
461
+
462
+ ---
463
+
464
+ ## Security
465
+
466
+ Found a vulnerability? Please report it via [GitHub Security Advisories](https://github.com/eva-solutions-org/auth-sdk/security/advisories/new) or email `arturo.rodas.gonzales@gmail.com`. **Do not open a public issue for security reports.** See [SECURITY.md](../../SECURITY.md) for the full disclosure process and response timeline.
467
+
468
+ ---
469
+
470
+ ## Contributing
471
+
472
+ Contributions are welcome. See [CONTRIBUTING.md](../../CONTRIBUTING.md) for the development workflow, changeset process, and coding conventions. Please read [CODE_OF_CONDUCT.md](../../CODE_OF_CONDUCT.md) before participating.
473
+
474
+ ---
475
+
476
+ ## License
477
+
478
+ [MIT](./LICENSE) © 2026 EVA Solutions
479
+
480
+ ---
481
+
482
+ *Built on [`jose`](https://github.com/panva/jose), [`hono`](https://hono.dev), [`react`](https://react.dev), and the broader TypeScript ecosystem.*
@@ -0,0 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_admin = require("../admin-Q6lbWqLM.cjs");
3
+ exports.ADMIN_ERROR_CODES = require_admin.ADMIN_ERROR_CODES;
4
+ exports.createAdminClient = require_admin.createAdminClient;
@@ -0,0 +1,2 @@
1
+ import { A as SetServiceClientGrantInput, C as RestoreUserResult, D as ServiceClientPublic, E as ServiceClientGrant, M as UpdateServiceClientInput, N as UpdateServiceClientResult, O as SetProductoPermisosAutorizablesInput, S as RepublishPendingWebhooksResult, T as ServiceClientDelegation, _ as ProductoPermisosAutorizablesResult, a as AdminClientConfig, b as ReassignEmpresaTitularResult, c as AdminSetContratacionInput, d as CreateServiceClientResult, f as ListProductosResult, g as MaintenanceCleanupResult, h as MaintenanceCleanupQuery, i as createAdminClient, j as UpdateProductoInput, k as SetServiceClientDelegationInput, l as CreateProductoInput, m as ListServiceClientGrantsResult, n as AdminErrorCode, o as AdminContratacionResult, p as ListServiceClientDelegationsResult, r as AdminClient, s as AdminEmpresaEstadoResult, t as ADMIN_ERROR_CODES, u as CreateServiceClientInput, v as ProductoResult, w as RotateSecretResult, x as RepublishPendingWebhooksQuery, y as ReassignEmpresaTitularInput } from "../index-DMVe5IJA.cjs";
2
+ export { ADMIN_ERROR_CODES, type AdminClient, type AdminClientConfig, type AdminContratacionResult, type AdminEmpresaEstadoResult, type AdminErrorCode, type AdminSetContratacionInput, type CreateProductoInput, type CreateServiceClientInput, type CreateServiceClientResult, type ListProductosResult, type ListServiceClientDelegationsResult, type ListServiceClientGrantsResult, type MaintenanceCleanupQuery, type MaintenanceCleanupResult, type ProductoPermisosAutorizablesResult, type ProductoResult, type ReassignEmpresaTitularInput, type ReassignEmpresaTitularResult, type RepublishPendingWebhooksQuery, type RepublishPendingWebhooksResult, type RestoreUserResult, type RotateSecretResult, type ServiceClientDelegation, type ServiceClientGrant, type ServiceClientPublic, type SetProductoPermisosAutorizablesInput, type SetServiceClientDelegationInput, type SetServiceClientGrantInput, type UpdateProductoInput, type UpdateServiceClientInput, type UpdateServiceClientResult, createAdminClient };
@@ -0,0 +1,2 @@
1
+ import { A as SetServiceClientGrantInput, C as RestoreUserResult, D as ServiceClientPublic, E as ServiceClientGrant, M as UpdateServiceClientInput, N as UpdateServiceClientResult, O as SetProductoPermisosAutorizablesInput, S as RepublishPendingWebhooksResult, T as ServiceClientDelegation, _ as ProductoPermisosAutorizablesResult, a as AdminClientConfig, b as ReassignEmpresaTitularResult, c as AdminSetContratacionInput, d as CreateServiceClientResult, f as ListProductosResult, g as MaintenanceCleanupResult, h as MaintenanceCleanupQuery, i as createAdminClient, j as UpdateProductoInput, k as SetServiceClientDelegationInput, l as CreateProductoInput, m as ListServiceClientGrantsResult, n as AdminErrorCode, o as AdminContratacionResult, p as ListServiceClientDelegationsResult, r as AdminClient, s as AdminEmpresaEstadoResult, t as ADMIN_ERROR_CODES, u as CreateServiceClientInput, v as ProductoResult, w as RotateSecretResult, x as RepublishPendingWebhooksQuery, y as ReassignEmpresaTitularInput } from "../index-CIH_2t7Y.js";
2
+ export { ADMIN_ERROR_CODES, type AdminClient, type AdminClientConfig, type AdminContratacionResult, type AdminEmpresaEstadoResult, type AdminErrorCode, type AdminSetContratacionInput, type CreateProductoInput, type CreateServiceClientInput, type CreateServiceClientResult, type ListProductosResult, type ListServiceClientDelegationsResult, type ListServiceClientGrantsResult, type MaintenanceCleanupQuery, type MaintenanceCleanupResult, type ProductoPermisosAutorizablesResult, type ProductoResult, type ReassignEmpresaTitularInput, type ReassignEmpresaTitularResult, type RepublishPendingWebhooksQuery, type RepublishPendingWebhooksResult, type RestoreUserResult, type RotateSecretResult, type ServiceClientDelegation, type ServiceClientGrant, type ServiceClientPublic, type SetProductoPermisosAutorizablesInput, type SetServiceClientDelegationInput, type SetServiceClientGrantInput, type UpdateProductoInput, type UpdateServiceClientInput, type UpdateServiceClientResult, createAdminClient };
@@ -0,0 +1 @@
1
+ export { t as ADMIN_ERROR_CODES, n as createAdminClient } from "../admin-DjvuvuI8.js";