@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.
- 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/host/views/otp-unlock.edge +2 -2
- package/build/index.d.ts +7 -7
- package/build/index.js +5 -5
- package/build/providers/authkit_server_provider.d.ts +2 -2
- package/build/providers/authkit_server_provider.js +26 -10
- package/build/services/booted_app.d.ts +8 -0
- package/build/services/booted_app.js +27 -0
- package/build/services/main.d.ts +8 -1
- package/build/services/main.js +10 -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 +44 -0
- package/build/src/host/account_login_url.js +58 -0
- package/build/src/host/account_paths.d.ts +120 -0
- package/build/src/host/account_paths.js +161 -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.d.ts +3 -1
- package/build/src/host/admin_sessions_service.js +36 -6
- 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.d.ts +4 -0
- package/build/src/host/console_session.js +9 -2
- package/build/src/host/controllers/account_apps_controller.js +3 -2
- package/build/src/host/controllers/account_confirm_controller.js +2 -6
- package/build/src/host/controllers/account_mfa_controller.js +63 -12
- package/build/src/host/controllers/account_orgs_controller.js +53 -30
- 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 +7 -5
- package/build/src/host/controllers/account_tokens_controller.js +14 -2
- package/build/src/host/controllers/interaction_controller.js +59 -22
- package/build/src/host/controllers/pat_introspection_controller.js +10 -0
- 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/middleware/account_auth.js +3 -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 +96 -2
- package/build/src/host/register_auth_host.js +196 -91
- package/build/src/host/renderers/edge_renderer.js +19 -1
- package/build/src/host/renderers/inertia_renderer.d.ts +63 -0
- package/build/src/host/renderers/inertia_renderer.js +63 -0
- 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 +1 -1
- 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,21 +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 { resolveRuntimeSettings } from
|
|
15
|
-
import { resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from
|
|
16
|
-
import { dispatchSecurityNotice } from
|
|
17
|
-
import {
|
|
18
|
-
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';
|
|
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(
|
|
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,
|
|
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(
|
|
76
|
-
emailChangeRequested: ctx.session.flashMessages.get(
|
|
77
|
-
emailChanged: ctx.session.flashMessages.get(
|
|
78
|
-
profileUpdated: ctx.session.flashMessages.get(
|
|
79
|
-
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,
|
|
80
82
|
trustedDevicesEnabled: cfg.trustedDevices.enabled,
|
|
81
|
-
trustedDevicesRevoked: ctx.session.flashMessages.get(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
116
|
-
return ctx.response.redirect(
|
|
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:
|
|
126
|
+
type: 'account.exported',
|
|
125
127
|
accountId: userId,
|
|
126
128
|
ip: ctx.request.ip?.() ?? null,
|
|
127
129
|
});
|
|
128
|
-
ctx.response.header(
|
|
129
|
-
ctx.response.header(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
168
|
-
return ctx.response.redirect(
|
|
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:
|
|
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(
|
|
180
|
+
const { resolveWorkflowEngine, enqueueAccountDeletion } = await import('../durable/index.js');
|
|
180
181
|
try {
|
|
181
|
-
const { revokeSessions } = await import(
|
|
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(
|
|
199
|
-
return ctx.response.redirect(
|
|
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(
|
|
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:
|
|
214
|
+
type: 'trusted_device.revoked',
|
|
214
215
|
accountId: userId,
|
|
215
216
|
ip: ctx.request.ip?.() ?? null,
|
|
216
217
|
});
|
|
217
|
-
ctx.session.flash(
|
|
218
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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 =
|
|
235
|
-
const file = ctx.request.file(
|
|
235
|
+
let via = 'url';
|
|
236
|
+
const file = ctx.request.file('avatar', {
|
|
236
237
|
size: `${cfg.uploads.avatars.maxSizeMb}mb`,
|
|
237
|
-
extnames: [
|
|
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,
|
|
243
|
-
size: translate(cfg.messages,
|
|
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 =
|
|
248
|
+
via = 'upload';
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
catch (error) {
|
|
251
252
|
if (error instanceof AvatarUploadError) {
|
|
252
|
-
ctx.session.flash(
|
|
253
|
-
return ctx.response.redirect(
|
|
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:
|
|
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(
|
|
271
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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(
|
|
294
|
-
return ctx.response.redirect(
|
|
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) ??
|
|
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(
|
|
306
|
+
ctx.session.flash('securityError', translate(cfg.messages, 'password.reused', {
|
|
307
307
|
count: histSettings.count,
|
|
308
308
|
}));
|
|
309
|
-
return ctx.response.redirect(
|
|
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(
|
|
327
|
-
return ctx.response.redirect(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
371
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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(
|
|
390
|
-
return ctx.response.redirect(
|
|
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(
|
|
398
|
-
return ctx.response.redirect(
|
|
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(
|
|
405
|
-
return ctx.response.redirect(
|
|
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(
|
|
411
|
-
return ctx.response.redirect(
|
|
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:
|
|
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}
|
|
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(
|
|
456
|
+
ctx.session.flash('emailChangeRequested', translate(cfg.messages, 'account.security.email_change_requested', {
|
|
457
457
|
email: newEmail,
|
|
458
458
|
}));
|
|
459
|
-
return ctx.response.redirect(
|
|
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(
|
|
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(
|
|
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 ===
|
|
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:
|
|
494
|
+
type: 'email_change.cancelled',
|
|
495
495
|
accountId: userId,
|
|
496
496
|
ip: ctx.request.ip?.() ?? null,
|
|
497
497
|
});
|
|
498
|
-
ctx.session.flash(
|
|
499
|
-
return ctx.response.redirect(
|
|
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(
|
|
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,
|
|
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:
|
|
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:
|
|
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,
|
|
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 {
|
|
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 { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
8
9
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
9
|
-
import {
|
|
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
|
-
|
|
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('
|
|
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('
|
|
77
|
+
return ctx.response.redirect(accountPath('tokens'));
|
|
66
78
|
}
|
|
67
79
|
}
|