@adonis-agora/authkit-server 0.47.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.
Files changed (190) 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 +22 -7
  15. package/build/index.js +18 -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/account_screen_props.d.ts +163 -0
  53. package/build/src/host/account_screen_props.js +23 -0
  54. package/build/src/host/admin_api/admin_orgs_service.d.ts +2 -2
  55. package/build/src/host/admin_api/admin_orgs_service.js +3 -2
  56. package/build/src/host/admin_api/admin_users_service.d.ts +12 -12
  57. package/build/src/host/admin_api/admin_users_service.js +20 -20
  58. package/build/src/host/admin_api/api_clients_controller.js +2 -2
  59. package/build/src/host/admin_api/api_misc_controller.js +2 -2
  60. package/build/src/host/admin_api/api_orgs_controller.js +3 -3
  61. package/build/src/host/admin_api/api_settings_controller.js +11 -5
  62. package/build/src/host/admin_api/api_users_controller.d.ts +2 -2
  63. package/build/src/host/admin_api/api_users_controller.js +37 -38
  64. package/build/src/host/admin_api/dto.d.ts +4 -4
  65. package/build/src/host/admin_clients_service.js +5 -4
  66. package/build/src/host/admin_console/admin_shell_controller.js +4 -2
  67. package/build/src/host/admin_console/console_audit_controller.js +1 -1
  68. package/build/src/host/admin_console/console_clients_controller.js +5 -3
  69. package/build/src/host/admin_console/console_impersonation_controller.js +1 -1
  70. package/build/src/host/admin_console/console_keys_controller.js +2 -4
  71. package/build/src/host/admin_console/console_orgs_controller.js +3 -3
  72. package/build/src/host/admin_console/console_roles_controller.js +30 -12
  73. package/build/src/host/admin_console/console_sessions_controller.js +3 -3
  74. package/build/src/host/admin_console/console_settings_controller.js +11 -5
  75. package/build/src/host/admin_console/console_users_controller.d.ts +2 -2
  76. package/build/src/host/admin_console/console_users_controller.js +46 -47
  77. package/build/src/host/admin_prefix.js +2 -2
  78. package/build/src/host/admin_sessions_service.js +1 -3
  79. package/build/src/host/adonis_auth_sync.d.ts +1 -1
  80. package/build/src/host/adonis_auth_user_provider.d.ts +1 -1
  81. package/build/src/host/app_key.js +1 -1
  82. package/build/src/host/avatar_storage.js +1 -3
  83. package/build/src/host/config_locks.js +1 -3
  84. package/build/src/host/console_session.js +1 -1
  85. package/build/src/host/controllers/account_apps_controller.js +3 -2
  86. package/build/src/host/controllers/account_confirm_controller.js +5 -8
  87. package/build/src/host/controllers/account_mfa_controller.js +72 -18
  88. package/build/src/host/controllers/account_orgs_controller.js +47 -27
  89. package/build/src/host/controllers/account_security_controller.d.ts +2 -2
  90. package/build/src/host/controllers/account_security_controller.js +116 -116
  91. package/build/src/host/controllers/account_session_controller.js +13 -8
  92. package/build/src/host/controllers/account_tokens_controller.js +3 -2
  93. package/build/src/host/controllers/interaction_controller.js +59 -22
  94. package/build/src/host/controllers/registration_controller.js +28 -13
  95. package/build/src/host/default_mailer.js +11 -7
  96. package/build/src/host/durable/account_deletion_workflow.d.ts +2 -2
  97. package/build/src/host/durable/account_deletion_workflow.js +14 -14
  98. package/build/src/host/durable/account_export_workflow.d.ts +2 -2
  99. package/build/src/host/durable/account_export_workflow.js +12 -12
  100. package/build/src/host/durable/index.d.ts +5 -5
  101. package/build/src/host/durable/index.js +6 -6
  102. package/build/src/host/i18n.d.ts +1376 -1376
  103. package/build/src/host/i18n.js +1380 -1380
  104. package/build/src/host/impersonation.js +1 -7
  105. package/build/src/host/key_rotation_actions.d.ts +1 -1
  106. package/build/src/host/login_attempt.js +35 -6
  107. package/build/src/host/login_notify.js +1 -1
  108. package/build/src/host/otp_lockout.js +1 -3
  109. package/build/src/host/rate_limit.js +1 -1
  110. package/build/src/host/register_auth_host.d.ts +30 -0
  111. package/build/src/host/register_auth_host.js +120 -74
  112. package/build/src/host/renderers/edge_renderer.js +15 -2
  113. package/build/src/host/renderers/inertia_renderer.d.ts +23 -58
  114. package/build/src/host/renderers/inertia_renderer.js +23 -58
  115. package/build/src/host/runtime_settings.js +1 -1
  116. package/build/src/host/runtime_toggles.d.ts +3 -3
  117. package/build/src/host/runtime_toggles.js +69 -24
  118. package/build/src/host/security_notice_service.d.ts +1 -1
  119. package/build/src/host/security_notice_service.js +1 -1
  120. package/build/src/host/session_context.js +1 -1
  121. package/build/src/host/sudo/index.d.ts +3 -3
  122. package/build/src/host/sudo/index.js +3 -3
  123. package/build/src/host/sudo/methods/magic_link.js +6 -5
  124. package/build/src/host/sudo/methods/passkey.js +7 -4
  125. package/build/src/host/sudo/methods/password.js +8 -3
  126. package/build/src/host/sudo/runtime.js +6 -13
  127. package/build/src/host/sudo_mode.js +3 -2
  128. package/build/src/host/ui-dist/assets/index-BQoTDiLV.js +137 -0
  129. package/build/src/host/ui-dist/index.html +1 -1
  130. package/build/src/keys/jwks_manager.js +1 -1
  131. package/build/src/keys/keystore.js +1 -1
  132. package/build/src/keys/keystore_manager.d.ts +2 -2
  133. package/build/src/keys/keystore_manager.js +2 -2
  134. package/build/src/keys/keystore_vault.js +8 -4
  135. package/build/src/mixins/with_audit_log.d.ts +1 -1
  136. package/build/src/mixins/with_auth_user.d.ts +1 -1
  137. package/build/src/mixins/with_auth_user.js +1 -1
  138. package/build/src/mixins/with_credentials.d.ts +1 -1
  139. package/build/src/mixins/with_mfa.d.ts +1 -1
  140. package/build/src/mixins/with_personal_access_token.d.ts +1 -1
  141. package/build/src/mixins/with_provider_identity.d.ts +1 -1
  142. package/build/src/mixins/with_webauthn_credential.d.ts +1 -1
  143. package/build/src/observability/diagnostics_bridge.js +2 -2
  144. package/build/src/observability/metrics_service.js +1 -1
  145. package/build/src/observability/telescope/data_providers.d.ts +1 -1
  146. package/build/src/observability/telescope/data_providers.js +29 -43
  147. package/build/src/observability/telescope/extension.d.ts +1 -1
  148. package/build/src/observability/telescope/extension.js +58 -58
  149. package/build/src/observability/telescope/index.d.ts +2 -2
  150. package/build/src/observability/telescope/index.js +2 -2
  151. package/build/src/observability/wire_provider_events.d.ts +1 -1
  152. package/build/src/observability/wire_provider_events.js +11 -11
  153. package/build/src/password/common_passwords.js +1 -1
  154. package/build/src/password/password_manager.d.ts +1 -1
  155. package/build/src/password/password_manager.js +4 -4
  156. package/build/src/pat/lucid_pat_store.js +2 -6
  157. package/build/src/pat/pat_tokens.js +1 -1
  158. package/build/src/provider/interaction_actions.js +1 -1
  159. package/build/src/provider/key_rotation_scheduler.js +3 -1
  160. package/build/src/provider/logout_sources.js +2 -8
  161. package/build/src/provider/oidc_service.d.ts +2 -2
  162. package/build/src/provider/oidc_service.js +22 -14
  163. package/build/src/provider/single_flight_lock.js +1 -3
  164. package/build/src/provider/token_exchange.d.ts +1 -1
  165. package/build/src/schema/ensure.js +3 -1
  166. package/build/stubs/ui/react/components/auth_shell.tsx +19 -19
  167. package/build/stubs/ui/react/pages/account/login.tsx +6 -7
  168. package/build/stubs/ui/react/pages/account/mfa.tsx +16 -15
  169. package/build/stubs/ui/react/pages/account/tokens.tsx +27 -17
  170. package/build/stubs/ui/react/pages/consent.tsx +12 -12
  171. package/build/stubs/ui/react/pages/forgot.tsx +7 -7
  172. package/build/stubs/ui/react/pages/login.tsx +22 -24
  173. package/build/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
  174. package/build/stubs/ui/react/pages/reset.tsx +10 -10
  175. package/build/stubs/ui/react/pages/signup.tsx +30 -16
  176. package/build/stubs/ui/react/pages/verify-email.tsx +6 -6
  177. package/package.json +2 -2
  178. package/stubs/main.ts +2 -2
  179. package/stubs/ui/react/components/auth_shell.tsx +19 -19
  180. package/stubs/ui/react/pages/account/login.tsx +6 -7
  181. package/stubs/ui/react/pages/account/mfa.tsx +16 -15
  182. package/stubs/ui/react/pages/account/tokens.tsx +27 -17
  183. package/stubs/ui/react/pages/consent.tsx +12 -12
  184. package/stubs/ui/react/pages/forgot.tsx +7 -7
  185. package/stubs/ui/react/pages/login.tsx +22 -24
  186. package/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
  187. package/stubs/ui/react/pages/reset.tsx +10 -10
  188. package/stubs/ui/react/pages/signup.tsx +30 -16
  189. package/stubs/ui/react/pages/verify-email.tsx +6 -6
  190. package/build/src/host/ui-dist/assets/index-BmI_J87g.js +0 -137
