@adonis-agora/authkit-server 1.0.0-rc.8 → 1.0.0-rc.9

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 (41) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/build/host/views/account/apps.edge +1 -5
  3. package/build/host/views/account/extension.edge +77 -0
  4. package/build/host/views/account/mfa.edge +2 -0
  5. package/build/host/views/account/partials/extension_actions.edge +14 -0
  6. package/build/host/views/account/partials/extension_block.edge +197 -0
  7. package/build/host/views/account/partials/extension_value.edge +8 -0
  8. package/build/host/views/account/partials/nav.edge +14 -0
  9. package/build/host/views/account/security.edge +1 -5
  10. package/build/host/views/account/tokens.edge +2 -2
  11. package/build/host/views/partials/styles.edge +1 -1
  12. package/build/providers/authkit_server_provider.js +5 -0
  13. package/build/src/account/extension.d.ts +323 -0
  14. package/build/src/account/extension.js +36 -0
  15. package/build/src/account/http.d.ts +71 -0
  16. package/build/src/account/http.js +369 -0
  17. package/build/src/account/mount.d.ts +35 -0
  18. package/build/src/account/mount.js +99 -0
  19. package/build/src/account/registry.d.ts +48 -0
  20. package/build/src/account/registry.js +229 -0
  21. package/build/src/account/runtime.d.ts +277 -0
  22. package/build/src/account/runtime.js +623 -0
  23. package/build/src/audit/audit_sink.d.ts +1 -1
  24. package/build/src/audit/audit_sink.js +4 -0
  25. package/build/src/define_config.d.ts +18 -0
  26. package/build/src/define_config.js +4 -0
  27. package/build/src/host/admin_api/dto.d.ts +1 -1
  28. package/build/src/host/auth_host_config.d.ts +3 -0
  29. package/build/src/host/i18n.d.ts +36 -0
  30. package/build/src/host/i18n.js +38 -0
  31. package/build/src/host/rate_limit.d.ts +8 -0
  32. package/build/src/host/rate_limit.js +9 -0
  33. package/build/src/host/register_auth_host.d.ts +6 -0
  34. package/build/src/host/register_auth_host.js +28 -0
  35. package/build/src/host/renderers/edge_renderer.js +5 -0
  36. package/build/src/host/renderers/inertia_renderer.js +7 -1
  37. package/build/src/host/screen_props.d.ts +28 -0
  38. package/build/src/host/ui-dist/assets/{index-DlKYAhpy.js → index-Dq3dJVBv.js} +26 -26
  39. package/build/src/host/ui-dist/index.html +1 -1
  40. package/build/types.d.ts +3 -1
  41. package/package.json +3 -2
@@ -1,5 +1,6 @@
1
1
  import type { AccessTokenFormat, AccessTokensConfig, JwksConfig, KeystoreStoreConfig, ObservabilityConfig, TtlConfig } from '@adonis-agora/authkit-core';
2
2
  import type { HttpContext } from '@adonisjs/core/http';
3
+ import type { AccountConsoleConfig } from './account/runtime.js';
3
4
  import type { AccountStore, AuthAccount } from './accounts/account_store.js';
4
5
  import { type AdapterFactory, adapters, type OidcAdapterClass } from './adapters/factory.js';
5
6
  import { type PersonalAgentsConfigInput, type ResolvedPersonalAgentsConfig } from './agents/config.js';
@@ -311,6 +312,11 @@ export interface ResolvedRateLimitConfig {
311
312
  * force ANTES do lockout por interaction (contador persistido no slot do código).
312
313
  */
313
314
  otpLogin: RateLimitBucket;
315
+ /**
316
+ * Bucket das actions das páginas de extension do console de conta
317
+ * (`POST /account/<página>/actions/*`), keyed pela CONTA. Default 30/min.
318
+ */
319
+ accountActions?: RateLimitBucket;
314
320
  store?: string;
315
321
  }
316
322
  export declare function resolveRateLimit(input?: RateLimitConfigInput): ResolvedRateLimitConfig;
