@authyon/server 0.2.0-beta.2 → 0.2.0-beta.21

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 CHANGED
@@ -5,7 +5,7 @@ SDK server-side para o [Authyon](https://authyon.com) — administração de amb
5
5
  ## Instalação da beta
6
6
 
7
7
  ```bash
8
- npm install --save-exact @authyon/server@0.2.0-beta.2
8
+ npm install --save-exact @authyon/server@0.2.0-beta.4
9
9
  ```
10
10
 
11
11
  Durante a beta, `npm install @authyon/server@beta` acompanha o prerelease mais recente. Fixar a versão exata é recomendado para builds reproduzíveis.
@@ -37,6 +37,17 @@ O client também aceita `httpLogger: { enabled, logger? }`. A configuração é
37
37
 
38
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
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
+
40
51
  ## Validação local com JWKS
41
52
 
42
53
  Para APIs de alto volume, crie uma vez um verificador com discovery automático:
@@ -109,7 +120,7 @@ await authyon.environment.audit.list({ take: 50 });
109
120
 
110
121
  Veja [`examples/organizationMembership.ts`](./examples/organizationMembership.ts) para o fluxo completo, incluindo as rotas de backend que o `@authyon/auth` chamaria.
111
122
 
112
- Namespaces disponíveis: `environment.users`, `environment.tenants` (com `.members` e `.roles` aninhados), `environment.roles`, `environment.permissions`, `environment.audit`.
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).
113
124
 
114
125
  ## Autorização local
115
126
 
@@ -180,6 +191,38 @@ Chamadas server-side propagam `clientIp` em `X-Forwarded-For`. O middleware Expr
180
191
  await authyon.validate(token, { clientIp: "203.0.113.10" });
181
192
  ```
182
193
 
194
+ ## Segurança (`security.*`)
195
+
196
+ OTP e aprovações ficam agrupados em `authyon.security`. Exige uma credencial de ambiente
197
+ com o escopo `authyon:otp:verify` (para `security.otp`) e/ou `authyon:financial:authorize`
198
+ (para `security.approvals`). Guia completo:
199
+ [`docs/security-otp-approvals.md`](../../docs/security-otp-approvals.md).
200
+
201
+ ```ts
202
+ // security.otp — só validar o código do app autenticador
203
+ const result = await authyon.security.otp.check(customerId, code);
204
+ if (!result.valid) console.log(result.attemptsRemaining, result.sessionsRevoked);
205
+
206
+ // security.approvals — aprovação presa a um payload livre e de uso único
207
+ const { id } = await authyon.security.approvals.create({
208
+ subjectId: customerId,
209
+ payload: { type: "pix", amount: 150.5, to: "maria@example.com" },
210
+ });
211
+ // ...cliente confirma com @authyon/auth → security.approvals.confirm
212
+ const { payload } = await authyon.security.approvals.consume(id);
213
+ ```
214
+
215
+ | Método | Endpoint |
216
+ | ------------------------------------ | --------------------------------------- |
217
+ | `security.otp.check(userId, code)` | `POST /env/users/{userId}/otp/verify` |
218
+ | `security.approvals.create(input)` | `POST /env/authorizations` |
219
+ | `security.approvals.get(id)` | `GET /env/authorizations/{id}` |
220
+ | `security.approvals.consume(id, p?)` | `POST /env/authorizations/{id}/consume` |
221
+
222
+ Cada código vale uma vez; 3 erros seguidos deslogam o usuário de todos os dispositivos e
223
+ 5 bloqueiam a verificação por 15 minutos. Se o seu servidor guarda o token do cliente
224
+ (BFF), o lado do cliente está em `authyon.user(accessToken).security.approvals`.
225
+
183
226
  ## Administração por tenant (`tenant()`)
184
227
 
185
228
  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`:
@@ -187,11 +230,30 @@ As credenciais de um tenant são próprias dele — use `authyon.tenant(credenti
187
230
  ```ts
188
231
  const acme = authyon.tenant({ clientId: acmeClientId, clientSecret: acmeClientSecret });
189
232
 
233
+ const identity = await acme.validate(); // POST /tenant/auth/validate
190
234
  await acme.members.list();
191
235
  await acme.members.add(userId, ["member"]);
192
236
  await acme.roles.create({ name: "viewer", permissions: ["reports:read"] });
