@adonis-agora/authkit-server 0.48.0 → 0.49.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.
package/build/index.d.ts CHANGED
@@ -46,6 +46,7 @@ export type { EventsConfigInput, ResolvedEventsConfig } from './src/events/dispa
46
46
  export { inertiaRenderer } from './src/host/renderers/inertia_renderer.js';
47
47
  export type { AuthkitScreen } from './src/host/renderers/inertia_renderer.js';
48
48
  export type { InertiaRendererOptions } from './src/host/renderers/inertia_renderer.js';
49
+ export type { AccountLoginProps, AccountSecurityProps, AccountMfaProps, AccountConfirmProps, AccountConfirmMethod, AccountEmailConfirmedProps, } from './src/host/account_screen_props.js';
49
50
  export { edgeRenderer } from './src/host/renderers/edge_renderer.js';
50
51
  export { brandFor, isFirstParty } from './src/host/branding.js';
51
52
  export type { BrandingConfig, ClientBrand } from './src/host/branding.js';
@@ -55,6 +56,20 @@ export type { AuthHostRenderer, AuthSocialConfig } from './src/define_config.js'
55
56
  export { registerAuthHost } from './src/host/register_auth_host.js';
56
57
  export type { AuthHostOptions } from './src/host/register_auth_host.js';
57
58
  export { getAdminPrefix, setAdminPrefix, normalizeAdminPrefix, getAdminApiPrefix, setAdminApiPrefix, normalizeAdminApiPrefix, } from './src/host/admin_prefix.js';
59
+ /**
60
+ * Helpers de path do console de conta (`/account/*`). Um host que precisa casar
61
+ * um middleware ou link com uma rota do console (ex.: `GET
62
+ * {accountPath('security')}/export`) deriva o path daqui em vez de hardcodar,
63
+ * respeitando os overrides de `accountRoutes`.
64
+ *
65
+ * ⚠️ Estes helpers leem um singleton de processo que só reflete os overrides
66
+ * DEPOIS que `registerAuthHost` roda (é ele quem chama `setAccountPaths` com a
67
+ * opção `accountRoutes`, no boot). Chamados antes disso, devolvem os defaults
68
+ * (`/account/*`). Componha URLs em runtime (dentro de handlers/factories de
69
+ * middleware), não em tempo de import de módulo.
70
+ */
71
+ export { accountPath, joinAccountPath, accountPrefix, } from './src/host/account_paths.js';
72
+ export type { AccountPathsOptions, AccountPathKey } from './src/host/account_paths.js';
58
73
  export { resolveRateLimit, resolveNotifications } from './src/define_config.js';
59
74
  export type { ResolvedNotificationsConfig } from './src/define_config.js';
60
75
  export type { RateLimitConfigInput, RateLimitBucket, ResolvedRateLimitConfig, } from './src/define_config.js';
package/build/index.js CHANGED
@@ -32,6 +32,19 @@ export { brandFor, isFirstParty } from './src/host/branding.js';
32
32
  export { resolveMessages, translate, DEFAULT_MESSAGES, PT_BR_MESSAGES, BUILTIN_MESSAGES, DEFAULT_LOCALE, } from './src/host/i18n.js';
33
33
  export { registerAuthHost } from './src/host/register_auth_host.js';
34
34
  export { getAdminPrefix, setAdminPrefix, normalizeAdminPrefix, getAdminApiPrefix, setAdminApiPrefix, normalizeAdminApiPrefix, } from './src/host/admin_prefix.js';
35
+ /**
36
+ * Helpers de path do console de conta (`/account/*`). Um host que precisa casar
37
+ * um middleware ou link com uma rota do console (ex.: `GET
38
+ * {accountPath('security')}/export`) deriva o path daqui em vez de hardcodar,
39
+ * respeitando os overrides de `accountRoutes`.
40
+ *
41
+ * ⚠️ Estes helpers leem um singleton de processo que só reflete os overrides
42
+ * DEPOIS que `registerAuthHost` roda (é ele quem chama `setAccountPaths` com a
43
+ * opção `accountRoutes`, no boot). Chamados antes disso, devolvem os defaults
44
+ * (`/account/*`). Componha URLs em runtime (dentro de handlers/factories de
45
+ * middleware), não em tempo de import de módulo.
46
+ */
47
+ export { accountPath, joinAccountPath, accountPrefix, } from './src/host/account_paths.js';
35
48
  export { resolveRateLimit, resolveNotifications } from './src/define_config.js';
36
49
  export { createAuthThrottles } from './src/host/rate_limit.js';