@@ -1251,6 +1257,16 @@ export interface AuthServerConfigInput {
1251
1257
  accountSession?: {
1252
1258
  acceptIdpSession?: boolean;
1253
1259
  };
1260
+ /**
1261
+ * Account console extensions: the pages other packages add to `/account/*`.
1262
+ *
1263
+ * - `subject(account, { activeOrg, tenant })`: who the account is in the app (default
1264
+ * `{ type: 'User', id: account.id }`); every extension receives it as `ctx.subject`.
1265
+ * - `extensions`: turn off, rename or reorder pages, by extension id
1266
+ * (`{ payments: { pages: { 'payments.invoices': { segment: 'faturas' } } } }`).
1267
+ * - `loadTimeoutMs`: budget of one page's `load()` (default 5000).
1268
+ */
1269
+ accountConsole?: AccountConsoleConfig;
1254
1270
  }
1255
1271
  export interface ResolvedServerConfig {
1256
1272
  customLoginMethods?: CustomLoginMethods;
@@ -1413,6 +1429,8 @@ export interface ResolvedServerConfig {
1413
1429
  accountSession: {
1414
1430
  acceptIdpSession: boolean;
1415
1431
  };
1432
+ /** Account console extensions. Ver {@link AuthServerConfigInput.accountConsole}. */
1433
+ accountConsole?: AccountConsoleConfig;
1416
1434
  }
1417
1435
  export declare function toSeconds(value: string | number | undefined, fallback: number): number;
1418
1436
  /**
@@ -27,6 +27,8 @@ const RATE_LIMIT_DEFAULTS = {
27
27
  sudo: { points: 10, duration: '1 min' },
28
28
  // Mais apertado que o login — verificação de código adivinhável.
29
29
  otpLogin: { points: 5, duration: '1 min' },
30
+ // Actions das extensions do console de conta, por conta.
31
+ accountActions: { points: 30, duration: '1 min' },
30
32
  };
31
33
  export function resolveRateLimit(input) {
32
34
  const enabled = input?.enabled ?? true;
@@ -37,6 +39,7 @@ export function resolveRateLimit(input) {
37
39
  adminIp: RATE_LIMIT_DEFAULTS.adminIp,
38
40
  sudo: RATE_LIMIT_DEFAULTS.sudo,
39
41
  otpLogin: RATE_LIMIT_DEFAULTS.otpLogin,
42
+ accountActions: RATE_LIMIT_DEFAULTS.accountActions,
40
43
  store: input?.store,
41
44
  };
42
45
  }
@@ -502,6 +505,7 @@ export function defineConfig(config) {
502
505
  // Opt-in: ausente = authkit nunca toca `ctx.auth` (comportamento de sempre).
503
506
  adonisAuth: config.adonisAuth,
504
507
  accountSession: { acceptIdpSession: config.accountSession?.acceptIdpSession !== false },
508
+ accountConsole: config.accountConsole,
505
509
  };
506
510
  });
507
511
  }
@@ -69,7 +69,7 @@ export declare function grantDto(grant: AdminGrant): {
69
69
  };
70
70
  export declare function auditDto(event: StoredAuditEvent): {
71
71
  id: string;
72
- type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.disabled" | "client.enabled" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "service_tenant.token_issued" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
72
+ type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.extension.action" | "account.extension.redirect" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.disabled" | "client.enabled" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "service_tenant.token_issued" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
73
73
  accountId: string | null;
74
74
  email: string | null;
75
75
  clientId: string | null;
@@ -1,4 +1,5 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AccountConsoleConfig } from '../account/runtime.js';
2
3
  import type { ResolvedPersonalAgentExtension } from '../agents/extension.js';
3
4
  import type { AuthSocialConfig, ResolvedRateLimitConfig } from '../define_config.js';
4
5
  import type { PolicyRouteOption } from './config_locks.js';
@@ -49,6 +50,8 @@ export interface AuthHostRuntimeConfig {
49
50
  * o console só entra por um `accountLoginUrl` do host (`registerAuthHost` recusa sem ele).
50
51
  */
51
52
  consoleNeedsLoginUrl?: boolean;
53
+ /** `accountConsole` do config: os overrides das páginas de extension. */
54
+ accountConsole?: AccountConsoleConfig;
52
55
  /**
53
56
  * API headless (Clerk-style). Presente quando o host a declarou — o provider o
54
57
  * stash no boot para `registerAuthHost` montar as rotas sem reler o config inteiro.
@@ -174,6 +174,24 @@ export declare const DEFAULT_MESSAGES: {
174
174
  'account.delete.deleted': string;
175
175
  'account.delete.invalid_confirmation': string;
176
176
  'account.delete.not_supported': string;
177
+ 'account.extension.logout': string;
178
+ 'account.extension.nav_label': string;
179
+ 'account.extension.unknown_block': string;
180
+ 'account.extension.unsafe_link': string;
181
+ 'account.extension.unsafe_script': string;
182
+ 'account.extension.load_failed': string;
183
+ 'account.extension.next_page': string;
184
+ 'account.extension.confirm_title': string;
185
+ 'account.extension.confirm_submit': string;
186
+ 'account.extension.cancel': string;
187
+ 'account.extension.action_failed': string;
188
+ 'account.extension.action_not_found': string;
189
+ 'account.extension.impersonation_refused': string;
190
+ 'account.extension.field_required': string;
191
+ 'account.extension.field_too_long': string;
192
+ 'account.extension.field_invalid': string;
193
+ 'account.extension.done': string;
194
+ 'account.extension.provider_loading': string;
177
195
  'account.apps.page_title': string;
178
196
  'account.apps.title': string;
179
197
  'account.apps.intro': string;
@@ -928,6 +946,24 @@ export declare const PT_BR_MESSAGES: {
928
946
  'account.delete.deleted': string;
929
947
  'account.delete.invalid_confirmation': string;
930
948
  'account.delete.not_supported': string;
949
+ 'account.extension.logout': string;
950
+ 'account.extension.nav_label': string;
951
+ 'account.extension.unknown_block': string;
952
+ 'account.extension.unsafe_link': string;
953
+ 'account.extension.unsafe_script': string;
954
+ 'account.extension.load_failed': string;
955
+ 'account.extension.next_page': string;
956
+ 'account.extension.confirm_title': string;
957
+ 'account.extension.confirm_submit': string;
958
+ 'account.extension.cancel': string;
959
+ 'account.extension.action_failed': string;
960
+ 'account.extension.action_not_found': string;
961
+ 'account.extension.impersonation_refused': string;
962
+ 'account.extension.field_required': string;
963
+ 'account.extension.field_too_long': string;
964
+ 'account.extension.field_invalid': string;
965
+ 'account.extension.done': string;
966
+ 'account.extension.provider_loading': string;
931
967
  'account.apps.page_title': string;
932
968
  'account.apps.title': string;
933
969
  'account.apps.intro': string;
@@ -182,6 +182,25 @@ export const DEFAULT_MESSAGES = {
182
182
  'account.delete.deleted': 'Your account has been permanently deleted.',
183
183
  'account.delete.invalid_confirmation': 'Confirmation failed. Your account was not deleted.',
184
184
  'account.delete.not_supported': 'Account deletion is not available in this installation.',
185
+ // Console de conta — páginas de extensions (account/extension).
186
+ 'account.extension.logout': 'Log out',
187
+ 'account.extension.nav_label': 'Account',
188
+ 'account.extension.unknown_block': 'This section cannot be shown here.',
189
+ 'account.extension.unsafe_link': 'This link was hidden because its address is not allowed.',
190
+ 'account.extension.unsafe_script': 'This section was hidden because it loads a script from an address that is not allowed.',
191
+ 'account.extension.load_failed': 'This page could not be loaded right now. Try again in a moment.',
192
+ 'account.extension.next_page': 'Next page',
193
+ 'account.extension.confirm_title': 'Confirm',
194
+ 'account.extension.confirm_submit': 'Continue',
195
+ 'account.extension.cancel': 'Cancel',
196
+ 'account.extension.action_failed': 'That did not work. Try again in a moment.',
197
+ 'account.extension.action_not_found': 'This action is not available.',
198
+ 'account.extension.impersonation_refused': 'This action is not available while acting as another user.',
199
+ 'account.extension.field_required': '{field} is required.',
200
+ 'account.extension.field_too_long': '{field} is too long.',
201
+ 'account.extension.field_invalid': '{field} has an invalid value.',
202
+ 'account.extension.done': 'Done.',
203
+ 'account.extension.provider_loading': 'Loading…',
185
204
  // Console de conta — apps com acesso (account/apps).
186
205
  'account.apps.page_title': 'Apps with access',
187
206
  'account.apps.title': 'Apps with access',
@@ -1023,6 +1042,25 @@ export const PT_BR_MESSAGES = {
1023
1042
  'account.delete.deleted': 'Sua conta foi deletada permanentemente.',
1024
1043
  'account.delete.invalid_confirmation': 'Confirmação falhou. Sua conta NÃO foi deletada.',
1025
1044
  'account.delete.not_supported': 'A deleção de conta não está disponível nesta instalação.',
1045
+ // Console de conta — páginas de extensions (account/extension).
1046
+ 'account.extension.logout': 'Sair',
1047
+ 'account.extension.nav_label': 'Conta',
1048
+ 'account.extension.unknown_block': 'Esta seção não pode ser exibida aqui.',
1049
+ 'account.extension.unsafe_link': 'Este link foi ocultado porque o endereço não é permitido.',
1050
+ 'account.extension.unsafe_script': 'Esta seção foi ocultada porque carrega um script de um endereço não permitido.',
1051
+ 'account.extension.load_failed': 'Não foi possível carregar esta página agora. Tente de novo em instantes.',
1052
+ 'account.extension.next_page': 'Próxima página',
1053
+ 'account.extension.confirm_title': 'Confirmar',
1054
+ 'account.extension.confirm_submit': 'Continuar',
1055
+ 'account.extension.cancel': 'Cancelar',
1056
+ 'account.extension.action_failed': 'Não deu certo. Tente de novo em instantes.',
1057
+ 'account.extension.action_not_found': 'Esta ação não está disponível.',
1058
+ 'account.extension.impersonation_refused': 'Esta ação não está disponível enquanto você age como outro usuário.',
1059
+ 'account.extension.field_required': '{field} é obrigatório.',
1060
+ 'account.extension.field_too_long': '{field} é longo demais.',
1061
+ 'account.extension.field_invalid': '{field} tem um valor inválido.',
1062
+ 'account.extension.done': 'Feito.',
1063
+ 'account.extension.provider_loading': 'Carregando…',
1026
1064
  // Console de conta — apps com acesso (account/apps).
1027
1065
  'account.apps.page_title': 'Apps com acesso',
1028
1066
  'account.apps.title': 'Apps com acesso',
@@ -36,6 +36,14 @@ export interface AuthThrottles {
36
36
  * anti-brute-force do código adivinhável, ANTES do lockout por interaction.
37
37
  */
38
38
  otpLogin: ThrottleMiddleware;
39
+ /**
40
+ * Actions das páginas de extension do console de conta, keyed pela CONTA da sessão
41
+ * (bucket `authkit_account_actions`). `accountActions` responde 429 com a tela
42
+ * `throttled` (formulários); `accountActionsJson` responde o 429 cru (JSON API) —
43
+ * mesmo bucket, mesma contagem.
44
+ */
45
+ accountActions: ThrottleMiddleware;
46
+ accountActionsJson: ThrottleMiddleware;
39
47
  }
