@adonis-agora/authkit-server 0.47.0 → 0.48.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 (188) hide show
  1. package/build/commands/clients_create.js +4 -4
  2. package/build/commands/configure.js +3 -2
  3. package/build/commands/doctor.js +8 -5
  4. package/build/commands/eject.js +1 -1
  5. package/build/commands/expire_scan.js +7 -5
  6. package/build/commands/import_users.js +1 -1
  7. package/build/commands/keys_rotate.js +7 -4
  8. package/build/host/views/account/apps.edge +5 -5
  9. package/build/host/views/account/login.edge +1 -1
  10. package/build/host/views/account/mfa.edge +7 -7
  11. package/build/host/views/account/orgs.edge +5 -5
  12. package/build/host/views/account/security.edge +10 -10
  13. package/build/host/views/account/tokens.edge +5 -5
  14. package/build/index.d.ts +7 -7
  15. package/build/index.js +5 -5
  16. package/build/providers/authkit_server_provider.d.ts +2 -2
  17. package/build/providers/authkit_server_provider.js +20 -11
  18. package/build/services/main.d.ts +1 -1
  19. package/build/services/main.js +2 -2
  20. package/build/src/accounts/lucid_account_store.d.ts +3 -3
  21. package/build/src/accounts/lucid_account_store.js +6 -8
  22. package/build/src/accounts/lucid_store/core.js +1 -1
  23. package/build/src/accounts/lucid_store/mfa.js +1 -1
  24. package/build/src/accounts/lucid_store/organizations.js +2 -4
  25. package/build/src/accounts/lucid_store/password_hygiene.js +1 -4
  26. package/build/src/accounts/lucid_store/shared.d.ts +1 -1
  27. package/build/src/accounts/lucid_stores.d.ts +3 -3
  28. package/build/src/accounts/lucid_stores.js +2 -2
  29. package/build/src/adapters/database_adapter.js +3 -1
  30. package/build/src/adapters/factory.js +1 -1
  31. package/build/src/adapters/redis_adapter.js +2 -1
  32. package/build/src/commands/expire_scan_command.js +19 -5
  33. package/build/src/commands/settings_commands.js +22 -5
  34. package/build/src/define_config.d.ts +20 -20
  35. package/build/src/define_config.js +62 -67
  36. package/build/src/doctor/checks.js +45 -20
  37. package/build/src/events/dispatcher.d.ts +1 -1
  38. package/build/src/events/dispatcher.js +10 -10
  39. package/build/src/host/account_api/account_api_controller.js +95 -39
  40. package/build/src/host/account_deletion_ops.d.ts +4 -4
  41. package/build/src/host/account_deletion_ops.js +5 -5
  42. package/build/src/host/account_deletion_service.d.ts +2 -2
  43. package/build/src/host/account_deletion_service.js +2 -2
  44. package/build/src/host/account_export_service.d.ts +1 -1
  45. package/build/src/host/account_export_service.js +4 -6
  46. package/build/src/host/account_home.d.ts +0 -8
  47. package/build/src/host/account_home.js +6 -4
  48. package/build/src/host/account_login_url.d.ts +6 -3
  49. package/build/src/host/account_login_url.js +16 -8
  50. package/build/src/host/account_paths.d.ts +120 -0
  51. package/build/src/host/account_paths.js +161 -0
  52. package/build/src/host/admin_api/admin_orgs_service.d.ts +2 -2
  53. package/build/src/host/admin_api/admin_orgs_service.js +3 -2
  54. package/build/src/host/admin_api/admin_users_service.d.ts +12 -12
  55. package/build/src/host/admin_api/admin_users_service.js +20 -20
  56. package/build/src/host/admin_api/api_clients_controller.js +2 -2
  57. package/build/src/host/admin_api/api_misc_controller.js +2 -2
  58. package/build/src/host/admin_api/api_orgs_controller.js +3 -3
  59. package/build/src/host/admin_api/api_settings_controller.js +11 -5
  60. package/build/src/host/admin_api/api_users_controller.d.ts +2 -2
  61. package/build/src/host/admin_api/api_users_controller.js +37 -38
  62. package/build/src/host/admin_api/dto.d.ts +4 -4
  63. package/build/src/host/admin_clients_service.js +5 -4
  64. package/build/src/host/admin_console/admin_shell_controller.js +4 -2
  65. package/build/src/host/admin_console/console_audit_controller.js +1 -1
  66. package/build/src/host/admin_console/console_clients_controller.js +5 -3
  67. package/build/src/host/admin_console/console_impersonation_controller.js +1 -1
  68. package/build/src/host/admin_console/console_keys_controller.js +2 -4
  69. package/build/src/host/admin_console/console_orgs_controller.js +3 -3
  70. package/build/src/host/admin_console/console_roles_controller.js +30 -12
  71. package/build/src/host/admin_console/console_sessions_controller.js +3 -3
  72. package/build/src/host/admin_console/console_settings_controller.js +11 -5
  73. package/build/src/host/admin_console/console_users_controller.d.ts +2 -2
  74. package/build/src/host/admin_console/console_users_controller.js +46 -47
  75. package/build/src/host/admin_prefix.js +2 -2
  76. package/build/src/host/admin_sessions_service.js +1 -3
  77. package/build/src/host/adonis_auth_sync.d.ts +1 -1
  78. package/build/src/host/adonis_auth_user_provider.d.ts +1 -1
  79. package/build/src/host/app_key.js +1 -1
  80. package/build/src/host/avatar_storage.js +1 -3
  81. package/build/src/host/config_locks.js +1 -3
  82. package/build/src/host/console_session.js +1 -1
  83. package/build/src/host/controllers/account_apps_controller.js +3 -2
  84. package/build/src/host/controllers/account_confirm_controller.js +2 -6
  85. package/build/src/host/controllers/account_mfa_controller.js +63 -12
  86. package/build/src/host/controllers/account_orgs_controller.js +47 -27
  87. package/build/src/host/controllers/account_security_controller.d.ts +2 -2
  88. package/build/src/host/controllers/account_security_controller.js +114 -115
  89. package/build/src/host/controllers/account_session_controller.js +5 -5
  90. package/build/src/host/controllers/account_tokens_controller.js +3 -2
  91. package/build/src/host/controllers/interaction_controller.js +59 -22
  92. package/build/src/host/controllers/registration_controller.js +28 -13
  93. package/build/src/host/default_mailer.js +11 -7
  94. package/build/src/host/durable/account_deletion_workflow.d.ts +2 -2
  95. package/build/src/host/durable/account_deletion_workflow.js +14 -14
  96. package/build/src/host/durable/account_export_workflow.d.ts +2 -2
  97. package/build/src/host/durable/account_export_workflow.js +12 -12
  98. package/build/src/host/durable/index.d.ts +5 -5
  99. package/build/src/host/durable/index.js +6 -6
  100. package/build/src/host/i18n.d.ts +1376 -1376
  101. package/build/src/host/i18n.js +1380 -1380
  102. package/build/src/host/impersonation.js +1 -7
  103. package/build/src/host/key_rotation_actions.d.ts +1 -1
  104. package/build/src/host/login_attempt.js +35 -6
  105. package/build/src/host/login_notify.js +1 -1
  106. package/build/src/host/otp_lockout.js +1 -3
  107. package/build/src/host/rate_limit.js +1 -1
  108. package/build/src/host/register_auth_host.d.ts +30 -0
  109. package/build/src/host/register_auth_host.js +120 -74
  110. package/build/src/host/renderers/edge_renderer.js +15 -2
  111. package/build/src/host/renderers/inertia_renderer.d.ts +9 -1
  112. package/build/src/host/renderers/inertia_renderer.js +9 -1
  113. package/build/src/host/runtime_settings.js +1 -1
  114. package/build/src/host/runtime_toggles.d.ts +3 -3
  115. package/build/src/host/runtime_toggles.js +69 -24
  116. package/build/src/host/security_notice_service.d.ts +1 -1
  117. package/build/src/host/security_notice_service.js +1 -1
  118. package/build/src/host/session_context.js +1 -1
  119. package/build/src/host/sudo/index.d.ts +3 -3
  120. package/build/src/host/sudo/index.js +3 -3
  121. package/build/src/host/sudo/methods/magic_link.js +6 -5
  122. package/build/src/host/sudo/methods/passkey.js +7 -4
  123. package/build/src/host/sudo/methods/password.js +8 -3
  124. package/build/src/host/sudo/runtime.js +6 -13
  125. package/build/src/host/sudo_mode.js +3 -2
  126. package/build/src/host/ui-dist/assets/index-BQoTDiLV.js +137 -0
  127. package/build/src/host/ui-dist/index.html +1 -1
  128. package/build/src/keys/jwks_manager.js +1 -1
  129. package/build/src/keys/keystore.js +1 -1
  130. package/build/src/keys/keystore_manager.d.ts +2 -2
  131. package/build/src/keys/keystore_manager.js +2 -2
  132. package/build/src/keys/keystore_vault.js +8 -4
  133. package/build/src/mixins/with_audit_log.d.ts +1 -1
  134. package/build/src/mixins/with_auth_user.d.ts +1 -1
  135. package/build/src/mixins/with_auth_user.js +1 -1
  136. package/build/src/mixins/with_credentials.d.ts +1 -1
  137. package/build/src/mixins/with_mfa.d.ts +1 -1
  138. package/build/src/mixins/with_personal_access_token.d.ts +1 -1
  139. package/build/src/mixins/with_provider_identity.d.ts +1 -1
  140. package/build/src/mixins/with_webauthn_credential.d.ts +1 -1
  141. package/build/src/observability/diagnostics_bridge.js +2 -2
  142. package/build/src/observability/metrics_service.js +1 -1
  143. package/build/src/observability/telescope/data_providers.d.ts +1 -1
  144. package/build/src/observability/telescope/data_providers.js +29 -43
  145. package/build/src/observability/telescope/extension.d.ts +1 -1
  146. package/build/src/observability/telescope/extension.js +58 -58
  147. package/build/src/observability/telescope/index.d.ts +2 -2
  148. package/build/src/observability/telescope/index.js +2 -2
  149. package/build/src/observability/wire_provider_events.d.ts +1 -1
  150. package/build/src/observability/wire_provider_events.js +11 -11
  151. package/build/src/password/common_passwords.js +1 -1
  152. package/build/src/password/password_manager.d.ts +1 -1
  153. package/build/src/password/password_manager.js +4 -4
  154. package/build/src/pat/lucid_pat_store.js +2 -6
  155. package/build/src/pat/pat_tokens.js +1 -1
  156. package/build/src/provider/interaction_actions.js +1 -1
  157. package/build/src/provider/key_rotation_scheduler.js +3 -1
  158. package/build/src/provider/logout_sources.js +2 -8
  159. package/build/src/provider/oidc_service.d.ts +2 -2
  160. package/build/src/provider/oidc_service.js +22 -14
  161. package/build/src/provider/single_flight_lock.js +1 -3
  162. package/build/src/provider/token_exchange.d.ts +1 -1
  163. package/build/src/schema/ensure.js +3 -1
  164. package/build/stubs/ui/react/components/auth_shell.tsx +19 -19
  165. package/build/stubs/ui/react/pages/account/login.tsx +6 -7
  166. package/build/stubs/ui/react/pages/account/mfa.tsx +16 -15
  167. package/build/stubs/ui/react/pages/account/tokens.tsx +27 -17
  168. package/build/stubs/ui/react/pages/consent.tsx +12 -12
  169. package/build/stubs/ui/react/pages/forgot.tsx +7 -7
  170. package/build/stubs/ui/react/pages/login.tsx +22 -24
  171. package/build/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
  172. package/build/stubs/ui/react/pages/reset.tsx +10 -10
  173. package/build/stubs/ui/react/pages/signup.tsx +30 -16
  174. package/build/stubs/ui/react/pages/verify-email.tsx +6 -6
  175. package/package.json +1 -1
  176. package/stubs/main.ts +2 -2
  177. package/stubs/ui/react/components/auth_shell.tsx +19 -19
  178. package/stubs/ui/react/pages/account/login.tsx +6 -7
  179. package/stubs/ui/react/pages/account/mfa.tsx +16 -15
  180. package/stubs/ui/react/pages/account/tokens.tsx +27 -17
  181. package/stubs/ui/react/pages/consent.tsx +12 -12
  182. package/stubs/ui/react/pages/forgot.tsx +7 -7
  183. package/stubs/ui/react/pages/login.tsx +22 -24
  184. package/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
  185. package/stubs/ui/react/pages/reset.tsx +10 -10
  186. package/stubs/ui/react/pages/signup.tsx +30 -16
  187. package/stubs/ui/react/pages/verify-email.tsx +6 -6
  188. package/build/src/host/ui-dist/assets/index-BmI_J87g.js +0 -137
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Prefixo e segmentos de tela do console de conta (`/account/*`) — singleton de
3
+ * processo, configurável/localizável.
4
+ *
5
+ * Definidos em tempo de registro das rotas (`registerAuthHost`, via a opção
6
+ * `accountRoutes`) e lidos em runtime por TODOS os pontos que constroem uma URL
7
+ * do console de conta: o registro de rotas, os redirects dos controllers, as
8
+ * URLs absolutas dos e-mails transacionais, os guards de sudo e as views Edge
9
+ * (injetados como prop global `accountPaths` pelo renderer).
10
+ *
11
+ * Por que módulo singleton e não binding do container? Mesma razão do
12
+ * `admin_prefix` e do `account_login_url`: os guards e o registro de rotas são
13
+ * closures construídas em tempo de registro, ANTES da primeira request, quando
14
+ * o container do AdonisJS ainda não existe. Um módulo ESM é inicializado uma vez
15
+ * por processo — ideal para configuração imutável de boot.
16
+ *
17
+ * ── O que é configurável e o que NÃO é ──────────────────────────────────────
18
+ * Configuráveis: o PREFIXO (`/account` → `/conta`) e o SEGMENTO de cada TELA
19
+ * navegável (`security` → `seguranca`, `confirm` → `confirmar`, ...). São paths
20
+ * que o usuário vê na barra de endereço e que um host pode querer no idioma dele.
21
+ *
22
+ * NÃO configuráveis (de propósito): os action-subpaths dos POSTs de dentro de
23
+ * cada tela — `/password`, `/email`, `/enroll`, `/confirm`, `/disable`,
24
+ * `/passkeys/options`, `/passkeys/verify`, `/delete`, `/export`, o `/magic-link`
25
+ * e o `/passkey` das rotas de sudo, etc. São endpoints de formulário invisíveis
26
+ * ao usuário (ele nunca digita `/account/security/password`), servem apenas de
27
+ * alvo de `<form action>`/`fetch`, e mantê-los fixos evita multiplicar a
28
+ * superfície de configuração (e a de bugs) sem nenhum ganho de UX. Renomear a
29
+ * tela já cobre o caso real "quero /conta/seguranca". Idem para o segmento fixo
30
+ * `api` da JSON API (`{prefix}/api/*`): ele segue o prefixo mas nunca é
31
+ * traduzido — é contrato de máquina (authkit-react), não de humano.
32
+ */
33
+ /** Prefixo default do console de conta. */
34
+ const DEFAULT_ACCOUNT_PREFIX = '/account';
35
+ /**
36
+ * Segmentos default de cada TELA navegável do console de conta. A chave é o
37
+ * identificador estável usado no código (`accountPath('security')`); o valor é
38
+ * o segmento de URL, sobrescrevível por `setAccountPaths`.
39
+ */
40
+ const DEFAULT_ACCOUNT_SEGMENTS = {
41
+ login: 'login',
42
+ logout: 'logout',
43
+ security: 'security',
44
+ mfa: 'mfa',
45
+ confirm: 'confirm',
46
+ tokens: 'tokens',
47
+ apps: 'apps',
48
+ orgs: 'orgs',
49
+ emailConfirm: 'email/confirm',
50
+ };
51
+ let _prefix = DEFAULT_ACCOUNT_PREFIX;
52
+ let _segments = { ...DEFAULT_ACCOUNT_SEGMENTS };
53
+ /**
54
+ * Normaliza o prefixo: começa com '/', sem trailing slash. Mesma semântica de
55
+ * `normalizeAdminPrefix`. `'/'` → `'/'` (raiz) é improvável mas seguro.
56
+ */
57
+ export function normalizeAccountPrefix(raw) {
58
+ let p = raw.trim();
59
+ if (!p.startsWith('/'))
60
+ p = `/${p}`;
61
+ p = p.replace(/\/+$/, '');
62
+ return p || '/';
63
+ }
64
+ /**
65
+ * Normaliza um segmento de tela: sem barras nas pontas (mas preserva barras
66
+ * internas, ex.: `'email/confirm'`). Valor vazio é ignorado pelo caller.
67
+ */
68
+ function normalizeSegment(raw) {
69
+ return raw.trim().replace(/^\/+/, '').replace(/\/+$/, '');
70
+ }
71
+ /**
72
+ * Define o prefixo e/ou os segmentos de tela do console de conta para este
73
+ * processo. Chamado UMA VEZ por `registerAuthHost` no boot da aplicação.
74
+ *
75
+ * Só sobrescreve o que foi passado — chamada sem `prefix` mantém o prefixo
76
+ * atual, e segmentos ausentes de `paths` mantêm o default. Valores
77
+ * vazios/whitespace são ignorados (caem no default), nunca produzem um path
78
+ * quebrado.
79
+ *
80
+ * @example
81
+ * setAccountPaths({ prefix: '/conta', paths: { security: 'seguranca', confirm: 'confirmar' } })
82
+ * accountPath('security') // → '/conta/seguranca'
83
+ * accountPath('confirm') // → '/conta/confirmar'
84
+ */
85
+ export function setAccountPaths(opts) {
86
+ if (!opts)
87
+ return;
88
+ if (opts.prefix !== undefined) {
89
+ const trimmed = opts.prefix.trim();
90
+ if (trimmed)
91
+ _prefix = normalizeAccountPrefix(trimmed);
92
+ }
93
+ if (opts.paths) {
94
+ for (const key of Object.keys(opts.paths)) {
95
+ const value = opts.paths[key];
96
+ if (value === undefined)
97
+ continue;
98
+ const seg = normalizeSegment(value);
99
+ if (seg)
100
+ _segments[key] = seg;
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Junta o prefixo do console de conta com um subpath arbitrário, tratando o
106
+ * prefixo raiz (`'/'`) como `''` na composição: sem isso o resultado seria
107
+ * `'//<sub>'`, que num `<form action>`/`fetch()` é URL protocol-relative (o
108
+ * browser lê `<sub>` como HOST, não como path).
109
+ *
110
+ * Base canônica de `accountPath()` e de QUALQUER outra composição
111
+ * prefixo+subpath fora das TELAS fixas (ex.: o `{prefix}/api` da JSON API em
112
+ * `register_auth_host`). Novas composições devem passar por aqui em vez de
113
+ * reimplementar `${accountPrefix()}/algo` — é exatamente essa duplicação que
114
+ * reproduz o bug do `//` quando o prefixo normaliza pra raiz.
115
+ *
116
+ * @example
117
+ * joinAccountPath('api') // default → '/account/api'
118
+ */
119
+ export function joinAccountPath(sub) {
120
+ const base = _prefix === '/' ? '' : _prefix;
121
+ return `${base}/${sub}`;
122
+ }
123
+ /**
124
+ * Retorna o path completo de uma TELA do console de conta (prefixo + segmento).
125
+ * Para os action-subpaths (POSTs de dentro da tela) concatene o subpath fixo:
126
+ * `accountPath('security') + '/password'`.
127
+ *
128
+ * @example
129
+ * accountPath('security') // default → '/account/security'
130
+ * accountPath('confirm') // default → '/account/confirm'
131
+ */
132
+ export function accountPath(key) {
133
+ return joinAccountPath(_segments[key]);
134
+ }
135
+ /**
136
+ * Retorna o prefixo BRUTO do console de conta (default `'/account'`; pode ser
137
+ * `'/'` na raiz). Para compor `{prefix}/algo` (ex.: a JSON API, cujo segmento
138
+ * `api` é fixo — contrato de máquina), use `joinAccountPath('algo')`: ela
139
+ * colapsa a raiz e evita o `//`. Este getter existe para os casos (ex.: os
140
+ * asserts de teste) que precisam do valor cru do prefixo.
141
+ */
142
+ export function accountPrefix() {
143
+ return _prefix;
144
+ }
145
+ /**
146
+ * Mapa `{ chave → path completo }` de TODAS as telas. Injetado como prop global
147
+ * `accountPaths` nas views Edge, para que os `<form action>`/`fetch()` respeitem
148
+ * os overrides sem cada view conhecer o prefixo.
149
+ */
150
+ export function accountPathsMap() {
151
+ const out = {};
152
+ for (const key of Object.keys(_segments)) {
153
+ out[key] = accountPath(key);
154
+ }
155
+ return out;
156
+ }
157
+ /** Restaura os defaults — uso em testes (isola o singleton entre casos). */
158
+ export function resetAccountPaths() {
159
+ _prefix = DEFAULT_ACCOUNT_PREFIX;
160
+ _segments = { ...DEFAULT_ACCOUNT_SEGMENTS };
161
+ }
@@ -1,7 +1,7 @@
1
+ import type { OrgInvitation, OrgMember, OrgSummary } from '../../accounts/account_store.js';
1
2
  import type { ResolvedServerConfig } from '../../define_config.js';
2
- import type { OrgSummary, OrgMember, OrgInvitation } from '../../accounts/account_store.js';
3
- import type { AdminActor } from './admin_users_service.js';
4
3
  import type { SettingsCapability } from '../runtime_settings.js';
4
+ import type { AdminActor } from './admin_users_service.js';
5
5
  export interface OrgWithMemberCount extends OrgSummary {
6
6
  memberCount: number;
7
7
  }
@@ -1,5 +1,6 @@
1
1
  import { supportsOrganizations } from '../../accounts/account_store.js';
2
- import { resolveRoleCatalogList, isRoleInCatalog } from '../runtime_toggles.js';
2
+ import { accountPath } from '../account_paths.js';
3
+ import { isRoleInCatalog, resolveRoleCatalogList } from '../runtime_toggles.js';
3
4
  /**
4
5
  * Lógica de gestão de organizações compartilhada entre o console admin (HTML)
5
6
  * e a Admin REST API (JSON). Usada pelos `AdminOrgsController` e `ApiOrgsController`.
@@ -267,7 +268,7 @@ export class AdminOrgsService {
267
268
  });
268
269
  // Dispara mail hook (best-effort)
269
270
  if (this.cfg.mail?.onOrgInvitation) {
270
- const acceptUrl = `${origin}/account/orgs/invitations/${token}/accept`;
271
+ const acceptUrl = `${origin}${accountPath('orgs')}/invitations/${token}/accept`;
271
272
  try {
272
273
  await this.cfg.mail.onOrgInvitation({
273
274
  email: input.email,
@@ -1,20 +1,20 @@
1
- import type { HttpContext } from "@adonisjs/core/http";
2
- import type { ResolvedServerConfig } from "../../define_config.js";
3
- import type { OidcService } from "../../provider/oidc_service.js";
4
- import type { AuthAccount } from "../../accounts/account_store.js";
5
- import { type DeletionResult } from "../account_deletion_service.js";
6
- import type { SettingsCapability } from "../runtime_settings.js";
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AuthAccount } from '../../accounts/account_store.js';
3
+ import type { ResolvedServerConfig } from '../../define_config.js';
4
+ import type { OidcService } from '../../provider/oidc_service.js';
5
+ import { type DeletionResult } from '../account_deletion_service.js';
6
+ import type { SettingsCapability } from '../runtime_settings.js';
7
7
  /** Quem disparou a operação (para auditoria). `admin-api` quando via REST API. */
8
8
  export interface AdminActor {
9
9
  actorId: string | null;
10
10
  ip: string | null;
11
11
  /** Marca metadata da auditoria — 'admin-api' nas escritas via REST, 'admin' no console HTML. */
12
- source?: "admin-api" | "admin";
12
+ source?: 'admin-api' | 'admin';
13
13
  }
14
14
  /** Resultado da deleção via admin: false quando o store não suporta hard delete. */
15
15
  export type DeleteUserResult = {
16
16
  ok: false;
17
- reason: "not_found" | "unsupported";
17
+ reason: 'not_found' | 'unsupported';
18
18
  } | {
19
19
  ok: true;
20
20
  result: DeletionResult;
@@ -34,7 +34,7 @@ export type EnqueueDeletion = (input: {
34
34
  actor: {
35
35
  actorId: string | null;
36
36
  ip: string | null;
37
- source: "admin" | "admin-api";
37
+ source: 'admin' | 'admin-api';
38
38
  };
39
39
  }) => Promise<string>;
40
40
  export interface CreateUserInput {
@@ -50,10 +50,10 @@ export type CreateUserResult = {
50
50
  invited: boolean;
51
51
  } | {
52
52
  ok: false;
53
- reason: "email_taken";
53
+ reason: 'email_taken';
54
54
  } | {
55
55
  ok: false;
56
- reason: "password_policy";
56
+ reason: 'password_policy';
57
57
  /** Chave i18n da regra violada + params para interpolar. */
58
58
  messageKey: string;
59
59
  messageParams?: Record<string, string | number>;
@@ -141,7 +141,7 @@ export declare class AdminUsersService {
141
141
  *
142
142
  * @returns código i18n/erro quando a operação deve ser bloqueada; `null` quando OK.
143
143
  */
144
- guardGlobalRolesChange(targetId: string, newRoles: string[], actorId: string | null): Promise<"last_admin" | "cannot_self_demote" | null>;
144
+ guardGlobalRolesChange(targetId: string, newRoles: string[], actorId: string | null): Promise<'last_admin' | 'cannot_self_demote' | null>;
145
145
  /** Atualiza nome/avatar (capacidade opcional). Retorna a conta ou null. */
146
146
  updateProfile(accountId: string, patch: {
147
147
  name?: string | null;
@@ -1,9 +1,9 @@
1
- import { randomBytes } from "node:crypto";
2
- import { supportsAccountDeletion, supportsAccountStatus, supportsProfile, supportsCountByGlobalRole, } from "../../accounts/account_store.js";
3
- import { sendPasswordResetEmail } from "../default_mailer.js";
4
- import { AccountDeletionService, } from "../account_deletion_service.js";
5
- import { PasswordPolicyError } from "../../password/password_manager.js";
6
- import { resolveEffectiveRolesCatalog } from "../runtime_toggles.js";
1
+ import { randomBytes } from 'node:crypto';
2
+ import { supportsAccountDeletion, supportsAccountStatus, supportsCountByGlobalRole, supportsProfile, } from '../../accounts/account_store.js';
3
+ import { PasswordPolicyError } from '../../password/password_manager.js';
4
+ import { AccountDeletionService } from '../account_deletion_service.js';
5
+ import { sendPasswordResetEmail } from '../default_mailer.js';
6
+ import { resolveEffectiveRolesCatalog } from '../runtime_toggles.js';
7
7
  /**
8
8
  * Lógica de gestão de usuários compartilhada entre o console admin (B6, HTML) e a
9
9
  * Admin REST API (R6, JSON). Encapsula o fluxo "create + invite", reset de senha,
@@ -24,9 +24,9 @@ export class AdminUsersService {
24
24
  const store = this.cfg.accountStore;
25
25
  const existing = await store.findByEmail(input.email);
26
26
  if (existing)
27
- return { ok: false, reason: "email_taken" };
27
+ return { ok: false, reason: 'email_taken' };
28
28
  const hasPassword = !!input.password;
29
- const initialPassword = input.password ?? randomBytes(24).toString("hex");
29
+ const initialPassword = input.password ?? randomBytes(24).toString('hex');
30
30
  let account;
31
31
  try {
32
32
  account = await store.create({
@@ -40,7 +40,7 @@ export class AdminUsersService {
40
40
  if (error instanceof PasswordPolicyError) {
41
41
  return {
42
42
  ok: false,
43
- reason: "password_policy",
43
+ reason: 'password_policy',
44
44
  messageKey: error.key,
45
45
  messageParams: error.params,
46
46
  };
@@ -48,7 +48,7 @@ export class AdminUsersService {
48
48
  throw error;
49
49
  }
50
50
  await this.cfg.audit?.record({
51
- type: "user.created",
51
+ type: 'user.created',
52
52
  accountId: account.id,
53
53
  email: input.email,
54
54
  actorId: actor.actorId,
@@ -70,7 +70,7 @@ export class AdminUsersService {
70
70
  return null;
71
71
  await this.sendResetEmail(ctx, account.email);
72
72
  await this.cfg.audit?.record({
73
- type: "user.password_reset_sent",
73
+ type: 'user.password_reset_sent',
74
74
  accountId,
75
75
  email: account.email,
76
76
  actorId: actor.actorId,
@@ -92,7 +92,7 @@ export class AdminUsersService {
92
92
  else
93
93
  await store.enableAccount(accountId);
94
94
  await this.cfg.audit?.record({
95
- type: disable ? "user.disabled" : "user.enabled",
95
+ type: disable ? 'user.disabled' : 'user.enabled',
96
96
  accountId,
97
97
  actorId: actor.actorId,
98
98
  ip: actor.ip,
@@ -123,7 +123,7 @@ export class AdminUsersService {
123
123
  // Roles que o usuário não tinha E que não estão no catálogo = inválidas.
124
124
  for (const role of roles) {
125
125
  if (!catalogNames.has(role) && !currentRoles.has(role)) {
126
- return "admin.roles.unknown_role";
126
+ return 'admin.roles.unknown_role';
127
127
  }
128
128
  }
129
129
  }
@@ -137,7 +137,7 @@ export class AdminUsersService {
137
137
  /** Roles globais que conferem acesso de admin (default ['ADMIN']). */
138
138
  adminRoles() {
139
139
  const roles = this.cfg.admin?.roles;
140
- return roles && roles.length > 0 ? roles : ["ADMIN"];
140
+ return roles && roles.length > 0 ? roles : ['ADMIN'];
141
141
  }
142
142
  /** Um conjunto de roles contém ao menos uma role de admin? */
143
143
  hasAdminRole(roles) {
@@ -226,11 +226,11 @@ export class AdminUsersService {
226
226
  return null;
227
227
  // Auto-rebaixamento: o ator removendo a própria role de admin.
228
228
  if (actorId && actorId === targetId)
229
- return "cannot_self_demote";
229
+ return 'cannot_self_demote';
230
230
  // Último admin: se o target é o único admin, a remoção causaria lockout.
231
231
  const admins = await this.countAdmins();
232
232
  if (admins <= 1)
233
- return "last_admin";
233
+ return 'last_admin';
234
234
  return null;
235
235
  }
236
236
  /** Atualiza nome/avatar (capacidade opcional). Retorna a conta ou null. */
@@ -251,15 +251,15 @@ export class AdminUsersService {
251
251
  async delete(oidc, accountId, actor, enqueue) {
252
252
  const store = this.cfg.accountStore;
253
253
  if (!supportsAccountDeletion(store))
254
- return { ok: false, reason: "unsupported" };
254
+ return { ok: false, reason: 'unsupported' };
255
255
  const account = await store.findById(accountId);
256
256
  if (!account)
257
- return { ok: false, reason: "not_found" };
258
- const source = actor.source === "admin" ? "admin" : "admin-api";
257
+ return { ok: false, reason: 'not_found' };
258
+ const source = actor.source === 'admin' ? 'admin' : 'admin-api';
259
259
  // Trilha administrativa ANTES do cascade — assim esta linha também é
260
260
  // anonimizada na etapa de anonimização do audit (não reintroduz PII).
261
261
  await this.cfg.audit?.record({
262
- type: "user.deleted",
262
+ type: 'user.deleted',
263
263
  accountId,
264
264
  email: account.email,
265
265
  actorId: actor.actorId,
@@ -1,7 +1,7 @@
1
1
  import '../augmentations.js';
2
2
  import { AdminClientsService } from '../admin_clients_service.js';
3
- import { clientDto, createdClientDto, apiError } from './dto.js';
4
- import { clientInputValidator, clientCreateInput, clientPartialInput } from '../admin_validators.js';
3
+ import { clientCreateInput, clientInputValidator, clientPartialInput, } from '../admin_validators.js';
4
+ import { apiError, clientDto, createdClientDto } from './dto.js';
5
5
  /** Resolve o serviço (== OidcService) + o AdminClientsService. */
6
6
  async function clientsService(ctx) {
7
7
  const service = await ctx.containerResolver.make('authkit.server');
@@ -1,9 +1,9 @@
1
1
  import '../augmentations.js';
2
- import { TokenVerifyService } from './token_verify_service.js';
3
2
  import { AdminSessionsService } from '../admin_sessions_service.js';
4
3
  import { computeAdminStats } from '../admin_stats_service.js';
5
- import { auditDto, apiError } from './dto.js';
6
4
  import { tokenVerifyValidator } from '../admin_validators.js';
5
+ import { apiError, auditDto } from './dto.js';
6
+ import { TokenVerifyService } from './token_verify_service.js';
7
7
  /**
8
8
  * Endpoints utilitários da Admin REST API: log de auditoria (`GET /audit`) e
9
9
  * introspecção genérica de token (`POST /tokens/verify`).
@@ -1,8 +1,8 @@
1
1
  import '../augmentations.js';
2
- import { AdminOrgsService } from './admin_orgs_service.js';
3
- import { orgDto, orgDetailDto, orgInvitationDto, apiError } from './dto.js';
2
+ import { orgAddMemberValidator, orgCreateValidator, orgInvitationValidator, orgMemberRoleValidator, orgUpdateValidator, } from '../admin_validators.js';
4
3
  import { resolveRuntimeSettings } from '../runtime_settings.js';
5
- import { orgCreateValidator, orgUpdateValidator, orgAddMemberValidator, orgMemberRoleValidator, orgInvitationValidator, } from '../admin_validators.js';
4
+ import { AdminOrgsService } from './admin_orgs_service.js';
5
+ import { apiError, orgDetailDto, orgDto, orgInvitationDto } from './dto.js';
6
6
  /** Lê a config + monta o actor `admin-api` para auditoria. */
7
7
  async function ctxBits(ctx) {
8
8
  const service = await ctx.containerResolver.make('authkit.server');
@@ -1,7 +1,7 @@
1
1
  import '../augmentations.js';
2
+ import { SettingLockedError, isSettingLocked, lockedSettingKeys } from '../config_locks.js';
2
3
  import { resolveRuntimeSettings } from '../runtime_settings.js';
3
- import { settingDto, apiError } from './dto.js';
4
- import { isSettingLocked, lockedSettingKeys, SettingLockedError } from '../config_locks.js';
4
+ import { apiError, settingDto } from './dto.js';
5
5
  /** 423 Locked: a setting foi travada via defineConfig (não editável em runtime). */
6
6
  function lockedResponse(ctx, err) {
7
7
  return ctx.response
@@ -16,8 +16,8 @@ function notSupported(ctx) {
16
16
  function resolveOrgId(ctx) {
17
17
  const qs = ctx.request.qs();
18
18
  if ('organizationId' in qs) {
19
- const v = qs['organizationId'];
20
- return v && v.trim() ? v.trim() : null;
19
+ const v = qs.organizationId;
20
+ return v?.trim() ? v.trim() : null;
21
21
  }
22
22
  return null;
23
23
  }
@@ -99,7 +99,13 @@ export default class ApiSettingsController {
99
99
  metadata: { key, value: body.value, organizationId: orgId },
100
100
  });
101
101
  const saved = await svc.getSetting(key, orgId);
102
- return settingDto({ key, organizationId: orgId, value: saved, updatedAt: new Date(), updatedBy: null });
102
+ return settingDto({
103
+ key,
104
+ organizationId: orgId,
105
+ value: saved,
106
+ updatedAt: new Date(),
107
+ updatedBy: null,
108
+ });
103
109
  }
104
110
  /** DELETE /settings/:key */
105
111
  async destroy(ctx) {
@@ -1,5 +1,5 @@
1
- import "../augmentations.js";
2
- import type { HttpContext } from "@adonisjs/core/http";
1
+ import '../augmentations.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  /**
4
4
  * Recurso de usuários da Admin REST API (R6). JSON puro (camelCase), erros no
5
5
  * envelope `{ error: { code, message } }`. Toda escrita audita com `actor: 'admin-api'`.
@@ -1,10 +1,10 @@
1
- import "../augmentations.js";
2
- import { AdminUsersService } from "./admin_users_service.js";
3
- import { AdminSessionsService } from "../admin_sessions_service.js";
4
- import { enrichSessionsWithContext } from "../session_context.js";
5
- import { resolveRuntimeSettings } from "../runtime_settings.js";
6
- import { userDto, sessionDto, grantDto, apiError } from "./dto.js";
7
- import { adminUserCreateValidator, adminUserUpdateValidator, } from "../admin_validators.js";
1
+ import '../augmentations.js';
2
+ import { AdminSessionsService } from '../admin_sessions_service.js';
3
+ import { adminUserCreateValidator, adminUserUpdateValidator } from '../admin_validators.js';
4
+ import { resolveRuntimeSettings } from '../runtime_settings.js';
5
+ import { enrichSessionsWithContext } from '../session_context.js';
6
+ import { AdminUsersService } from './admin_users_service.js';
7
+ import { apiError, grantDto, sessionDto, userDto } from './dto.js';
8
8
  const PAGE_SIZE = 20;
9
9
  /**
10
10
  * Lê a config + monta o actor `admin-api` para auditoria. O `actorId` recebe o
@@ -13,12 +13,12 @@ const PAGE_SIZE = 20;
13
13
  * vazar o segredo. `null` apenas se a guard não anexou (defensivo).
14
14
  */
15
15
  async function ctxBits(ctx) {
16
- const service = await ctx.containerResolver.make("authkit.server");
16
+ const service = await ctx.containerResolver.make('authkit.server');
17
17
  const cfg = service.config;
18
18
  const actor = {
19
19
  actorId: ctx.adminApiKeyId ?? null,
20
20
  ip: ctx.request.ip?.() ?? null,
21
- source: "admin-api",
21
+ source: 'admin-api',
22
22
  };
23
23
  return { service, cfg, actor };
24
24
  }
@@ -30,10 +30,9 @@ export default class ApiUsersController {
30
30
  /** GET /users — listagem paginada com busca por e-mail. */
31
31
  async index(ctx) {
32
32
  const { cfg } = await ctxBits(ctx);
33
- const search = ctx.request.input("search", "").trim();
34
- const page = Math.max(1, Number.parseInt(ctx.request.input("page", "1"), 10) || 1);
35
- const limit = Math.max(1, Math.min(100, Number.parseInt(ctx.request.input("limit", String(PAGE_SIZE)), 10) ||
36
- PAGE_SIZE));
33
+ const search = ctx.request.input('search', '').trim();
34
+ const page = Math.max(1, Number.parseInt(ctx.request.input('page', '1'), 10) || 1);
35
+ const limit = Math.max(1, Math.min(100, Number.parseInt(ctx.request.input('limit', String(PAGE_SIZE)), 10) || PAGE_SIZE));
37
36
  const result = await cfg.accountStore.listAccounts({ search, page, limit });
38
37
  const users = new AdminUsersService(cfg);
39
38
  const data = await Promise.all(result.data.map(async (u) => userDto(u, await users.isDisabled(u.id))));
@@ -42,10 +41,10 @@ export default class ApiUsersController {
42
41
  /** GET /users/:id */
43
42
  async show(ctx) {
44
43
  const { cfg } = await ctxBits(ctx);
45
- const id = ctx.request.param("id");
44
+ const id = ctx.request.param('id');
46
45
  const account = await cfg.accountStore.findById(id);
47
46
  if (!account)
48
- return ctx.response.notFound(apiError("not_found", "Usuário não encontrado."));
47
+ return ctx.response.notFound(apiError('not_found', 'Usuário não encontrado.'));
49
48
  const disabled = await new AdminUsersService(cfg).isDisabled(id);
50
49
  return userDto(account, disabled);
51
50
  }
@@ -61,10 +60,10 @@ export default class ApiUsersController {
61
60
  invite: invite ?? false,
62
61
  }, actor);
63
62
  if (!result.ok) {
64
- if (result.reason === "password_policy") {
65
- return ctx.response.badRequest(apiError("password_policy", cfg.messages[result.messageKey] ?? result.messageKey));
63
+ if (result.reason === 'password_policy') {
64
+ return ctx.response.badRequest(apiError('password_policy', cfg.messages[result.messageKey] ?? result.messageKey));
66
65
  }
67
- return ctx.response.conflict(apiError("email_taken", "Já existe uma conta com este e-mail."));
66
+ return ctx.response.conflict(apiError('email_taken', 'Já existe uma conta com este e-mail.'));
68
67
  }
69
68
  ctx.response.status(201);
70
69
  return { ...userDto(result.account), invited: result.invited };
@@ -72,10 +71,10 @@ export default class ApiUsersController {
72
71
  /** PATCH /users/:id — { globalRoles?, name?, avatarUrl? }. */
73
72
  async update(ctx) {
74
73
  const { cfg, actor } = await ctxBits(ctx);
75
- const id = ctx.request.param("id");
74
+ const id = ctx.request.param('id');
76
75
  const account = await cfg.accountStore.findById(id);
77
76
  if (!account)
78
- return ctx.response.notFound(apiError("not_found", "Usuário não encontrado."));
77
+ return ctx.response.notFound(apiError('not_found', 'Usuário não encontrado.'));
79
78
  const users = new AdminUsersService(cfg);
80
79
  const { globalRoles: roles, name, avatarUrl, } = await ctx.request.validateUsing(adminUserUpdateValidator);
81
80
  if (Array.isArray(roles)) {
@@ -83,11 +82,11 @@ export default class ApiUsersController {
83
82
  // Proteções de escalonamento/lockout: último admin + auto-rebaixamento.
84
83
  // O actorId vem do id da API key (M9); self-demote só aplica se identificável.
85
84
  const guard = await users.guardGlobalRolesChange(id, normalized, actor.actorId);
86
- if (guard === "last_admin") {
87
- return ctx.response.conflict(apiError("last_admin", "Não é possível remover a última conta com a role de administrador."));
85
+ if (guard === 'last_admin') {
86
+ return ctx.response.conflict(apiError('last_admin', 'Não é possível remover a última conta com a role de administrador.'));
88
87
  }
89
- if (guard === "cannot_self_demote") {
90
- return ctx.response.conflict(apiError("cannot_self_demote", "Você não pode remover a sua própria role de administrador."));
88
+ if (guard === 'cannot_self_demote') {
89
+ return ctx.response.conflict(apiError('cannot_self_demote', 'Você não pode remover a sua própria role de administrador.'));
91
90
  }
92
91
  // Valida contra o catálogo (mesmo caminho do console) — roles fora do
93
92
  // catálogo (e que o usuário não tinha) → 422.
@@ -96,7 +95,7 @@ export default class ApiUsersController {
96
95
  if (errorKey) {
97
96
  return ctx.response
98
97
  .status(422)
99
- .send(apiError("invalid_role", cfg.messages[errorKey] ?? errorKey));
98
+ .send(apiError('invalid_role', cfg.messages[errorKey] ?? errorKey));
100
99
  }
101
100
  }
102
101
  if (name !== undefined || avatarUrl !== undefined) {
@@ -113,20 +112,20 @@ export default class ApiUsersController {
113
112
  /** DELETE /users/:id — deleção completa (cascade) da conta. */
114
113
  async destroy(ctx) {
115
114
  const { service, cfg, actor } = await ctxBits(ctx);
116
- const id = ctx.request.param("id");
115
+ const id = ctx.request.param('id');
117
116
  // OPT-IN durável: passa o enqueue (cascade async); senão delete() roda síncrono.
118
117
  const enqueue = cfg.accountLifecycle?.durable
119
- ? (await import("../durable/index.js")).enqueueDeletionVia(ctx.containerResolver)
118
+ ? (await import('../durable/index.js')).enqueueDeletionVia(ctx.containerResolver)
120
119
  : undefined;
121
120
  const outcome = await new AdminUsersService(cfg).delete(service, id, actor, enqueue);
122
121
  if (!outcome.ok) {
123
- if (outcome.reason === "not_found") {
124
- return ctx.response.notFound(apiError("not_found", "Usuário não encontrado."));
122
+ if (outcome.reason === 'not_found') {
123
+ return ctx.response.notFound(apiError('not_found', 'Usuário não encontrado.'));
125
124
  }
126
- return ctx.response.conflict(apiError("capability_unsupported", "O store de contas não suporta deletar usuários."));
125
+ return ctx.response.conflict(apiError('capability_unsupported', 'O store de contas não suporta deletar usuários.'));
127
126
  }
128
127
  // Modo durável: a deleção foi ENFILEIRADA (cascade async) — responde 202-style.
129
- if ("enqueued" in outcome) {
128
+ if ('enqueued' in outcome) {
130
129
  return { id, deleted: true, enqueued: true, runId: outcome.runId };
131
130
  }
132
131
  return { id, deleted: true, ...outcome.result };
@@ -141,26 +140,26 @@ export default class ApiUsersController {
141
140
  }
142
141
  async #setStatus(ctx, disable) {
143
142
  const { cfg, actor } = await ctxBits(ctx);
144
- const id = ctx.request.param("id");
143
+ const id = ctx.request.param('id');
145
144
  const applied = await new AdminUsersService(cfg).setStatus(id, disable, actor);
146
145
  if (!applied) {
147
- return ctx.response.conflict(apiError("capability_unsupported", "O store de contas não suporta habilitar/desabilitar."));
146
+ return ctx.response.conflict(apiError('capability_unsupported', 'O store de contas não suporta habilitar/desabilitar.'));
148
147
  }
149
148
  return { id, disabled: disable };
150
149
  }
151
150
  /** POST /users/:id/reset-password — envia o e-mail de reset. */
152
151
  async resetPassword(ctx) {
153
152
  const { cfg, actor } = await ctxBits(ctx);
154
- const id = ctx.request.param("id");
153
+ const id = ctx.request.param('id');
155
154
  const account = await new AdminUsersService(cfg).resetPassword(ctx, id, actor);
156
155
  if (!account)
157
- return ctx.response.notFound(apiError("not_found", "Usuário não encontrado."));
156
+ return ctx.response.notFound(apiError('not_found', 'Usuário não encontrado.'));
158
157
  return { id, sent: true };
159
158
  }
160
159
  /** GET /users/:id/sessions — sessões (enriquecidas com contexto) + grants ativos. */
161
160
  async sessions(ctx) {
162
161
  const { service, cfg } = await ctxBits(ctx);
163
- const id = ctx.request.param("id");
162
+ const id = ctx.request.param('id');
164
163
  const admin = new AdminSessionsService(service);
165
164
  const sessions = await enrichSessionsWithContext(cfg, id, await admin.listSessions(id));
166
165
  const grants = await admin.listGrants(id);
@@ -173,10 +172,10 @@ export default class ApiUsersController {
173
172
  /** POST /users/:id/revoke-sessions — revoga todas as sessões/grants. */
174
173
  async revokeSessions(ctx) {
175
174
  const { service, cfg, actor } = await ctxBits(ctx);
176
- const id = ctx.request.param("id");
175
+ const id = ctx.request.param('id');
177
176
  const result = await new AdminSessionsService(service).revokeAll(id);
178
177
  await cfg.audit?.record({
179
- type: "session.revoked_all",
178
+ type: 'session.revoked_all',
180
179
  accountId: id,
181
180
  actorId: actor.actorId,
182
181
  ip: actor.ip,
@@ -1,9 +1,9 @@
1
1
  import type { AuthAccount } from '../../accounts/account_store.js';
2
- import type { OrgSummary, OrgMember, OrgInvitation } from '../../accounts/account_store.js';
3
- import type { OrgDetail } from './admin_orgs_service.js';
4
- import type { AdminClient, CreatedClient } from '../admin_clients_service.js';
5
- import type { AdminSession, AdminGrant } from '../admin_sessions_service.js';
2
+ import type { OrgInvitation, OrgMember, OrgSummary } from '../../accounts/account_store.js';
6
3
  import type { StoredAuditEvent } from '../../audit/audit_sink.js';
4
+ import type { AdminClient, CreatedClient } from '../admin_clients_service.js';
5
+ import type { AdminGrant, AdminSession } from '../admin_sessions_service.js';
6
+ import type { OrgDetail } from './admin_orgs_service.js';
7
7
  /** Projeta uma conta para a forma JSON (camelCase) da Admin REST API. */
8
8
  export declare function userDto(account: AuthAccount, disabled?: boolean): {
9
9
  id: string;