37
50
  /**
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Tipos de props das telas do console de conta (`/account/*`) — a FONTE ÚNICA da
3
+ * verdade sobre o shape que cada página React recebe.
4
+ *
5
+ * ── Por que esses tipos existem ──────────────────────────────────────────────
6
+ * Um host que cria as telas do console em React próprio (via `inertiaRenderer`,
7
+ * em vez das views Edge built-in) precisa tipar o componente da página. Antes
8
+ * disso, o único "contrato" era o DOCBLOCK do `inertiaRenderer`, copiado à mão —
9
+ * frágil: um docblock desatualizado já enganou um host. Agora o shape vem daqui,
10
+ * e os CONTROLLERS REAIS (`account_session_controller`, `account_security_-
11
+ * controller`, `account_mfa_controller`, `account_confirm_controller`) satisfazem
12
+ * (`satisfies Omit<…, 'messages'>`) exatamente estes tipos ao chamar `render()`.
13
+ * Se o payload de um controller divergir do tipo — campo a mais, a menos, tipo
14
+ * trocado — o `tsc` do pacote quebra. Esse é o objetivo: um único ponto muda, e
15
+ * o compilador força o outro a acompanhar.
16
+ *
17
+ * ── Sobre a prop `messages` ──────────────────────────────────────────────────
18
+ * `messages` (catálogo i18n) é injetada pelo `inertiaRenderer` como shared prop,
19
+ * NÃO pelos controllers. Por isso ela faz parte destes tipos (é o que a página
20
+ * React recebe), mas os controllers satisfazem `Omit<…, 'messages'>` — eles nunca
21
+ * passam `messages` no literal do `render()`.
22
+ */
23
+ import type { PasskeySummary } from '../accounts/account_store.js';
24
+ import type { AuthMessages } from './i18n.js';
25
+ import type { SudoMethodDescriptor } from './sudo/types.js';
26
+ /**
27
+ * Props da tela `account/login` (tela de login do console de conta).
28
+ *
29
+ * - `csrfToken`: token para o campo `_csrf` do formulário.
30
+ * - `returnTo`: caminho interno de destino pós-login (já validado pelo servidor —
31
+ * só caminhos internos) ou `null`. Quando presente, o formulário deve incluir
32
+ * `<input type="hidden" name="return_to" value={returnTo} />`; o servidor
33
+ * revalida no POST.
34
+ * - `error`: mensagem de erro de autenticação localizada (credenciais inválidas,
35
+ * conta bloqueada/desabilitada). Ausente quando não há erro.
36
+ * - `messages`: catálogo i18n (injetado pelo renderer).
37
+ */
38
+ export interface AccountLoginProps {
39
+ csrfToken: string;
40
+ returnTo: string | null;
41
+ error?: string;
42
+ messages: AuthMessages;
43
+ }
44
+ /**
45
+ * Props da tela `account/security` (perfil, senha, e-mail, sessões, export,
46
+ * danger-zone). Todas as flags de `*Supported` degradam a UI quando o store não
47
+ * suporta a capacidade correspondente.
48
+ *
49
+ * - `supported`: `false` quando o store não suporta o self-service de segurança.
50
+ * - `profileSupported`: `true` quando dá para editar nome/avatar (`updateProfile`).
51
+ * - `avatarUploadSupported`: `true` quando algum backend (drive OU media) armazena o upload.
52
+ * - `email` / `name` / `avatarUrl`: valores atuais da conta (`''` se ausentes).
53
+ * - `passwordChanged` / `emailChangeRequested` / `emailChanged` / `profileUpdated`
54
+ * / `error` / `trustedDevicesRevoked` / `deleteError`: flashes localizados ou `null`.
55
+ * - `trustedDevicesEnabled`: recurso de dispositivos confiáveis ligado.
56
+ * - `sessionsSupported`: `true` quando o adapter OIDC enumera as sessões ativas.
57
+ * - `sessions`: sessões ativas da própria conta (vazio quando não suportado).
58
+ * `loginTs` é ISO ou `''`.
59
+ * - `exportSupported`: sempre `true` (portabilidade/LGPD para a conta logada).
60
+ * - `deletionSupported`: `true` quando o store suporta hard delete.
61
+ * - `messages`: catálogo i18n (injetado pelo renderer).
62
+ */
63
+ export interface AccountSecurityProps {
64
+ csrfToken: string;
65
+ supported: boolean;
66
+ profileSupported: boolean;
67
+ avatarUploadSupported: boolean;
68
+ email: string;
69
+ name: string;
70
+ avatarUrl: string;
71
+ passwordChanged: string | null;
72
+ emailChangeRequested: string | null;
73
+ emailChanged: string | null;
74
+ profileUpdated: string | null;
75
+ error: string | null;
76
+ trustedDevicesEnabled: boolean;
77
+ trustedDevicesRevoked: string | null;
78
+ sessionsSupported: boolean;
79
+ sessions: Array<{
80
+ loginTs: string;
81
+ browser: string;
82
+ os: string;
83
+ ip: string;
84
+ location: string;
85
+ }>;
86
+ exportSupported: boolean;
87
+ deletionSupported: boolean;
88
+ deleteError: string | null;
89
+ messages: AuthMessages;
90
+ }
91
+ /**
92
+ * Props da tela `account/mfa` (TOTP + passkeys). As props VARIAM por action, e é
93
+ * por isso que as específicas de passo são opcionais:
94
+ *
95
+ * - `index` manda o estado base + a lista de passkeys (`passkeysSupported` /
96
+ * `passkeys`).
97
+ * - `enroll` (após `POST /mfa/enroll`) acrescenta o passo do QR: `enrolling: true`,
98
+ * `secret` (base32 para entrada manual) e `qrDataUrl` (data-URL do `otpauth://`).
99
+ * - `confirm` com código inválido reexibe `enrolling: true` + `error`, com
100
+ * `secret`/`qrDataUrl` `null` (o segredo pendente NÃO é regenerado).
101
+ *
102
+ * - `enabled`: `true` quando o TOTP já está confirmado.
103
+ * - `recoveryCodes`: códigos recém-gerados (exibidos UMA vez) ou `null`.
104
+ * - `messages`: catálogo i18n (injetado pelo renderer).
105
+ */
106
+ export interface AccountMfaProps {
107
+ csrfToken: string;
108
+ enabled: boolean;
109
+ recoveryCodes: string[] | null;
110
+ /** Presente em `index`: `true` quando o store persiste credenciais WebAuthn. */
111
+ passkeysSupported?: boolean;
112
+ /** Presente em `index`: passkeys cadastradas (vazio quando não suportado). */
113
+ passkeys?: PasskeySummary[];
114
+ /** Passo de enroll/confirm: `true` mostra QR/segredo + campo de código. */
115
+ enrolling?: boolean;
116
+ /** Segredo TOTP (base32) no passo de enroll; `null` na reexibição do confirm. */
117
+ secret?: string | null;
118
+ /** QR do `otpauth://` como data-URL no passo de enroll; `null` na reexibição. */
119
+ qrDataUrl?: string | null;
120
+ /** Erro localizado (ex.: código TOTP inválido no confirm). */
121
+ error?: string;
122
+ messages: AuthMessages;
123
+ }
124
+ /**
125
+ * Um método de sudo, como a tela `account/confirm` o recebe: o descritor do SPI
126
+ * (`SudoMethodDescriptor`) acrescido do `id` estável do método. A tela renderiza
127
+ * por `kind` (`form`/`action`/`redirect`/`webauthn`); `endpoint` é o POST de
128
+ * verificação (para `webauthn`, as options ficam em `${endpoint}/options`).
129
+ */
130
+ export type AccountConfirmMethod = {
131
+ id: string;
132
+ } & SudoMethodDescriptor;
133
+ /**
134
+ * Props da tela `account/confirm` (sudo — confirmar identidade).
135
+ *
136
+ * - `csrfToken`: token para o POST de cada método.
137
+ * - `returnTo`: caminho interno de destino após confirmar (validado) ou `null`.
138
+ * - `error`: flash de erro da última tentativa ou `null`.
139
+ * - `notice`: flash informativo (ex.: "link de confirmação enviado") ou `null`.
140
+ * - `methods`: métodos de sudo disponíveis para a conta (ver {@link AccountConfirmMethod}).
141
+ * - `preferredId`: `id` do último método usado (destaque na UI) ou `null`.
142
+ * - `messages`: catálogo i18n (injetado pelo renderer).
143
+ */
144
+ export interface AccountConfirmProps {
145
+ csrfToken: string;
146
+ returnTo: string | null;
147
+ error: string | null;
148
+ notice: string | null;
149
+ methods: AccountConfirmMethod[];
150
+ preferredId: string | null;
151
+ messages: AuthMessages;
152
+ }
153
+ /**
154
+ * Props da tela `account/email-confirmed` (terminal do link de troca de e-mail).
155
+ *
156
+ * - `ok`: `true` quando o token era válido e o novo e-mail foi aplicado; `false`
157
+ * para token inválido/expirado ou store sem suporte.
158
+ * - `messages`: catálogo i18n (injetado pelo renderer).
159
+ */
160
+ export interface AccountEmailConfirmedProps {
161
+ ok: boolean;
162
+ messages: AuthMessages;
163
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tipos de props das telas do console de conta (`/account/*`) — a FONTE ÚNICA da
3
+ * verdade sobre o shape que cada página React recebe.
4
+ *
5
+ * ── Por que esses tipos existem ──────────────────────────────────────────────
6
+ * Um host que cria as telas do console em React próprio (via `inertiaRenderer`,
7
+ * em vez das views Edge built-in) precisa tipar o componente da página. Antes
8
+ * disso, o único "contrato" era o DOCBLOCK do `inertiaRenderer`, copiado à mão —
9
+ * frágil: um docblock desatualizado já enganou um host. Agora o shape vem daqui,
10
+ * e os CONTROLLERS REAIS (`account_session_controller`, `account_security_-
11
+ * controller`, `account_mfa_controller`, `account_confirm_controller`) satisfazem
12
+ * (`satisfies Omit<…, 'messages'>`) exatamente estes tipos ao chamar `render()`.
13
+ * Se o payload de um controller divergir do tipo — campo a mais, a menos, tipo
14
+ * trocado — o `tsc` do pacote quebra. Esse é o objetivo: um único ponto muda, e
15
+ * o compilador força o outro a acompanhar.
16
+ *
17
+ * ── Sobre a prop `messages` ──────────────────────────────────────────────────
18
+ * `messages` (catálogo i18n) é injetada pelo `inertiaRenderer` como shared prop,
19
+ * NÃO pelos controllers. Por isso ela faz parte destes tipos (é o que a página
20
+ * React recebe), mas os controllers satisfazem `Omit<…, 'messages'>` — eles nunca
21
+ * passam `messages` no literal do `render()`.
22
+ */
23
+ export {};
@@ -37,7 +37,7 @@ export default class AccountConfirmController {
37
37
  ctx.logger?.warn({ method: m.id }, `authkit: método de sudo "${m.id}" está em config.sudo.methods mas não teve rotas montadas por registerAuthHost — a tela vai oferecer uma opção cujo endpoint não existe`);
38
38
  }
39
39
  }