40
48
  /**
41
49
  * Service do `@adonisjs/limiter` resolvido de forma preguiçosa. Tipado como `any`
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
+ import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
9
10
  import { brandFor } from './branding.js';
10
11
  let limiterServicePromise;
11
12
  /**
@@ -73,6 +74,12 @@ function buildThrottle(name, bucket, store, keyOf) {
73
74
  export function createAuthThrottles(config) {
74
75
  if (!config.enabled)
75
76
  return undefined;
77
+ const accountActions = buildThrottle('authkit_account_actions', config.accountActions ?? { points: 30, duration: '1 min' }, config.store, (ctx) => {
78
+ const accountId = ctx.session?.get?.(ACCOUNT_SESSION_KEY);
79
+ return typeof accountId === 'string' && accountId !== ''
80
+ ? `account:${accountId}`
81
+ : `account-ip:${ctx.request.ip?.() ?? 'unknown'}`;
82
+ });
76
83
  return {
77
84
  // Login/signup/forgot/reset: keyed por IP (default do HttpLimiter).
78
85
  // `friendly`: rota de BROWSER — 429 vira a tela themeável `throttled`
@@ -102,6 +109,8 @@ export function createAuthThrottles(config) {
102
109
  // Verificação de código OTP: keyed por IP (default), bucket próprio e mais
103
110
  // apertado que o login. O namespace do nome mantém a contagem separada.
104
111
  otpLogin: withFriendly429(buildThrottle('authkit_otp_login', config.otpLogin, config.store)),
112
+ accountActions: withFriendly429(accountActions),
113
+ accountActionsJson: accountActions,
105
114
  };
106
115
  }
107
116
  /**
@@ -1,4 +1,5 @@
1
1
  import type { Router } from '@adonisjs/core/http';
2
+ import { type AccountExtensionRouteEntry } from '../account/mount.js';
2
3
  import type { AuthSocialConfig, RateLimitConfigInput } from '../define_config.js';
3
4
  import { type AccountPathKey, type AccountPathsOptions } from './account_paths.js';
4
5
  import type { PolicyRouteOption } from './config_locks.js';
@@ -312,6 +313,11 @@ export interface AuthHostRouteMap {
312
313
  loginUrl: string;
313
314
  /** Quais telas foram efetivamente montadas. */
