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