@adonis-agora/authkit-client 0.20.4 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +957 -0
  2. package/README.md +47 -12
  3. package/build/auth.d.ts +2 -0
  4. package/build/auth.js +1 -0
  5. package/build/authkit_context_middleware.d.ts +1 -0
  6. package/build/authkit_context_middleware.js +2 -0
  7. package/build/commands/configure.d.ts +10 -0
  8. package/build/commands/configure.js +57 -2
  9. package/build/index.d.ts +3 -2
  10. package/build/index.js +1 -1
  11. package/build/providers/authkit_client_provider.d.ts +8 -1
  12. package/build/providers/authkit_client_provider.js +28 -12
  13. package/build/src/authenticator.d.ts +4 -3
  14. package/build/src/authenticator.js +6 -3
  15. package/build/src/host/authkit_client_guard.d.ts +51 -36
  16. package/build/src/host/authkit_client_guard.js +97 -46
  17. package/build/src/host/dashboard_guard.d.ts +3 -7
  18. package/build/src/host/dashboard_guard.js +3 -7
  19. package/build/src/host/request_authenticator.d.ts +28 -10
  20. package/build/src/host/request_authenticator.js +5 -3
  21. package/build/src/middleware/authkit_context_middleware.d.ts +3 -4
  22. package/build/src/middleware/authkit_context_middleware.js +5 -4
  23. package/build/src/middleware/backchannel_revocation_middleware.d.ts +1 -1
  24. package/build/src/middleware/backchannel_revocation_middleware.js +1 -1
  25. package/build/src/middleware/require_org_middleware.d.ts +3 -2
  26. package/build/src/middleware/require_org_middleware.js +5 -4
  27. package/build/src/resolvers/dpop_proof.d.ts +34 -0
  28. package/build/src/resolvers/dpop_proof.js +92 -0
  29. package/build/src/resolvers/factory.d.ts +5 -0
  30. package/build/src/resolvers/factory.js +1 -0
  31. package/build/src/resolvers/jwt_resolver.d.ts +9 -0
  32. package/build/src/resolvers/jwt_resolver.js +47 -3
  33. package/build/stubs/config/auth.stub +30 -0
  34. package/build/stubs/config/authkit_client.stub +2 -1
  35. package/build/stubs/middleware/auth_middleware.stub +25 -0
  36. package/build/stubs/middleware/silent_auth_middleware.stub +16 -0
  37. package/build/types.d.ts +5 -32
  38. package/build/types.js +1 -1
  39. package/package.json +12 -17
  40. package/skills/authkit-rp-client/SKILL.md +102 -47
  41. package/stubs/config/auth.stub +30 -0
  42. package/stubs/config/authkit_client.stub +2 -1
  43. package/stubs/middleware/auth_middleware.stub +25 -0
  44. package/stubs/middleware/silent_auth_middleware.stub +16 -0
  45. package/build/src/host/ctx_auth.d.ts +0 -6
  46. package/build/src/host/ctx_auth.js +0 -8
  47. package/build/src/middleware/auth_middleware.d.ts +0 -13
  48. package/build/src/middleware/auth_middleware.js +0 -13
  49. package/build/src/middleware/authkit_middleware.d.ts +0 -6
  50. package/build/src/middleware/authkit_middleware.js +0 -24
  51. package/build/src/middleware/silent_auth_middleware.d.ts +0 -13
  52. package/build/src/middleware/silent_auth_middleware.js +0 -15
package/README.md CHANGED
@@ -8,8 +8,16 @@ Authorization Server (`@adonis-agora/authkit-server`), validando o ID token loca
8
8
  node ace add @adonis-agora/authkit-client
9
9
  # ou: pnpm add @adonis-agora/authkit-client && node ace configure @adonis-agora/authkit-client
10
10
  ```
11
- O `configure` publica `config/authkit_client.ts`, o controller `app/controllers/oidc_session_controller.ts`
12
- (login/callback/logout) e registra o provider + o middleware `authkit_middleware`.
11
+ `@adonisjs/auth` é peer obrigatório: `ctx.auth` é o autenticador do `@adonisjs/auth`, apoiado
12
+ na sessão do AuthKit pelo `authkitClientGuard`. O `configure`:
13
+
14
+ - instala `@adonisjs/auth` se o app ainda não depende dele (`--no-install` só imprime o comando);
15
+ - publica `config/authkit_client.ts` e `config/auth.ts` (`authkitClientGuard` +
16
+ `sessionUserProvider` com `--model`, default `#models/user`; um `config/auth.ts` existente não é
17
+ tocado e o guard a adicionar é impresso);
18
+ - publica `app/middleware/auth_middleware.ts` / `silent_auth_middleware.ts` (named `auth` e
19
+ `silentAuth`) e o controller `app/controllers/oidc_session_controller.ts`;
20
+ - registra os providers e, no `router`, `initialize_auth_middleware` + `authkit_context_middleware`.
13
21
 
