@adonis-agora/authkit-server 0.34.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/LICENSE +21 -0
- package/README.md +158 -0
- package/build/assets/grafana/authkit-dashboard.json +118 -0
- package/build/commands/clients_create.d.ts +27 -0
- package/build/commands/clients_create.js +121 -0
- package/build/commands/commands.json +292 -0
- package/build/commands/configure.d.ts +2 -0
- package/build/commands/configure.js +45 -0
- package/build/commands/doctor.d.ts +10 -0
- package/build/commands/doctor.js +113 -0
- package/build/commands/eject.d.ts +11 -0
- package/build/commands/eject.js +96 -0
- package/build/commands/expire_scan.d.ts +28 -0
- package/build/commands/expire_scan.js +92 -0
- package/build/commands/import_users.d.ts +17 -0
- package/build/commands/import_users.js +80 -0
- package/build/commands/keys_rotate.d.ts +21 -0
- package/build/commands/keys_rotate.js +126 -0
- package/build/commands/main.d.ts +12 -0
- package/build/commands/main.js +38 -0
- package/build/commands/settings_get.d.ts +18 -0
- package/build/commands/settings_get.js +43 -0
- package/build/commands/settings_list.d.ts +17 -0
- package/build/commands/settings_list.js +39 -0
- package/build/commands/settings_set.d.ts +20 -0
- package/build/commands/settings_set.js +55 -0
- package/build/commands/settings_unset.d.ts +18 -0
- package/build/commands/settings_unset.js +46 -0
- package/build/commands/ui_preset.d.ts +4 -0
- package/build/commands/ui_preset.js +32 -0
- package/build/database/migrations/make_authkit_oidc_table.d.ts +6 -0
- package/build/database/migrations/make_authkit_oidc_table.js +19 -0
- package/build/host/ui/admin.html +29 -0
- package/build/host/views/account/apps.edge +59 -0
- package/build/host/views/account/confirm.edge +70 -0
- package/build/host/views/account/email-confirmed.edge +16 -0
- package/build/host/views/account/login.edge +33 -0
- package/build/host/views/account/mfa.edge +152 -0
- package/build/host/views/account/orgs.edge +58 -0
- package/build/host/views/account/security.edge +215 -0
- package/build/host/views/account/tokens.edge +75 -0
- package/build/host/views/consent.edge +20 -0
- package/build/host/views/forgot.edge +43 -0
- package/build/host/views/login.edge +251 -0
- package/build/host/views/maintenance.edge +32 -0
- package/build/host/views/mfa-challenge.edge +107 -0
- package/build/host/views/otp-unlock.edge +40 -0
- package/build/host/views/partials/styles.edge +2 -0
- package/build/host/views/reset.edge +30 -0
- package/build/host/views/signup.edge +62 -0
- package/build/host/views/verify-email.edge +17 -0
- package/build/index.d.ts +115 -0
- package/build/index.js +76 -0
- package/build/password/common_passwords.txt +10000 -0
- package/build/providers/authkit_server_provider.d.ts +27 -0
- package/build/providers/authkit_server_provider.js +242 -0
- package/build/src/accounts/account_store.d.ts +578 -0
- package/build/src/accounts/account_store.js +60 -0
- package/build/src/accounts/lucid_account_store.d.ts +156 -0
- package/build/src/accounts/lucid_account_store.js +294 -0
- package/build/src/accounts/lucid_store/core.d.ts +9 -0
- package/build/src/accounts/lucid_store/core.js +306 -0
- package/build/src/accounts/lucid_store/mfa.d.ts +15 -0
- package/build/src/accounts/lucid_store/mfa.js +108 -0
- package/build/src/accounts/lucid_store/organizations.d.ts +13 -0
- package/build/src/accounts/lucid_store/organizations.js +294 -0
- package/build/src/accounts/lucid_store/password_hygiene.d.ts +20 -0
- package/build/src/accounts/lucid_store/password_hygiene.js +130 -0
- package/build/src/accounts/lucid_store/provider_identity.d.ts +8 -0
- package/build/src/accounts/lucid_store/provider_identity.js +59 -0
- package/build/src/accounts/lucid_store/shared.d.ts +113 -0
- package/build/src/accounts/lucid_store/shared.js +130 -0
- package/build/src/accounts/lucid_store/status_profile.d.ts +34 -0
- package/build/src/accounts/lucid_store/status_profile.js +100 -0
- package/build/src/accounts/lucid_store/webauthn.d.ts +8 -0
- package/build/src/accounts/lucid_store/webauthn.js +140 -0
- package/build/src/accounts/lucid_stores.d.ts +45 -0
- package/build/src/accounts/lucid_stores.js +30 -0
- package/build/src/adapters/adapter_contract.d.ts +37 -0
- package/build/src/adapters/adapter_contract.js +1 -0
- package/build/src/adapters/database_adapter.d.ts +21 -0
- package/build/src/adapters/database_adapter.js +79 -0
- package/build/src/adapters/factory.d.ts +30 -0
- package/build/src/adapters/factory.js +43 -0
- package/build/src/adapters/redis_adapter.d.ts +24 -0
- package/build/src/adapters/redis_adapter.js +122 -0
- package/build/src/audit/audit_sink.d.ts +63 -0
- package/build/src/audit/audit_sink.js +1 -0
- package/build/src/audit/lucid_audit_sink.d.ts +10 -0
- package/build/src/audit/lucid_audit_sink.js +99 -0
- package/build/src/commands/expire_scan_command.d.ts +60 -0
- package/build/src/commands/expire_scan_command.js +229 -0
- package/build/src/commands/import_clients.d.ts +40 -0
- package/build/src/commands/import_clients.js +79 -0
- package/build/src/commands/import_users.d.ts +51 -0
- package/build/src/commands/import_users.js +94 -0
- package/build/src/commands/resolve_config.d.ts +13 -0
- package/build/src/commands/resolve_config.js +18 -0
- package/build/src/commands/settings_commands.d.ts +47 -0
- package/build/src/commands/settings_commands.js +270 -0
- package/build/src/controllers/oidc_callback_controller.d.ts +22 -0
- package/build/src/controllers/oidc_callback_controller.js +33 -0
- package/build/src/define_config.d.ts +772 -0
- package/build/src/define_config.js +351 -0
- package/build/src/doctor/checks.d.ts +197 -0
- package/build/src/doctor/checks.js +858 -0
- package/build/src/events/dispatcher.d.ts +45 -0
- package/build/src/events/dispatcher.js +117 -0
- package/build/src/host/account_api/account_api_controller.d.ts +245 -0
- package/build/src/host/account_api/account_api_controller.js +827 -0
- package/build/src/host/account_deletion_ops.d.ts +80 -0
- package/build/src/host/account_deletion_ops.js +118 -0
- package/build/src/host/account_deletion_service.d.ts +63 -0
- package/build/src/host/account_deletion_service.js +133 -0
- package/build/src/host/account_export_service.d.ts +76 -0
- package/build/src/host/account_export_service.js +140 -0
- package/build/src/host/account_home.d.ts +11 -0
- package/build/src/host/account_home.js +11 -0
- package/build/src/host/account_lockout.d.ts +86 -0
- package/build/src/host/account_lockout.js +185 -0
- package/build/src/host/active_org_cookie.d.ts +28 -0
- package/build/src/host/active_org_cookie.js +47 -0
- package/build/src/host/admin_api/admin_api_guard.d.ts +16 -0
- package/build/src/host/admin_api/admin_api_guard.js +59 -0
- package/build/src/host/admin_api/admin_orgs_service.d.ts +125 -0
- package/build/src/host/admin_api/admin_orgs_service.js +324 -0
- package/build/src/host/admin_api/admin_users_service.d.ts +163 -0
- package/build/src/host/admin_api/admin_users_service.js +308 -0
- package/build/src/host/admin_api/api_clients_controller.d.ts +54 -0
- package/build/src/host/admin_api/api_clients_controller.js +89 -0
- package/build/src/host/admin_api/api_keys_controller.d.ts +19 -0
- package/build/src/host/admin_api/api_keys_controller.js +36 -0
- package/build/src/host/admin_api/api_misc_controller.d.ts +19 -0
- package/build/src/host/admin_api/api_misc_controller.js +44 -0
- package/build/src/host/admin_api/api_orgs_controller.d.ts +108 -0
- package/build/src/host/admin_api/api_orgs_controller.js +189 -0
- package/build/src/host/admin_api/api_settings_controller.d.ts +55 -0
- package/build/src/host/admin_api/api_settings_controller.js +134 -0
- package/build/src/host/admin_api/api_users_controller.d.ts +108 -0
- package/build/src/host/admin_api/api_users_controller.js +187 -0
- package/build/src/host/admin_api/dto.d.ts +131 -0
- package/build/src/host/admin_api/dto.js +122 -0
- package/build/src/host/admin_api/token_verify_service.d.ts +34 -0
- package/build/src/host/admin_api/token_verify_service.js +74 -0
- package/build/src/host/admin_clients_service.d.ts +88 -0
- package/build/src/host/admin_clients_service.js +181 -0
- package/build/src/host/admin_console/admin_shell_controller.d.ts +27 -0
- package/build/src/host/admin_console/admin_shell_controller.js +167 -0
- package/build/src/host/admin_console/console_audit_controller.d.ts +17 -0
- package/build/src/host/admin_console/console_audit_controller.js +25 -0
- package/build/src/host/admin_console/console_clients_controller.d.ts +53 -0
- package/build/src/host/admin_console/console_clients_controller.js +102 -0
- package/build/src/host/admin_console/console_impersonation_controller.d.ts +23 -0
- package/build/src/host/admin_console/console_impersonation_controller.js +45 -0
- package/build/src/host/admin_console/console_keys_controller.d.ts +24 -0
- package/build/src/host/admin_console/console_keys_controller.js +41 -0
- package/build/src/host/admin_console/console_orgs_controller.d.ts +96 -0
- package/build/src/host/admin_console/console_orgs_controller.js +238 -0
- package/build/src/host/admin_console/console_overview_controller.d.ts +27 -0
- package/build/src/host/admin_console/console_overview_controller.js +44 -0
- package/build/src/host/admin_console/console_roles_controller.d.ts +31 -0
- package/build/src/host/admin_console/console_roles_controller.js +108 -0
- package/build/src/host/admin_console/console_sessions_controller.d.ts +91 -0
- package/build/src/host/admin_console/console_sessions_controller.js +125 -0
- package/build/src/host/admin_console/console_settings_controller.d.ts +47 -0
- package/build/src/host/admin_console/console_settings_controller.js +115 -0
- package/build/src/host/admin_console/console_users_controller.d.ts +110 -0
- package/build/src/host/admin_console/console_users_controller.js +212 -0
- package/build/src/host/admin_prefix.d.ts +50 -0
- package/build/src/host/admin_prefix.js +77 -0
- package/build/src/host/admin_sessions_service.d.ts +119 -0
- package/build/src/host/admin_sessions_service.js +284 -0
- package/build/src/host/admin_stats_service.d.ts +44 -0
- package/build/src/host/admin_stats_service.js +130 -0
- package/build/src/host/admin_validators.d.ts +247 -0
- package/build/src/host/admin_validators.js +197 -0
- package/build/src/host/augmentations.d.ts +12 -0
- package/build/src/host/augmentations.js +1 -0
- package/build/src/host/auth_host_config.d.ts +22 -0
- package/build/src/host/auth_host_config.js +13 -0
- package/build/src/host/avatar_storage.d.ts +68 -0
- package/build/src/host/avatar_storage.js +166 -0
- package/build/src/host/bot_protection.d.ts +187 -0
- package/build/src/host/bot_protection.js +132 -0
- package/build/src/host/branding.d.ts +19 -0
- package/build/src/host/branding.js +8 -0
- package/build/src/host/config_locks.d.ts +38 -0
- package/build/src/host/config_locks.js +70 -0
- package/build/src/host/console_session.d.ts +27 -0
- package/build/src/host/console_session.js +36 -0
- package/build/src/host/controllers/account_apps_controller.d.ts +15 -0
- package/build/src/host/controllers/account_apps_controller.js +61 -0
- package/build/src/host/controllers/account_confirm_controller.d.ts +24 -0
- package/build/src/host/controllers/account_confirm_controller.js +144 -0
- package/build/src/host/controllers/account_mfa_controller.d.ts +28 -0
- package/build/src/host/controllers/account_mfa_controller.js +208 -0
- package/build/src/host/controllers/account_orgs_controller.d.ts +80 -0
- package/build/src/host/controllers/account_orgs_controller.js +401 -0
- package/build/src/host/controllers/account_security_controller.d.ts +43 -0
- package/build/src/host/controllers/account_security_controller.js +546 -0
- package/build/src/host/controllers/account_session_controller.d.ts +21 -0
- package/build/src/host/controllers/account_session_controller.js +113 -0
- package/build/src/host/controllers/account_tokens_controller.d.ts +7 -0
- package/build/src/host/controllers/account_tokens_controller.js +67 -0
- package/build/src/host/controllers/interaction_controller.d.ts +125 -0
- package/build/src/host/controllers/interaction_controller.js +981 -0
- package/build/src/host/controllers/pat_introspection_controller.d.ts +22 -0
- package/build/src/host/controllers/pat_introspection_controller.js +46 -0
- package/build/src/host/controllers/registration_controller.d.ts +18 -0
- package/build/src/host/controllers/registration_controller.js +382 -0
- package/build/src/host/controllers/social_controller.d.ts +8 -0
- package/build/src/host/controllers/social_controller.js +89 -0
- package/build/src/host/csrf.d.ts +29 -0
- package/build/src/host/csrf.js +14 -0
- package/build/src/host/default_mailer.d.ts +112 -0
- package/build/src/host/default_mailer.js +417 -0
- package/build/src/host/durable/account_deletion_workflow.d.ts +48 -0
- package/build/src/host/durable/account_deletion_workflow.js +86 -0
- package/build/src/host/durable/account_export_workflow.d.ts +68 -0
- package/build/src/host/durable/account_export_workflow.js +93 -0
- package/build/src/host/durable/index.d.ts +68 -0
- package/build/src/host/durable/index.js +77 -0
- package/build/src/host/email_templates.d.ts +39 -0
- package/build/src/host/email_templates.js +69 -0
- package/build/src/host/geo.d.ts +22 -0
- package/build/src/host/geo.js +39 -0
- package/build/src/host/i18n.d.ts +1416 -0
- package/build/src/host/i18n.js +1583 -0
- package/build/src/host/impersonation.d.ts +25 -0
- package/build/src/host/impersonation.js +31 -0
- package/build/src/host/key_rotation.d.ts +14 -0
- package/build/src/host/key_rotation.js +27 -0
- package/build/src/host/key_rotation_actions.d.ts +42 -0
- package/build/src/host/key_rotation_actions.js +27 -0
- package/build/src/host/login_attempt.d.ts +98 -0
- package/build/src/host/login_attempt.js +211 -0
- package/build/src/host/login_notify.d.ts +27 -0
- package/build/src/host/login_notify.js +142 -0
- package/build/src/host/middleware/account_auth.d.ts +7 -0
- package/build/src/host/middleware/account_auth.js +11 -0
- package/build/src/host/otp_lockout.d.ts +103 -0
- package/build/src/host/otp_lockout.js +221 -0
- package/build/src/host/rate_limit.d.ts +39 -0
- package/build/src/host/rate_limit.js +94 -0
- package/build/src/host/register_auth_host.d.ts +97 -0
- package/build/src/host/register_auth_host.js +466 -0
- package/build/src/host/renderers/edge_renderer.d.ts +12 -0
- package/build/src/host/renderers/edge_renderer.js +38 -0
- package/build/src/host/renderers/inertia_renderer.d.ts +70 -0
- package/build/src/host/renderers/inertia_renderer.js +54 -0
- package/build/src/host/runtime_settings.d.ts +181 -0
- package/build/src/host/runtime_settings.js +307 -0
- package/build/src/host/runtime_toggles.d.ts +807 -0
- package/build/src/host/runtime_toggles.js +784 -0
- package/build/src/host/security_notice_service.d.ts +36 -0
- package/build/src/host/security_notice_service.js +87 -0
- package/build/src/host/session_context.d.ts +23 -0
- package/build/src/host/session_context.js +89 -0
- package/build/src/host/sudo_mode.d.ts +56 -0
- package/build/src/host/sudo_mode.js +99 -0
- package/build/src/host/svg_chart.d.ts +15 -0
- package/build/src/host/svg_chart.js +45 -0
- package/build/src/host/trusted_device.d.ts +61 -0
- package/build/src/host/trusted_device.js +65 -0
- package/build/src/host/ui-dist/assets/index-CRWcmsex.js +137 -0
- package/build/src/host/ui-dist/assets/index-DTSmD4RU.css +1 -0
- package/build/src/host/ui-dist/index.html +17 -0
- package/build/src/host/user_agent.d.ts +18 -0
- package/build/src/host/user_agent.js +50 -0
- package/build/src/host/validators.d.ts +127 -0
- package/build/src/host/validators.js +53 -0
- package/build/src/keys/jwks_manager.d.ts +6 -0
- package/build/src/keys/jwks_manager.js +11 -0
- package/build/src/keys/keystore.d.ts +52 -0
- package/build/src/keys/keystore.js +68 -0
- package/build/src/keys/keystore_codec.d.ts +20 -0
- package/build/src/keys/keystore_codec.js +42 -0
- package/build/src/keys/keystore_crypto.d.ts +9 -0
- package/build/src/keys/keystore_crypto.js +27 -0
- package/build/src/keys/keystore_manager.d.ts +43 -0
- package/build/src/keys/keystore_manager.js +104 -0
- package/build/src/keys/keystore_vault.d.ts +113 -0
- package/build/src/keys/keystore_vault.js +265 -0
- package/build/src/mixins/json_column.d.ts +33 -0
- package/build/src/mixins/json_column.js +38 -0
- package/build/src/mixins/with_audit_log.d.ts +19 -0
- package/build/src/mixins/with_audit_log.js +39 -0
- package/build/src/mixins/with_auth_user.d.ts +24 -0
- package/build/src/mixins/with_auth_user.js +46 -0
- package/build/src/mixins/with_credentials.d.ts +20 -0
- package/build/src/mixins/with_credentials.js +29 -0
- package/build/src/mixins/with_mfa.d.ts +35 -0
- package/build/src/mixins/with_mfa.js +21 -0
- package/build/src/mixins/with_personal_access_token.d.ts +19 -0
- package/build/src/mixins/with_personal_access_token.js +42 -0
- package/build/src/mixins/with_provider_identity.d.ts +20 -0
- package/build/src/mixins/with_provider_identity.js +32 -0
- package/build/src/mixins/with_webauthn_credential.d.ts +37 -0
- package/build/src/mixins/with_webauthn_credential.js +46 -0
- package/build/src/observability/diagnostics_bridge.d.ts +6 -0
- package/build/src/observability/diagnostics_bridge.js +27 -0
- package/build/src/observability/metrics_controller.d.ts +5 -0
- package/build/src/observability/metrics_controller.js +24 -0
- package/build/src/observability/metrics_service.d.ts +2 -0
- package/build/src/observability/metrics_service.js +7 -0
- package/build/src/observability/otel_recorder.d.ts +10 -0
- package/build/src/observability/otel_recorder.js +59 -0
- package/build/src/observability/telescope/data_providers.d.ts +24 -0
- package/build/src/observability/telescope/data_providers.js +235 -0
- package/build/src/observability/telescope/extension.d.ts +29 -0
- package/build/src/observability/telescope/extension.js +167 -0
- package/build/src/observability/telescope/index.d.ts +8 -0
- package/build/src/observability/telescope/index.js +8 -0
- package/build/src/observability/wire_provider_events.d.ts +12 -0
- package/build/src/observability/wire_provider_events.js +30 -0
- package/build/src/password/common_passwords.d.ts +31 -0
- package/build/src/password/common_passwords.js +73 -0
- package/build/src/password/password_manager.d.ts +163 -0
- package/build/src/password/password_manager.js +191 -0
- package/build/src/password/policy.d.ts +71 -0
- package/build/src/password/policy.js +55 -0
- package/build/src/password/pwned.d.ts +36 -0
- package/build/src/password/pwned.js +52 -0
- package/build/src/pat/lucid_pat_store.d.ts +6 -0
- package/build/src/pat/lucid_pat_store.js +62 -0
- package/build/src/pat/pat_store.d.ts +31 -0
- package/build/src/pat/pat_store.js +1 -0
- package/build/src/pat/pat_tokens.d.ts +4 -0
- package/build/src/pat/pat_tokens.js +9 -0
- package/build/src/provider/build_provider.d.ts +45 -0
- package/build/src/provider/build_provider.js +219 -0
- package/build/src/provider/device_sources.d.ts +6 -0
- package/build/src/provider/device_sources.js +65 -0
- package/build/src/provider/interaction_actions.d.ts +36 -0
- package/build/src/provider/interaction_actions.js +55 -0
- package/build/src/provider/key_rotation_scheduler.d.ts +28 -0
- package/build/src/provider/key_rotation_scheduler.js +40 -0
- package/build/src/provider/keystore_reload.d.ts +21 -0
- package/build/src/provider/keystore_reload.js +39 -0
- package/build/src/provider/logout_sources.d.ts +13 -0
- package/build/src/provider/logout_sources.js +64 -0
- package/build/src/provider/oidc_service.d.ts +81 -0
- package/build/src/provider/oidc_service.js +254 -0
- package/build/src/provider/single_flight_lock.d.ts +18 -0
- package/build/src/provider/single_flight_lock.js +31 -0
- package/build/src/provider/token_exchange.d.ts +21 -0
- package/build/src/provider/token_exchange.js +124 -0
- package/build/src/register_routes.d.ts +16 -0
- package/build/src/register_routes.js +21 -0
- package/build/src/schema/ensure.d.ts +41 -0
- package/build/src/schema/ensure.js +262 -0
- package/build/stubs/config/authkit.stub +20 -0
- package/build/stubs/config/authkit_react.stub +52 -0
- package/build/stubs/main.d.ts +1 -0
- package/build/stubs/main.js +2 -0
- package/build/stubs/models/auth_user.stub +22 -0
- package/build/stubs/ui/edge/views/consent.edge +13 -0
- package/build/stubs/ui/edge/views/login.edge +19 -0
- package/build/stubs/ui/react/components/auth_shell.tsx +67 -0
- package/build/stubs/ui/react/pages/account/login.tsx +56 -0
- package/build/stubs/ui/react/pages/account/mfa.tsx +132 -0
- package/build/stubs/ui/react/pages/account/tokens.tsx +88 -0
- package/build/stubs/ui/react/pages/consent.tsx +39 -0
- package/build/stubs/ui/react/pages/forgot.tsx +44 -0
- package/build/stubs/ui/react/pages/login.tsx +171 -0
- package/build/stubs/ui/react/pages/mfa-challenge.tsx +72 -0
- package/build/stubs/ui/react/pages/reset.tsx +58 -0
- package/build/stubs/ui/react/pages/signup.tsx +78 -0
- package/build/stubs/ui/react/pages/verify-email.tsx +24 -0
- package/build/types.d.ts +7 -0
- package/build/types.js +1 -0
- package/package.json +147 -0
- package/stubs/config/authkit.stub +20 -0
- package/stubs/config/authkit_react.stub +52 -0
- package/stubs/main.ts +2 -0
- package/stubs/models/auth_user.stub +22 -0
- package/stubs/ui/edge/views/consent.edge +13 -0
- package/stubs/ui/edge/views/login.edge +19 -0
- package/stubs/ui/react/components/auth_shell.tsx +67 -0
- package/stubs/ui/react/pages/account/login.tsx +56 -0
- package/stubs/ui/react/pages/account/mfa.tsx +132 -0
- package/stubs/ui/react/pages/account/tokens.tsx +88 -0
- package/stubs/ui/react/pages/consent.tsx +39 -0
- package/stubs/ui/react/pages/forgot.tsx +44 -0
- package/stubs/ui/react/pages/login.tsx +171 -0
- package/stubs/ui/react/pages/mfa-challenge.tsx +72 -0
- package/stubs/ui/react/pages/reset.tsx +58 -0
- package/stubs/ui/react/pages/signup.tsx +78 -0
- package/stubs/ui/react/pages/verify-email.tsx +24 -0
|
@@ -0,0 +1,546 @@
|
|
|
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, isDriveAvailable, 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
|
+
/** Resolve os password history settings em runtime (fail-safe). */
|
|
19
|
+
async function resolvePasswordHistorySettings(ctx) {
|
|
20
|
+
try {
|
|
21
|
+
const runtimeSettings = await resolveRuntimeSettings(ctx);
|
|
22
|
+
if (runtimeSettings && (await runtimeSettings.isTablePresent())) {
|
|
23
|
+
return await resolveEffectivePasswordHistory(runtimeSettings);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// DB não disponível ou tabela ausente → usa defaults.
|
|
28
|
+
}
|
|
29
|
+
return { enabled: false, count: 5 };
|
|
30
|
+
}
|
|
31
|
+
/** Resolve os settings de troca de e-mail em runtime (fail-safe). */
|
|
32
|
+
async function resolveEmailChangeSettings(ctx) {
|
|
33
|
+
try {
|
|
34
|
+
const runtimeSettings = await resolveRuntimeSettings(ctx);
|
|
35
|
+
if (runtimeSettings && (await runtimeSettings.isTablePresent())) {
|
|
36
|
+
return await resolveEffectiveEmailChange(runtimeSettings);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// DB não disponível ou tabela ausente → usa defaults.
|
|
41
|
+
}
|
|
42
|
+
return { enabled: true, ttlHours: 24, requirePassword: true };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Self-service de segurança da conta (console de conta): trocar a senha e o
|
|
46
|
+
* e-mail. A troca de senha exige a senha ATUAL (verifyCredentials). A troca de
|
|
47
|
+
* e-mail exige a senha atual e dispara um link de confirmação para o NOVO
|
|
48
|
+
* endereço (consumido em GET /account/email/confirm). Degrada graciosamente se o
|
|
49
|
+
* store não suportar a capacidade ({@link supportsAccountSecurity}).
|
|
50
|
+
*/
|
|
51
|
+
export default class AccountSecurityController {
|
|
52
|
+
async index(ctx) {
|
|
53
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
54
|
+
const cfg = service.config;
|
|
55
|
+
const render = cfg.render;
|
|
56
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
57
|
+
const account = await cfg.accountStore.findById(userId);
|
|
58
|
+
// Sessões ativas da PRÓPRIA conta (enriquecidas com contexto de dispositivo),
|
|
59
|
+
// capability-probed: só quando o adapter OIDC enumera.
|
|
60
|
+
const adminSessions = new AdminSessionsService(service);
|
|
61
|
+
const sessionsSupported = adminSessions.canList;
|
|
62
|
+
const ownSessions = sessionsSupported
|
|
63
|
+
? await enrichSessionsWithContext(cfg, userId, await adminSessions.listSessions(userId))
|
|
64
|
+
: [];
|
|
65
|
+
return render(ctx, "account/security", {
|
|
66
|
+
csrfToken: ctx.request.csrfToken,
|
|
67
|
+
supported: supportsAccountSecurity(cfg.accountStore),
|
|
68
|
+
profileSupported: supportsProfile(cfg.accountStore),
|
|
69
|
+
// Só mostramos o input de arquivo se o drive do app estiver disponível.
|
|
70
|
+
avatarUploadSupported: await isDriveAvailable(),
|
|
71
|
+
email: account?.email ?? "",
|
|
72
|
+
name: account?.name ?? "",
|
|
73
|
+
avatarUrl: account?.avatarUrl ?? "",
|
|
74
|
+
passwordChanged: ctx.session.flashMessages.get("passwordChanged") ?? null,
|
|
75
|
+
emailChangeRequested: ctx.session.flashMessages.get("emailChangeRequested") ?? null,
|
|
76
|
+
emailChanged: ctx.session.flashMessages.get("emailChanged") ?? null,
|
|
77
|
+
profileUpdated: ctx.session.flashMessages.get("profileUpdated") ?? null,
|
|
78
|
+
error: ctx.session.flashMessages.get("securityError") ?? null,
|
|
79
|
+
trustedDevicesEnabled: cfg.trustedDevices.enabled,
|
|
80
|
+
trustedDevicesRevoked: ctx.session.flashMessages.get("trustedDevicesRevoked") ?? null,
|
|
81
|
+
sessionsSupported,
|
|
82
|
+
sessions: ownSessions.map((s) => ({
|
|
83
|
+
loginTs: s.loginTs ? new Date(s.loginTs * 1000).toISOString() : "",
|
|
84
|
+
browser: s.browser ?? "",
|
|
85
|
+
os: s.os ?? "",
|
|
86
|
+
ip: s.ip ?? "",
|
|
87
|
+
location: s.location ?? "",
|
|
88
|
+
})),
|
|
89
|
+
// Export de dados (portabilidade) sempre disponível para a conta logada.
|
|
90
|
+
exportSupported: true,
|
|
91
|
+
// Deleção de conta (LGPD): só quando o store suporta hard delete.
|
|
92
|
+
deletionSupported: supportsAccountDeletion(cfg.accountStore),
|
|
93
|
+
deleteError: ctx.session.flashMessages.get("deleteError") ?? null,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* GET /account/security/export — baixa um JSON com os dados da conta logada
|
|
98
|
+
* (perfil, identidades, apps autorizados, sessões, passkeys e audit do usuário).
|
|
99
|
+
* NUNCA inclui segredos. Audita `account.exported`.
|
|
100
|
+
*/
|
|
101
|
+
async exportData(ctx) {
|
|
102
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
103
|
+
const cfg = service.config;
|
|
104
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
105
|
+
if (cfg.accountLifecycle?.durable) {
|
|
106
|
+
// OPT-IN durável: enfileira o workflow de export (coleta + persiste artefato
|
|
107
|
+
// + entrega async). Isolado no subpath durável — só carregado neste ramo.
|
|
108
|
+
const { resolveWorkflowEngine, enqueueAccountExport } = await import("../durable/index.js");
|
|
109
|
+
const engine = await resolveWorkflowEngine(ctx.containerResolver);
|
|
110
|
+
await enqueueAccountExport(engine, {
|
|
111
|
+
accountId: userId,
|
|
112
|
+
ip: ctx.request.ip?.() ?? null,
|
|
113
|
+
});
|
|
114
|
+
ctx.session.flash("exportRequested", translate(cfg.messages, "account.export.requested"));
|
|
115
|
+
return ctx.response.redirect("/account/security");
|
|
116
|
+
}
|
|
117
|
+
// Caminho SÍNCRONO de sempre (byte-idêntico): download inline do JSON.
|
|
118
|
+
const payload = await new AccountExportService(service).export(userId);
|
|
119
|
+
if (!payload) {
|
|
120
|
+
return ctx.response.notFound();
|
|
121
|
+
}
|
|
122
|
+
await cfg.audit?.record({
|
|
123
|
+
type: "account.exported",
|
|
124
|
+
accountId: userId,
|
|
125
|
+
ip: ctx.request.ip?.() ?? null,
|
|
126
|
+
});
|
|
127
|
+
ctx.response.header("content-type", "application/json; charset=utf-8");
|
|
128
|
+
ctx.response.header("content-disposition", `attachment; filename="authkit-data-export-${userId}.json"`);
|
|
129
|
+
return ctx.response.send(JSON.stringify(payload, null, 2));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* POST /account/security/delete — deleção self-service da conta (danger zone).
|
|
133
|
+
* Exige confirmação: a senha ATUAL (se confere via verifyCredentials) OU o
|
|
134
|
+
* e-mail digitado igual ao da conta (contas passwordless). Roda o cascade
|
|
135
|
+
* completo e encerra a sessão.
|
|
136
|
+
*/
|
|
137
|
+
async deleteAccount(ctx) {
|
|
138
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
139
|
+
const cfg = service.config;
|
|
140
|
+
const store = cfg.accountStore;
|
|
141
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
142
|
+
if (!supportsAccountDeletion(store)) {
|
|
143
|
+
return ctx.response.redirect("/account/security");
|
|
144
|
+
}
|
|
145
|
+
// Sudo mode gate (defesa em profundidade — confirmação inline continua abaixo).
|
|
146
|
+
const sudoSettingsDel = await resolveRuntimeSettings(ctx);
|
|
147
|
+
const sudoResultDel = await requireSudo(ctx, sudoSettingsDel);
|
|
148
|
+
if (sudoResultDel !== true)
|
|
149
|
+
return sudoResultDel;
|
|
150
|
+
const account = await store.findById(userId);
|
|
151
|
+
if (!account) {
|
|
152
|
+
return ctx.response.redirect("/account/login");
|
|
153
|
+
}
|
|
154
|
+
const { currentPassword, confirmEmail } = await ctx.request.validateUsing(deleteAccountValidator);
|
|
155
|
+
// Confirmação: senha atual correta OU e-mail digitado batendo com o da conta
|
|
156
|
+
// (case-insensitive). Sem nenhuma das duas → recusa (não deleta).
|
|
157
|
+
let confirmed = false;
|
|
158
|
+
if (currentPassword) {
|
|
159
|
+
confirmed = !!(await store.verifyCredentials(account.email, currentPassword));
|
|
160
|
+
}
|
|
161
|
+
if (!confirmed && confirmEmail) {
|
|
162
|
+
confirmed =
|
|
163
|
+
confirmEmail.trim().toLowerCase() === account.email.toLowerCase();
|
|
164
|
+
}
|
|
165
|
+
if (!confirmed) {
|
|
166
|
+
ctx.session.flash("deleteError", translate(cfg.messages, "account.delete.invalid_confirmation"));
|
|
167
|
+
return ctx.response.redirect("/account/security");
|
|
168
|
+
}
|
|
169
|
+
const actor = {
|
|
170
|
+
actorId: userId,
|
|
171
|
+
ip: ctx.request.ip?.() ?? null,
|
|
172
|
+
source: "self",
|
|
173
|
+
};
|
|
174
|
+
if (cfg.accountLifecycle?.durable) {
|
|
175
|
+
// OPT-IN durável (self-service): logout IMEDIATO (revoga as sessões/grants
|
|
176
|
+
// OIDC do ator de forma síncrona) + enfileira o resto do cascade async.
|
|
177
|
+
// Isolado no subpath durável — só carregado neste ramo.
|
|
178
|
+
const { resolveWorkflowEngine, enqueueAccountDeletion } = await import("../durable/index.js");
|
|
179
|
+
try {
|
|
180
|
+
const { revokeSessions } = await import("../account_deletion_ops.js");
|
|
181
|
+
await revokeSessions(service, userId);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// best-effort: o logout do cookie abaixo já tira o usuário; o workflow
|
|
185
|
+
// re-revoga as sessões na sua própria etapa idempotente.
|
|
186
|
+
}
|
|
187
|
+
const engine = await resolveWorkflowEngine(ctx.containerResolver);
|
|
188
|
+
await enqueueAccountDeletion(engine, { accountId: userId, actor });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Caminho SÍNCRONO de sempre (byte-idêntico): cascade completo in-process.
|
|
192
|
+
await new AccountDeletionService(service).delete(userId, actor);
|
|
193
|
+
}
|
|
194
|
+
// Encerra a sessão e leva ao login com a mensagem de sucesso.
|
|
195
|
+
ctx.session.forget(ACCOUNT_SESSION_KEY);
|
|
196
|
+
ctx.session.flash("accountDeleted", translate(cfg.messages, "account.delete.deleted"));
|
|
197
|
+
return ctx.response.redirect("/account/login");
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* POST /account/security/trusted-devices/revoke
|
|
201
|
+
* Limpa o cookie de dispositivo confiável DESTE navegador (o MFA volta a ser
|
|
202
|
+
* exigido aqui). Revogação global por-dispositivo não existe sem estado
|
|
203
|
+
* server-side; re-enrolar o MFA invalida a confiança em TODOS os dispositivos.
|
|
204
|
+
*/
|
|
205
|
+
async revokeTrustedDevices(ctx) {
|
|
206
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
207
|
+
const cfg = service.config;
|
|
208
|
+
ctx.response.clearCookie(TRUSTED_DEVICE_COOKIE);
|
|
209
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
210
|
+
await cfg.audit?.record({
|
|
211
|
+
type: "trusted_device.revoked",
|
|
212
|
+
accountId: userId,
|
|
213
|
+
ip: ctx.request.ip?.() ?? null,
|
|
214
|
+
});
|
|
215
|
+
ctx.session.flash("trustedDevicesRevoked", translate(cfg.messages, "account.security.trusted_devices_revoked"));
|
|
216
|
+
return ctx.response.redirect("/account/security");
|
|
217
|
+
}
|
|
218
|
+
/** POST /account/security/profile — atualiza nome + avatar do próprio perfil. */
|
|
219
|
+
async updateProfile(ctx) {
|
|
220
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
221
|
+
const cfg = service.config;
|
|
222
|
+
const store = cfg.accountStore;
|
|
223
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
224
|
+
if (!supportsProfile(store)) {
|
|
225
|
+
return ctx.response.redirect("/account/security");
|
|
226
|
+
}
|
|
227
|
+
const { name, avatarUrl } = await ctx.request.validateUsing(updateProfileValidator);
|
|
228
|
+
// Upload de avatar via o drive do app (opt-in pela presença do arquivo).
|
|
229
|
+
// Se um arquivo for enviado e o drive estiver disponível, a URL resultante
|
|
230
|
+
// tem prioridade sobre o input de URL; senão caímos no avatarUrl (texto).
|
|
231
|
+
let resolvedAvatarUrl = avatarUrl ?? null;
|
|
232
|
+
let via = "url";
|
|
233
|
+
const file = ctx.request.file("avatar", {
|
|
234
|
+
size: `${cfg.uploads.avatars.maxSizeMb}mb`,
|
|
235
|
+
extnames: ["jpg", "jpeg", "png", "webp"],
|
|
236
|
+
});
|
|
237
|
+
if (file) {
|
|
238
|
+
try {
|
|
239
|
+
const uploadedUrl = await storeAvatar(ctx, cfg.uploads, file, userId, {
|
|
240
|
+
extname: translate(cfg.messages, "account.profile.avatar_invalid_type"),
|
|
241
|
+
size: translate(cfg.messages, "account.profile.avatar_too_large"),
|
|
242
|
+
});
|
|
243
|
+
if (uploadedUrl) {
|
|
244
|
+
resolvedAvatarUrl = uploadedUrl;
|
|
245
|
+
via = "upload";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof AvatarUploadError) {
|
|
250
|
+
ctx.session.flash("securityError", error.message);
|
|
251
|
+
return ctx.response.redirect("/account/security");
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Campos ausentes no form viram string vazia (limpa o valor); enviamos null
|
|
257
|
+
// para limpar, ou o valor trimado.
|
|
258
|
+
await store.updateProfile(userId, {
|
|
259
|
+
name: name ?? null,
|
|
260
|
+
avatarUrl: resolvedAvatarUrl,
|
|
261
|
+
});
|
|
262
|
+
await cfg.audit?.record({
|
|
263
|
+
type: "profile.updated",
|
|
264
|
+
accountId: userId,
|
|
265
|
+
ip: ctx.request.ip?.() ?? null,
|
|
266
|
+
metadata: { via },
|
|
267
|
+
});
|
|
268
|
+
ctx.session.flash("profileUpdated", translate(cfg.messages, "account.profile.updated"));
|
|
269
|
+
return ctx.response.redirect("/account/security");
|
|
270
|
+
}
|
|
271
|
+
async changePassword(ctx) {
|
|
272
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
273
|
+
const cfg = service.config;
|
|
274
|
+
const store = cfg.accountStore;
|
|
275
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
276
|
+
if (!supportsAccountSecurity(store)) {
|
|
277
|
+
return ctx.response.redirect("/account/security");
|
|
278
|
+
}
|
|
279
|
+
// Sudo mode gate (defesa em profundidade — a verificação de senha ATUAL continua abaixo).
|
|
280
|
+
const sudoSettings = await resolveRuntimeSettings(ctx);
|
|
281
|
+
const sudoResult = await requireSudo(ctx, sudoSettings);
|
|
282
|
+
if (sudoResult !== true)
|
|
283
|
+
return sudoResult;
|
|
284
|
+
const { currentPassword, newPassword } = await ctx.request.validateUsing(changePasswordValidator);
|
|
285
|
+
const account = await store.findById(userId);
|
|
286
|
+
// Confirma a senha ATUAL pelo e-mail da conta.
|
|
287
|
+
const verified = account
|
|
288
|
+
? await store.verifyCredentials(account.email, currentPassword)
|
|
289
|
+
: null;
|
|
290
|
+
if (!verified) {
|
|
291
|
+
ctx.session.flash("securityError", translate(cfg.messages, "errors.invalid_credentials"));
|
|
292
|
+
return ctx.response.redirect("/account/security");
|
|
293
|
+
}
|
|
294
|
+
// Verificação de histórico de senhas (disallow_password_reuse).
|
|
295
|
+
// Capability-probed: sem `isPasswordReused` ou com setting desligada → no-op.
|
|
296
|
+
if (supportsPasswordHistory(store)) {
|
|
297
|
+
const histSettings = await resolvePasswordHistorySettings(ctx);
|
|
298
|
+
if (histSettings.enabled) {
|
|
299
|
+
// Aplica o pepper à senha candidata antes de comparar com os hashes históricos
|
|
300
|
+
// (que foram gravados já com pepper). Pepper ausente → identidade.
|
|
301
|
+
const pepperedNew = cfg.accountStore.__passwordManager?.applyCurrentPepper?.(newPassword) ?? newPassword;
|
|
302
|
+
const reused = await store.isPasswordReused(userId, pepperedNew, histSettings.count);
|
|
303
|
+
if (reused) {
|
|
304
|
+
ctx.session.flash("securityError", translate(cfg.messages, "password.reused", {
|
|
305
|
+
count: histSettings.count,
|
|
306
|
+
}));
|
|
307
|
+
return ctx.response.redirect("/account/security");
|
|
308
|
+
}
|
|
309
|
+
// Grava o hash ATUAL no histórico antes de trocar.
|
|
310
|
+
// O hash atual está na linha do DB — buscamos via store.findById + raw model.
|
|
311
|
+
const rawRow = await cfg.accountStore.__getRawRow?.(userId);
|
|
312
|
+
if (rawRow?.password) {
|
|
313
|
+
await store.recordPasswordHistory(userId, rawRow.password);
|
|
314
|
+
await store.prunePasswordHistory(userId, histSettings.count);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
await store.changePassword(userId, newPassword);
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
// Política de senha violada → flash com a regra e volta à tela de segurança.
|
|
323
|
+
if (error instanceof PasswordPolicyError) {
|
|
324
|
+
ctx.session.flash("securityError", translate(cfg.messages, error.key, error.params));
|
|
325
|
+
return ctx.response.redirect("/account/security");
|
|
326
|
+
}
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
await cfg.audit?.record({
|
|
330
|
+
type: "password.changed",
|
|
331
|
+
accountId: userId,
|
|
332
|
+
ip: ctx.request.ip?.() ?? null,
|
|
333
|
+
});
|
|
334
|
+
// M2: troca de senha INVALIDA as sessões/grants OIDC da conta. A sessão do
|
|
335
|
+
// console de conta (cookie Adonis, ACCOUNT_SESSION_KEY) NÃO é tocada por
|
|
336
|
+
// revokeAll — ela só destrói sessões/grants OIDC — então o usuário corrente
|
|
337
|
+
// permanece logado no console, enquanto todas as sessões SSO derivadas da
|
|
338
|
+
// senha antiga são derrubadas. Best-effort/fail-safe.
|
|
339
|
+
try {
|
|
340
|
+
const sessions = new AdminSessionsService(service);
|
|
341
|
+
const result = await sessions.revokeAll(userId);
|
|
342
|
+
await cfg.audit?.record({
|
|
343
|
+
type: "session.revoked_all",
|
|
344
|
+
accountId: userId,
|
|
345
|
+
actorId: userId,
|
|
346
|
+
ip: ctx.request.ip?.() ?? null,
|
|
347
|
+
metadata: {
|
|
348
|
+
sessions: result.sessions,
|
|
349
|
+
grants: result.grants,
|
|
350
|
+
accessTokens: result.accessTokens,
|
|
351
|
+
refreshTokens: result.refreshTokens,
|
|
352
|
+
source: "password-change",
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
// fail-safe: a troca de senha já vale; a invalidação é best-effort.
|
|
358
|
+
}
|
|
359
|
+
// Notificação de segurança ao titular da conta (best-effort, fail-safe).
|
|
360
|
+
if (account) {
|
|
361
|
+
await dispatchSecurityNotice(ctx, {
|
|
362
|
+
account: { id: userId, email: account.email },
|
|
363
|
+
kind: "password_changed",
|
|
364
|
+
ip: ctx.request.ip?.() ?? null,
|
|
365
|
+
timestamp: new Date().toISOString(),
|
|
366
|
+
}, cfg.mail, cfg.audit);
|
|
367
|
+
}
|
|
368
|
+
ctx.session.flash("passwordChanged", translate(cfg.messages, "account.security.password_changed"));
|
|
369
|
+
return ctx.response.redirect("/account/security");
|
|
370
|
+
}
|
|
371
|
+
async changeEmail(ctx) {
|
|
372
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
373
|
+
const cfg = service.config;
|
|
374
|
+
const store = cfg.accountStore;
|
|
375
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
376
|
+
if (!supportsAccountSecurity(store)) {
|
|
377
|
+
return ctx.response.redirect("/account/security");
|
|
378
|
+
}
|
|
379
|
+
// Sudo mode gate.
|
|
380
|
+
const sudoSettingsEmail = await resolveRuntimeSettings(ctx);
|
|
381
|
+
const sudoResultEmail = await requireSudo(ctx, sudoSettingsEmail);
|
|
382
|
+
if (sudoResultEmail !== true)
|
|
383
|
+
return sudoResultEmail;
|
|
384
|
+
// Resolve os settings de troca de e-mail em runtime.
|
|
385
|
+
const emailChangeSettings = await resolveEmailChangeSettings(ctx);
|
|
386
|
+
if (!emailChangeSettings.enabled) {
|
|
387
|
+
ctx.session.flash("securityError", translate(cfg.messages, "account.security.email_change_disabled"));
|
|
388
|
+
return ctx.response.redirect("/account/security");
|
|
389
|
+
}
|
|
390
|
+
const { currentPassword, newEmail } = await ctx.request.validateUsing(changeEmailValidator);
|
|
391
|
+
const account = await store.findById(userId);
|
|
392
|
+
// requirePassword: se ligado (default true), exige senha atual.
|
|
393
|
+
if (emailChangeSettings.requirePassword) {
|
|
394
|
+
if (!currentPassword) {
|
|
395
|
+
ctx.session.flash("securityError", translate(cfg.messages, "errors.invalid_credentials"));
|
|
396
|
+
return ctx.response.redirect("/account/security");
|
|
397
|
+
}
|
|
398
|
+
const verified = account
|
|
399
|
+
? await store.verifyCredentials(account.email, currentPassword)
|
|
400
|
+
: null;
|
|
401
|
+
if (!verified) {
|
|
402
|
+
ctx.session.flash("securityError", translate(cfg.messages, "errors.invalid_credentials"));
|
|
403
|
+
return ctx.response.redirect("/account/security");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const issued = await store.requestEmailChange(userId, newEmail);
|
|
407
|
+
if (!issued) {
|
|
408
|
+
ctx.session.flash("securityError", translate(cfg.messages, "errors.email_taken"));
|
|
409
|
+
return ctx.response.redirect("/account/security");
|
|
410
|
+
}
|
|
411
|
+
await cfg.audit?.record({
|
|
412
|
+
type: "email_change.requested",
|
|
413
|
+
accountId: userId,
|
|
414
|
+
email: newEmail,
|
|
415
|
+
ip: ctx.request.ip?.() ?? null,
|
|
416
|
+
});
|
|
417
|
+
const origin = `${ctx.request.protocol()}://${ctx.request.host()}`;
|
|
418
|
+
const confirmUrl = `${origin}/account/email/confirm?token=${encodeURIComponent(issued.token)}`;
|
|
419
|
+
// 1. Link de confirmação → NOVO e-mail (hook onEmailChangeConfirm > onEmailVerification > default).
|
|
420
|
+
if (cfg.mail?.onEmailChangeConfirm) {
|
|
421
|
+
await cfg.mail.onEmailChangeConfirm({
|
|
422
|
+
email: newEmail,
|
|
423
|
+
confirmUrl,
|
|
424
|
+
token: issued.token,
|
|
425
|
+
oldEmail: account?.email ?? "",
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
else if (cfg.mail?.onEmailVerification) {
|
|
429
|
+
// Retrocompat: hook legado onEmailVerification (ainda funciona como override).
|
|
430
|
+
await cfg.mail.onEmailVerification({
|
|
431
|
+
email: newEmail,
|
|
432
|
+
verifyUrl: confirmUrl,
|
|
433
|
+
token: issued.token,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
await sendEmailChangeConfirmationEmail(ctx, {
|
|
438
|
+
email: newEmail,
|
|
439
|
+
confirmUrl,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
// 2. Aviso de segurança → e-mail ATUAL (best-effort, fail-safe).
|
|
443
|
+
if (account) {
|
|
444
|
+
if (cfg.mail?.onEmailChangeNotice) {
|
|
445
|
+
await cfg.mail.onEmailChangeNotice({ email: account.email, newEmail });
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
await sendEmailChangeNoticeEmail(ctx, {
|
|
449
|
+
email: account.email,
|
|
450
|
+
newEmail,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
ctx.session.flash("emailChangeRequested", translate(cfg.messages, "account.security.email_change_requested", {
|
|
455
|
+
email: newEmail,
|
|
456
|
+
}));
|
|
457
|
+
return ctx.response.redirect("/account/security");
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* POST /account/security/email/cancel — cancela uma troca de e-mail pendente
|
|
461
|
+
* (limpa o token pending sem alterar o e-mail da conta).
|
|
462
|
+
*/
|
|
463
|
+
async cancelEmailChange(ctx) {
|
|
464
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
465
|
+
const cfg = service.config;
|
|
466
|
+
const store = cfg.accountStore;
|
|
467
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
468
|
+
if (!supportsAccountSecurity(store)) {
|
|
469
|
+
return ctx.response.redirect("/account/security");
|
|
470
|
+
}
|
|
471
|
+
// Implementação: issuePendingToken(userId, null) limpa o token; reutilizamos
|
|
472
|
+
// requestEmailChange com um valor sentinela → não, melhor usar cancelEmailChange
|
|
473
|
+
// se o store o suportar, senão fallback: sobreescrevemos o token pendente com um
|
|
474
|
+
// token fictício que nunca será confirmado (mas abre nova solicitação).
|
|
475
|
+
// A forma mais limpa é verificar se o store tem cancelEmailChange; se não tiver,
|
|
476
|
+
// chamamos requestEmailChange com o próprio e-mail atual (limpa o pending antigo).
|
|
477
|
+
const cancelFn = store.cancelEmailChange;
|
|
478
|
+
if (typeof cancelFn === "function") {
|
|
479
|
+
await cancelFn.call(store, userId);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
// Fallback: sobrescreve o pending com o próprio e-mail (não tem efeito após confirm).
|
|
483
|
+
const account = await store.findById(userId);
|
|
484
|
+
if (account) {
|
|
485
|
+
// Limpa o token pending emitindo um novo token para o mesmo e-mail e depois
|
|
486
|
+
// confirmando imediatamente — ou simplesmente apaga o campo via requestEmailChange
|
|
487
|
+
// com o mesmo e-mail (que é idempotente e limpa o anterior).
|
|
488
|
+
await store.requestEmailChange(userId, account.email);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
await cfg.audit?.record({
|
|
492
|
+
type: "email_change.cancelled",
|
|
493
|
+
accountId: userId,
|
|
494
|
+
ip: ctx.request.ip?.() ?? null,
|
|
495
|
+
});
|
|
496
|
+
ctx.session.flash("emailChangeRequested", translate(cfg.messages, "account.security.email_change_cancelled"));
|
|
497
|
+
return ctx.response.redirect("/account/security");
|
|
498
|
+
}
|
|
499
|
+
/** GET /account/email/confirm?token=... — consome o token e aplica o novo e-mail. */
|
|
500
|
+
async confirmEmail(ctx) {
|
|
501
|
+
const service = await ctx.containerResolver.make("authkit.server");
|
|
502
|
+
const cfg = service.config;
|
|
503
|
+
const store = cfg.accountStore;
|
|
504
|
+
const render = cfg.render;
|
|
505
|
+
if (!supportsAccountSecurity(store)) {
|
|
506
|
+
return render(ctx, "account/email-confirmed", { ok: false });
|
|
507
|
+
}
|
|
508
|
+
const token = ctx.request.qs().token ?? "";
|
|
509
|
+
const result = await store.confirmEmailChange(token);
|
|
510
|
+
if (result.ok) {
|
|
511
|
+
await cfg.audit?.record({
|
|
512
|
+
type: "email_change.confirmed",
|
|
513
|
+
accountId: result.account.id,
|
|
514
|
+
email: result.newEmail,
|
|
515
|
+
ip: ctx.request.ip?.() ?? null,
|
|
516
|
+
metadata: { oldEmail: result.oldEmail },
|
|
517
|
+
});
|
|
518
|
+
// Aviso de conclusão ao e-mail ANTIGO: "seu e-mail foi alterado de A para B".
|
|
519
|
+
// Best-effort, fail-safe.
|
|
520
|
+
if (cfg.mail?.onEmailChangeNotice) {
|
|
521
|
+
// Reusa onEmailChangeNotice para o aviso pós-confirmação ao endereço antigo.
|
|
522
|
+
await cfg.mail
|
|
523
|
+
.onEmailChangeNotice({
|
|
524
|
+
email: result.oldEmail,
|
|
525
|
+
newEmail: result.newEmail,
|
|
526
|
+
})
|
|
527
|
+
.catch(() => { });
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
await sendEmailChangedCompletedEmail(ctx, {
|
|
531
|
+
oldEmail: result.oldEmail,
|
|
532
|
+
newEmail: result.newEmail,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
// Notificação de segurança ao NOVO e-mail (email_changed) — best-effort.
|
|
536
|
+
await dispatchSecurityNotice(ctx, {
|
|
537
|
+
account: { id: result.account.id, email: result.newEmail },
|
|
538
|
+
kind: "email_changed",
|
|
539
|
+
ip: ctx.request.ip?.() ?? null,
|
|
540
|
+
timestamp: new Date().toISOString(),
|
|
541
|
+
metadata: { oldEmail: result.oldEmail, newEmail: result.newEmail },
|
|
542
|
+
}, cfg.mail, cfg.audit);
|
|
543
|
+
}
|
|
544
|
+
return render(ctx, "account/email-confirmed", { ok: result.ok });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import '../augmentations.js';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
/**
|
|
4
|
+
* Valida um valor de `return_to` recebido da query-string ou de um campo hidden.
|
|
5
|
+
*
|
|
6
|
+
* Regras de segurança (anti open-redirect):
|
|
7
|
+
* - Deve ser uma string não-vazia.
|
|
8
|
+
* - Deve começar com `/`.
|
|
9
|
+
* - NÃO pode começar com `//` (esquema-relativo, ex.: `//evil.com`).
|
|
10
|
+
* - NÃO pode conter `://` (URL absoluta com esquema, ex.: `https://evil.com`).
|
|
11
|
+
* - NÃO pode conter `\` (backslash). Browsers normalizam `\`→`/`, então
|
|
12
|
+
* `/\evil.com` vira `//evil.com` (esquema-relativo) → open-redirect. (L9)
|
|
13
|
+
*
|
|
14
|
+
* Retorna o valor validado ou `null` quando inválido/ausente.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateReturnTo(value: unknown): string | null;
|
|
17
|
+
export default class AccountSessionController {
|
|
18
|
+
show(ctx: HttpContext): Promise<any>;
|
|
19
|
+
login(ctx: HttpContext): Promise<any>;
|
|
20
|
+
logout(ctx: HttpContext): Promise<void>;
|
|
21
|
+
}
|