@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.
- package/build/commands/clients_create.js +4 -4
- package/build/commands/configure.js +3 -2
- package/build/commands/doctor.js +8 -5
- package/build/commands/eject.js +1 -1
- package/build/commands/expire_scan.js +7 -5
- package/build/commands/import_users.js +1 -1
- package/build/commands/keys_rotate.js +7 -4
- package/build/host/views/account/apps.edge +5 -5
- package/build/host/views/account/login.edge +1 -1
- package/build/host/views/account/mfa.edge +7 -7
- package/build/host/views/account/orgs.edge +5 -5
- package/build/host/views/account/security.edge +10 -10
- package/build/host/views/account/tokens.edge +5 -5
- package/build/index.d.ts +22 -7
- package/build/index.js +18 -5
- package/build/providers/authkit_server_provider.d.ts +2 -2
- package/build/providers/authkit_server_provider.js +20 -11
- package/build/services/main.d.ts +1 -1
- package/build/services/main.js +2 -2
- package/build/src/accounts/lucid_account_store.d.ts +3 -3
- package/build/src/accounts/lucid_account_store.js +6 -8
- package/build/src/accounts/lucid_store/core.js +1 -1
- package/build/src/accounts/lucid_store/mfa.js +1 -1
- package/build/src/accounts/lucid_store/organizations.js +2 -4
- package/build/src/accounts/lucid_store/password_hygiene.js +1 -4
- package/build/src/accounts/lucid_store/shared.d.ts +1 -1
- package/build/src/accounts/lucid_stores.d.ts +3 -3
- package/build/src/accounts/lucid_stores.js +2 -2
- package/build/src/adapters/database_adapter.js +3 -1
- package/build/src/adapters/factory.js +1 -1
- package/build/src/adapters/redis_adapter.js +2 -1
- package/build/src/commands/expire_scan_command.js +19 -5
- package/build/src/commands/settings_commands.js +22 -5
- package/build/src/define_config.d.ts +20 -20
- package/build/src/define_config.js +62 -67
- package/build/src/doctor/checks.js +45 -20
- package/build/src/events/dispatcher.d.ts +1 -1
- package/build/src/events/dispatcher.js +10 -10
- package/build/src/host/account_api/account_api_controller.js +95 -39
- package/build/src/host/account_deletion_ops.d.ts +4 -4
- package/build/src/host/account_deletion_ops.js +5 -5
- package/build/src/host/account_deletion_service.d.ts +2 -2
- package/build/src/host/account_deletion_service.js +2 -2
- package/build/src/host/account_export_service.d.ts +1 -1
- package/build/src/host/account_export_service.js +4 -6
- package/build/src/host/account_home.d.ts +0 -8
- package/build/src/host/account_home.js +6 -4
- package/build/src/host/account_login_url.d.ts +6 -3
- package/build/src/host/account_login_url.js +16 -8
- package/build/src/host/account_paths.d.ts +120 -0
- package/build/src/host/account_paths.js +161 -0
- package/build/src/host/account_screen_props.d.ts +163 -0
- package/build/src/host/account_screen_props.js +23 -0
- package/build/src/host/admin_api/admin_orgs_service.d.ts +2 -2
- package/build/src/host/admin_api/admin_orgs_service.js +3 -2
- package/build/src/host/admin_api/admin_users_service.d.ts +12 -12
- package/build/src/host/admin_api/admin_users_service.js +20 -20
- package/build/src/host/admin_api/api_clients_controller.js +2 -2
- package/build/src/host/admin_api/api_misc_controller.js +2 -2
- package/build/src/host/admin_api/api_orgs_controller.js +3 -3
- package/build/src/host/admin_api/api_settings_controller.js +11 -5
- package/build/src/host/admin_api/api_users_controller.d.ts +2 -2
- package/build/src/host/admin_api/api_users_controller.js +37 -38
- package/build/src/host/admin_api/dto.d.ts +4 -4
- package/build/src/host/admin_clients_service.js +5 -4
- package/build/src/host/admin_console/admin_shell_controller.js +4 -2
- package/build/src/host/admin_console/console_audit_controller.js +1 -1
- package/build/src/host/admin_console/console_clients_controller.js +5 -3
- package/build/src/host/admin_console/console_impersonation_controller.js +1 -1
- package/build/src/host/admin_console/console_keys_controller.js +2 -4
- package/build/src/host/admin_console/console_orgs_controller.js +3 -3
- package/build/src/host/admin_console/console_roles_controller.js +30 -12
- package/build/src/host/admin_console/console_sessions_controller.js +3 -3
- package/build/src/host/admin_console/console_settings_controller.js +11 -5
- package/build/src/host/admin_console/console_users_controller.d.ts +2 -2
- package/build/src/host/admin_console/console_users_controller.js +46 -47
- package/build/src/host/admin_prefix.js +2 -2
- package/build/src/host/admin_sessions_service.js +1 -3
- package/build/src/host/adonis_auth_sync.d.ts +1 -1
- package/build/src/host/adonis_auth_user_provider.d.ts +1 -1
- package/build/src/host/app_key.js +1 -1
- package/build/src/host/avatar_storage.js +1 -3
- package/build/src/host/config_locks.js +1 -3
- package/build/src/host/console_session.js +1 -1
- package/build/src/host/controllers/account_apps_controller.js +3 -2
- package/build/src/host/controllers/account_confirm_controller.js +5 -8
- package/build/src/host/controllers/account_mfa_controller.js +72 -18
- package/build/src/host/controllers/account_orgs_controller.js +47 -27
- package/build/src/host/controllers/account_security_controller.d.ts +2 -2
- package/build/src/host/controllers/account_security_controller.js +116 -116
- package/build/src/host/controllers/account_session_controller.js +13 -8
- package/build/src/host/controllers/account_tokens_controller.js +3 -2
- package/build/src/host/controllers/interaction_controller.js +59 -22
- package/build/src/host/controllers/registration_controller.js +28 -13
- package/build/src/host/default_mailer.js +11 -7
- package/build/src/host/durable/account_deletion_workflow.d.ts +2 -2
- package/build/src/host/durable/account_deletion_workflow.js +14 -14
- package/build/src/host/durable/account_export_workflow.d.ts +2 -2
- package/build/src/host/durable/account_export_workflow.js +12 -12
- package/build/src/host/durable/index.d.ts +5 -5
- package/build/src/host/durable/index.js +6 -6
- package/build/src/host/i18n.d.ts +1376 -1376
- package/build/src/host/i18n.js +1380 -1380
- package/build/src/host/impersonation.js +1 -7
- package/build/src/host/key_rotation_actions.d.ts +1 -1
- package/build/src/host/login_attempt.js +35 -6
- package/build/src/host/login_notify.js +1 -1
- package/build/src/host/otp_lockout.js +1 -3
- package/build/src/host/rate_limit.js +1 -1
- package/build/src/host/register_auth_host.d.ts +30 -0
- package/build/src/host/register_auth_host.js +120 -74
- package/build/src/host/renderers/edge_renderer.js +15 -2
- package/build/src/host/renderers/inertia_renderer.d.ts +23 -58
- package/build/src/host/renderers/inertia_renderer.js +23 -58
- package/build/src/host/runtime_settings.js +1 -1
- package/build/src/host/runtime_toggles.d.ts +3 -3
- package/build/src/host/runtime_toggles.js +69 -24
- package/build/src/host/security_notice_service.d.ts +1 -1
- package/build/src/host/security_notice_service.js +1 -1
- package/build/src/host/session_context.js +1 -1
- package/build/src/host/sudo/index.d.ts +3 -3
- package/build/src/host/sudo/index.js +3 -3
- package/build/src/host/sudo/methods/magic_link.js +6 -5
- package/build/src/host/sudo/methods/passkey.js +7 -4
- package/build/src/host/sudo/methods/password.js +8 -3
- package/build/src/host/sudo/runtime.js +6 -13
- package/build/src/host/sudo_mode.js +3 -2
- package/build/src/host/ui-dist/assets/index-BQoTDiLV.js +137 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/keys/jwks_manager.js +1 -1
- package/build/src/keys/keystore.js +1 -1
- package/build/src/keys/keystore_manager.d.ts +2 -2
- package/build/src/keys/keystore_manager.js +2 -2
- package/build/src/keys/keystore_vault.js +8 -4
- package/build/src/mixins/with_audit_log.d.ts +1 -1
- package/build/src/mixins/with_auth_user.d.ts +1 -1
- package/build/src/mixins/with_auth_user.js +1 -1
- package/build/src/mixins/with_credentials.d.ts +1 -1
- package/build/src/mixins/with_mfa.d.ts +1 -1
- package/build/src/mixins/with_personal_access_token.d.ts +1 -1
- package/build/src/mixins/with_provider_identity.d.ts +1 -1
- package/build/src/mixins/with_webauthn_credential.d.ts +1 -1
- package/build/src/observability/diagnostics_bridge.js +2 -2
- package/build/src/observability/metrics_service.js +1 -1
- package/build/src/observability/telescope/data_providers.d.ts +1 -1
- package/build/src/observability/telescope/data_providers.js +29 -43
- package/build/src/observability/telescope/extension.d.ts +1 -1
- package/build/src/observability/telescope/extension.js +58 -58
- package/build/src/observability/telescope/index.d.ts +2 -2
- package/build/src/observability/telescope/index.js +2 -2
- package/build/src/observability/wire_provider_events.d.ts +1 -1
- package/build/src/observability/wire_provider_events.js +11 -11
- package/build/src/password/common_passwords.js +1 -1
- package/build/src/password/password_manager.d.ts +1 -1
- package/build/src/password/password_manager.js +4 -4
- package/build/src/pat/lucid_pat_store.js +2 -6
- package/build/src/pat/pat_tokens.js +1 -1
- package/build/src/provider/interaction_actions.js +1 -1
- package/build/src/provider/key_rotation_scheduler.js +3 -1
- package/build/src/provider/logout_sources.js +2 -8
- package/build/src/provider/oidc_service.d.ts +2 -2
- package/build/src/provider/oidc_service.js +22 -14
- package/build/src/provider/single_flight_lock.js +1 -3
- package/build/src/provider/token_exchange.d.ts +1 -1
- package/build/src/schema/ensure.js +3 -1
- package/build/stubs/ui/react/components/auth_shell.tsx +19 -19
- package/build/stubs/ui/react/pages/account/login.tsx +6 -7
- package/build/stubs/ui/react/pages/account/mfa.tsx +16 -15
- package/build/stubs/ui/react/pages/account/tokens.tsx +27 -17
- package/build/stubs/ui/react/pages/consent.tsx +12 -12
- package/build/stubs/ui/react/pages/forgot.tsx +7 -7
- package/build/stubs/ui/react/pages/login.tsx +22 -24
- package/build/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
- package/build/stubs/ui/react/pages/reset.tsx +10 -10
- package/build/stubs/ui/react/pages/signup.tsx +30 -16
- package/build/stubs/ui/react/pages/verify-email.tsx +6 -6
- package/package.json +2 -2
- package/stubs/main.ts +2 -2
- package/stubs/ui/react/components/auth_shell.tsx +19 -19
- package/stubs/ui/react/pages/account/login.tsx +6 -7
- package/stubs/ui/react/pages/account/mfa.tsx +16 -15
- package/stubs/ui/react/pages/account/tokens.tsx +27 -17
- package/stubs/ui/react/pages/consent.tsx +12 -12
- package/stubs/ui/react/pages/forgot.tsx +7 -7
- package/stubs/ui/react/pages/login.tsx +22 -24
- package/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
- package/stubs/ui/react/pages/reset.tsx +10 -10
- package/stubs/ui/react/pages/signup.tsx +30 -16
- package/stubs/ui/react/pages/verify-email.tsx +6 -6
- package/build/src/host/ui-dist/assets/index-BmI_J87g.js +0 -137
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { requireSudo } from
|
|
19
|
-
import {
|
|
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(
|
|
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
|
-
|
|
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(
|
|
77
|
-
emailChangeRequested: ctx.session.flashMessages.get(
|
|
78
|
-
emailChanged: ctx.session.flashMessages.get(
|
|
79
|
-
profileUpdated: ctx.session.flashMessages.get(
|
|
80
|
-
error: ctx.session.flashMessages.get(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
117
|
-
return ctx.response.redirect(
|
|
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:
|
|
127
|
+
type: 'account.exported',
|
|
126
128
|
accountId: userId,
|
|
127
129
|
ip: ctx.request.ip?.() ?? null,
|
|
128
130
|
});
|
|
129
|
-
ctx.response.header(
|
|
130
|
-
ctx.response.header(
|
|
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(
|
|
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(
|
|
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(
|
|
169
|
-
return ctx.response.redirect(
|
|
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:
|
|
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(
|
|
181
|
+
const { resolveWorkflowEngine, enqueueAccountDeletion } = await import('../durable/index.js');
|
|
181
182
|
try {
|
|
182
|
-
const { revokeSessions } = await import(
|
|
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(
|
|
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(
|
|
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:
|
|
215
|
+
type: 'trusted_device.revoked',
|
|
215
216
|
accountId: userId,
|
|
216
217
|
ip: ctx.request.ip?.() ?? null,
|
|
217
218
|
});
|
|
218
|
-
ctx.session.flash(
|
|
219
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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 =
|
|
236
|
-
const file = ctx.request.file(
|
|
236
|
+
let via = 'url';
|
|
237
|
+
const file = ctx.request.file('avatar', {
|
|
237
238
|
size: `${cfg.uploads.avatars.maxSizeMb}mb`,
|
|
238
|
-
extnames: [
|
|
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,
|
|
244
|
-
size: translate(cfg.messages,
|
|
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 =
|
|
249
|
+
via = 'upload';
|
|
249
250
|
}
|
|
250
251
|
}
|
|
251
252
|
catch (error) {
|
|
252
253
|
if (error instanceof AvatarUploadError) {
|
|
253
|
-
ctx.session.flash(
|
|
254
|
-
return ctx.response.redirect(
|
|
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:
|
|
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(
|
|
272
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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(
|
|
295
|
-
return ctx.response.redirect(
|
|
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) ??
|
|
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(
|
|
307
|
+
ctx.session.flash('securityError', translate(cfg.messages, 'password.reused', {
|
|
308
308
|
count: histSettings.count,
|
|
309
309
|
}));
|
|
310
|
-
return ctx.response.redirect(
|
|
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(
|
|
328
|
-
return ctx.response.redirect(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
372
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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(
|
|
391
|
-
return ctx.response.redirect(
|
|
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(
|
|
399
|
-
return ctx.response.redirect(
|
|
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(
|
|
406
|
-
return ctx.response.redirect(
|
|
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(
|
|
412
|
-
return ctx.response.redirect(
|
|
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:
|
|
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}
|
|
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(
|
|
457
|
+
ctx.session.flash('emailChangeRequested', translate(cfg.messages, 'account.security.email_change_requested', {
|
|
458
458
|
email: newEmail,
|
|
459
459
|
}));
|
|
460
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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 ===
|
|
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:
|
|
495
|
+
type: 'email_change.cancelled',
|
|
496
496
|
accountId: userId,
|
|
497
497
|
ip: ctx.request.ip?.() ?? null,
|
|
498
498
|
});
|
|
499
|
-
ctx.session.flash(
|
|
500
|
-
return ctx.response.redirect(
|
|
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(
|
|
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,
|
|
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:
|
|
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:
|
|
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,
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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('
|
|
77
|
+
return ctx.response.redirect(accountPath('tokens'));
|
|
77
78
|
}
|
|
78
79
|
}
|