14
22
  ## Rotas de sessão
15
23
 
@@ -23,7 +31,6 @@ import { registerOidcClient } from '@adonis-agora/authkit-client'
23
31
 
24
32
  registerOidcClient(router, {
25
33
  prefix: '/auth', // default
26
- loginMiddleware: middleware.guest(),
27
34
  redirects: { byGlobalRole: { ADMIN: '/admin' }, default: '/' },
28
35
  afterLogin: async (ctx, identity) => undefined, // retornar string redireciona
29
36
  postLogoutRedirect: '/', // default: origem do redirectUri + '/'
@@ -51,25 +58,52 @@ router.post('/auth/logout', [OidcSessionController, 'logout'])
51
58
  > `startSession`/`endSession` são os caminhos que mantêm a invariante.
52
59
 
53
60
  ## Uso por request
54
- `ctx.auth` é populado pelo `authkit_middleware`:
61
+ Rotas usam `middleware.auth()` / `middleware.silentAuth()` e os controllers leem o usuário em
62
+ `ctx.auth`:
55
63
  ```ts
56
- const identity = await ctx.auth.getIdentity() // claims OIDC validadas (ou null)
57
- const user = await ctx.auth.getUser() // model do app (via resolveUser)
58
- ctx.auth.hasGlobalRole('ADMIN') // síncrono, das claims (roles do token)
64
+ // app/controllers/dashboard_controller.ts
65
+ export default class DashboardController {
66
+ async handle({ auth, authkit, inertia }: HttpContext) {
67
+ const user = auth.getUserOrFail() // atrás de middleware.auth()
68
+ const isAdmin = authkit.hasGlobalRole('ADMIN') // síncrono, das claims do token
69
+ return inertia.render('dashboard', { user, isAdmin })
70
+ }
71
+ }
59
72
  ```
73
+ O guard pega o usuário do `resolveUser` (ex.: `lucidMirror`, que cria a linha no primeiro login)
74
+ e cai no provider quando não há `resolveUser`. Sem tabela local de usuários, use
75
+ `authkitIdentityProvider()` como provider: `ctx.auth.user` é a `Identity`.
76
+
77
+ `ctx.authkit` (populado pelo `authkit_context_middleware`) traz o que só o AuthKit sabe — a
78
+ `Identity` (`getIdentity()`), `hasGlobalRole`, a org ativa e `toSharedProps()` para o
79
+ `@adonis-agora/authkit-react` — inclusive em rotas públicas.
60
80
 
61
81
  > **Autorização por app-role saiu do AuthKit.** O AuthKit só autentica e conhece as `globalRoles` do
62
82
  > token. Roles do app (numa tabela do domínio) e permissões são do `@adonis-agora/authz` — configure
63
- > o seam `resolveRoles` lá. Guard de rota por role vai pra authz/Bouncer, não para o `authkit_middleware`
83
+ > o seam `resolveRoles` lá. Guard de rota por role vai pra authz/Bouncer, não para o `middleware.auth()`
64
84
  > (que só exige login).
65
85
 
66
86
  ## Resolução de sessão
67
87
  `resolvers.jwt({ tokenSource })` valida o **ID token** (JWT) por JWKS:
68
88
  - `tokenSource: 'session'` (default) — lê o token set do `@adonisjs/session`.
69
- - `tokenSource: 'bearer'` — lê do header `Authorization` (SPA/API).
89
+ - `tokenSource: 'bearer'` — lê do header `Authorization` (SPA/API): `Bearer <jwt>` ou
90
+ `DPoP <jwt>` com a prova no header `DPoP` (token com `cnf.jkt` enviado como `Bearer` é recusado).
91
+
92
+ Rotas de API ganham um guard próprio com `resolver`:
93
+ ```ts
94
+ // config/auth.ts
95
+ api: authkitClientGuard({
96
+ provider: authkitIdentityProvider(),
97
+ resolver: resolvers.jwt({ tokenSource: 'bearer' }), // ou resolvers.opaque/resolvers.pat
98
+ }),
99
+ ```
100
+ ```ts
101
+ // start/routes.ts
102
+ router.group(() => { /* … */ }).prefix('/api').use(middleware.auth({ guards: ['api'] }))
103
+ ```
70
104
 
71
105
  ## Topologias de banco
72
- A identidade (`ctx.auth.identity`) sempre vem do **token validado** e independe da
106
+ A identidade (`ctx.authkit.identity`) sempre vem do **token validado** e independe da
73
107
  topologia de banco. O que muda é como `resolveUser` obtém o model do app.
74
108
 
75
109
  ### Mesmo banco + schemas
@@ -102,8 +136,9 @@ Roles do app (a tabela de roles do próprio domínio) não passam mais pelo Auth
102
136
  `resolveRoles` do `@adonis-agora/authz`, que as une às `globalRoles` do token para decidir permissão.
103
137
 
104
138
  ## Ordem de middleware (importante)
105
- O middleware do `@adonisjs/session` DEVE rodar ANTES do `authkit_middleware` no stack `router`
106
- (o resolver lê o token da session). Garanta essa ordem em `start/kernel.ts`.
139
+ No stack `router`: `@adonisjs/session` → `@adonisjs/auth/initialize_auth_middleware` →
140
+ `authkit_context_middleware` (o resolver lê o token da session). O `configure` já registra nessa
141
+ ordem.
107
142
 
108
143
  ## Observabilidade (opcional)
109
144
  O `Authenticator` registra métricas de resolução (`authkit.resolve.duration` e
@@ -0,0 +1,2 @@
1
+ export type { IdentityUserProviderContract } from './src/host/authkit_client_guard.js';
2
+ export { AuthkitClientGuard, authkitClientGuard, authkitIdentityProvider, } from './src/host/authkit_client_guard.js';
package/build/auth.js ADDED
@@ -0,0 +1 @@
1
+ export { AuthkitClientGuard, authkitClientGuard, authkitIdentityProvider, } from './src/host/authkit_client_guard.js';
@@ -0,0 +1 @@
1
+ export { default } from './src/middleware/authkit_context_middleware.js';
@@ -0,0 +1,2 @@
1
+ // `@adonis-agora/authkit-client/authkit_context_middleware`: populates `ctx.authkit`.
2
+ export { default } from './src/middleware/authkit_context_middleware.js';
@@ -1,2 +1,12 @@
1
1
  import type Configure from '@adonisjs/core/commands/configure';
2
+ /**
3
+ * `node ace configure @adonis-agora/authkit-client`
4
+ *
5
+ * `ctx.auth` comes from `@adonisjs/auth`, backed by the AuthKit session through
6
+ * `authkitClientGuard`, and `ctx.authkit` carries the AuthKit-only surface.
7
+ * Flags:
8
+ *
9
+ * - `--model=<import>`: the user model the guard's provider loads (default `#models/user`).
10
+ * - `--no-install`: print the install command instead of installing `@adonisjs/auth`.
11
+ */
2
12
  export declare function configure(command: Configure): Promise<void>;
@@ -1,11 +1,46 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
1
3
  import { stubsRoot } from '../stubs/main.js';
4
+ const ADONIS_AUTH = '@adonisjs/auth';
5
+ /**
6
+ * `node ace configure @adonis-agora/authkit-client`
7
+ *
8
+ * `ctx.auth` comes from `@adonisjs/auth`, backed by the AuthKit session through
9
+ * `authkitClientGuard`, and `ctx.authkit` carries the AuthKit-only surface.
10
+ * Flags:
11
+ *
12
+ * - `--model=<import>`: the user model the guard's provider loads (default `#models/user`).
13
+ * - `--no-install`: print the install command instead of installing `@adonisjs/auth`.
14
+ */
2
15
  export async function configure(command) {
16
+ const flags = (command.parsedFlags ?? {});
17
+ const modelImport = typeof flags.model === 'string' && flags.model.trim() ? flags.model.trim() : '#models/user';
3
18
  const codemods = await command.createCodemods();
4
19
  await codemods.makeUsingStub(stubsRoot, 'config/authkit_client.stub', {});
5
20
  await codemods.makeUsingStub(stubsRoot, 'controllers/oidc_session_controller.stub', {});
6
- await codemods.updateRcFile((rc) => rc.addProvider('@adonis-agora/authkit-client/authkit_client_provider'));
21
+ await codemods.updateRcFile((rc) => {
22
+ rc.addProvider('@adonisjs/auth/auth_provider');
23
+ rc.addProvider('@adonis-agora/authkit-client/authkit_client_provider');
24
+ });
25
+ await ensureAdonisAuth(command, codemods, flags.install !== false);
26
+ const authConfigPath = command.app.configPath('auth.ts');
27
+ if (existsSync(authConfigPath)) {
28
+ command.logger.warning('config/auth.ts already exists, so it was left as is. Add the AuthKit guard to it:\n' +
29
+ " import { authkitClientGuard } from '@adonis-agora/authkit-client/auth'\n" +
30
+ ` web: authkitClientGuard({ provider: sessionUserProvider({ model: () => import('${modelImport}') }) }),`);
31
+ }
32
+ else {
33
+ await codemods.makeUsingStub(stubsRoot, 'config/auth.stub', { modelImport });
34
+ }
35
+ await codemods.makeUsingStub(stubsRoot, 'middleware/auth_middleware.stub', {});
36
+ await codemods.makeUsingStub(stubsRoot, 'middleware/silent_auth_middleware.stub', {});
7
37
  await codemods.registerMiddleware('router', [
8
- { path: '@adonis-agora/authkit-client/authkit_middleware' },
38
+ { path: '@adonisjs/auth/initialize_auth_middleware' },
39
+ { path: '@adonis-agora/authkit-client/authkit_context_middleware' },
40
+ ]);
41
+ await codemods.registerMiddleware('named', [
42
+ { name: 'auth', path: '#middleware/auth_middleware' },
43
+ { name: 'silentAuth', path: '#middleware/silent_auth_middleware' },
9
44
  ]);
10
45
  await codemods.defineEnvValidations({
11
46
  leadingComment: 'Variáveis do @adonis-agora/authkit-client (OIDC client)',
@@ -22,4 +57,24 @@ export async function configure(command) {
22
57
  command.logger.info('Registre as rotas de sessão em start/routes.ts com registerOidcClient(router) ' +
23
58
  "(import { registerOidcClient } from '@adonis-agora/authkit-client'). " +
24
59
  'O app/controllers/oidc_session_controller.ts publicado é só para customizar o fluxo.');
60
+ command.logger.info(`ctx.auth usa o authkitClientGuard (config/auth.ts, model ${modelImport}): proteja rotas com ` +
61
+ 'middleware.auth() e leia auth.user. Para criar o usuário local no primeiro login, ' +
62
+ 'configure resolveUser: lucidMirror(User, …) em config/authkit_client.ts.');
63
+ }
64
+ /** Installs `@adonisjs/auth` when the app does not depend on it yet. */
65
+ async function ensureAdonisAuth(command, codemods, install) {
66
+ let manifest = {};
67
+ try {
68
+ manifest = JSON.parse(await readFile(command.app.makePath('package.json'), 'utf8'));
69
+ }
70
+ catch {
71
+ // No readable package.json: treat the dependency as missing.
72
+ }
73
+ if (manifest.dependencies?.[ADONIS_AUTH] || manifest.devDependencies?.[ADONIS_AUTH])
74
+ return;
75
+ const packages = [{ name: ADONIS_AUTH, isDevDependency: false }];
76
+ if (install)
77
+ await codemods.installPackages(packages);
78
+ else
79
+ await codemods.listPackagesToInstall(packages);
25
80
  }
package/build/index.d.ts CHANGED
@@ -13,10 +13,11 @@ export type { DiscoverOptions, OidcEndpoints } from './src/discovery.js';
13
13
  export { conventionEndpoints, discoverEndpoints, OidcDiscoveryError } from './src/discovery.js';
14
14
  export type { CreateDpopProofInput, DpopKeyPair } from './src/dpop.js';
15
15
  export { createDpopProof, dpopJwkThumbprint, generateDpopKeyPair, } from './src/dpop.js';
16
- export type { UnauthorizedAccessConstructor } from './src/host/authkit_client_guard.js';
17
- export { AuthkitClientGuard, authkitClientGuard } from './src/host/authkit_client_guard.js';
16
+ export type { IdentityUserProviderContract } from './src/host/authkit_client_guard.js';
17
+ export { AuthkitClientGuard, authkitClientGuard, authkitIdentityProvider, } from './src/host/authkit_client_guard.js';
18
18
  export type { DashboardGuardOptions } from './src/host/dashboard_guard.js';
19
19
  export { authkitDashboardAuthorize, authkitDashboardMiddleware, isAuthkitAdmin, } from './src/host/dashboard_guard.js';
20
+ export type { AuthkitUser, AuthkitUserType } from './src/host/request_authenticator.js';
20
21
  export { getAuthkit } from './src/host/request_authenticator.js';
21
22
  export type { ResiliencePolicy } from './src/http/resilient_fetch.js';
22
23
  export { resilientFetch } from './src/http/resilient_fetch.js';
package/build/index.js CHANGED
@@ -6,7 +6,7 @@ export { BACKCHANNEL_LOGOUT_EVENT, InMemorySessionIndex, InvalidLogoutTokenError
6
6
  export { defineConfig, resolvers } from './src/define_config.js';
7
7
  export { conventionEndpoints, discoverEndpoints, OidcDiscoveryError } from './src/discovery.js';
8
8
  export { createDpopProof, dpopJwkThumbprint, generateDpopKeyPair, } from './src/dpop.js';
9
- export { AuthkitClientGuard, authkitClientGuard } from './src/host/authkit_client_guard.js';
9
+ export { AuthkitClientGuard, authkitClientGuard, authkitIdentityProvider, } from './src/host/authkit_client_guard.js';
10
10
  export { authkitDashboardAuthorize, authkitDashboardMiddleware, isAuthkitAdmin, } from './src/host/dashboard_guard.js';
11
11
  export { getAuthkit } from './src/host/request_authenticator.js';
12
12
  export { resilientFetch } from './src/http/resilient_fetch.js';
@@ -7,6 +7,7 @@ import { type AuthorizationRequest, type CompleteAuthorizationParams, type Compl
7
7
  import type { ResolvedClientConfig } from '../src/define_config.js';
8
8
  import { type OidcEndpoints } from '../src/discovery.js';
9
9
  import { type IdTokenClaims, type ValidateIdTokenOptions } from '../src/id_token.js';
10
+ import type { ResolverFactory } from '../src/resolvers/factory.js';
10
11
  import type { TokenSet } from '../src/types.js';
11
12
  /** Deps injetáveis (relógio/fetch) para testar `maybeRefresh` sem rede/tempo real. */
12
13
  export interface RefreshDeps {
@@ -161,7 +162,13 @@ export declare class AuthkitClientManager {
161
162
  * sobrevive à sessão que a parqueou.
162
163
  */
163
164
  startSession(ctx: HttpContext, tokenSet: TokenSet): void;
164
- createAuthenticator(ctx: HttpContext): Promise<Authenticator>;
165
+ /**
166
+ * The request's {@link Authenticator}. `options.resolver` replaces the configured
167
+ * resolver for this authenticator only (e.g. a guard for bearer-token API routes).
168
+ */
169
+ createAuthenticator(ctx: HttpContext, options?: {
170
+ resolver?: ResolverFactory;
171
+ }): Promise<Authenticator>;
165
172
  }
166
173
  export default class AuthkitClientProvider {
167
174
  protected app: ApplicationService;
@@ -41,17 +41,29 @@ export class AuthkitClientManager {
41
41
  this.deps = deps;
42
42
  this.#logger = deps.logger ?? { warn: () => { } };
43
43
  }
44
- async #getResolver() {
44
+ #extraResolvers = new WeakMap();
45
+ #buildResolver(factory) {
46
+ return factory.resolver({
47
+ issuer: this.config.issuer,
48
+ clientId: this.config.clientId,
49
+ clientSecret: this.config.clientSecret,
50
+ sessionKey: this.config.sessionKey,
51
+ globalRolesClaim: this.config.globalRolesClaim,
52
+ endpoints: () => this.endpoints(),
53
+ logger: this.#logger,
54
+ });
55
+ }
56
+ async #getResolver(factory) {
57
+ if (factory && factory !== this.config.resolverFactory) {
58
+ let resolver = this.#extraResolvers.get(factory);
59
+ if (!resolver) {
60
+ resolver = this.#buildResolver(factory);
61
+ this.#extraResolvers.set(factory, resolver);
62
+ }
63
+ return resolver;
64
+ }
45
65
  if (!this.#resolverCache) {
46
- this.#resolverCache = await this.config.resolverFactory.resolver({
47
- issuer: this.config.issuer,
48
- clientId: this.config.clientId,
49
- clientSecret: this.config.clientSecret,
50
- sessionKey: this.config.sessionKey,
51
- globalRolesClaim: this.config.globalRolesClaim,
52
- endpoints: () => this.endpoints(),
53
- logger: this.#logger,
54
- });
66
+ this.#resolverCache = await this.#buildResolver(this.config.resolverFactory);
55
67
  }
56
68
  return this.#resolverCache;
57
69
  }
@@ -445,8 +457,12 @@ export class AuthkitClientManager {
445
457
  session.forget(this.#impersonatorKey);
446
458
  session.put(this.config.sessionKey, tokenSet);
447
459
  }
448
- async createAuthenticator(ctx) {
449
- const resolver = await this.#getResolver();
460
+ /**
461
+ * The request's {@link Authenticator}. `options.resolver` replaces the configured
462
+ * resolver for this authenticator only (e.g. a guard for bearer-token API routes).
463
+ */
464
+ async createAuthenticator(ctx, options = {}) {
465
+ const resolver = await this.#getResolver(options.resolver);
450
466
  const sessionKey = this.config.sessionKey;
451
467
  return new Authenticator(ctx, {
452
468
  resolver,
@@ -13,9 +13,8 @@ export interface AuthenticatorDeps {
13
13
  }
14
14
  /**
15
15
  * Autenticador do request. O parâmetro `TUser` é o tipo do usuário do app que `resolveUser`
16
- * devolve — o app o fixa via augmentation de `HttpContext.auth` (`auth: Authenticator<AppUser>`),
17
- * e aí `getUser()` passa a devolver `AppUser | null` em todo call-site, sem cast. Default `unknown`
18
- * mantém o comportamento anterior (nada a mudar em quem não augmenta).
16
+ * devolve — o app o fixa pela interface `AuthkitUserType`, e aí `getUser()` passa a devolver
17
+ * `AppUser | null` em todo call-site, sem cast. Default `unknown`.
19
18
  */
20
19
  export declare class Authenticator<TUser = unknown> {
21
20
  #private;
@@ -26,6 +25,8 @@ export declare class Authenticator<TUser = unknown> {
26
25
  get identity(): Identity | null;
27
26
  authenticate(): Promise<Identity>;
28
27
  check(): Promise<boolean>;
28
+ /** `true` when the app configured `resolveUser` (e.g. `lucidMirror`) for this client. */
29
+ get resolvesUser(): boolean;
29
30
  hasGlobalRole(role: string): boolean;
30
31
  getUser(): Promise<TUser | null>;
31
32
  /**
@@ -2,9 +2,8 @@ import { AUTHKIT_METRICS, NoopRecorder } from '@adonis-agora/authkit-core';
2
2
  import { populateContext } from './observability/context_bridge.js';
3
3
  /**
4
4
  * Autenticador do request. O parâmetro `TUser` é o tipo do usuário do app que `resolveUser`
5
- * devolve — o app o fixa via augmentation de `HttpContext.auth` (`auth: Authenticator<AppUser>`),
6
- * e aí `getUser()` passa a devolver `AppUser | null` em todo call-site, sem cast. Default `unknown`
7
- * mantém o comportamento anterior (nada a mudar em quem não augmenta).
5
+ * devolve — o app o fixa pela interface `AuthkitUserType`, e aí `getUser()` passa a devolver
6
+ * `AppUser | null` em todo call-site, sem cast. Default `unknown`.
8
7
  */
9
8
  export class Authenticator {
10
9
  ctx;
@@ -53,6 +52,10 @@ export class Authenticator {
53
52
  async check() {
54
53
  return (await this.getIdentity()) !== null;
55
54
  }
55
+ /** `true` when the app configured `resolveUser` (e.g. `lucidMirror`) for this client. */
56
+ get resolvesUser() {
57
+ return typeof this.deps.resolveUser === 'function';
58
+ }
56
59
  hasGlobalRole(role) {
57
60
  return this.#identity?.globalRoles.includes(role) ?? false;
58
61
  }
@@ -1,29 +1,33 @@
1
- import type { symbols } from '@adonisjs/auth';
1
+ import type { Identity } from '@adonis-agora/authkit-core';
2
+ import { type symbols } from '@adonisjs/auth';
2
3
  import type { AuthClientResponse, GuardConfigProvider } from '@adonisjs/auth/types';
3
- import type { SessionUserProviderContract } from '@adonisjs/auth/types/session';
4
+ import type { SessionGuardUser, SessionUserProviderContract } from '@adonisjs/auth/types/session';
4
5
  import type { HttpContext } from '@adonisjs/core/http';
5
6
  import type { ConfigProvider } from '@adonisjs/core/types';
6
7
  import type { Authenticator } from '../authenticator.js';
7
- /**
8
- * Construtor do `E_UNAUTHORIZED_ACCESS` do `@adonisjs/auth`, capturado no boot
9
- * (ver {@link authkitClientGuard}) para que `getUserOrFail()` — que o contrato
10
- * exige SÍNCRONO — possa lançá-lo sem um `await import()`.
11
- */
12
- export type UnauthorizedAccessConstructor = new (message: string, options: {
13
- guardDriverName: string;
14
- redirectTo?: string;
15
- }) => Error;
8
+ import type { ResolverFactory } from '../resolvers/factory.js';
9
+ import { type AuthkitUser } from './request_authenticator.js';
16
10
  /** O tipo de usuário REAL por trás de um user provider do `@adonisjs/auth`. */
17
11
  type RealUser<UserProvider> = UserProvider extends SessionUserProviderContract<infer User> ? User : never;
18
12
  /**
19
- * Guard de `@adonisjs/auth` apoiado na sessão OIDC do `@adonis-agora/authkit-client`.
13
+ * User provider que pode montar o usuário direto da `Identity` (sem banco). É o
14
+ * que {@link authkitIdentityProvider} devolve; o guard o usa antes de `findById`.
15
+ */
16
+ export interface IdentityUserProviderContract<User> extends SessionUserProviderContract<User> {
17
+ fromIdentity(identity: Identity): Promise<SessionGuardUser<User> | null>;
18
+ }
19
+ /**
20
+ * Guard de `@adonisjs/auth` apoiado na sessão do `@adonis-agora/authkit-client`.
20
21
  *
21
22
  * A fonte da verdade de "quem está logado" é o `SessionResolver` do authkit
22
- * (JWT/opaco/PAT, conforme `config/authkit_client.ts`) — o guard NÃO reimplementa
23
- * resolução de sessão nem lê a sessão por conta própria. Ele só faz a ponte:
24
- * pega o `sub` da `Identity` (`identity.userId`) e pede o usuário do app ao
25
- * `provider`, exatamente como o `sessionGuard` nativo faria com o id que ele
26
- * guarda na sessão.
23
+ * (sessão OIDC, bearer JWT, opaco/introspecção ou PAT, conforme
24
+ * `config/authkit_client.ts` ou a opção `resolver` do guard). O guard não lê a
25
+ * sessão por conta própria.
26
+ *
27
+ * O usuário vem, nesta ordem: do `resolveUser` de `config/authkit_client.ts`
28
+ * (ex.: `lucidMirror`, que cria a linha local no primeiro login; é a MESMA
29
+ * instância de `ctx.authkit.getUser()`), do provider montando o usuário da
30
+ * `Identity` ({@link authkitIdentityProvider}), ou de `provider.findById(sub)`.
27
31
  *
28
32
  * Consequência: `middleware.auth()`, `ctx.auth.user`, `auth.check()` e o Bouncer
29
33
  * passam a funcionar sobre uma sessão do authkit.
@@ -36,20 +40,21 @@ export declare class AuthkitClientGuard<UserProvider extends SessionUserProvider
36
40
  authenticationAttempted: boolean;
37
41
  isAuthenticated: boolean;
38
42
  user?: RealUser<UserProvider>;
39
- constructor(_name: string, ctx: HttpContext, userProvider: UserProvider, unauthorized: UnauthorizedAccessConstructor);
43
+ constructor(_name: string, ctx: HttpContext, userProvider: UserProvider, options?: {
44
+ resolver?: ResolverFactory;
45
+ });
40
46
  /**
41
- * O {@link Authenticator} do authkit desta request (a `Identity` crua,
47
+ * O {@link Authenticator} do authkit que este guard usou (a `Identity` crua,
42
48
  * `hasGlobalRole`, `toSharedProps`). Só existe depois de uma tentativa de
43
49
  * autenticação; use `ctx.authkit` (via `authkit_context_middleware`) quando
44
50
  * precisar dele em rota pública.
45
51
  */
46
- get authkit(): Authenticator | undefined;
52
+ get authkit(): Authenticator<AuthkitUser> | undefined;
47
53
  /**
48
- * Autentica a request: resolve a sessão do authkit e mapeia o `sub` para o
49
- * usuário do app via `provider.findById`. Lança `E_UNAUTHORIZED_ACCESS` (o
50
- * MESMO erro dos guards nativos, então o `middleware.auth()` e o handler de
51
- * exceção do host tratam igual) quando não há sessão OU quando a sessão é
52
- * válida mas não existe usuário local correspondente (fail-closed).
54
+ * Autentica a request. Lança `E_UNAUTHORIZED_ACCESS` (o MESMO erro dos guards
55
+ * nativos, então o `middleware.auth()` e o handler de exceção do host tratam
56
+ * igual) quando não há sessão OU quando a sessão é válida mas nenhuma fonte
57
+ * devolve usuário (fail-closed).
53
58
  */
54
59
  authenticate(): Promise<RealUser<UserProvider>>;
55
60
  /** Igual aos guards nativos: `authenticate()` sem lançar. */
@@ -71,7 +76,8 @@ export declare class AuthkitClientGuard<UserProvider extends SessionUserProvider
71
76
  * ```ts
72
77
  * import { defineConfig } from '@adonisjs/auth'
73
78
  * import { sessionUserProvider } from '@adonisjs/auth/session'
74
- * import { authkitClientGuard } from '@adonis-agora/authkit-client'
79
+ * import { authkitClientGuard, authkitIdentityProvider } from '@adonis-agora/authkit-client/auth'
80
+ * import { resolvers } from '@adonis-agora/authkit-client'
75
81
  *
76
82
  * export default defineConfig({
77
83
  * default: 'web',
@@ -79,22 +85,31 @@ export declare class AuthkitClientGuard<UserProvider extends SessionUserProvider
79
85
  * web: authkitClientGuard({
80
86
  * provider: sessionUserProvider({ model: () => import('#models/user') }),
81
87
  * }),
88
+ * // API routes authenticated by a bearer access token.
89
+ * api: authkitClientGuard({
90
+ * provider: authkitIdentityProvider(),
91
+ * resolver: resolvers.jwt({ tokenSource: 'bearer' }),
92
+ * }),
82
93
  * },
83
94
  * })
84
95
  * ```
85
96
  *
86
- * O `provider` é o MESMO contrato do `sessionGuard` nativo, então qualquer user
87
- * provider de session serve. O `findById` recebe `identity.userId` (a claim
88
- * `sub`) — a coluna de id do model precisa ser essa (é o que o `lucidMirror()`
89
- * grava).
90
- *
91
- * Precedência com `resolveUser` de `config/authkit_client.ts`: são superfícies
92
- * SEPARADAS e ambas continuam valendo. `ctx.auth.user` (framework) vem SEMPRE do
93
- * `provider`; `ctx.authkit.getUser()` (authkit) continua vindo do `resolveUser`.
94
- * O guard nunca chama `resolveUser`. Com o guard configurado, o normal é apagar
95
- * o `resolveUser` e deixar só o `provider`.
97
+ * - `provider`: any session user provider. Its `findById` receives `identity.userId`
98
+ * (the `sub` claim). With `resolveUser` in `config/authkit_client.ts`, the guard
99
+ * uses that user first and the provider is the fallback.
100
+ * - `resolver`: replaces the resolver of `config/authkit_client.ts` for this guard
101
+ * only (bearer JWT, opaque, PAT). Without it the guard uses the configured one.
96
102
  */
97
103
  export declare function authkitClientGuard<UserProvider extends SessionUserProviderContract<unknown>>(config: {
98
104
  provider: UserProvider | ConfigProvider<UserProvider>;
105
+ resolver?: ResolverFactory;
99
106
  }): GuardConfigProvider<(ctx: HttpContext) => AuthkitClientGuard<UserProvider>>;
107
+ /**
108
+ * User provider with no database: `ctx.auth.user` is the AuthKit {@link Identity}
109
+ * of the request (`userId`, `email`, `globalRoles`, `profile`, the raw claims).
110
+ * Use it when the app keeps no local user table, or for API guards that only need
111
+ * the token's subject. When `config/authkit_client.ts` sets `resolveUser`, the
112
+ * guard uses that user instead.
113
+ */
114
+ export declare function authkitIdentityProvider(): IdentityUserProviderContract<Identity>;
100
115
  export {};