314
315
  screens: Record<keyof AccountScreensOptions, boolean>;
316
+ /**
317
+ * Account console extension pages (filled once every provider booted): id, path, label (a
318
+ * literal, or the message key) and order. `useAccountExtensions()` gives the localized nav.
319
+ */
320
+ extensions: AccountExtensionRouteEntry[];
315
321
  };
316
322
  /** Console admin: prefixo resolvido, ou `null` quando não foi montado. */
317
323
  admin: {
@@ -1,4 +1,5 @@
1
1
  import { getBootedApp } from '../../services/booted_app.js';
2
+ import { mountAccountExtensions } from '../account/mount.js';
2
3
  import { registerAgoraConsole } from '../console_switcher.js';
3
4
  import { resolveRateLimit } from '../define_config.js';
4
5
  import { accountHome } from './account_home.js';
@@ -988,6 +989,32 @@ export function registerAuthHost(router, opts = {}) {
988
989
  router.post(`${apiBase}/mfa/passkeys/verify`, [C.accountMfaApi, 'passkeyRegisterVerify']);
989
990
  })
990
991
  .use([accountGuard]);
992
+ // Account console extensions (`@adonis-agora/authkit-server/account-extension`): pages other
993
+ // packages register from their provider's `boot()`. Same guard as the built-in screens; the
994
+ // page routes mount once every provider booted. `account: false` turns them off too.
995
+ const extensionEntries = [];
996
+ if (accountOpt !== false) {
997
+ const builtins = {};
998
+ if (mountSecurity)
999
+ builtins.security = securityPath;
1000
+ if (mountMfa)
1001
+ builtins.mfa = mfaPath;
1002
+ if (mountTokens)
1003
+ builtins.tokens = tokensPath;
1004
+ if (mountApps)
1005
+ builtins.apps = appsPath;
1006
+ if (mountOrgs)
1007
+ builtins.orgs = orgsPath;
1008
+ mountAccountExtensions(router, {
1009
+ guard: accountGuard,
1010
+ ...(throttles
1011
+ ? { throttle: { html: throttles.accountActions, json: throttles.accountActionsJson } }
1012
+ : {}),
1013
+ overrides: hostCfg?.accountConsole?.extensions,
1014
+ builtins,
1015
+ entries: extensionEntries,
1016
+ });
1017
+ }
991
1018
  // API headless (Clerk-style) — montada fora do `accountGuard` (não depende da