193
237
  ```
194
238
 
239
+ `validate()` confirma o bearer tenant-client atual e devolve `tenantId`,
240
+ `credentialId`, `clientId` e as permissões vigentes da credencial. A resposta
241
+ vem do backend depois da checagem de revogação, rotação de secret e tenant
242
+ desabilitado; não confie só na claim `scope` quando precisar do estado atual.
243
+
244
+ Em middleware, quando você já recebeu o bearer tenant-client na requisição, não
245
+ use `clientId`/`clientSecret`; valide o token diretamente:
246
+
247
+ ```ts
248
+ const validation = await authyon.tenantAuth.validate(tenantBearerToken, {
249
+ clientIp: "203.0.113.10",
250
+ });
251
+
252
+ if (!validation.valid) {
253
+ // responda 401
254
+ }
255
+ ```
256
+
195
257
  ## Descoberta
196
258
 
197
259
  ```ts
@@ -206,3 +268,239 @@ npm install
206
268
  npm run build # dist/ (ESM + CJS + .d.ts via tsup)
207
269
  npm run typecheck
208
270
  ```
271
+
272
+ ### Emitir uma credencial de integração do tenant
273
+
274
+ Requer o endpoint `POST /env/tenants/{tenantId}/credentials` no backend Authyon
275
+ e uma credencial de ambiente com `authyon:credentials:manage`.
276
+
277
+ ```ts
278
+ const credential = await authyon.environment.tenants.credentials.create(tenantId, {
279
+ description: "ERP financeiro",
280
+ permissions: ["monkeypay:transactions:read"],
281
+ });
282
+ // Entregue clientId e clientSecret ao operador: o segredo só é retornado uma vez.
283
+ ```
284
+
285
+ As permissões são explícitas e pertencem à integração; não há concessão automática de acesso total.
286
+
287
+ ## Credenciais: paginação, busca e detalhe
288
+
289
+ ```ts
290
+ const page = await authyon.environment.tenants.credentials.listPage(tenantId, {
291
+ search: "transactions",
292
+ skip: 0,
293
+ take: 25,
294
+ });
295
+ const detail = await authyon.environment.tenants.credentials.get(tenantId, credentialId);
296
+ ```
297
+
298
+ `page` contém `data`, `page` (a página atual, começando em 1), `perPage` (o
299
+ limite por página), `total`, `pages`, `hasNext` e `hasPrev`. Até a API de
300
+ 2026-10-21 os dois campos se chamavam `perPage` (página) e `pageSize`
301
+ (limite), nessa ordem trocada. `take` aceita 1–100 (padrão da API: 25),
302
+ `skip` é um deslocamento a partir de zero e `search` aceita até 200 caracteres.
303
+ A API pesquisa clientId, descrição e permissões antes de paginar.
304
+
305
+ O método existente `credentials.list(tenantId)` continua retornando um array:
306
+ aceita a resposta antiga e percorre todas as páginas da API atual. Prefira
307
+ `listPage` para tabelas ou grandes volumes. A leitura completa detecta páginas
308
+ sobrepostas/sem progresso e tem limite de 1.000 páginas; alterações concorrentes
309
+ podem exigir reiniciar a leitura, pois a API usa paginação por deslocamento.
310
+
311
+ O detalhe contém `permissions`, `createdBy` (`id`, `type`, `displayName`),
312
+ `createdAt`, `updatedAt`, `lastUsedAt`, `secretRotatedAt`, `revokedAt`, `isActive`,
313
+ `expiresAt`, `ageSeconds`, `lifetimeSeconds` e `accessTokenLifetimeSeconds`.
314
+ Listagem e detalhe projetam somente campos permitidos, sem segredo ou hash.
315
+ Registros antigos podem ter autor e atualização nulos. A credencial atual não
316
+ expira automaticamente: `expiresAt` é nulo; `lifetimeSeconds` é nulo enquanto
317
+ ativa e representa sua duração até a revogação quando revogada. A validade do
318
+ access token é separada (atualmente 1.800 segundos).
319
+
320
+ ## Scopes e ciclo de vida das credenciais
321
+
322
+ `scopes` é um alias de `permissions` na criação/atualização. Use somente um dos
323
+ campos, com uma lista explícita no formato `namespace:resource:action`. O SDK
324
+ normaliza espaços, caixa e duplicatas; não concede permissões automaticamente.
325
+ A API continua responsável pela autorização. O contrato antigo
326
+ `CreateTenantCredentialInput` e chamadas usando `permissions` são preservados.
327
+
328
+ ```ts
329
+ const issued = await authyon.environment.tenants.credentials.create(tenantId, {
330
+ description: "ERP financeiro",
331
+ scopes: ["monkeypay:transactions:read"],
332
+ });
333
+ // Armazene issued.clientSecret no gerenciador de segredos. Não registre a resposta.
334
+
335
+ await authyon.environment.tenants.credentials.listPage(tenantId, {
336
+ search: "ERP",
337
+ skip: 0,
338
+ take: 25,
339
+ });
340
+ await authyon.environment.tenants.credentials.get(tenantId, credentialId);
341
+ await authyon.environment.tenants.credentials.create(tenantId, {
342
+ description: "Leitura",
343
+ permissions: ["monkeypay:transactions:read"],
344
+ });
345
+ await authyon.environment.tenants.credentials.updateScopes(tenantId, credentialId, [
346
+ "monkeypay:transactions:read",
347
+ ]);
348
+ await authyon.environment.tenants.credentials.updatePermissions(tenantId, credentialId, {
349
+ permissions: ["monkeypay:transactions:read"],
350
+ });
351
+ const rotated = await authyon.environment.tenants.credentials.rotate(tenantId, credentialId);
352
+ await authyon.environment.tenants.credentials.revoke(tenantId, credentialId);
353
+ ```
354
+
355
+ Criação e rotação retornam o segredo uma única vez. Alterar permissões substitui a lista
356
+ completa. A concessão OAuth continua recebendo apenas `clientId`/`clientSecret`;
357
+ `scopes` não é um parâmetro de redução de acesso na troca de token.
358
+
359
+ Todas as operações usam os endpoints externos `/env` com a credencial de
360
+ ambiente configurada no cliente. O SDK não repete mutações automaticamente.
361
+
362
+ ## Filtros do tenant
363
+
364
+ `search` é um substring só, ORado entre nome, slug, descrição e os dois metadata.
365
+ Quando mais de uma condição precisa valer — `kind IN (...) AND cnpj IN (...)` — use
366
+ `filter`, que roda dentro da consulta e por isso continua paginando:
367
+
368
+ ```ts
369
+ import { tenantFilter } from "@authyon/server";
370
+
371
+ const page = await authyon.environment.tenants.list({
372
+ filter: [
373
+ tenantFilter.in("public.kind", ["reseller", "partner"]),
374
+ tenantFilter.in("custom.cnpj", ["00000000000100", "11111111000199"]),
375
+ ],
376
+ take: 100,
377
+ });
378
+
379
+ page.total; // quantos casam com o filtro, não quantos existem no ambiente
380
+ ```
381
+
382
+ Cada entrada é uma condição; várias são ANDadas entre si e com o `search`, e a
383
+ vírgula dentro de um `in` é o OR. Os escopos são `public` (publicMetadata),
384
+ `private` (privateMetadata) e `custom` (customFields — este o `search` nunca
385
+ cobriu). Operadores: `eq`, `in`, `like` (ILIKE, com os metacaracteres escapados)
386
+ e `exists`. Caminho aninhado vale até três níveis: `private.billing.plan`.
387
+
388
+ O construtor valida antes de sair: escopo desconhecido, chave com espaço, mais de
389
+ 25 valores num `in` ou vírgula dentro de um valor levantam `RangeError` em vez de
390
+ virarem `400 tenant.filter.invalid`. No máximo 10 filtros por requisição.
391
+
392
+ Precisa da API Authyon de 2026-10-22 ou mais nova; contra uma anterior o `filter`
393
+ é ignorado, o que aparece como página larga demais e não como erro.
394
+
395
+ ## Membros do tenant
396
+
397
+ ```ts
398
+ const user = authyon.user(() => getCurrentUserAccessToken());
399
+ await user.tenants.members.invite(tenantId, {
400
+ email: "pessoa@example.com",
401
+ roles: ["reader"],
402
+ });
403
+ await user.tenants.members.list(tenantId, { skip: 0, take: 25 });
404
+ await user.tenants.members.remove(tenantId, userId);
405
+
406
+ // Pelo plano administrativo, a listagem vem no envelope paginado de sempre
407
+ // (`data`, `total`, `hasNext`), com `take` 50 por padrão e teto 100.
408
+ const { data: membros, total } = await authyon.environment.tenants.members.list(tenantId);
409
+
410
+ // Administração por máquina: adicione uma conta existente por ID.
411
+ await authyon.environment.tenants.members.add(tenantId, userId, ["reader"]);
412
+ await authyon.environment.tenants.members.remove(tenantId, userId);
413
+ // Os métodos tenant(credentials).members.add/remove existentes continuam disponíveis.
414
+ ```
415
+
416
+ Configure `envKey` para `user()`. `members.invite` associa um usuário já
417
+ cadastrado no ambiente pelo e-mail; não cria conta e não envia link por e-mail.
418
+ Para isso, use os convites abaixo.
419
+ A API exige associação/permissões do usuário chamador. No browser, os métodos
420
+ existentes `@authyon/auth` → `organization.members.invite/remove` continuam
421
+ atendendo esse fluxo. Nunca importe `@authyon/server` no browser.
422
+
423
+ ## Convites de tenant (e-mail ou link)
424
+
425
+ Convide um e-mail que talvez ainda não tenha conta — aceitar cria a conta, já com o
426
+ e-mail confirmado. A resposta sempre traz o `acceptUrl` completo; `sendEmail: false`
427
+ só gera o link para você enviar pelo seu canal.
428
+
429
+ ```ts
430
+ // Pelo ambiente (escopo authyon:users:write; listar exige authyon:tenants:read).
431
+ const invite = await authyon.environment.tenants.invites.create(tenantId, {
432
+ email: "pessoa@example.com",
433
+ roles: ["reader"],
434
+ sendEmail: false, // padrão: true
435
+ // Opcional: para onde mandar a pessoa depois de aceitar. Precisa estar
436
+ // cadastrado (match exato) nos redirect URIs do ambiente.
437
+ redirectUri: "https://app.example.com/bem-vindo",
438
+ });
439
+ invite.acceptUrl; // só aparece aqui — a API guarda apenas o hash do token
440
+
441
+ await authyon.environment.tenants.invites.list(tenantId);
442
+ await authyon.environment.tenants.invites.resend(tenantId, invite.id, { sendEmail: true });
443
+ await authyon.environment.tenants.invites.revoke(tenantId, invite.id);
444
+
445
+ // Pela credencial do tenant, ou agindo como o usuário (permissão tenants:members:invite).
446
+ await authyon.tenant(credentials).invites.create({ email: "pessoa@example.com" });
447
+ await authyon.user(token).tenants.invites.create(tenantId, { email: "pessoa@example.com" });
448
+
449
+ // Página de aceite no seu backend: públicas, usam só o envKey.
450
+ const preview = await authyon.environment.tenants.invites.preview(token);
451
+ const accepted = await authyon.environment.tenants.invites.accept({
452
+ token,
453
+ password: preview.accountExists ? undefined : password,
454
+ // Opcional. Com metadata, a chamada vai para /env/tenant-invites/accept usando
455
+ // clientId/clientSecret (escopo authyon:users:write) — só o backend pode definir.
456
+ publicMetadata: { plano: "pro" },
457
+ privateMetadata: { crmId: "42" },
458
+ // customFields: { ... } — obsoleto, prefira os metadata acima.
459
+ });
460
+ if (accepted.redirectUri) redirect(accepted.redirectUri); // o redirect fica por sua conta
461
+ ```
462
+
463
+ O link vale 7 dias e uma única vez. `resend` gera um link novo e invalida o anterior.
464
+ Por padrão o link aponta para a página do Authyon; configure a _URL do convite para
465
+ tenant_ nas configurações do ambiente para usar a sua (ela recebe `token` e `env`).
466
+ Aceitar não faz login.
467
+
468
+ ## Redefinição de senha pelo seu próprio e-mail
469
+
470
+ Para a sua aplicação disparar o e-mail de redefinição em vez do Authyon, emita o token no
471
+ backend (escopo `authyon:users:password`) e entregue você mesmo:
472
+
473
+ ```ts
474
+ const reset = await authyon.environment.users.issuePasswordReset({
475
+ email: "pessoa@example.com",
476
+ // Opcional: para onde mandar a pessoa depois de trocar a senha. Precisa estar
477
+ // cadastrado (match exato) nos redirect URIs do ambiente.
478
+ redirectUri: "https://app.example.com/login",
479
+ // sendEmail: true faria o Authyon enviar também; o padrão é false.
480
+ });
481
+ reset.token; // só aparece aqui — a API guarda apenas o hash
482
+ reset.resetUrl; // o link que o e-mail do Authyon levaria
483
+ reset.expiresAt; // 30 minutos
484
+
485
+ // Página de redefinição no seu backend: públicas, usam só o envKey.
486
+ const preview = await authyon.environment.users.validatePasswordReset(token); // não gasta o token
487
+ const done = await authyon.environment.users.confirmPasswordReset(token, newPassword);
488
+ if (done.redirectUri) redirect(done.redirectUri); // o redirect fica por sua conta
489
+ ```
490
+
491
+ Quem tem o token consegue definir a senha: envie somente para o endereço da própria conta e
492
+ não registre em log. Emitir um token novo invalida o anterior da mesma conta. Um endereço
493
+ sem conta responde `404 user.not_found`; token desconhecido, usado ou expirado responde
494
+ `401 user.password_reset.invalid`.
495
+
496
+ ## Compatibilidade e segurança do transporte
497
+
498
+ As chamadas exigem os endpoints externos atuais da API e a migração
499
+ `ClientCredentialMetadata` para metadados
500
+ persistidos. O SDK não altera o banco nem publica uma versão automaticamente.
501
+
502
+ O adaptador Fetch agora rejeita redirects para evitar reenviar tokens/segredos;
503
+ configure a URL final HTTPS da API. Adaptadores HTTP personalizados devem manter
504
+ essa política. Os logs internos omitem corpos/headers e ocultam tokens de
505
+ convite presentes no caminho. A auditoria de operações permanece na API; o SDK
506
+ não gera eventos duplicados ao consultar listagens de auditoria.
@@ -0,0 +1,129 @@
1
+ import { L as JwksTokenVerifier, A as AuthyonServerClient } from './authyonServerClient-COrfN4o_.js';
2
+
3
+ type AbilitySubject = string | Record<string, unknown>;
4
+ type AbilityConditions = Record<string, unknown>;
5
+ interface AbilityRule {
6
+ action: string | string[];
7
+ subject: string | string[];
8
+ inverted?: boolean;
9
+ conditions?: AbilityConditions;
10
+ fields?: string[];
11
+ reason?: string;
12
+ }
13
+ interface AuthyonPermissionSource {
14
+ permissions?: readonly string[] | null;
15
+ roles?: readonly string[] | null;
16
+ /** OAuth scopes are also accepted and interpreted as Authyon permissions. */
17
+ scope?: string | null;
18
+ }
19
+ /** A declarative group of permissions evaluated against an Authyon permission source. */
20
+ interface PermissionGroup {
21
+ /** Permissions that must all be granted. */
22
+ allOf?: readonly string[];
23
+ /** Permissions where at least one must be granted. */
24
+ anyOf?: readonly string[];
25
+ }
26
+ interface AuthyonAbilityOptions {
27
+ rules?: readonly AbilityRule[];
28
+ /** Rules added when the source contains a matching Authyon role. */
29
+ roleRules?: Readonly<Record<string, readonly AbilityRule[]>>;
30
+ roles?: readonly string[] | null;
31
+ detectSubjectType?: (subject: Record<string, unknown>) => string;
32
+ }
33
+ type AbilityEvent = "updated";
34
+ type AbilityListener = (rules: readonly AbilityRule[]) => void;
35
+ /** Isomorphic, deny-by-default authorization engine backed by Authyon permissions. */
36
+ declare class AuthyonAbility {
37
+ private readonly detectSubjectType;
38
+ private currentRules;
39
+ private readonly listeners;
40
+ constructor(rules?: readonly AbilityRule[], detectSubjectType?: (subject: Record<string, unknown>) => string);
41
+ get rules(): readonly AbilityRule[];
42
+ can(action: string, subject: AbilitySubject, field?: string): boolean;
43
+ cannot(action: string, subject: AbilitySubject, field?: string): boolean;
44
+ rulesFor(action: string, subject: string): readonly AbilityRule[];
45
+ update(rules: readonly AbilityRule[]): void;
46
+ on(event: AbilityEvent, listener: AbilityListener): () => void;
47
+ }
48
+ /** Fluent rule builder with CASL-like `can` and `cannot` methods. */
49
+ declare class AuthyonAbilityBuilder {
50
+ readonly rules: AbilityRule[];
51
+ can(action: string | string[], subject: string | string[], conditions?: AbilityConditions, fields?: string[]): this;
52
+ cannot(action: string | string[], subject: string | string[], conditions?: AbilityConditions, fields?: string[], reason?: string): this;
53
+ build(options?: Pick<AuthyonAbilityOptions, "detectSubjectType">): AuthyonAbility;
54
+ }
55
+ /** Creates an ability from Authyon permissions, OAuth scope and optional role rules. */
56
+ declare function createAuthyonAbility(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AuthyonAbility;
57
+ /** Checks an Authyon permission string using the same wildcard rules as an ability. */
58
+ declare function hasPermission(source: AuthyonPermissionSource | readonly string[], requiredPermission: string): boolean;
59
+ /**
60
+ * Checks a permission group using Authyon's standard permission and wildcard rules.
61
+ * Empty or omitted groups do not add authorization requirements.
62
+ */
63
+ declare function hasPermissionGroup(source: AuthyonPermissionSource | readonly string[], group: PermissionGroup): boolean;
64
+ /** Converts Authyon's `subject:action` permission strings to authorization rules. */
65
+ declare function createAuthyonRules(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AbilityRule[];
66
+
67
+ interface AbilityRequirement {
68
+ action: string;
69
+ subject: AbilitySubject;
70
+ field?: string;
71
+ }
72
+ interface AuthorizationContext {
73
+ token: string;
74
+ userId?: string;
75
+ ability: AuthyonAbility;
76
+ permissions: readonly string[];
77
+ roles: readonly string[];
78
+ }
79
+ interface AuthorizationOptions extends AuthyonAbilityOptions {
80
+ requirement?: AbilityRequirement;
81
+ /** Uses database-backed validation by default; introspection can be selected when desired. */
82
+ verification?: "validate" | "introspect" | "jwks";
83
+ /** Required for local JWKS verification. Create once and reuse its key cache. */
84
+ jwksVerifier?: JwksTokenVerifier;
85
+ /** Real end-user IP, obtained from trusted application infrastructure. */
86
+ clientIp?: string;
87
+ }
88
+ declare class AuthorizationError extends Error {
89
+ readonly status: 401 | 403 | 503;
90
+ readonly code: "authorization.unauthenticated" | "authorization.forbidden" | "authorization.unavailable";
91
+ constructor(status: 401 | 403 | 503, code: "authorization.unauthenticated" | "authorization.forbidden" | "authorization.unavailable", message: string);
92
+ }
93
+ /** Validates a bearer token and creates an Authyon-backed ability for server runtimes. */
94
+ declare function authorizeToken(client: AuthyonServerClient, token: string, options?: AuthorizationOptions): Promise<AuthorizationContext>;
95
+ /** Works with Web Request objects used by Next.js Route Handlers and other Fetch runtimes. */
96
+ declare function authorizeRequest(client: AuthyonServerClient, request: Pick<Request, "headers">, options?: RequestAuthorizationOptions): Promise<AuthorizationContext>;
97
+ interface RequestAuthorizationOptions extends AuthorizationOptions {
98
+ /** Platform-aware resolver. Only read headers that your trusted proxy overwrites. */
99
+ resolveClientIp?: (request: Pick<Request, "headers">) => string | undefined;
100
+ }
101
+ interface ExpressRequestLike {
102
+ headers: Record<string, string | string[] | undefined>;
103
+ ip?: string;
104
+ socket?: {
105
+ remoteAddress?: string;
106
+ };
107
+ authyon?: AuthorizationContext;
108
+ }
109
+ interface ExpressResponseLike {
110
+ status(code: number): ExpressResponseLike;
111
+ json(body: unknown): unknown;
112
+ }
113
+ type ExpressNext = (error?: unknown) => void;
114
+ interface ExpressAuthorizationMiddlewareOptions extends AuthorizationOptions {
115
+ /** Property assigned to the request. Defaults to `authyon`. */
116
+ requestProperty?: string;
117
+ /** Pass authorization errors to Express error middleware instead of writing a JSON response. */
118
+ passErrorsToNext?: boolean;
119
+ /** Trust Express's proxy-aware `request.ip`. Disabled by default. */
120
+ trustProxy?: boolean;
121
+ /** Overrides all built-in IP resolution; return only a trusted, single client IP. */
122
+ resolveClientIp?: (request: ExpressRequestLike) => string | undefined;
123
+ }
124
+ /** Dependency-free middleware compatible with Express and similar Connect frameworks. */
125
+ declare function createExpressAuthorizationMiddleware(client: AuthyonServerClient, options?: ExpressAuthorizationMiddlewareOptions): (request: ExpressRequestLike & Record<string, unknown>, response: ExpressResponseLike, next: ExpressNext) => Promise<void>;
126
+ /** Converts an authorization error into a Fetch Response suitable for Next.js. */
127
+ declare function createAuthorizationErrorResponse(error: unknown): Response;
128
+
129
+ export { type AbilitySubject as A, type ExpressAuthorizationMiddlewareOptions as E, type PermissionGroup as P, type RequestAuthorizationOptions as R, AuthorizationError as a, type AbilityConditions as b, type AbilityEvent as c, type AbilityListener as d, type AbilityRequirement as e, type AbilityRule as f, type AuthorizationContext as g, type AuthorizationOptions as h, AuthyonAbility as i, AuthyonAbilityBuilder as j, type AuthyonAbilityOptions as k, type AuthyonPermissionSource as l, type ExpressNext as m, type ExpressRequestLike as n, type ExpressResponseLike as o, authorizeRequest as p, authorizeToken as q, createAuthorizationErrorResponse as r, createAuthyonAbility as s, createAuthyonRules as t, createExpressAuthorizationMiddleware as u, hasPermission as v, hasPermissionGroup as w };
@@ -0,0 +1,129 @@
1
+ import { L as JwksTokenVerifier, A as AuthyonServerClient } from './authyonServerClient-COrfN4o_.cjs';
2
+
3
+ type AbilitySubject = string | Record<string, unknown>;
4
+ type AbilityConditions = Record<string, unknown>;
5
+ interface AbilityRule {
6
+ action: string | string[];
7
+ subject: string | string[];
8
+ inverted?: boolean;
9
+ conditions?: AbilityConditions;
10
+ fields?: string[];
11
+ reason?: string;
12
+ }
13
+ interface AuthyonPermissionSource {
14
+ permissions?: readonly string[] | null;
15
+ roles?: readonly string[] | null;
16
+ /** OAuth scopes are also accepted and interpreted as Authyon permissions. */
17
+ scope?: string | null;
18
+ }
19
+ /** A declarative group of permissions evaluated against an Authyon permission source. */
20
+ interface PermissionGroup {
21
+ /** Permissions that must all be granted. */
22
+ allOf?: readonly string[];
23
+ /** Permissions where at least one must be granted. */
24
+ anyOf?: readonly string[];
25
+ }
26
+ interface AuthyonAbilityOptions {
27
+ rules?: readonly AbilityRule[];
28
+ /** Rules added when the source contains a matching Authyon role. */
29
+ roleRules?: Readonly<Record<string, readonly AbilityRule[]>>;
30
+ roles?: readonly string[] | null;
31
+ detectSubjectType?: (subject: Record<string, unknown>) => string;
32
+ }
33
+ type AbilityEvent = "updated";
34
+ type AbilityListener = (rules: readonly AbilityRule[]) => void;
35
+ /** Isomorphic, deny-by-default authorization engine backed by Authyon permissions. */
36
+ declare class AuthyonAbility {
37
+ private readonly detectSubjectType;
38
+ private currentRules;
39
+ private readonly listeners;
40
+ constructor(rules?: readonly AbilityRule[], detectSubjectType?: (subject: Record<string, unknown>) => string);
41
+ get rules(): readonly AbilityRule[];
42
+ can(action: string, subject: AbilitySubject, field?: string): boolean;
43
+ cannot(action: string, subject: AbilitySubject, field?: string): boolean;
44
+ rulesFor(action: string, subject: string): readonly AbilityRule[];
45
+ update(rules: readonly AbilityRule[]): void;
46
+ on(event: AbilityEvent, listener: AbilityListener): () => void;
47
+ }
48
+ /** Fluent rule builder with CASL-like `can` and `cannot` methods. */
49
+ declare class AuthyonAbilityBuilder {
50
+ readonly rules: AbilityRule[];
51
+ can(action: string | string[], subject: string | string[], conditions?: AbilityConditions, fields?: string[]): this;
52
+ cannot(action: string | string[], subject: string | string[], conditions?: AbilityConditions, fields?: string[], reason?: string): this;
53
+ build(options?: Pick<AuthyonAbilityOptions, "detectSubjectType">): AuthyonAbility;
54
+ }
55
+ /** Creates an ability from Authyon permissions, OAuth scope and optional role rules. */
56
+ declare function createAuthyonAbility(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AuthyonAbility;
57
+ /** Checks an Authyon permission string using the same wildcard rules as an ability. */
58
+ declare function hasPermission(source: AuthyonPermissionSource | readonly string[], requiredPermission: string): boolean;
59
+ /**
60
+ * Checks a permission group using Authyon's standard permission and wildcard rules.
61
+ * Empty or omitted groups do not add authorization requirements.
62
+ */
63
+ declare function hasPermissionGroup(source: AuthyonPermissionSource | readonly string[], group: PermissionGroup): boolean;
64
+ /** Converts Authyon's `subject:action` permission strings to authorization rules. */
65
+ declare function createAuthyonRules(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AbilityRule[];
66
+
67
+ interface AbilityRequirement {
68
+ action: string;
69
+ subject: AbilitySubject;
70
+ field?: string;
71
+ }
72
+ interface AuthorizationContext {
73
+ token: string;
74
+ userId?: string;
75
+ ability: AuthyonAbility;
76
+ permissions: readonly string[];
77
+ roles: readonly string[];
78
+ }
79
+ interface AuthorizationOptions extends AuthyonAbilityOptions {
80
+ requirement?: AbilityRequirement;
81
+ /** Uses database-backed validation by default; introspection can be selected when desired. */
82
+ verification?: "validate" | "introspect" | "jwks";
83
+ /** Required for local JWKS verification. Create once and reuse its key cache. */
84
+ jwksVerifier?: JwksTokenVerifier;
85
+ /** Real end-user IP, obtained from trusted application infrastructure. */
86
+ clientIp?: string;
87
+ }
88
+ declare class AuthorizationError extends Error {
89
+ readonly status: 401 | 403 | 503;
90
+ readonly code: "authorization.unauthenticated" | "authorization.forbidden" | "authorization.unavailable";
91
+ constructor(status: 401 | 403 | 503, code: "authorization.unauthenticated" | "authorization.forbidden" | "authorization.unavailable", message: string);
92
+ }
93
+ /** Validates a bearer token and creates an Authyon-backed ability for server runtimes. */
94
+ declare function authorizeToken(client: AuthyonServerClient, token: string, options?: AuthorizationOptions): Promise<AuthorizationContext>;
95
+ /** Works with Web Request objects used by Next.js Route Handlers and other Fetch runtimes. */
96
+ declare function authorizeRequest(client: AuthyonServerClient, request: Pick<Request, "headers">, options?: RequestAuthorizationOptions): Promise<AuthorizationContext>;
97
+ interface RequestAuthorizationOptions extends AuthorizationOptions {
98
+ /** Platform-aware resolver. Only read headers that your trusted proxy overwrites. */
99
+ resolveClientIp?: (request: Pick<Request, "headers">) => string | undefined;
100
+ }
101
+ interface ExpressRequestLike {
102
+ headers: Record<string, string | string[] | undefined>;
103
+ ip?: string;
104
+ socket?: {
105
+ remoteAddress?: string;
106
+ };
107
+ authyon?: AuthorizationContext;
108
+ }
109
+ interface ExpressResponseLike {
110
+ status(code: number): ExpressResponseLike;
111
+ json(body: unknown): unknown;
112
+ }
113
+ type ExpressNext = (error?: unknown) => void;
114
+ interface ExpressAuthorizationMiddlewareOptions extends AuthorizationOptions {
115
+ /** Property assigned to the request. Defaults to `authyon`. */
116
+ requestProperty?: string;
117
+ /** Pass authorization errors to Express error middleware instead of writing a JSON response. */
118
+ passErrorsToNext?: boolean;
119
+ /** Trust Express's proxy-aware `request.ip`. Disabled by default. */
120
+ trustProxy?: boolean;
121
+ /** Overrides all built-in IP resolution; return only a trusted, single client IP. */
122
+ resolveClientIp?: (request: ExpressRequestLike) => string | undefined;
123
+ }
124
+ /** Dependency-free middleware compatible with Express and similar Connect frameworks. */
125
+ declare function createExpressAuthorizationMiddleware(client: AuthyonServerClient, options?: ExpressAuthorizationMiddlewareOptions): (request: ExpressRequestLike & Record<string, unknown>, response: ExpressResponseLike, next: ExpressNext) => Promise<void>;
126
+ /** Converts an authorization error into a Fetch Response suitable for Next.js. */
127
+ declare function createAuthorizationErrorResponse(error: unknown): Response;
128
+
129
+ export { type AbilitySubject as A, type ExpressAuthorizationMiddlewareOptions as E, type PermissionGroup as P, type RequestAuthorizationOptions as R, AuthorizationError as a, type AbilityConditions as b, type AbilityEvent as c, type AbilityListener as d, type AbilityRequirement as e, type AbilityRule as f, type AuthorizationContext as g, type AuthorizationOptions as h, AuthyonAbility as i, AuthyonAbilityBuilder as j, type AuthyonAbilityOptions as k, type AuthyonPermissionSource as l, type ExpressNext as m, type ExpressRequestLike as n, type ExpressResponseLike as o, authorizeRequest as p, authorizeToken as q, createAuthorizationErrorResponse as r, createAuthyonAbility as s, createAuthyonRules as t, createExpressAuthorizationMiddleware as u, hasPermission as v, hasPermissionGroup as w };