40
- return c.cfg.render(ctx, 'account/confirm', {
40
+ const props = {
41
41
  csrfToken: ctx.request.csrfToken,
42
42
  returnTo: c.returnTo,
43
43
  error: ctx.session.flashMessages.get('confirmError') ?? null,
@@ -47,6 +47,7 @@ export default class AccountConfirmController {
47
47
  notice: ctx.session.flashMessages.get('confirmNotice') ?? null,
48
48
  methods,
49
49
  preferredId: ctx.session.get(LAST_METHOD_SESSION_KEY) ?? null,
50
- });
50
+ };
51
+ return c.cfg.render(ctx, 'account/confirm', props);
51
52
  }
52
53
  }
@@ -40,13 +40,14 @@ export default class AccountMfaController {
40
40
  // Passkeys disponíveis quando o store as suporta (model de credenciais wired).
41
41
  const passkeysSupported = supportsPasskeys(cfg.accountStore);
42
42
  const passkeys = passkeysSupported ? await cfg.accountStore.listPasskeys(userId) : [];
43
- return render(ctx, 'account/mfa', {
43
+ const props = {
44
44
  csrfToken: ctx.request.csrfToken,
45
45
  enabled: state.enabled,
46
46
  recoveryCodes: recoveryCodes ?? null,
47
47
  passkeysSupported,
48
48
  passkeys,
49
- });
49
+ };
50
+ return render(ctx, 'account/mfa', props);
50
51
  }