992
1019
  // sessão de CONTA), autenticada pelo resolver do host (`headless.resolveAccountId`).
993
1020
  // `PUT` usa CSRF igual à API do console (middleware de shield do host).
@@ -1217,6 +1244,7 @@ export function registerAuthHost(router, opts = {}) {
1217
1244
  mfa: mountMfa,
1218
1245
  apps: mountApps,
1219
1246
  },
1247
+ extensions: extensionEntries,
1220
1248
  },
1221
1249
  admin: resolvedAdminPrefix ? { prefix: resolvedAdminPrefix } : null,
1222
1250
  adminApi: resolvedAdminApiPrefix ? { prefix: resolvedAdminApiPrefix } : null,
@@ -1,3 +1,4 @@
1
+ import { accountNavFor } from '../../account/runtime.js';
1
2
  import { getAccountLoginUrl } from '../account_login_url.js';
2
3
  import { accountPathsMap } from '../account_paths.js';
3
4
  import { DEFAULT_MESSAGES, translate } from '../i18n.js';
@@ -40,9 +41,13 @@ export async function renderEdgeView(ctx, view, props) {
40
41
  // `accountRoutes` — sem cada view conhecer o prefixo. Os action-subpaths
41
42
  // fixos (`/password`, `/passkeys/verify`, ...) são concatenados na própria view.
42
43
  const accountPaths = accountPathsMap();
44
+ // `accountNav` como prop global das telas do console de conta: telas embutidas montadas e
45
+ // páginas de extension visíveis para a conta (partial `authkit::account/partials/nav`).
46
+ const accountNav = props.accountNav ?? (await accountNavFor(ctx, view, props));
43
47
  return ctx.view.render(`authkit::${view}`, {
44
48
  loginUrl,
45
49
  accountPaths,
50
+ accountNav,
46
51
  ...props,
47
52
  t,
48
53
  messages,
@@ -1,3 +1,4 @@
1
+ import { accountNavFor } from '../../account/runtime.js';
1
2
  import { DEFAULT_MESSAGES } from '../i18n.js';
2
3
  import { renderEdgeView } from './edge_renderer.js';
3
4
  /**
@@ -68,6 +69,11 @@ export function inertiaRenderer(opts) {
68
69
  // Happy path: view vai ao Inertia.
69
70
  // `messages` vai como shared prop para as páginas React traduzirem.
70
71
  const messages = await resolveMessagesFromCtx(ctx);
71
- return ctx.inertia.render(`${opts.prefix}/${view}`, { ...props, messages });
72
+ const accountNav = props.accountNav ?? (await accountNavFor(ctx, view, props));
73
+ return ctx.inertia.render(`${opts.prefix}/${view}`, {
74
+ ...(accountNav ? { accountNav } : {}),
75
+ ...props,
76
+ messages,
77
+ });
72
78
  };
73
79
  }
@@ -8,6 +8,8 @@
8
8
  * typed per view, so a controller that drifts from these types fails the
9
9
  * package's `tsc`.
10
10
  */
11
+ import type { ResolvedActionStep } from '../account/http.js';
12
+ import type { AccountNavEntry, ResolvedAccountPage } from '../account/runtime.js';
11
13
  import type { OrgInvitation, OrgMember, OrgSummary } from '../accounts/account_store.js';
12
14
  import type { PatRecord } from '../pat/pat_store.js';
13
15
  import type { AccountConfirmProps, AccountEmailConfirmedProps, AccountLoginProps, AccountMfaProps, AccountSecurityProps } from './account_screen_props.js';
@@ -279,6 +281,31 @@ export interface AccountOrgsProps extends WithMessages {
279
281
  invitation?: OrgInvitation | null;
280
282
  token?: string;
281
283
  }
284
+ /**
285
+ * `account/extension` — a page an account console extension added. `page` is the resolved page
286
+ * (texts translated, values formatted); `step` is set on the confirmation step of an action.
287
+ * `accountNav` (the console navigation) is added by the renderer to every account screen.
288
+ */
289
+ export interface AccountExtensionProps extends WithMessages {
290
+ csrfToken: string;
291
+ page: ResolvedAccountPage;
292
+ step: ResolvedActionStep | null;
293
+ flash: {
294
+ tone: 'success' | 'danger';
295
+ text: string;
296
+ } | null;
297
+ /** The response's CSP nonce, for the scripts of `component` blocks. */
298
+ cspNonce: string | null;
299
+ accountNav?: AccountNavEntry[] | null;
300
+ /** `<html lang>` of the console locale. */
301
+ htmlLang?: string;
302
+ /** Edge only: the `<script>` tag of a `component` block's script (nonce included). */
303
+ scriptTag?: (script: {
304
+ src: string;
305
+ integrity: string | null;
306
+ module: boolean;
307
+ }) => string;
308
+ }
282
309
  /** Props of every screen, by view name. */
283
310
  export interface AuthkitScreenProps {
284
311
  login: LoginProps;
@@ -302,6 +329,7 @@ export interface AuthkitScreenProps {
302
329
  'account/orgs': AccountOrgsProps;
303
330
  'account/confirm': AccountConfirmProps;
304
331
  'account/email-confirmed': AccountEmailConfirmedProps;
332
+ 'account/extension': AccountExtensionProps;
305
333
  }
306
334
  /** Every screen authkit-server renders. */
307
335
  export type AuthkitScreen = keyof AuthkitScreenProps;