@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
package/README.md
CHANGED
|
@@ -1,3 +1,482 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @eva_solutions/auth-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Official TypeScript client for the EVA Auth Service — secure cookie-based auth with JWT ES256, JWKS rotation, S2S signing, and webhooks.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@eva_solutions/auth-sdk)
|
|
6
|
+
[](https://www.npmjs.com/package/@eva_solutions/auth-sdk)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://github.com/eva-solutions-org/auth-sdk/actions/workflows/ci.yml)
|
|
9
|
+
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
10
|
+
[](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,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-jAO62-OD.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-DBZoLU4E.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";
|