51
52
  /**
52
53
  * POST /account/mfa/passkeys/options — gera as opções de registro de passkey
@@ -178,14 +179,15 @@ export default class AccountMfaController {
178
179
  }
179
180
  // QR renderizado server-side como data-URL e passado como prop.
180
181
  const qrDataUrl = await QRCode.toDataURL(started.otpauthUri);
181
- return render(ctx, 'account/mfa', {
182
+ const props = {
182
183
  csrfToken: ctx.request.csrfToken,
183
184
  enabled: false,
184
185
  enrolling: true,
185
186
  secret: started.secret,
186
187
  qrDataUrl,
187
188
  recoveryCodes: null,
188
- });
189
+ };
190
+ return render(ctx, 'account/mfa', props);
189
191
  }
190
192
  /** POST /account/mfa/confirm — confirma o código; sucesso = ativa e mostra recovery codes. */
191
193
  async confirm(ctx) {
@@ -199,7 +201,7 @@ export default class AccountMfaController {
199
201
  // Reenvia o passo de confirmação com erro SEM regenerar o segredo pendente
200
202
  // (o usuário já escaneou o QR; um novo segredo invalidaria o app autenticador).
201
203
  // Mostra só o campo de código para nova tentativa.
202
- return render(ctx, 'account/mfa', {
204
+ const props = {
203
205
  csrfToken: ctx.request.csrfToken,
204
206
  enabled: false,
205
207
  enrolling: true,
@@ -207,7 +209,8 @@ export default class AccountMfaController {
207
209
  qrDataUrl: null,
208
210
  error: translate(cfg.messages, 'errors.invalid_code'),
209
211
  recoveryCodes: null,
210
- });
212
+ };
213
+ return render(ctx, 'account/mfa', props);
211
214
  }
212
215
  await cfg.audit?.record({
213
216
  type: 'mfa.enabled',
@@ -65,7 +65,7 @@ export default class AccountSecurityController {
65
65
  const ownSessions = sessionsSupported
66
66
  ? await enrichSessionsWithContext(cfg, userId, await adminSessions.listSessions(userId))
67
67
  : [];
68
- return render(ctx, 'account/security', {
68
+ const props = {
69
69
  csrfToken: ctx.request.csrfToken,
70
70
  supported: supportsAccountSecurity(cfg.accountStore),
71
71
  profileSupported: supportsProfile(cfg.accountStore),
@@ -94,7 +94,8 @@ export default class AccountSecurityController {
94
94
  // Deleção de conta (LGPD): só quando o store suporta hard delete.
95
95
  deletionSupported: supportsAccountDeletion(cfg.accountStore),
96
96
  deleteError: ctx.session.flashMessages.get('deleteError') ?? null,
97
- });
97
+ };
98
+ return render(ctx, 'account/security', props);
98
99
  }
99
100
  /**
100
101
  * GET /account/security/export — baixa um JSON com os dados da conta logada
@@ -48,7 +48,11 @@ export default class AccountSessionController {
48
48
  // Lê e valida o return_to da query-string — descarta valores inválidos (open-redirect).
49
49
  const rawReturnTo = ctx.request.qs?.()?.return_to ?? ctx.request.input?.('return_to');
50
50
  const returnTo = validateReturnTo(rawReturnTo);
51
- return render(ctx, 'account/login', { csrfToken: ctx.request.csrfToken, returnTo });
51
+ const props = {
52
+ csrfToken: ctx.request.csrfToken,
53
+ returnTo,
54
+ };
55
+ return render(ctx, 'account/login', props);
52
56
  }
53
57
  async login(ctx) {
54
58
  const service = await ctx.containerResolver.make('authkit.server');
@@ -74,7 +78,7 @@ export default class AccountSessionController {
74
78
  settings: settings ?? undefined,
75
79
  });
76
80
  if (!result.ok) {
77
- return render(ctx, 'account/login', {
81
+ const props = {
78
82
  csrfToken: ctx.request.csrfToken,
79
83
  returnTo,
80
84
  error: result.locked
@@ -84,7 +88,8 @@ export default class AccountSessionController {
84
88
  : result.disabled
85
89
  ? translate(cfg.messages, 'errors.account_disabled')
86
90
  : translate(cfg.messages, 'errors.invalid_credentials'),
87
- });
91
+ };
92
+ return render(ctx, 'account/login', props);
88
93
  }
89
94
  const acc = result.account;
90
95
  // M5 (session fixation): regenera a sessão IMEDIATAMENTE após autenticar e
@@ -58,76 +58,33 @@ export type AuthkitScreen = 'login' | 'signup' | 'consent' | 'forgot' | 'reset'
58
58
  *
59
59
  * ---
60
60
  *
61
- * ### Props da tela `account/login`
61
+ * ### Props das telas de conta — tipos exportados (fonte única)
62
62
  *
63
- * | Prop | Tipo | Descrição |
64
- * |-------------|---------------------|-----------|
65
- * | `csrfToken` | `string` | Token CSRF para o campo `_csrf` do formulário. |
66
- * | `returnTo` | `string \| null` | Caminho interno de destino pós-login (já validado pelo servidor — só caminhos internos). Quando presente, o formulário deve incluir `<input type="hidden" name="return_to" value={returnTo} />`. O servidor revalida o valor no POST; hosts com tela custom precisam propagar esse hidden input. |
67
- * | `error` | `string \| undefined` | Mensagem de erro de autenticação localizada (credenciais inválidas, conta bloqueada, etc.). |
68
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
63
+ * Um host que escreve as telas do console em React tipa cada página com os tipos
64
+ * de props exportados do pacote, em vez de copiar o shape à mão deste docblock —
65
+ * que ficou desatualizado no passado. Cada tipo é a FONTE ÚNICA da verdade: os
66
+ * controllers satisfazem (`satisfies Omit<…, 'messages'>`) exatamente o mesmo
67
+ * tipo ao chamar `render()`, então divergência quebra o `tsc` do pacote. Ver
68
+ * `src/host/account_screen_props.ts`.
69
69
  *
70
- * ### Props da tela `account/security`
70
+ * | Tela | Tipo exportado |
71
+ * |---------------------------|----------------------------|
72
+ * | `account/login` | {@link AccountLoginProps} |
73
+ * | `account/security` | {@link AccountSecurityProps} |
74
+ * | `account/mfa` | {@link AccountMfaProps} |
75
+ * | `account/confirm` | {@link AccountConfirmProps} |
76
+ * | `account/email-confirmed` | {@link AccountEmailConfirmedProps} |
71
77
  *
72
- * | Prop | Tipo | Descrição |
73
- * |-------------------------|---------------------|-----------|
74
- * | `csrfToken` | `string` | Token CSRF para os formulários da tela. |
75
- * | `supported` | `boolean` | `false` quando o store não suporta o self-service de segurança (troca de senha/e-mail) — a tela deve degradar. |
76
- * | `profileSupported` | `boolean` | `true` quando o store suporta editar nome/avatar (`updateProfile`). |
77
- * | `avatarUploadSupported` | `boolean` | `true` quando algum backend (drive OU media) pode armazenar o upload de avatar. |
78
- * | `email` | `string` | E-mail atual da conta (`''` se ausente). |
79
- * | `name` | `string` | Nome atual da conta (`''` se ausente). |
80
- * | `avatarUrl` | `string` | URL do avatar atual (`''` se ausente). |
81
- * | `passwordChanged` | `string \| null` | Flash de sucesso da troca de senha (mensagem localizada) ou `null`. |
82
- * | `emailChangeRequested` | `string \| null` | Flash: link de confirmação de troca de e-mail enviado (ou cancelamento) ou `null`. |
83
- * | `emailChanged` | `string \| null` | Flash: troca de e-mail concluída ou `null`. |
84
- * | `profileUpdated` | `string \| null` | Flash: perfil atualizado ou `null`. |
85
- * | `error` | `string \| null` | Flash de erro de segurança (senha inválida, e-mail em uso, política violada) ou `null`. |
86
- * | `trustedDevicesEnabled` | `boolean` | `true` quando o recurso de dispositivos confiáveis está ligado. |
87
- * | `trustedDevicesRevoked` | `string \| null` | Flash: confiança deste navegador revogada ou `null`. |
88
- * | `sessionsSupported` | `boolean` | `true` quando o adapter OIDC enumera as sessões ativas da conta. |
89
- * | `sessions` | `Array<{ loginTs: string; browser: string; os: string; ip: string; location: string }>` | Sessões ativas da própria conta (vazio quando não suportado). `loginTs` é ISO ou `''`. |
90
- * | `exportSupported` | `boolean` | Sempre `true` — export de dados (portabilidade/LGPD) disponível para a conta logada. |
91
- * | `deletionSupported` | `boolean` | `true` quando o store suporta hard delete (danger zone). |
92
- * | `deleteError` | `string \| null` | Flash de erro da confirmação de deleção ou `null`. |
93
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
78
+ * A prop `messages` (catálogo i18n {@link AuthMessages}) é injetada por este
79
+ * renderer como shared prop e faz parte de todos esses tipos — os controllers,
80
+ * que não a passam, satisfazem `Omit<…, 'messages'>`.
94
81
  *
95
- * ### Props da tela `account/mfa`
82
+ * ```tsx
83
+ * import type { AccountSecurityProps } from '@adonis-agora/authkit-server'
96
84
  *
97
- * As props variam por action: `index` manda o estado base; `enroll` acrescenta
98
- * `enrolling`/`secret`/`qrDataUrl` (passo do QR); `confirm` com código inválido
99
- * reenvia `enrolling: true` + `error` (sem regenerar o segredo).
100
- *
101
- * | Prop | Tipo | Descrição |
102
- * |---------------------|---------------------|-----------|
103
- * | `csrfToken` | `string` | Token CSRF para os formulários de enroll/confirm/disable e passkeys. |
104
- * | `enabled` | `boolean` | `true` quando o TOTP já está confirmado (habilitado) para a conta. |
105
- * | `recoveryCodes` | `string[] \| null` | Códigos de recuperação recém-gerados (exibidos UMA vez após enroll/confirm) ou `null`. |
106
- * | `passkeysSupported` | `boolean` | `true` quando o store persiste credenciais WebAuthn (passkeys). |
107
- * | `passkeys` | `Array<{ id: string; label?: string; createdAt: string }>` | Passkeys cadastradas (vazio quando não suportado). `id` é base64url; `createdAt` é ISO. Presente em `index`. |
108
- * | `enrolling` | `boolean \| undefined` | `true` no passo de enrollment (após `POST /mfa/enroll` e na reexibição do `confirm` com código inválido) — a tela mostra o QR/segredo e o campo de código. Ausente em `index`. |
109
- * | `secret` | `string \| null \| undefined` | Segredo TOTP em texto (base32) para entrada manual, exibido no passo de enroll. `null` na reexibição do `confirm` (o segredo pendente NÃO é regenerado). Ausente em `index`. |
110
- * | `qrDataUrl` | `string \| null \| undefined` | QR code do `otpauth://` como data-URL (`<img src>`), no passo de enroll. `null` na reexibição do `confirm`. Ausente em `index`. |
111
- * | `error` | `string \| undefined` | Mensagem de erro localizada (ex.: código TOTP inválido no `confirm`). Ausente quando não há erro. |
112
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
113
- *
114
- * ### Props da tela `account/confirm` (sudo — confirmar identidade)
115
- *
116
- * | Prop | Tipo | Descrição |
117
- * |---------------|---------------------|-----------|
118
- * | `csrfToken` | `string` | Token CSRF para o POST de cada método. |
119
- * | `returnTo` | `string \| null` | Caminho interno de destino após confirmar (validado pelo servidor) ou `null`. |
120
- * | `error` | `string \| null` | Flash de erro da última tentativa de confirmação ou `null`. |
121
- * | `notice` | `string \| null` | Flash informativo (ex.: "link de confirmação enviado") ou `null`. |
122
- * | `methods` | `Array<{ id: string; labelKey: string; kind: 'form' \| 'action' \| 'redirect' \| 'webauthn'; endpoint: string; fields?: Array<{ name: string; type: 'password' \| 'text'; labelKey: string }> }>` | Métodos de sudo disponíveis para a conta. A tela renderiza por `kind`; `endpoint` é o POST de verificação (`webauthn` pede options em `${endpoint}/options`). |
123
- * | `preferredId` | `string \| null` | `id` do último método usado (destaque na UI) ou `null`. |
124
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
125
- *
126
- * ### Props da tela `account/email-confirmed` (terminal do link de troca de e-mail)
127
- *
128
- * | Prop | Tipo | Descrição |
129
- * |------------|----------------|-----------|
130
- * | `ok` | `boolean` | `true` quando o token era válido e o novo e-mail foi aplicado; `false` para token inválido/expirado ou store sem suporte. A tela mostra sucesso ou falha conforme o valor. |
131
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
85
+ * export default function Security(props: AccountSecurityProps) {
86
+ * return <form action="/account/security/password" method="post">…</form>
87
+ * }
88
+ * ```
132
89
  */
133
90
  export declare function inertiaRenderer(opts: InertiaRendererOptions): (ctx: HttpContext, view: string, props: Record<string, unknown>) => Promise<any>;
@@ -29,77 +29,34 @@ async function resolveMessagesFromCtx(ctx) {
29
29
  *
30
30
  * ---
31
31
  *
32
- * ### Props da tela `account/login`
32
+ * ### Props das telas de conta — tipos exportados (fonte única)
33
33
  *
34
- * | Prop | Tipo | Descrição |
35
- * |-------------|---------------------|-----------|
36
- * | `csrfToken` | `string` | Token CSRF para o campo `_csrf` do formulário. |
37
- * | `returnTo` | `string \| null` | Caminho interno de destino pós-login (já validado pelo servidor — só caminhos internos). Quando presente, o formulário deve incluir `<input type="hidden" name="return_to" value={returnTo} />`. O servidor revalida o valor no POST; hosts com tela custom precisam propagar esse hidden input. |
38
- * | `error` | `string \| undefined` | Mensagem de erro de autenticação localizada (credenciais inválidas, conta bloqueada, etc.). |
39
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
34
+ * Um host que escreve as telas do console em React tipa cada página com os tipos
35
+ * de props exportados do pacote, em vez de copiar o shape à mão deste docblock —
36
+ * que ficou desatualizado no passado. Cada tipo é a FONTE ÚNICA da verdade: os
37
+ * controllers satisfazem (`satisfies Omit<…, 'messages'>`) exatamente o mesmo
38
+ * tipo ao chamar `render()`, então divergência quebra o `tsc` do pacote. Ver
39
+ * `src/host/account_screen_props.ts`.
40
40
  *
41
- * ### Props da tela `account/security`
41
+ * | Tela | Tipo exportado |
42
+ * |---------------------------|----------------------------|
43
+ * | `account/login` | {@link AccountLoginProps} |
44
+ * | `account/security` | {@link AccountSecurityProps} |
45
+ * | `account/mfa` | {@link AccountMfaProps} |
46
+ * | `account/confirm` | {@link AccountConfirmProps} |
47
+ * | `account/email-confirmed` | {@link AccountEmailConfirmedProps} |
42
48
  *
43
- * | Prop | Tipo | Descrição |
44
- * |-------------------------|---------------------|-----------|
45
- * | `csrfToken` | `string` | Token CSRF para os formulários da tela. |
46
- * | `supported` | `boolean` | `false` quando o store não suporta o self-service de segurança (troca de senha/e-mail) — a tela deve degradar. |
47
- * | `profileSupported` | `boolean` | `true` quando o store suporta editar nome/avatar (`updateProfile`). |
48
- * | `avatarUploadSupported` | `boolean` | `true` quando algum backend (drive OU media) pode armazenar o upload de avatar. |
49
- * | `email` | `string` | E-mail atual da conta (`''` se ausente). |
50
- * | `name` | `string` | Nome atual da conta (`''` se ausente). |
51
- * | `avatarUrl` | `string` | URL do avatar atual (`''` se ausente). |
52
- * | `passwordChanged` | `string \| null` | Flash de sucesso da troca de senha (mensagem localizada) ou `null`. |
53
- * | `emailChangeRequested` | `string \| null` | Flash: link de confirmação de troca de e-mail enviado (ou cancelamento) ou `null`. |
54
- * | `emailChanged` | `string \| null` | Flash: troca de e-mail concluída ou `null`. |
55
- * | `profileUpdated` | `string \| null` | Flash: perfil atualizado ou `null`. |
56
- * | `error` | `string \| null` | Flash de erro de segurança (senha inválida, e-mail em uso, política violada) ou `null`. |
57
- * | `trustedDevicesEnabled` | `boolean` | `true` quando o recurso de dispositivos confiáveis está ligado. |
58
- * | `trustedDevicesRevoked` | `string \| null` | Flash: confiança deste navegador revogada ou `null`. |
59
- * | `sessionsSupported` | `boolean` | `true` quando o adapter OIDC enumera as sessões ativas da conta. |
60
- * | `sessions` | `Array<{ loginTs: string; browser: string; os: string; ip: string; location: string }>` | Sessões ativas da própria conta (vazio quando não suportado). `loginTs` é ISO ou `''`. |
61
- * | `exportSupported` | `boolean` | Sempre `true` — export de dados (portabilidade/LGPD) disponível para a conta logada. |
62
- * | `deletionSupported` | `boolean` | `true` quando o store suporta hard delete (danger zone). |
63
- * | `deleteError` | `string \| null` | Flash de erro da confirmação de deleção ou `null`. |
64
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
49
+ * A prop `messages` (catálogo i18n {@link AuthMessages}) é injetada por este
50
+ * renderer como shared prop e faz parte de todos esses tipos — os controllers,
51
+ * que não a passam, satisfazem `Omit<…, 'messages'>`.
65
52
  *
66
- * ### Props da tela `account/mfa`
53
+ * ```tsx
54
+ * import type { AccountSecurityProps } from '@adonis-agora/authkit-server'
67
55
  *
68
- * As props variam por action: `index` manda o estado base; `enroll` acrescenta
69
- * `enrolling`/`secret`/`qrDataUrl` (passo do QR); `confirm` com código inválido
70
- * reenvia `enrolling: true` + `error` (sem regenerar o segredo).
71
- *
72
- * | Prop | Tipo | Descrição |
73
- * |---------------------|---------------------|-----------|
74
- * | `csrfToken` | `string` | Token CSRF para os formulários de enroll/confirm/disable e passkeys. |
75
- * | `enabled` | `boolean` | `true` quando o TOTP já está confirmado (habilitado) para a conta. |
76
- * | `recoveryCodes` | `string[] \| null` | Códigos de recuperação recém-gerados (exibidos UMA vez após enroll/confirm) ou `null`. |
77
- * | `passkeysSupported` | `boolean` | `true` quando o store persiste credenciais WebAuthn (passkeys). |
78
- * | `passkeys` | `Array<{ id: string; label?: string; createdAt: string }>` | Passkeys cadastradas (vazio quando não suportado). `id` é base64url; `createdAt` é ISO. Presente em `index`. |
79
- * | `enrolling` | `boolean \| undefined` | `true` no passo de enrollment (após `POST /mfa/enroll` e na reexibição do `confirm` com código inválido) — a tela mostra o QR/segredo e o campo de código. Ausente em `index`. |
80
- * | `secret` | `string \| null \| undefined` | Segredo TOTP em texto (base32) para entrada manual, exibido no passo de enroll. `null` na reexibição do `confirm` (o segredo pendente NÃO é regenerado). Ausente em `index`. |
81
- * | `qrDataUrl` | `string \| null \| undefined` | QR code do `otpauth://` como data-URL (`<img src>`), no passo de enroll. `null` na reexibição do `confirm`. Ausente em `index`. |
82
- * | `error` | `string \| undefined` | Mensagem de erro localizada (ex.: código TOTP inválido no `confirm`). Ausente quando não há erro. |
83
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
84
- *
85
- * ### Props da tela `account/confirm` (sudo — confirmar identidade)
86
- *
87
- * | Prop | Tipo | Descrição |
88
- * |---------------|---------------------|-----------|
89
- * | `csrfToken` | `string` | Token CSRF para o POST de cada método. |
90
- * | `returnTo` | `string \| null` | Caminho interno de destino após confirmar (validado pelo servidor) ou `null`. |
91
- * | `error` | `string \| null` | Flash de erro da última tentativa de confirmação ou `null`. |
92
- * | `notice` | `string \| null` | Flash informativo (ex.: "link de confirmação enviado") ou `null`. |
93
- * | `methods` | `Array<{ id: string; labelKey: string; kind: 'form' \| 'action' \| 'redirect' \| 'webauthn'; endpoint: string; fields?: Array<{ name: string; type: 'password' \| 'text'; labelKey: string }> }>` | Métodos de sudo disponíveis para a conta. A tela renderiza por `kind`; `endpoint` é o POST de verificação (`webauthn` pede options em `${endpoint}/options`). |
94
- * | `preferredId` | `string \| null` | `id` do último método usado (destaque na UI) ou `null`. |
95
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
96
- *
97
- * ### Props da tela `account/email-confirmed` (terminal do link de troca de e-mail)
98
- *
99
- * | Prop | Tipo | Descrição |
100
- * |------------|----------------|-----------|
101
- * | `ok` | `boolean` | `true` quando o token era válido e o novo e-mail foi aplicado; `false` para token inválido/expirado ou store sem suporte. A tela mostra sucesso ou falha conforme o valor. |
102
- * | `messages` | `AuthMessages` | Catálogo de mensagens i18n. |
56
+ * export default function Security(props: AccountSecurityProps) {
57
+ * return <form action="/account/security/password" method="post">…</form>
58
+ * }
59
+ * ```
103
60
  */
104
61
  export function inertiaRenderer(opts) {
105
62
  const allowed = opts.views ? new Set(opts.views) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",
@@ -148,7 +148,7 @@
148
148
  "react-error-boundary": "6.1.2",
149
149
  "nuqs": "2.8.9",
150
150
  "recharts": "3.8.1",
151
- "@adonis-agora/authkit-react": "0.16.0"
151
+ "@adonis-agora/authkit-react": "0.17.0"
152
152
  },
153
153
  "scripts": {
154
154
  "build": "node scripts/build_host_css.mjs && node scripts/build_webauthn.mjs && node scripts/build_ui.mjs && node -e \"const fs=require('node:fs');for(const d of ['build/stubs','build/host/views'])fs.rmSync(d,{recursive:true,force:true})\" && tsc && node -e \"require('node:fs').cpSync('src/host/assets','build/src/host/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\" && node -e \"const fs=require('node:fs');if(fs.existsSync('assets'))fs.cpSync('assets','build/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('src/host/views','build/host/views',{recursive:true})\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/host/ui',{recursive:true});fs.readdirSync('src/host/ui').filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync('src/host/ui/'+f,'build/host/ui/'+f))\" && node -e \"require('node:fs').copyFileSync('commands/commands.json','build/commands/commands.json')\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/password',{recursive:true});fs.copyFileSync('src/password/common_passwords.txt','build/password/common_passwords.txt')\"",