@@ -1,22 +1,23 @@
1
- import "../augmentations.js";
2
- import { ACCOUNT_SESSION_KEY } from "../middleware/account_auth.js";
3
- import { getAccountLoginUrl } from "../account_login_url.js";
4
- import { supportsAccountSecurity, supportsAccountDeletion, supportsProfile, supportsPasswordHistory, } from "../../accounts/account_store.js";
5
- import { changePasswordValidator, changeEmailValidator, deleteAccountValidator, updateProfileValidator, } from "../validators.js";
6
- import { sendEmailChangeConfirmationEmail, sendEmailChangeNoticeEmail, sendEmailChangedCompletedEmail, } from "../default_mailer.js";
7
- import { storeAvatar, isAvatarUploadSupported, AvatarUploadError, } from "../avatar_storage.js";
8
- import { translate } from "../i18n.js";
9
- import { TRUSTED_DEVICE_COOKIE } from "../trusted_device.js";
10
- import { AccountDeletionService } from "../account_deletion_service.js";
11
- import { AccountExportService } from "../account_export_service.js";
12
- import { AdminSessionsService } from "../admin_sessions_service.js";
13
- import { enrichSessionsWithContext } from "../session_context.js";
14
- import { PasswordPolicyError } from "../../password/password_manager.js";
15
- import { resolveRuntimeSettings } from "../runtime_settings.js";
16
- import { resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from "../runtime_toggles.js";
17
- import { dispatchSecurityNotice } from "../security_notice_service.js";
18
- import { requireSudo } from "../sudo_mode.js";
19
- import { syncAdonisAuthLogout } from "../adonis_auth_sync.js";
1
+ import '../augmentations.js';
2
+ import { supportsAccountDeletion, supportsAccountSecurity, supportsPasswordHistory, supportsProfile, } from '../../accounts/account_store.js';
3
+ import { PasswordPolicyError } from '../../password/password_manager.js';
4
+ import { AccountDeletionService } from '../account_deletion_service.js';
5
+ import { AccountExportService } from '../account_export_service.js';
6
+ import { getAccountLoginUrl } from '../account_login_url.js';
7
+ import { accountPath } from '../account_paths.js';
8
+ import { AdminSessionsService } from '../admin_sessions_service.js';
9
+ import { syncAdonisAuthLogout } from '../adonis_auth_sync.js';
10
+ import { AvatarUploadError, isAvatarUploadSupported, storeAvatar } from '../avatar_storage.js';
11
+ import { sendEmailChangeConfirmationEmail, sendEmailChangeNoticeEmail, sendEmailChangedCompletedEmail, } from '../default_mailer.js';
12
+ import { translate } from '../i18n.js';
13
+ import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
14
+ import { resolveRuntimeSettings } from '../runtime_settings.js';
15
+ import { resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from '../runtime_toggles.js';
16
+ import { dispatchSecurityNotice } from '../security_notice_service.js';
17
+ import { enrichSessionsWithContext } from '../session_context.js';
18
+ import { requireSudo } from '../sudo_mode.js';
19
+ import { TRUSTED_DEVICE_COOKIE } from '../trusted_device.js';
20
+ import { changeEmailValidator, changePasswordValidator, deleteAccountValidator, updateProfileValidator, } from '../validators.js';
20
21
  /** Resolve os password history settings em runtime (fail-safe). */
21
22
  async function resolvePasswordHistorySettings(ctx) {
22
23
  try {
@@ -52,7 +53,7 @@ async function resolveEmailChangeSettings(ctx) {
52
53
  */
53
54
  export default class AccountSecurityController {
54
55
  async index(ctx) {
55
- const service = await ctx.containerResolver.make("authkit.server");
56
+ const service = await ctx.containerResolver.make('authkit.server');
56
57
  const cfg = service.config;
57
58
  const render = cfg.render;
58
59
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
@@ -64,36 +65,37 @@ export default class AccountSecurityController {
64
65
  const ownSessions = sessionsSupported
65
66
  ? await enrichSessionsWithContext(cfg, userId, await adminSessions.listSessions(userId))
66
67
  : [];
67
- return render(ctx, "account/security", {
68
+ const props = {
68
69
  csrfToken: ctx.request.csrfToken,
69
70
  supported: supportsAccountSecurity(cfg.accountStore),
70
71
  profileSupported: supportsProfile(cfg.accountStore),
71
72
  // Só mostramos o input de arquivo se ALGUM backend (drive OU media) puder armazenar.
72
73
  avatarUploadSupported: await isAvatarUploadSupported(cfg.uploads),
73
- email: account?.email ?? "",
74
- name: account?.name ?? "",
75
- avatarUrl: account?.avatarUrl ?? "",
76
- passwordChanged: ctx.session.flashMessages.get("passwordChanged") ?? null,
77
- emailChangeRequested: ctx.session.flashMessages.get("emailChangeRequested") ?? null,
78
- emailChanged: ctx.session.flashMessages.get("emailChanged") ?? null,
79
- profileUpdated: ctx.session.flashMessages.get("profileUpdated") ?? null,
80
- error: ctx.session.flashMessages.get("securityError") ?? null,
74
+ email: account?.email ?? '',
75
+ name: account?.name ?? '',
76
+ avatarUrl: account?.avatarUrl ?? '',
77
+ passwordChanged: ctx.session.flashMessages.get('passwordChanged') ?? null,
78
+ emailChangeRequested: ctx.session.flashMessages.get('emailChangeRequested') ?? null,
79
+ emailChanged: ctx.session.flashMessages.get('emailChanged') ?? null,
80
+ profileUpdated: ctx.session.flashMessages.get('profileUpdated') ?? null,
81
+ error: ctx.session.flashMessages.get('securityError') ?? null,
81
82
  trustedDevicesEnabled: cfg.trustedDevices.enabled,
82
- trustedDevicesRevoked: ctx.session.flashMessages.get("trustedDevicesRevoked") ?? null,
83
+ trustedDevicesRevoked: ctx.session.flashMessages.get('trustedDevicesRevoked') ?? null,
83
84
  sessionsSupported,
84
85
  sessions: ownSessions.map((s) => ({
85
- loginTs: s.loginTs ? new Date(s.loginTs * 1000).toISOString() : "",
86
- browser: s.browser ?? "",
87
- os: s.os ?? "",
88
- ip: s.ip ?? "",
89
- location: s.location ?? "",
86
+ loginTs: s.loginTs ? new Date(s.loginTs * 1000).toISOString() : '',
87
+ browser: s.browser ?? '',
88
+ os: s.os ?? '',
89
+ ip: s.ip ?? '',
90
+ location: s.location ?? '',
90
91
  })),
91
92
  // Export de dados (portabilidade) sempre disponível para a conta logada.
92
93
  exportSupported: true,
93
94
  // Deleção de conta (LGPD): só quando o store suporta hard delete.
94
95
  deletionSupported: supportsAccountDeletion(cfg.accountStore),
95
- deleteError: ctx.session.flashMessages.get("deleteError") ?? null,
96
- });
96
+ deleteError: ctx.session.flashMessages.get('deleteError') ?? null,
97
+ };
98
+ return render(ctx, 'account/security', props);
97
99
  }
98
100
  /**
99
101
  * GET /account/security/export — baixa um JSON com os dados da conta logada
@@ -101,20 +103,20 @@ export default class AccountSecurityController {
101
103
  * NUNCA inclui segredos. Audita `account.exported`.
102
104
  */
103
105
  async exportData(ctx) {
104
- const service = await ctx.containerResolver.make("authkit.server");
106
+ const service = await ctx.containerResolver.make('authkit.server');
105
107
  const cfg = service.config;
106
108
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
107
109
  if (cfg.accountLifecycle?.durable) {
108
110
  // OPT-IN durável: enfileira o workflow de export (coleta + persiste artefato
109
111
  // + entrega async). Isolado no subpath durável — só carregado neste ramo.
110
- const { resolveWorkflowEngine, enqueueAccountExport } = await import("../durable/index.js");
112
+ const { resolveWorkflowEngine, enqueueAccountExport } = await import('../durable/index.js');
111
113
  const engine = await resolveWorkflowEngine(ctx.containerResolver);
112
114
  await enqueueAccountExport(engine, {
113
115
  accountId: userId,
114
116
  ip: ctx.request.ip?.() ?? null,
115
117
  });
116
- ctx.session.flash("exportRequested", translate(cfg.messages, "account.export.requested"));
117
- return ctx.response.redirect("/account/security");
118
+ ctx.session.flash('exportRequested', translate(cfg.messages, 'account.export.requested'));
119
+ return ctx.response.redirect(accountPath('security'));
118
120
  }
119
121
  // Caminho SÍNCRONO de sempre (byte-idêntico): download inline do JSON.
120
122
  const payload = await new AccountExportService(service).export(userId);
@@ -122,12 +124,12 @@ export default class AccountSecurityController {
122
124
  return ctx.response.notFound();
123
125
  }
124
126
  await cfg.audit?.record({
125
- type: "account.exported",
127
+ type: 'account.exported',
126
128
  accountId: userId,
127
129
  ip: ctx.request.ip?.() ?? null,
128
130
  });
129
- ctx.response.header("content-type", "application/json; charset=utf-8");
130
- ctx.response.header("content-disposition", `attachment; filename="authkit-data-export-${userId}.json"`);
131
+ ctx.response.header('content-type', 'application/json; charset=utf-8');
132
+ ctx.response.header('content-disposition', `attachment; filename="authkit-data-export-${userId}.json"`);
131
133
  return ctx.response.send(JSON.stringify(payload, null, 2));
132
134
  }
133
135
  /**
@@ -137,12 +139,12 @@ export default class AccountSecurityController {
137
139
  * completo e encerra a sessão.
138
140
  */
139
141
  async deleteAccount(ctx) {
140
- const service = await ctx.containerResolver.make("authkit.server");
142
+ const service = await ctx.containerResolver.make('authkit.server');
141
143
  const cfg = service.config;
142
144
  const store = cfg.accountStore;
143
145
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
144
146
  if (!supportsAccountDeletion(store)) {
145
- return ctx.response.redirect("/account/security");
147
+ return ctx.response.redirect(accountPath('security'));
146
148
  }
147
149
  // Sudo mode gate (defesa em profundidade — confirmação inline continua abaixo).
148
150
  const sudoSettingsDel = await resolveRuntimeSettings(ctx);
@@ -161,25 +163,24 @@ export default class AccountSecurityController {
161
163
  confirmed = !!(await store.verifyCredentials(account.email, currentPassword));
162
164
  }
163
165
  if (!confirmed && confirmEmail) {
164
- confirmed =
165
- confirmEmail.trim().toLowerCase() === account.email.toLowerCase();
166
+ confirmed = confirmEmail.trim().toLowerCase() === account.email.toLowerCase();
166
167
  }
167
168
  if (!confirmed) {
168
- ctx.session.flash("deleteError", translate(cfg.messages, "account.delete.invalid_confirmation"));
169
- return ctx.response.redirect("/account/security");
169
+ ctx.session.flash('deleteError', translate(cfg.messages, 'account.delete.invalid_confirmation'));
170
+ return ctx.response.redirect(accountPath('security'));
170
171
  }
171
172
  const actor = {
172
173
  actorId: userId,
173
174
  ip: ctx.request.ip?.() ?? null,
174
- source: "self",
175
+ source: 'self',
175
176
  };
176
177
  if (cfg.accountLifecycle?.durable) {
177
178
  // OPT-IN durável (self-service): logout IMEDIATO (revoga as sessões/grants
178
179
  // OIDC do ator de forma síncrona) + enfileira o resto do cascade async.
179
180
  // Isolado no subpath durável — só carregado neste ramo.
180
- const { resolveWorkflowEngine, enqueueAccountDeletion } = await import("../durable/index.js");
181
+ const { resolveWorkflowEngine, enqueueAccountDeletion } = await import('../durable/index.js');
181
182
  try {
182
- const { revokeSessions } = await import("../account_deletion_ops.js");
183
+ const { revokeSessions } = await import('../account_deletion_ops.js');
183
184
  await revokeSessions(service, userId);
184
185
  }
185
186
  catch {
@@ -196,7 +197,7 @@ export default class AccountSecurityController {
196
197
  // Encerra a sessão e leva ao login com a mensagem de sucesso.
197
198
  ctx.session.forget(ACCOUNT_SESSION_KEY);
198
199
  await syncAdonisAuthLogout(ctx, cfg);
199
- ctx.session.flash("accountDeleted", translate(cfg.messages, "account.delete.deleted"));
200
+ ctx.session.flash('accountDeleted', translate(cfg.messages, 'account.delete.deleted'));
200
201
  return ctx.response.redirect(getAccountLoginUrl());
201
202
  }
202
203
  /**
@@ -206,52 +207,52 @@ export default class AccountSecurityController {
206
207
  * server-side; re-enrolar o MFA invalida a confiança em TODOS os dispositivos.
207
208
  */
208
209
  async revokeTrustedDevices(ctx) {
209
- const service = await ctx.containerResolver.make("authkit.server");
210
+ const service = await ctx.containerResolver.make('authkit.server');
210
211
  const cfg = service.config;
211
212
  ctx.response.clearCookie(TRUSTED_DEVICE_COOKIE);
212
213
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
213
214
  await cfg.audit?.record({
214
- type: "trusted_device.revoked",
215
+ type: 'trusted_device.revoked',
215
216
  accountId: userId,
216
217
  ip: ctx.request.ip?.() ?? null,
217
218
  });
218
- ctx.session.flash("trustedDevicesRevoked", translate(cfg.messages, "account.security.trusted_devices_revoked"));
219
- return ctx.response.redirect("/account/security");
219
+ ctx.session.flash('trustedDevicesRevoked', translate(cfg.messages, 'account.security.trusted_devices_revoked'));
220
+ return ctx.response.redirect(accountPath('security'));
220
221
  }
221
222
  /** POST /account/security/profile — atualiza nome + avatar do próprio perfil. */
222
223
  async updateProfile(ctx) {
223
- const service = await ctx.containerResolver.make("authkit.server");
224
+ const service = await ctx.containerResolver.make('authkit.server');
224
225
  const cfg = service.config;
225
226
  const store = cfg.accountStore;
226
227
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
227
228
  if (!supportsProfile(store)) {
228
- return ctx.response.redirect("/account/security");
229
+ return ctx.response.redirect(accountPath('security'));
229
230
  }
230
231
  const { name, avatarUrl } = await ctx.request.validateUsing(updateProfileValidator);
231
232
  // Upload de avatar via o drive do app (opt-in pela presença do arquivo).
232
233
  // Se um arquivo for enviado e o drive estiver disponível, a URL resultante
233
234
  // tem prioridade sobre o input de URL; senão caímos no avatarUrl (texto).
234
235
  let resolvedAvatarUrl = avatarUrl ?? null;
235
- let via = "url";
236
- const file = ctx.request.file("avatar", {
236
+ let via = 'url';
237
+ const file = ctx.request.file('avatar', {
237
238
  size: `${cfg.uploads.avatars.maxSizeMb}mb`,
238
- extnames: ["jpg", "jpeg", "png", "webp"],
239
+ extnames: ['jpg', 'jpeg', 'png', 'webp'],
239
240
  });
240
241
  if (file) {
241
242
  try {
242
243
  const uploadedUrl = await storeAvatar(ctx, cfg.uploads, file, userId, {
243
- extname: translate(cfg.messages, "account.profile.avatar_invalid_type"),
244
- size: translate(cfg.messages, "account.profile.avatar_too_large"),
244
+ extname: translate(cfg.messages, 'account.profile.avatar_invalid_type'),
245
+ size: translate(cfg.messages, 'account.profile.avatar_too_large'),
245
246
  });
246
247
  if (uploadedUrl) {
247
248
  resolvedAvatarUrl = uploadedUrl;
248
- via = "upload";
249
+ via = 'upload';
249
250
  }
250
251
  }
251
252
  catch (error) {
252
253
  if (error instanceof AvatarUploadError) {
253
- ctx.session.flash("securityError", error.message);
254
- return ctx.response.redirect("/account/security");
254
+ ctx.session.flash('securityError', error.message);
255
+ return ctx.response.redirect(accountPath('security'));
255
256
  }
256
257
  throw error;
257
258
  }
@@ -263,21 +264,21 @@ export default class AccountSecurityController {
263
264
  avatarUrl: resolvedAvatarUrl,
264
265
  });
265
266
  await cfg.audit?.record({
266
- type: "profile.updated",
267
+ type: 'profile.updated',
267
268
  accountId: userId,
268
269
  ip: ctx.request.ip?.() ?? null,
269
270
  metadata: { via },
270
271
  });
271
- ctx.session.flash("profileUpdated", translate(cfg.messages, "account.profile.updated"));
272
- return ctx.response.redirect("/account/security");
272
+ ctx.session.flash('profileUpdated', translate(cfg.messages, 'account.profile.updated'));
273
+ return ctx.response.redirect(accountPath('security'));
273
274
  }
274
275
  async changePassword(ctx) {
275
- const service = await ctx.containerResolver.make("authkit.server");
276
+ const service = await ctx.containerResolver.make('authkit.server');
276
277
  const cfg = service.config;
277
278
  const store = cfg.accountStore;
278
279
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
279
280
  if (!supportsAccountSecurity(store)) {
280
- return ctx.response.redirect("/account/security");
281
+ return ctx.response.redirect(accountPath('security'));
281
282
  }
282
283
  // Sudo mode gate (defesa em profundidade — a verificação de senha ATUAL continua abaixo).
283
284
  const sudoSettings = await resolveRuntimeSettings(ctx);
@@ -287,12 +288,10 @@ export default class AccountSecurityController {
287
288
  const { currentPassword, newPassword } = await ctx.request.validateUsing(changePasswordValidator);
288
289
  const account = await store.findById(userId);
289
290
  // Confirma a senha ATUAL pelo e-mail da conta.
290
- const verified = account
291
- ? await store.verifyCredentials(account.email, currentPassword)
292
- : null;
291
+ const verified = account ? await store.verifyCredentials(account.email, currentPassword) : null;
293
292
  if (!verified) {
294
- ctx.session.flash("securityError", translate(cfg.messages, "errors.invalid_credentials"));
295
- return ctx.response.redirect("/account/security");
293
+ ctx.session.flash('securityError', translate(cfg.messages, 'errors.invalid_credentials'));
294
+ return ctx.response.redirect(accountPath('security'));
296
295
  }
297
296
  // Verificação de histórico de senhas (disallow_password_reuse).
298
297
  // Capability-probed: sem `isPasswordReused` ou com setting desligada → no-op.
@@ -301,13 +300,14 @@ export default class AccountSecurityController {
301
300
  if (histSettings.enabled) {
302
301
  // Aplica o pepper à senha candidata antes de comparar com os hashes históricos
303
302
  // (que foram gravados já com pepper). Pepper ausente → identidade.
304
- const pepperedNew = cfg.accountStore.__passwordManager?.applyCurrentPepper?.(newPassword) ?? newPassword;
303
+ const pepperedNew = cfg.accountStore.__passwordManager?.applyCurrentPepper?.(newPassword) ??
304
+ newPassword;
305
305
  const reused = await store.isPasswordReused(userId, pepperedNew, histSettings.count);
306
306
  if (reused) {
307
- ctx.session.flash("securityError", translate(cfg.messages, "password.reused", {
307
+ ctx.session.flash('securityError', translate(cfg.messages, 'password.reused', {
308
308
  count: histSettings.count,
309
309
  }));
310
- return ctx.response.redirect("/account/security");
310
+ return ctx.response.redirect(accountPath('security'));
311
311
  }
312
312
  // Grava o hash ATUAL no histórico antes de trocar.
313
313
  // O hash atual está na linha do DB — buscamos via store.findById + raw model.
@@ -324,13 +324,13 @@ export default class AccountSecurityController {
324
324
  catch (error) {
325
325
  // Política de senha violada → flash com a regra e volta à tela de segurança.
326
326
  if (error instanceof PasswordPolicyError) {
327
- ctx.session.flash("securityError", translate(cfg.messages, error.key, error.params));
328
- return ctx.response.redirect("/account/security");
327
+ ctx.session.flash('securityError', translate(cfg.messages, error.key, error.params));
328
+ return ctx.response.redirect(accountPath('security'));
329
329
  }
330
330
  throw error;
331
331
  }
332
332
  await cfg.audit?.record({
333
- type: "password.changed",
333
+ type: 'password.changed',
334
334
  accountId: userId,
335
335
  ip: ctx.request.ip?.() ?? null,
336
336
  });
@@ -343,7 +343,7 @@ export default class AccountSecurityController {
343
343
  const sessions = new AdminSessionsService(service);
344
344
  const result = await sessions.revokeAll(userId);
345
345
  await cfg.audit?.record({
346
- type: "session.revoked_all",
346
+ type: 'session.revoked_all',
347
347
  accountId: userId,
348
348
  actorId: userId,
349
349
  ip: ctx.request.ip?.() ?? null,
@@ -352,7 +352,7 @@ export default class AccountSecurityController {
352
352
  grants: result.grants,
353
353
  accessTokens: result.accessTokens,
354
354
  refreshTokens: result.refreshTokens,
355
- source: "password-change",
355
+ source: 'password-change',
356
356
  },
357
357
  });
358
358
  }
@@ -363,21 +363,21 @@ export default class AccountSecurityController {
363
363
  if (account) {
364
364
  await dispatchSecurityNotice(ctx, {
365
365
  account: { id: userId, email: account.email },
366
- kind: "password_changed",
366
+ kind: 'password_changed',
367
367
  ip: ctx.request.ip?.() ?? null,
368
368
  timestamp: new Date().toISOString(),
369
369
  }, cfg.mail, cfg.audit);
370
370
  }
371
- ctx.session.flash("passwordChanged", translate(cfg.messages, "account.security.password_changed"));
372
- return ctx.response.redirect("/account/security");
371
+ ctx.session.flash('passwordChanged', translate(cfg.messages, 'account.security.password_changed'));
372
+ return ctx.response.redirect(accountPath('security'));
373
373
  }
374
374
  async changeEmail(ctx) {
375
- const service = await ctx.containerResolver.make("authkit.server");
375
+ const service = await ctx.containerResolver.make('authkit.server');
376
376
  const cfg = service.config;
377
377
  const store = cfg.accountStore;
378
378
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
379
379
  if (!supportsAccountSecurity(store)) {
380
- return ctx.response.redirect("/account/security");
380
+ return ctx.response.redirect(accountPath('security'));
381
381
  }
382
382
  // Sudo mode gate.
383
383
  const sudoSettingsEmail = await resolveRuntimeSettings(ctx);
@@ -387,45 +387,45 @@ export default class AccountSecurityController {
387
387
  // Resolve os settings de troca de e-mail em runtime.
388
388
  const emailChangeSettings = await resolveEmailChangeSettings(ctx);
389
389
  if (!emailChangeSettings.enabled) {
390
- ctx.session.flash("securityError", translate(cfg.messages, "account.security.email_change_disabled"));
391
- return ctx.response.redirect("/account/security");
390
+ ctx.session.flash('securityError', translate(cfg.messages, 'account.security.email_change_disabled'));
391
+ return ctx.response.redirect(accountPath('security'));
392
392
  }
393
393
  const { currentPassword, newEmail } = await ctx.request.validateUsing(changeEmailValidator);
394
394
  const account = await store.findById(userId);
395
395
  // requirePassword: se ligado (default true), exige senha atual.
396
396
  if (emailChangeSettings.requirePassword) {
397
397
  if (!currentPassword) {
398
- ctx.session.flash("securityError", translate(cfg.messages, "errors.invalid_credentials"));
399
- return ctx.response.redirect("/account/security");
398
+ ctx.session.flash('securityError', translate(cfg.messages, 'errors.invalid_credentials'));
399
+ return ctx.response.redirect(accountPath('security'));
400
400
  }
401
401
  const verified = account
402
402
  ? await store.verifyCredentials(account.email, currentPassword)
403
403
  : null;
404
404
  if (!verified) {
405
- ctx.session.flash("securityError", translate(cfg.messages, "errors.invalid_credentials"));
406
- return ctx.response.redirect("/account/security");
405
+ ctx.session.flash('securityError', translate(cfg.messages, 'errors.invalid_credentials'));
406
+ return ctx.response.redirect(accountPath('security'));
407
407
  }
408
408
  }
409
409
  const issued = await store.requestEmailChange(userId, newEmail);
410
410
  if (!issued) {
411
- ctx.session.flash("securityError", translate(cfg.messages, "errors.email_taken"));
412
- return ctx.response.redirect("/account/security");
411
+ ctx.session.flash('securityError', translate(cfg.messages, 'errors.email_taken'));
412
+ return ctx.response.redirect(accountPath('security'));
413
413
  }
414
414
  await cfg.audit?.record({
415
- type: "email_change.requested",
415
+ type: 'email_change.requested',
416
416
  accountId: userId,
417
417
  email: newEmail,
418
418
  ip: ctx.request.ip?.() ?? null,
419
419
  });
420
420
  const origin = `${ctx.request.protocol()}://${ctx.request.host()}`;
421
- const confirmUrl = `${origin}/account/email/confirm?token=${encodeURIComponent(issued.token)}`;
421
+ const confirmUrl = `${origin}${accountPath('emailConfirm')}?token=${encodeURIComponent(issued.token)}`;
422
422
  // 1. Link de confirmação → NOVO e-mail (hook onEmailChangeConfirm > onEmailVerification > default).
423
423
  if (cfg.mail?.onEmailChangeConfirm) {
424
424
  await cfg.mail.onEmailChangeConfirm({
425
425
  email: newEmail,
426
426
  confirmUrl,
427
427
  token: issued.token,
428
- oldEmail: account?.email ?? "",
428
+ oldEmail: account?.email ?? '',
429
429
  });
430
430
  }
431
431
  else if (cfg.mail?.onEmailVerification) {
@@ -454,22 +454,22 @@ export default class AccountSecurityController {
454
454
  });
455
455
  }
456
456
  }
457
- ctx.session.flash("emailChangeRequested", translate(cfg.messages, "account.security.email_change_requested", {
457
+ ctx.session.flash('emailChangeRequested', translate(cfg.messages, 'account.security.email_change_requested', {
458
458
  email: newEmail,
459
459
  }));
460
- return ctx.response.redirect("/account/security");
460
+ return ctx.response.redirect(accountPath('security'));
461
461
  }
462
462
  /**
463
463
  * POST /account/security/email/cancel — cancela uma troca de e-mail pendente
464
464
  * (limpa o token pending sem alterar o e-mail da conta).
465
465
  */
466
466
  async cancelEmailChange(ctx) {
467
- const service = await ctx.containerResolver.make("authkit.server");
467
+ const service = await ctx.containerResolver.make('authkit.server');
468
468
  const cfg = service.config;
469
469
  const store = cfg.accountStore;
470
470
  const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
471
471
  if (!supportsAccountSecurity(store)) {
472
- return ctx.response.redirect("/account/security");
472
+ return ctx.response.redirect(accountPath('security'));
473
473
  }
474
474
  // Implementação: issuePendingToken(userId, null) limpa o token; reutilizamos
475
475
  // requestEmailChange com um valor sentinela → não, melhor usar cancelEmailChange
@@ -478,7 +478,7 @@ export default class AccountSecurityController {
478
478
  // A forma mais limpa é verificar se o store tem cancelEmailChange; se não tiver,
479
479
  // chamamos requestEmailChange com o próprio e-mail atual (limpa o pending antigo).
480
480
  const cancelFn = store.cancelEmailChange;
481
- if (typeof cancelFn === "function") {
481
+ if (typeof cancelFn === 'function') {
482
482
  await cancelFn.call(store, userId);
483
483
  }
484
484
  else {
@@ -492,27 +492,27 @@ export default class AccountSecurityController {
492
492
  }
493
493
  }
494
494
  await cfg.audit?.record({
495
- type: "email_change.cancelled",
495
+ type: 'email_change.cancelled',
496
496
  accountId: userId,
497
497
  ip: ctx.request.ip?.() ?? null,
498
498
  });
499
- ctx.session.flash("emailChangeRequested", translate(cfg.messages, "account.security.email_change_cancelled"));
500
- return ctx.response.redirect("/account/security");
499
+ ctx.session.flash('emailChangeRequested', translate(cfg.messages, 'account.security.email_change_cancelled'));
500
+ return ctx.response.redirect(accountPath('security'));
501
501
  }
502
502
  /** GET /account/email/confirm?token=... — consome o token e aplica o novo e-mail. */
503
503
  async confirmEmail(ctx) {
504
- const service = await ctx.containerResolver.make("authkit.server");
504
+ const service = await ctx.containerResolver.make('authkit.server');
505
505
  const cfg = service.config;
506
506
  const store = cfg.accountStore;
507
507
  const render = cfg.render;
508
508
  if (!supportsAccountSecurity(store)) {
509
- return render(ctx, "account/email-confirmed", { ok: false });
509
+ return render(ctx, 'account/email-confirmed', { ok: false });
510
510
  }
511
- const token = ctx.request.qs().token ?? "";
511
+ const token = ctx.request.qs().token ?? '';
512
512
  const result = await store.confirmEmailChange(token);
513
513
  if (result.ok) {
514
514
  await cfg.audit?.record({
515
- type: "email_change.confirmed",
515
+ type: 'email_change.confirmed',
516
516
  accountId: result.account.id,
517
517
  email: result.newEmail,
518
518
  ip: ctx.request.ip?.() ?? null,
@@ -538,12 +538,12 @@ export default class AccountSecurityController {
538
538
  // Notificação de segurança ao NOVO e-mail (email_changed) — best-effort.
539
539
  await dispatchSecurityNotice(ctx, {
540
540
  account: { id: result.account.id, email: result.newEmail },
541
- kind: "email_changed",
541
+ kind: 'email_changed',
542
542
  ip: ctx.request.ip?.() ?? null,
543
543
  timestamp: new Date().toISOString(),
544
544
  metadata: { oldEmail: result.oldEmail, newEmail: result.newEmail },
545
545
  }, cfg.mail, cfg.audit);
546
546
  }
547
- return render(ctx, "account/email-confirmed", { ok: result.ok });
547
+ return render(ctx, 'account/email-confirmed', { ok: result.ok });
548
548
  }
549
549
  }
@@ -1,13 +1,13 @@
1
1
  import '../augmentations.js';
2
- import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
2
+ import { accountHome } from '../account_home.js';
3
+ import { getAccountLoginUrl } from '../account_login_url.js';
4
+ import { syncAdonisAuthLogin, syncAdonisAuthLogout } from '../adonis_auth_sync.js';
3
5
  import { translate } from '../i18n.js';
4
6
  import { attemptPasswordLogin } from '../login_attempt.js';
5
7
  import { notifyLoginSuccess } from '../login_notify.js';
6
- import { markSudo } from '../sudo_mode.js';
7
- import { accountHome } from '../account_home.js';
8
- import { getAccountLoginUrl } from '../account_login_url.js';
8
+ import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
9
9
  import { resolveRuntimeSettings } from '../runtime_settings.js';
10
- import { syncAdonisAuthLogin, syncAdonisAuthLogout } from '../adonis_auth_sync.js';
10
+ import { markSudo } from '../sudo_mode.js';
11
11
  /**
12
12
  * Valida um valor de `return_to` recebido da query-string ou de um campo hidden.
13
13
  *
@@ -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
@@ -1,4 +1,5 @@
1
1
  import '../augmentations.js';
2
+ import { accountPath } from '../account_paths.js';
2
3
  import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
3
4
  import { resolveRuntimeSettings } from '../runtime_settings.js';
4
5
  import { requireSudo } from '../sudo_mode.js';
@@ -49,7 +50,7 @@ export default class AccountTokensController {
49
50
  ip: ctx.request.ip?.() ?? null,
50
51
  metadata: { patId: pat.id, name: pat.name },
51
52
  });
52
- return ctx.response.redirect('/account/tokens');
53
+ return ctx.response.redirect(accountPath('tokens'));
53
54
  }
54
55
  async destroy(ctx) {
55
56
  const service = await ctx.containerResolver.make('authkit.server');
@@ -73,6 +74,6 @@ export default class AccountTokensController {
73
74
  metadata: { patId },
74
75
  });
75
76
  }
76
- return ctx.response.redirect('/account/tokens');
77
+ return ctx.response.redirect(accountPath('tokens'));
77
78
  }
78
79
  }