@authyon/server 0.2.0-beta.20 → 0.2.0-beta.22
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/README.md +541 -473
- package/dist/{authorization-DfFCoDQ6.d.ts → authorization-BLENxur8.d.cts} +18 -4
- package/dist/{authorization-DcA5oKZT.d.cts → authorization-Clf0Jx-U.d.ts} +18 -4
- package/dist/{authyonServerClient-UA7HdjtD.d.cts → authyonServerClient-CW46xIRD.d.cts} +233 -11
- package/dist/{authyonServerClient-UA7HdjtD.d.ts → authyonServerClient-CW46xIRD.d.ts} +233 -11
- package/dist/bff.cjs +8 -1
- package/dist/bff.d.cts +1 -1
- package/dist/bff.d.ts +1 -1
- package/dist/bff.js +1 -1
- package/dist/{chunk-TDJNBD3Z.js → chunk-SUL2HLRR.js} +8 -1
- package/dist/{chunk-NKWQMYB3.js → chunk-WF26WYSX.js} +37 -7
- package/dist/index.cjs +239 -16
- package/dist/index.d.cts +54 -4
- package/dist/index.d.ts +54 -4
- package/dist/index.js +190 -9
- package/dist/next.cjs +11 -1
- package/dist/next.d.cts +2 -2
- package/dist/next.d.ts +2 -2
- package/dist/next.js +1 -1
- package/package.json +81 -81
package/README.md
CHANGED
|
@@ -1,473 +1,541 @@
|
|
|
1
|
-
# @authyon/server
|
|
2
|
-
|
|
3
|
-
SDK server-side para o [Authyon](https://authyon.com) — administração de ambiente/tenants (OAuth client-credentials) e verificação de access token (publishable key). Nunca importe este pacote em código de browser.
|
|
4
|
-
|
|
5
|
-
## Instalação da beta
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install --save-exact @authyon/server@0.2.0-beta.4
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Durante a beta, `npm install @authyon/server@beta` acompanha o prerelease mais recente. Fixar a versão exata é recomendado para builds reproduzíveis.
|
|
12
|
-
|
|
13
|
-
O pacote possui uma condição de exportação `browser` que falha imediatamente se um bundler tentar incluí-lo no frontend. URLs remotas exigem HTTPS e cada request tem timeout padrão de 15 segundos, configurável por `timeoutMs`.
|
|
14
|
-
|
|
15
|
-
O client aceita o mesmo contrato `HttpAdapter` de `@authyon/auth`, útil para tracing, mocks e stacks HTTP próprias:
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
import { createClient, FetchHttpAdapter } from "@authyon/server";
|
|
19
|
-
|
|
20
|
-
const authyon = createClient({
|
|
21
|
-
envKey: process.env.AUTHYON_ENV_KEY,
|
|
22
|
-
httpAdapter: new FetchHttpAdapter(globalThis.fetch),
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Quando houver credenciais, adapter e observabilidade, o builder mantém cada etapa explícita:
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
const authyon = new AuthyonServerClientBuilder()
|
|
30
|
-
.withEnvironmentKey(process.env.AUTHYON_ENV_KEY)
|
|
31
|
-
.withEnvironmentCredentials(process.env.AUTHYON_CLIENT_ID, process.env.AUTHYON_CLIENT_SECRET)
|
|
32
|
-
.withHttpLogger(httpLogger)
|
|
33
|
-
.build();
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
O client também aceita `httpLogger: { enabled, logger? }`. A configuração é mutável, portanto `enabled` pode ser alterado em runtime. Os eventos não incluem headers, bodies nem valores de query string, e erros do logger nunca interrompem requests.
|
|
37
|
-
|
|
38
|
-
`AuthyonError`, `ErrorCodes` e `error.interpret()` também são exportados por este pacote. Use `category`, `action`, `retryable`, `retryAfter` e `requestId` para implementar middleware e observabilidade sem comparar mensagens humanas.
|
|
39
|
-
|
|
40
|
-
## Sessão web no BFF (código ainda não publicado)
|
|
41
|
-
|
|
42
|
-
O novo entrypoint `@authyon/server/bff` oferece login, 2FA, sessão HttpOnly,
|
|
43
|
-
renovação sob demanda, logout e troca de organização. Os tokens ficam no servidor,
|
|
44
|
-
com adaptador Redis cifrado e compare-and-swap para concorrência entre instâncias.
|
|
45
|
-
Reutiliza os endpoints existentes e o cliente de validação server-side.
|
|
46
|
-
|
|
47
|
-
Veja [contrato e integração](../../docs/bff.md) e [análise/plano](../../docs/bff-session-plan.md).
|
|
48
|
-
O entrypoint padrão e `@authyon/server/next` mantêm seu comportamento anterior;
|
|
49
|
-
a adoção do transporte BFF pelo consumidor é explícita.
|
|
50
|
-
|
|
51
|
-
## Validação local com JWKS
|
|
52
|
-
|
|
53
|
-
Para APIs de alto volume, crie uma vez um verificador com discovery automático:
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
const verifier = await authyon.createJwksTokenVerifier({
|
|
57
|
-
audience: process.env.AUTHYON_AUDIENCE,
|
|
58
|
-
algorithms: ["RS256"],
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const { claims } = await verifier.verifyAccessToken(token);
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Ele valida assinatura, algoritmo, issuer, audience, tipo e claims temporais, com cache e rotação de chaves. Use `verification: "jwks"` em `authorizeRequest()` ou `createExpressAuthorizationMiddleware()`. Para ações sensíveis que precisam detectar revogação imediatamente, mantenha o padrão `validate`.
|
|
65
|
-
|
|
66
|
-
## Instalação
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm install @authyon/server
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Chaves e credenciais
|
|
73
|
-
|
|
74
|
-
| Credencial | Uso |
|
|
75
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
-
| `envKey` (`pk_...`) | Selecionado em **toda** chamada via `X-Authyon-Environment` (Test/Live). Não é sensível — é a mesma chave do frontend, mas sozinha não autoriza nada. |
|
|
77
|
-
| `clientId` / `clientSecret` (ambiente) | Credenciais OAuth client-credentials de nível ambiente, mintadas no console. O SDK troca por um access token (`POST /env/oauth/token`) e o renova sozinho, usado em todo `environment.*` / `permissions.*`. **Nunca** exponha ao navegador. |
|
|
78
|
-
| `clientId` / `clientSecret` (tenant) | Credenciais por-tenant, usadas via `authyon.tenant({ clientId, clientSecret })` para as chamadas `TenantManagement` (`/tenant/...`). |
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
import { createClient } from "@authyon/server";
|
|
82
|
-
|
|
83
|
-
const authyon = createClient({
|
|
84
|
-
envKey: process.env.AUTHYON_ENV_KEY, // pk_live_...
|
|
85
|
-
clientId: process.env.AUTHYON_CLIENT_ID, // ec_live_...
|
|
86
|
-
clientSecret: process.env.AUTHYON_CLIENT_SECRET,
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
> `@authyon/auth` também exporta um `createClient`. Se algum dia precisar dos dois no mesmo arquivo, use um alias no import: `import { createClient as createServerClient } from "@authyon/server"`.
|
|
91
|
-
|
|
92
|
-
## Verificação de token
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
const { active, sub } = await authyon.introspect(token); // leve
|
|
96
|
-
const { user, organization } = await authyon.validate(token); // recomendado — cross-checa revogação
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Veja [`examples/tokenVerification.ts`](./examples/tokenVerification.ts) para um middleware completo.
|
|
100
|
-
|
|
101
|
-
## Administração de ambiente (`environment.*`)
|
|
102
|
-
|
|
103
|
-
Usa as credenciais de ambiente (`clientId`/`clientSecret`) automaticamente — o SDK minta e renova o token sozinho.
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
const org = await authyon.environment.tenants.create({ name: "Acme", slug: "acme" });
|
|
107
|
-
const tenants = await authyon.environment.tenants.list({
|
|
108
|
-
search: "acme",
|
|
109
|
-
skip: 0,
|
|
110
|
-
take: 20,
|
|
111
|
-
});
|
|
112
|
-
await authyon.environment.tenants.members.add(org.id, userId, ["owner"]);
|
|
113
|
-
await authyon.environment.tenants.members.assignRole(org.id, userId, "billing-admin");
|
|
114
|
-
await authyon.environment.tenants.members.remove(org.id, userId);
|
|
115
|
-
|
|
116
|
-
await authyon.environment.users.list({ search: "acme.com" });
|
|
117
|
-
await authyon.environment.roles.create({ name: "support", permissions: ["tickets:read"] });
|
|
118
|
-
await authyon.environment.audit.list({ take: 50 });
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Veja [`examples/organizationMembership.ts`](./examples/organizationMembership.ts) para o fluxo completo, incluindo as rotas de backend que o `@authyon/auth` chamaria.
|
|
122
|
-
|
|
123
|
-
Namespaces disponíveis: `environment.users`, `environment.tenants` (com `.members` e `.roles` aninhados), `environment.roles`, `environment.permissions`, `environment.audit`. OTP e aprovações ficam em `security` (veja abaixo).
|
|
124
|
-
|
|
125
|
-
## Autorização local
|
|
126
|
-
|
|
127
|
-
`createAuthyonAbility()` transforma o resultado de `validate()`, `introspect()` ou qualquer objeto com `permissions`, `roles` e `scope` em verificações locais no estilo CASL:
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
import { createAuthyonAbility } from "@authyon/server";
|
|
131
|
-
|
|
132
|
-
const identity = await authyon.validate(token);
|
|
133
|
-
const ability = createAuthyonAbility(identity);
|
|
134
|
-
|
|
135
|
-
if (!ability.can("read", "reports")) {
|
|
136
|
-
throw new Error("Forbidden");
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Regras condicionais e de domínio podem ser acrescentadas no segundo argumento. O mecanismo é deny-by-default e não substitui a validação do token nem a autorização aplicada pela API.
|
|
141
|
-
|
|
142
|
-
### Next.js Server Side
|
|
143
|
-
|
|
144
|
-
Em Route Handlers, Server Actions ou SSR, mantenha `@authyon/server` em um módulo marcado com `server-only`:
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
import "server-only";
|
|
148
|
-
import { createAuthorizationErrorResponse, authorizeRequest, createClient } from "@authyon/server";
|
|
149
|
-
|
|
150
|
-
const authyon = createClient({
|
|
151
|
-
envKey: process.env.AUTHYON_ENV_KEY,
|
|
152
|
-
clientId: process.env.AUTHYON_CLIENT_ID,
|
|
153
|
-
clientSecret: process.env.AUTHYON_CLIENT_SECRET,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
export async function GET(request: Request) {
|
|
157
|
-
try {
|
|
158
|
-
const auth = await authorizeRequest(authyon, request, {
|
|
159
|
-
requirement: { action: "read", subject: "reports" },
|
|
160
|
-
});
|
|
161
|
-
return Response.json({ userId: auth.userId });
|
|
162
|
-
} catch (error) {
|
|
163
|
-
return createAuthorizationErrorResponse(error);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Express e frameworks Connect
|
|
169
|
-
|
|
170
|
-
O middleware não adiciona Express como dependência e disponibiliza `request.authyon`:
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
import { createExpressAuthorizationMiddleware } from "@authyon/server";
|
|
174
|
-
|
|
175
|
-
app.get(
|
|
176
|
-
"/reports",
|
|
177
|
-
createExpressAuthorizationMiddleware(authyon, {
|
|
178
|
-
requirement: { action: "read", subject: "reports" },
|
|
179
|
-
}),
|
|
180
|
-
(request, response) => response.json({ userId: request.authyon.userId }),
|
|
181
|
-
);
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Use `authorizeToken()` em NestJS, Fastify, Hono, tRPC ou integrações próprias. `passErrorsToNext: true` encaminha erros ao middleware de erros do Express.
|
|
185
|
-
|
|
186
|
-
### IP real do cliente
|
|
187
|
-
|
|
188
|
-
Chamadas server-side propagam `clientIp` em `X-Forwarded-For
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
|
218
|
-
|
|
|
219
|
-
| `security.
|
|
220
|
-
| `security.approvals.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
await acme.
|
|
236
|
-
await acme.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
npm
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
await
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
//
|
|
378
|
-
await authyon.environment.tenants.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
`
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1
|
+
# @authyon/server
|
|
2
|
+
|
|
3
|
+
SDK server-side para o [Authyon](https://authyon.com) — administração de ambiente/tenants (OAuth client-credentials) e verificação de access token (publishable key). Nunca importe este pacote em código de browser.
|
|
4
|
+
|
|
5
|
+
## Instalação da beta
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-exact @authyon/server@0.2.0-beta.4
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Durante a beta, `npm install @authyon/server@beta` acompanha o prerelease mais recente. Fixar a versão exata é recomendado para builds reproduzíveis.
|
|
12
|
+
|
|
13
|
+
O pacote possui uma condição de exportação `browser` que falha imediatamente se um bundler tentar incluí-lo no frontend. URLs remotas exigem HTTPS e cada request tem timeout padrão de 15 segundos, configurável por `timeoutMs`.
|
|
14
|
+
|
|
15
|
+
O client aceita o mesmo contrato `HttpAdapter` de `@authyon/auth`, útil para tracing, mocks e stacks HTTP próprias:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createClient, FetchHttpAdapter } from "@authyon/server";
|
|
19
|
+
|
|
20
|
+
const authyon = createClient({
|
|
21
|
+
envKey: process.env.AUTHYON_ENV_KEY,
|
|
22
|
+
httpAdapter: new FetchHttpAdapter(globalThis.fetch),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Quando houver credenciais, adapter e observabilidade, o builder mantém cada etapa explícita:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const authyon = new AuthyonServerClientBuilder()
|
|
30
|
+
.withEnvironmentKey(process.env.AUTHYON_ENV_KEY)
|
|
31
|
+
.withEnvironmentCredentials(process.env.AUTHYON_CLIENT_ID, process.env.AUTHYON_CLIENT_SECRET)
|
|
32
|
+
.withHttpLogger(httpLogger)
|
|
33
|
+
.build();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
O client também aceita `httpLogger: { enabled, logger? }`. A configuração é mutável, portanto `enabled` pode ser alterado em runtime. Os eventos não incluem headers, bodies nem valores de query string, e erros do logger nunca interrompem requests.
|
|
37
|
+
|
|
38
|
+
`AuthyonError`, `ErrorCodes` e `error.interpret()` também são exportados por este pacote. Use `category`, `action`, `retryable`, `retryAfter` e `requestId` para implementar middleware e observabilidade sem comparar mensagens humanas.
|
|
39
|
+
|
|
40
|
+
## Sessão web no BFF (código ainda não publicado)
|
|
41
|
+
|
|
42
|
+
O novo entrypoint `@authyon/server/bff` oferece login, 2FA, sessão HttpOnly,
|
|
43
|
+
renovação sob demanda, logout e troca de organização. Os tokens ficam no servidor,
|
|
44
|
+
com adaptador Redis cifrado e compare-and-swap para concorrência entre instâncias.
|
|
45
|
+
Reutiliza os endpoints existentes e o cliente de validação server-side.
|
|
46
|
+
|
|
47
|
+
Veja [contrato e integração](../../docs/bff.md) e [análise/plano](../../docs/bff-session-plan.md).
|
|
48
|
+
O entrypoint padrão e `@authyon/server/next` mantêm seu comportamento anterior;
|
|
49
|
+
a adoção do transporte BFF pelo consumidor é explícita.
|
|
50
|
+
|
|
51
|
+
## Validação local com JWKS
|
|
52
|
+
|
|
53
|
+
Para APIs de alto volume, crie uma vez um verificador com discovery automático:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const verifier = await authyon.createJwksTokenVerifier({
|
|
57
|
+
audience: process.env.AUTHYON_AUDIENCE,
|
|
58
|
+
algorithms: ["RS256"],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const { claims } = await verifier.verifyAccessToken(token);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Ele valida assinatura, algoritmo, issuer, audience, tipo e claims temporais, com cache e rotação de chaves. Use `verification: "jwks"` em `authorizeRequest()` ou `createExpressAuthorizationMiddleware()`. Para ações sensíveis que precisam detectar revogação imediatamente, mantenha o padrão `validate`.
|
|
65
|
+
|
|
66
|
+
## Instalação
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install @authyon/server
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Chaves e credenciais
|
|
73
|
+
|
|
74
|
+
| Credencial | Uso |
|
|
75
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `envKey` (`pk_...`) | Selecionado em **toda** chamada via `X-Authyon-Environment` (Test/Live). Não é sensível — é a mesma chave do frontend, mas sozinha não autoriza nada. |
|
|
77
|
+
| `clientId` / `clientSecret` (ambiente) | Credenciais OAuth client-credentials de nível ambiente, mintadas no console. O SDK troca por um access token (`POST /env/oauth/token`) e o renova sozinho, usado em todo `environment.*` / `permissions.*`. **Nunca** exponha ao navegador. |
|
|
78
|
+
| `clientId` / `clientSecret` (tenant) | Credenciais por-tenant, usadas via `authyon.tenant({ clientId, clientSecret })` para as chamadas `TenantManagement` (`/tenant/...`). |
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createClient } from "@authyon/server";
|
|
82
|
+
|
|
83
|
+
const authyon = createClient({
|
|
84
|
+
envKey: process.env.AUTHYON_ENV_KEY, // pk_live_...
|
|
85
|
+
clientId: process.env.AUTHYON_CLIENT_ID, // ec_live_...
|
|
86
|
+
clientSecret: process.env.AUTHYON_CLIENT_SECRET,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> `@authyon/auth` também exporta um `createClient`. Se algum dia precisar dos dois no mesmo arquivo, use um alias no import: `import { createClient as createServerClient } from "@authyon/server"`.
|
|
91
|
+
|
|
92
|
+
## Verificação de token
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const { active, sub } = await authyon.introspect(token); // leve
|
|
96
|
+
const { user, organization } = await authyon.validate(token); // recomendado — cross-checa revogação
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Veja [`examples/tokenVerification.ts`](./examples/tokenVerification.ts) para um middleware completo.
|
|
100
|
+
|
|
101
|
+
## Administração de ambiente (`environment.*`)
|
|
102
|
+
|
|
103
|
+
Usa as credenciais de ambiente (`clientId`/`clientSecret`) automaticamente — o SDK minta e renova o token sozinho.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const org = await authyon.environment.tenants.create({ name: "Acme", slug: "acme" });
|
|
107
|
+
const tenants = await authyon.environment.tenants.list({
|
|
108
|
+
search: "acme",
|
|
109
|
+
skip: 0,
|
|
110
|
+
take: 20,
|
|
111
|
+
});
|
|
112
|
+
await authyon.environment.tenants.members.add(org.id, userId, ["owner"]);
|
|
113
|
+
await authyon.environment.tenants.members.assignRole(org.id, userId, "billing-admin");
|
|
114
|
+
await authyon.environment.tenants.members.remove(org.id, userId);
|
|
115
|
+
|
|
116
|
+
await authyon.environment.users.list({ search: "acme.com" });
|
|
117
|
+
await authyon.environment.roles.create({ name: "support", permissions: ["tickets:read"] });
|
|
118
|
+
await authyon.environment.audit.list({ take: 50 });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Veja [`examples/organizationMembership.ts`](./examples/organizationMembership.ts) para o fluxo completo, incluindo as rotas de backend que o `@authyon/auth` chamaria.
|
|
122
|
+
|
|
123
|
+
Namespaces disponíveis: `environment.users`, `environment.tenants` (com `.members` e `.roles` aninhados), `environment.roles`, `environment.permissions`, `environment.audit`. OTP e aprovações ficam em `security` (veja abaixo).
|
|
124
|
+
|
|
125
|
+
## Autorização local
|
|
126
|
+
|
|
127
|
+
`createAuthyonAbility()` transforma o resultado de `validate()`, `introspect()` ou qualquer objeto com `permissions`, `roles` e `scope` em verificações locais no estilo CASL:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { createAuthyonAbility } from "@authyon/server";
|
|
131
|
+
|
|
132
|
+
const identity = await authyon.validate(token);
|
|
133
|
+
const ability = createAuthyonAbility(identity);
|
|
134
|
+
|
|
135
|
+
if (!ability.can("read", "reports")) {
|
|
136
|
+
throw new Error("Forbidden");
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Regras condicionais e de domínio podem ser acrescentadas no segundo argumento. O mecanismo é deny-by-default e não substitui a validação do token nem a autorização aplicada pela API.
|
|
141
|
+
|
|
142
|
+
### Next.js Server Side
|
|
143
|
+
|
|
144
|
+
Em Route Handlers, Server Actions ou SSR, mantenha `@authyon/server` em um módulo marcado com `server-only`:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import "server-only";
|
|
148
|
+
import { createAuthorizationErrorResponse, authorizeRequest, createClient } from "@authyon/server";
|
|
149
|
+
|
|
150
|
+
const authyon = createClient({
|
|
151
|
+
envKey: process.env.AUTHYON_ENV_KEY,
|
|
152
|
+
clientId: process.env.AUTHYON_CLIENT_ID,
|
|
153
|
+
clientSecret: process.env.AUTHYON_CLIENT_SECRET,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export async function GET(request: Request) {
|
|
157
|
+
try {
|
|
158
|
+
const auth = await authorizeRequest(authyon, request, {
|
|
159
|
+
requirement: { action: "read", subject: "reports" },
|
|
160
|
+
});
|
|
161
|
+
return Response.json({ userId: auth.userId });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return createAuthorizationErrorResponse(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Express e frameworks Connect
|
|
169
|
+
|
|
170
|
+
O middleware não adiciona Express como dependência e disponibiliza `request.authyon`:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { createExpressAuthorizationMiddleware } from "@authyon/server";
|
|
174
|
+
|
|
175
|
+
app.get(
|
|
176
|
+
"/reports",
|
|
177
|
+
createExpressAuthorizationMiddleware(authyon, {
|
|
178
|
+
requirement: { action: "read", subject: "reports" },
|
|
179
|
+
}),
|
|
180
|
+
(request, response) => response.json({ userId: request.authyon.userId }),
|
|
181
|
+
);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Use `authorizeToken()` em NestJS, Fastify, Hono, tRPC ou integrações próprias. `passErrorsToNext: true` encaminha erros ao middleware de erros do Express.
|
|
185
|
+
|
|
186
|
+
### IP real do cliente
|
|
187
|
+
|
|
188
|
+
Chamadas server-side propagam `clientIp` em `X-Authyon-Client-Ip` (e em `X-Forwarded-For`, para o que estiver na frente do seu backend). A API resolve o endereço do chamador pelo primeiro header e acredita nele quando a chamada está autenticada por credencial de máquina — uma chamada servidor-a-servidor não tem navegador dentro dela, e sem isso toda linha de auditoria da sua integração nomeia o gateway do contêiner, que não identifica ninguém. Muda a trilha, nunca um portão: todo controle com chave de IP roda antes da autenticação e segue decidindo pelo peer real.
|
|
189
|
+
|
|
190
|
+
O middleware Express usa o IP do socket por padrão; atrás de um proxy confiável, configure o próprio Express e use `trustProxy: true`. Para Next.js, passe `resolveClientIp` ao `authorizeRequest()` lendo somente headers que sua infraestrutura sobrescreve. Não confie cegamente em `X-Forwarded-For` recebido da internet.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
await authyon.validate(token, { clientIp: "203.0.113.10" });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Segurança (`security.*`)
|
|
197
|
+
|
|
198
|
+
OTP e aprovações ficam agrupados em `authyon.security`. Exige uma credencial de ambiente
|
|
199
|
+
com o escopo `authyon:otp:verify` (para `security.otp`) e/ou `authyon:financial:authorize`
|
|
200
|
+
(para `security.approvals`). Guia completo:
|
|
201
|
+
[`docs/security-otp-approvals.md`](../../docs/security-otp-approvals.md).
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// security.otp — só validar o código do app autenticador
|
|
205
|
+
const result = await authyon.security.otp.check(customerId, code);
|
|
206
|
+
if (!result.valid) console.log(result.attemptsRemaining, result.sessionsRevoked);
|
|
207
|
+
|
|
208
|
+
// security.approvals — aprovação presa a um payload livre e de uso único
|
|
209
|
+
const { id } = await authyon.security.approvals.create({
|
|
210
|
+
subjectId: customerId,
|
|
211
|
+
payload: { type: "pix", amount: 150.5, to: "maria@example.com" },
|
|
212
|
+
});
|
|
213
|
+
// ...cliente confirma com @authyon/auth → security.approvals.confirm
|
|
214
|
+
const { payload } = await authyon.security.approvals.consume(id);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
| Método | Endpoint |
|
|
218
|
+
| ------------------------------------ | --------------------------------------- |
|
|
219
|
+
| `security.otp.check(userId, code)` | `POST /env/users/{userId}/otp/verify` |
|
|
220
|
+
| `security.approvals.create(input)` | `POST /env/authorizations` |
|
|
221
|
+
| `security.approvals.get(id)` | `GET /env/authorizations/{id}` |
|
|
222
|
+
| `security.approvals.consume(id, p?)` | `POST /env/authorizations/{id}/consume` |
|
|
223
|
+
|
|
224
|
+
Cada código vale uma vez; 3 erros seguidos deslogam o usuário de todos os dispositivos e
|
|
225
|
+
5 bloqueiam a verificação por 15 minutos. Se o seu servidor guarda o token do cliente
|
|
226
|
+
(BFF), o lado do cliente está em `authyon.user(accessToken).security.approvals`.
|
|
227
|
+
|
|
228
|
+
## Administração por tenant (`tenant()`)
|
|
229
|
+
|
|
230
|
+
As credenciais de um tenant são próprias dele — use `authyon.tenant(credentials)` para obter um cliente escopado, que minta e cacheia seu próprio token via `POST /tenant/oauth/token`:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const acme = authyon.tenant({ clientId: acmeClientId, clientSecret: acmeClientSecret });
|
|
234
|
+
|
|
235
|
+
const identity = await acme.validate(); // POST /tenant/auth/validate
|
|
236
|
+
await acme.members.list();
|
|
237
|
+
await acme.members.add(userId, ["member"]);
|
|
238
|
+
await acme.roles.create({ name: "viewer", permissions: ["reports:read"] });
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`validate()` confirma o bearer tenant-client atual e devolve `tenantId`,
|
|
242
|
+
`credentialId`, `clientId` e as permissões vigentes da credencial. A resposta
|
|
243
|
+
vem do backend depois da checagem de revogação, rotação de secret e tenant
|
|
244
|
+
desabilitado; não confie só na claim `scope` quando precisar do estado atual.
|
|
245
|
+
|
|
246
|
+
Em middleware, quando você já recebeu o bearer tenant-client na requisição, não
|
|
247
|
+
use `clientId`/`clientSecret`; valide o token diretamente:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const validation = await authyon.tenantAuth.validate(tenantBearerToken, {
|
|
251
|
+
clientIp: "203.0.113.10",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (!validation.valid) {
|
|
255
|
+
// responda 401
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Descoberta
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
await authyon.discovery.jwks(); // GET /.well-known/jwks.json
|
|
263
|
+
await authyon.discovery.openidConfiguration(); // GET /.well-known/openid-configuration
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Build
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
npm install
|
|
270
|
+
npm run build # dist/ (ESM + CJS + .d.ts via tsup)
|
|
271
|
+
npm run typecheck
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Emitir uma credencial de integração do tenant
|
|
275
|
+
|
|
276
|
+
Requer o endpoint `POST /env/tenants/{tenantId}/credentials` no backend Authyon
|
|
277
|
+
e uma credencial de ambiente com `authyon:credentials:manage`.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
const credential = await authyon.environment.tenants.credentials.create(tenantId, {
|
|
281
|
+
description: "ERP financeiro",
|
|
282
|
+
permissions: ["monkeypay:transactions:read"],
|
|
283
|
+
});
|
|
284
|
+
// Entregue clientId e clientSecret ao operador: o segredo só é retornado uma vez.
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
As permissões são explícitas e pertencem à integração; não há concessão automática de acesso total.
|
|
288
|
+
|
|
289
|
+
## Credenciais: paginação, busca e detalhe
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const page = await authyon.environment.tenants.credentials.listPage(tenantId, {
|
|
293
|
+
search: "transactions",
|
|
294
|
+
skip: 0,
|
|
295
|
+
take: 25,
|
|
296
|
+
});
|
|
297
|
+
const detail = await authyon.environment.tenants.credentials.get(tenantId, credentialId);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`page` contém `data`, `page` (a página atual, começando em 1), `perPage` (o
|
|
301
|
+
limite por página), `total`, `pages`, `hasNext` e `hasPrev`. Até a API de
|
|
302
|
+
2026-10-21 os dois campos se chamavam `perPage` (página) e `pageSize`
|
|
303
|
+
(limite), nessa ordem trocada. `take` aceita 1–100 (padrão da API: 25),
|
|
304
|
+
`skip` é um deslocamento a partir de zero e `search` aceita até 200 caracteres.
|
|
305
|
+
A API pesquisa clientId, descrição e permissões antes de paginar.
|
|
306
|
+
|
|
307
|
+
O método existente `credentials.list(tenantId)` continua retornando um array:
|
|
308
|
+
aceita a resposta antiga e percorre todas as páginas da API atual. Prefira
|
|
309
|
+
`listPage` para tabelas ou grandes volumes. A leitura completa detecta páginas
|
|
310
|
+
sobrepostas/sem progresso e tem limite de 1.000 páginas; alterações concorrentes
|
|
311
|
+
podem exigir reiniciar a leitura, pois a API usa paginação por deslocamento.
|
|
312
|
+
|
|
313
|
+
O detalhe contém `permissions`, `createdBy` (`id`, `type`, `displayName`),
|
|
314
|
+
`createdAt`, `updatedAt`, `lastUsedAt`, `secretRotatedAt`, `revokedAt`, `isActive`,
|
|
315
|
+
`expiresAt`, `ageSeconds`, `lifetimeSeconds` e `accessTokenLifetimeSeconds`.
|
|
316
|
+
Listagem e detalhe projetam somente campos permitidos, sem segredo ou hash.
|
|
317
|
+
Registros antigos podem ter autor e atualização nulos. A credencial atual não
|
|
318
|
+
expira automaticamente: `expiresAt` é nulo; `lifetimeSeconds` é nulo enquanto
|
|
319
|
+
ativa e representa sua duração até a revogação quando revogada. A validade do
|
|
320
|
+
access token é separada (atualmente 1.800 segundos).
|
|
321
|
+
|
|
322
|
+
## Scopes e ciclo de vida das credenciais
|
|
323
|
+
|
|
324
|
+
`scopes` é um alias de `permissions` na criação/atualização. Use somente um dos
|
|
325
|
+
campos, com uma lista explícita no formato `namespace:resource:action`. O SDK
|
|
326
|
+
normaliza espaços, caixa e duplicatas; não concede permissões automaticamente.
|
|
327
|
+
A API continua responsável pela autorização. O contrato antigo
|
|
328
|
+
`CreateTenantCredentialInput` e chamadas usando `permissions` são preservados.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const issued = await authyon.environment.tenants.credentials.create(tenantId, {
|
|
332
|
+
description: "ERP financeiro",
|
|
333
|
+
scopes: ["monkeypay:transactions:read"],
|
|
334
|
+
});
|
|
335
|
+
// Armazene issued.clientSecret no gerenciador de segredos. Não registre a resposta.
|
|
336
|
+
|
|
337
|
+
await authyon.environment.tenants.credentials.listPage(tenantId, {
|
|
338
|
+
search: "ERP",
|
|
339
|
+
skip: 0,
|
|
340
|
+
take: 25,
|
|
341
|
+
});
|
|
342
|
+
await authyon.environment.tenants.credentials.get(tenantId, credentialId);
|
|
343
|
+
await authyon.environment.tenants.credentials.create(tenantId, {
|
|
344
|
+
description: "Leitura",
|
|
345
|
+
permissions: ["monkeypay:transactions:read"],
|
|
346
|
+
});
|
|
347
|
+
await authyon.environment.tenants.credentials.updateScopes(tenantId, credentialId, [
|
|
348
|
+
"monkeypay:transactions:read",
|
|
349
|
+
]);
|
|
350
|
+
await authyon.environment.tenants.credentials.updatePermissions(tenantId, credentialId, {
|
|
351
|
+
permissions: ["monkeypay:transactions:read"],
|
|
352
|
+
});
|
|
353
|
+
const rotated = await authyon.environment.tenants.credentials.rotate(tenantId, credentialId);
|
|
354
|
+
await authyon.environment.tenants.credentials.revoke(tenantId, credentialId);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Criação e rotação retornam o segredo uma única vez. Alterar permissões substitui a lista
|
|
358
|
+
completa. A concessão OAuth continua recebendo apenas `clientId`/`clientSecret`;
|
|
359
|
+
`scopes` não é um parâmetro de redução de acesso na troca de token.
|
|
360
|
+
|
|
361
|
+
Todas as operações usam os endpoints externos `/env` com a credencial de
|
|
362
|
+
ambiente configurada no cliente. O SDK não repete mutações automaticamente.
|
|
363
|
+
|
|
364
|
+
## Hierarquia de tenants
|
|
365
|
+
|
|
366
|
+
Um tenant pode ficar embaixo de outro. A listagem pagina um nível por vez, que é o
|
|
367
|
+
que permite desenhar o primeiro nível sem ler a subárvore inteira:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
const { data: raizes } = await authyon.environment.tenants.roots({ take: 50 });
|
|
371
|
+
|
|
372
|
+
// childCount já vem na página: expanda só onde há o que expandir.
|
|
373
|
+
for (const t of raizes.filter((t) => (t.childCount ?? 0) > 0)) {
|
|
374
|
+
const { data: filhos } = await authyon.environment.tenants.children(t.id);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Criar embaixo de outro.
|
|
378
|
+
await authyon.environment.tenants.create({
|
|
379
|
+
name: "Filial Sul",
|
|
380
|
+
slug: "filial-sul",
|
|
381
|
+
parentTenantId: holdingId,
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`list()` continua devolvendo todos os tenants em qualquer profundidade — é o que um
|
|
386
|
+
seletor plano quer. `get()` traz `ancestors`, a cadeia acima do tenant com a raiz
|
|
387
|
+
primeiro, então um breadcrumb custa uma chamada e não uma por nível.
|
|
388
|
+
|
|
389
|
+
O ambiente nasce com `maxTenantDepth: 1` (só raízes), então criar um sub-tenant
|
|
390
|
+
responde `tenant.depth.not_allowed` até um operador aumentar o teto no console.
|
|
391
|
+
Alcançar para baixo é opt-in: as permissões `subtenants:tree:read|create|manage|switch`
|
|
392
|
+
são concedidas no nó a que o chamador pertence, e ter `tenants:settings:manage` no pai
|
|
393
|
+
não concede nada no filho. `disable()` e `delete()` desativam a subárvore junto.
|
|
394
|
+
|
|
395
|
+
Detalhes e erros no [guia de hierarquia](../../docs/tenantHierarchy.md).
|
|
396
|
+
|
|
397
|
+
## Filtros do tenant
|
|
398
|
+
|
|
399
|
+
`search` é um substring só, ORado entre nome, slug, descrição e os dois metadata.
|
|
400
|
+
Quando mais de uma condição precisa valer — `kind IN (...) AND cnpj IN (...)` — use
|
|
401
|
+
`filter`, que roda dentro da consulta e por isso continua paginando:
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
import { tenantFilter } from "@authyon/server";
|
|
405
|
+
|
|
406
|
+
const page = await authyon.environment.tenants.list({
|
|
407
|
+
filter: [
|
|
408
|
+
tenantFilter.in("public.kind", ["reseller", "partner"]),
|
|
409
|
+
tenantFilter.in("custom.cnpj", ["00000000000100", "11111111000199"]),
|
|
410
|
+
],
|
|
411
|
+
take: 100,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
page.total; // quantos casam com o filtro, não quantos existem no ambiente
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Cada entrada é uma condição; várias são ANDadas entre si e com o `search`, e a
|
|
418
|
+
vírgula dentro de um `in` é o OR. Os escopos são `public` (publicMetadata),
|
|
419
|
+
`private` (privateMetadata) e `custom` (customFields — este o `search` nunca
|
|
420
|
+
cobriu). Operadores: `eq`, `in`, `like` (ILIKE, com os metacaracteres escapados)
|
|
421
|
+
e `exists`. Caminho aninhado vale até três níveis: `private.billing.plan`.
|
|
422
|
+
|
|
423
|
+
O construtor valida antes de sair: escopo desconhecido, chave com espaço, mais de
|
|
424
|
+
25 valores num `in` ou vírgula dentro de um valor levantam `RangeError` em vez de
|
|
425
|
+
virarem `400 tenant.filter.invalid`. No máximo 10 filtros por requisição.
|
|
426
|
+
|
|
427
|
+
Precisa da API Authyon de 2026-10-22 ou mais nova; contra uma anterior o `filter`
|
|
428
|
+
é ignorado, o que aparece como página larga demais e não como erro.
|
|
429
|
+
|
|
430
|
+
## Membros do tenant
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
const user = authyon.user(() => getCurrentUserAccessToken());
|
|
434
|
+
await user.tenants.members.invite(tenantId, {
|
|
435
|
+
email: "pessoa@example.com",
|
|
436
|
+
roles: ["reader"],
|
|
437
|
+
});
|
|
438
|
+
await user.tenants.members.list(tenantId, { skip: 0, take: 25 });
|
|
439
|
+
await user.tenants.members.remove(tenantId, userId);
|
|
440
|
+
|
|
441
|
+
// Pelo plano administrativo, a listagem vem no envelope paginado de sempre
|
|
442
|
+
// (`data`, `total`, `hasNext`), com `take` 50 por padrão e teto 100.
|
|
443
|
+
const { data: membros, total } = await authyon.environment.tenants.members.list(tenantId);
|
|
444
|
+
|
|
445
|
+
// Administração por máquina: adicione uma conta existente por ID.
|
|
446
|
+
await authyon.environment.tenants.members.add(tenantId, userId, ["reader"]);
|
|
447
|
+
await authyon.environment.tenants.members.remove(tenantId, userId);
|
|
448
|
+
// Os métodos tenant(credentials).members.add/remove existentes continuam disponíveis.
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Configure `envKey` para `user()`. `members.invite` associa um usuário já
|
|
452
|
+
cadastrado no ambiente pelo e-mail; não cria conta e não envia link por e-mail.
|
|
453
|
+
Para isso, use os convites abaixo.
|
|
454
|
+
A API exige associação/permissões do usuário chamador. No browser, os métodos
|
|
455
|
+
existentes `@authyon/auth` → `organization.members.invite/remove` continuam
|
|
456
|
+
atendendo esse fluxo. Nunca importe `@authyon/server` no browser.
|
|
457
|
+
|
|
458
|
+
## Convites de tenant (e-mail ou link)
|
|
459
|
+
|
|
460
|
+
Convide um e-mail que talvez ainda não tenha conta — aceitar cria a conta, já com o
|
|
461
|
+
e-mail confirmado. A resposta sempre traz o `acceptUrl` completo; `sendEmail: false`
|
|
462
|
+
só gera o link para você enviar pelo seu canal.
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// Pelo ambiente (escopo authyon:users:write; listar exige authyon:tenants:read).
|
|
466
|
+
const invite = await authyon.environment.tenants.invites.create(tenantId, {
|
|
467
|
+
email: "pessoa@example.com",
|
|
468
|
+
roles: ["reader"],
|
|
469
|
+
sendEmail: false, // padrão: true
|
|
470
|
+
// Opcional: para onde mandar a pessoa depois de aceitar. Precisa estar
|
|
471
|
+
// cadastrado (match exato) nos redirect URIs do ambiente.
|
|
472
|
+
redirectUri: "https://app.example.com/bem-vindo",
|
|
473
|
+
});
|
|
474
|
+
invite.acceptUrl; // só aparece aqui — a API guarda apenas o hash do token
|
|
475
|
+
|
|
476
|
+
await authyon.environment.tenants.invites.list(tenantId);
|
|
477
|
+
await authyon.environment.tenants.invites.resend(tenantId, invite.id, { sendEmail: true });
|
|
478
|
+
await authyon.environment.tenants.invites.revoke(tenantId, invite.id);
|
|
479
|
+
|
|
480
|
+
// Pela credencial do tenant, ou agindo como o usuário (permissão tenants:members:invite).
|
|
481
|
+
await authyon.tenant(credentials).invites.create({ email: "pessoa@example.com" });
|
|
482
|
+
await authyon.user(token).tenants.invites.create(tenantId, { email: "pessoa@example.com" });
|
|
483
|
+
|
|
484
|
+
// Página de aceite no seu backend: públicas, usam só o envKey.
|
|
485
|
+
const preview = await authyon.environment.tenants.invites.preview(token);
|
|
486
|
+
const accepted = await authyon.environment.tenants.invites.accept({
|
|
487
|
+
token,
|
|
488
|
+
password: preview.accountExists ? undefined : password,
|
|
489
|
+
// Opcional. Com metadata, a chamada vai para /env/tenant-invites/accept usando
|
|
490
|
+
// clientId/clientSecret (escopo authyon:users:write) — só o backend pode definir.
|
|
491
|
+
publicMetadata: { plano: "pro" },
|
|
492
|
+
privateMetadata: { crmId: "42" },
|
|
493
|
+
// customFields: { ... } — obsoleto, prefira os metadata acima.
|
|
494
|
+
});
|
|
495
|
+
if (accepted.redirectUri) redirect(accepted.redirectUri); // o redirect fica por sua conta
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
O link vale 7 dias e uma única vez. `resend` gera um link novo e invalida o anterior.
|
|
499
|
+
Por padrão o link aponta para a página do Authyon; configure a _URL do convite para
|
|
500
|
+
tenant_ nas configurações do ambiente para usar a sua (ela recebe `token` e `env`).
|
|
501
|
+
Aceitar não faz login.
|
|
502
|
+
|
|
503
|
+
## Redefinição de senha pelo seu próprio e-mail
|
|
504
|
+
|
|
505
|
+
Para a sua aplicação disparar o e-mail de redefinição em vez do Authyon, emita o token no
|
|
506
|
+
backend (escopo `authyon:users:password`) e entregue você mesmo:
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
const reset = await authyon.environment.users.issuePasswordReset({
|
|
510
|
+
email: "pessoa@example.com",
|
|
511
|
+
// Opcional: para onde mandar a pessoa depois de trocar a senha. Precisa estar
|
|
512
|
+
// cadastrado (match exato) nos redirect URIs do ambiente.
|
|
513
|
+
redirectUri: "https://app.example.com/login",
|
|
514
|
+
// sendEmail: true faria o Authyon enviar também; o padrão é false.
|
|
515
|
+
});
|
|
516
|
+
reset.token; // só aparece aqui — a API guarda apenas o hash
|
|
517
|
+
reset.resetUrl; // o link que o e-mail do Authyon levaria
|
|
518
|
+
reset.expiresAt; // 30 minutos
|
|
519
|
+
|
|
520
|
+
// Página de redefinição no seu backend: públicas, usam só o envKey.
|
|
521
|
+
const preview = await authyon.environment.users.validatePasswordReset(token); // não gasta o token
|
|
522
|
+
const done = await authyon.environment.users.confirmPasswordReset(token, newPassword);
|
|
523
|
+
if (done.redirectUri) redirect(done.redirectUri); // o redirect fica por sua conta
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Quem tem o token consegue definir a senha: envie somente para o endereço da própria conta e
|
|
527
|
+
não registre em log. Emitir um token novo invalida o anterior da mesma conta. Um endereço
|
|
528
|
+
sem conta responde `404 user.not_found`; token desconhecido, usado ou expirado responde
|
|
529
|
+
`401 user.password_reset.invalid`.
|
|
530
|
+
|
|
531
|
+
## Compatibilidade e segurança do transporte
|
|
532
|
+
|
|
533
|
+
As chamadas exigem os endpoints externos atuais da API e a migração
|
|
534
|
+
`ClientCredentialMetadata` para metadados
|
|
535
|
+
persistidos. O SDK não altera o banco nem publica uma versão automaticamente.
|
|
536
|
+
|
|
537
|
+
O adaptador Fetch agora rejeita redirects para evitar reenviar tokens/segredos;
|
|
538
|
+
configure a URL final HTTPS da API. Adaptadores HTTP personalizados devem manter
|
|
539
|
+
essa política. Os logs internos omitem corpos/headers e ocultam tokens de
|
|
540
|
+
convite presentes no caminho. A auditoria de operações permanece na API; o SDK
|
|
541
|
+
não gera eventos duplicados ao consultar listagens de auditoria.
|