@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,25 @@
|
|
|
1
|
+
import type { ResolvedServerConfig } from '../define_config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parâmetros PRONTOS do RFC 8693 Token Exchange para o admin assumir a identidade
|
|
4
|
+
* de um usuário-alvo. NÃO é um bypass de auth: o exchange real (no
|
|
5
|
+
* `token_endpoint`) exige um access token do PRÓPRIO admin como `subject_token`
|
|
6
|
+
* (que o handler valida ter a role admin) e um client habilitado ao grant
|
|
7
|
+
* token-exchange. O painel só monta os parâmetros + um curl pronto — honesto com
|
|
8
|
+
* o mecanismo que já existe em `provider/token_exchange.ts`.
|
|
9
|
+
*/
|
|
10
|
+
export interface ImpersonationPanel {
|
|
11
|
+
tokenEndpoint: string;
|
|
12
|
+
grantType: string;
|
|
13
|
+
subjectTokenType: string;
|
|
14
|
+
requestedSubject: string;
|
|
15
|
+
/** Client habilitado ao grant token-exchange usado no exemplo. */
|
|
16
|
+
clientId: string;
|
|
17
|
+
/** Comando curl pronto (subject_token como placeholder a preencher). */
|
|
18
|
+
curl: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Monta o painel de impersonation para o `targetId`. Retorna `null` quando NENHUM
|
|
22
|
+
* client da config tem o grant token-exchange habilitado (sem ele o fluxo não
|
|
23
|
+
* funciona) — a UI então mostra a instrução de habilitar o grant.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildImpersonationPanel(cfg: Pick<ResolvedServerConfig, 'issuer' | 'clients'>, targetId: string): ImpersonationPanel | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const TOKEN_EXCHANGE = 'urn:ietf:params:oauth:grant-type:token-exchange';
|
|
2
|
+
const ACCESS_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
|
|
3
|
+
/**
|
|
4
|
+
* Monta o painel de impersonation para o `targetId`. Retorna `null` quando NENHUM
|
|
5
|
+
* client da config tem o grant token-exchange habilitado (sem ele o fluxo não
|
|
6
|
+
* funciona) — a UI então mostra a instrução de habilitar o grant.
|
|
7
|
+
*/
|
|
8
|
+
export function buildImpersonationPanel(cfg, targetId) {
|
|
9
|
+
const client = cfg.clients.find((c) => (c.grants ?? []).includes(TOKEN_EXCHANGE));
|
|
10
|
+
if (!client)
|
|
11
|
+
return null;
|
|
12
|
+
const tokenEndpoint = `${cfg.issuer.replace(/\/+$/, '')}/token`;
|
|
13
|
+
const auth = client.clientSecret
|
|
14
|
+
? ` -u '${client.clientId}:${client.clientSecret}' \\\n`
|
|
15
|
+
: ` -d 'client_id=${client.clientId}' \\\n`;
|
|
16
|
+
const curl = `curl -X POST '${tokenEndpoint}' \\\n` +
|
|
17
|
+
auth +
|
|
18
|
+
` -d 'grant_type=${TOKEN_EXCHANGE}' \\\n` +
|
|
19
|
+
` -d 'subject_token=<ADMIN_ACCESS_TOKEN>' \\\n` +
|
|
20
|
+
` -d 'subject_token_type=${ACCESS_TOKEN_TYPE}' \\\n` +
|
|
21
|
+
` -d 'requested_subject=${targetId}' \\\n` +
|
|
22
|
+
` -d 'scope=openid profile email'`;
|
|
23
|
+
return {
|
|
24
|
+
tokenEndpoint,
|
|
25
|
+
grantType: TOKEN_EXCHANGE,
|
|
26
|
+
subjectTokenType: ACCESS_TOKEN_TYPE,
|
|
27
|
+
requestedSubject: targetId,
|
|
28
|
+
clientId: client.clientId,
|
|
29
|
+
curl,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SettingsCapability } from './runtime_settings.js';
|
|
2
|
+
export interface KeyRotationSetting {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
maxAgeDays?: number;
|
|
5
|
+
keep?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvedKeyRotationSetting {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
maxAgeDays: number;
|
|
10
|
+
keep: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const KEY_ROTATION_DEFAULTS: ResolvedKeyRotationSetting;
|
|
13
|
+
/** Resolve a setting `key_rotation` em runtime (fail-safe → defaults). */
|
|
14
|
+
export declare function resolveEffectiveKeyRotation(settings: SettingsCapability): Promise<ResolvedKeyRotationSetting>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SETTING_KEYS } from './runtime_toggles.js';
|
|
2
|
+
export const KEY_ROTATION_DEFAULTS = {
|
|
3
|
+
enabled: false,
|
|
4
|
+
maxAgeDays: 90,
|
|
5
|
+
keep: 2,
|
|
6
|
+
};
|
|
7
|
+
/** Resolve a setting `key_rotation` em runtime (fail-safe → defaults). */
|
|
8
|
+
export async function resolveEffectiveKeyRotation(settings) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = await settings.getSetting(SETTING_KEYS.KEY_ROTATION);
|
|
11
|
+
if (raw === null || raw === undefined)
|
|
12
|
+
return KEY_ROTATION_DEFAULTS;
|
|
13
|
+
if (typeof raw !== 'object' || Array.isArray(raw))
|
|
14
|
+
return KEY_ROTATION_DEFAULTS;
|
|
15
|
+
const s = raw;
|
|
16
|
+
return {
|
|
17
|
+
enabled: typeof s.enabled === 'boolean' ? s.enabled : KEY_ROTATION_DEFAULTS.enabled,
|
|
18
|
+
maxAgeDays: typeof s.maxAgeDays === 'number' && s.maxAgeDays >= 1
|
|
19
|
+
? Math.floor(s.maxAgeDays)
|
|
20
|
+
: KEY_ROTATION_DEFAULTS.maxAgeDays,
|
|
21
|
+
keep: typeof s.keep === 'number' && s.keep >= 1 ? Math.floor(s.keep) : KEY_ROTATION_DEFAULTS.keep,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return KEY_ROTATION_DEFAULTS;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ResolvedKeyRotationSetting } from './key_rotation.js';
|
|
2
|
+
import type { SettingsCapability } from './runtime_settings.js';
|
|
3
|
+
import type { ManagedKeyInfo } from '../keys/keystore.js';
|
|
4
|
+
/**
|
|
5
|
+
* Ações compartilhadas de rotação de chave. Funções puras-ish que tanto a Admin
|
|
6
|
+
* REST API (Bearer) quanto o console (sessão) chamam, evitando duplicar a lógica
|
|
7
|
+
* de status (idade + política + ETA) e de rotação (validação de keep/retire).
|
|
8
|
+
*/
|
|
9
|
+
export interface KeysStatus {
|
|
10
|
+
ageDays: number;
|
|
11
|
+
policy: ResolvedKeyRotationSetting;
|
|
12
|
+
nextRotationInDays: number | null;
|
|
13
|
+
keys: ManagedKeyInfo[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Status da chave de assinatura managed. Retorna null quando o serviço não tem
|
|
17
|
+
* keystore gerenciável (jwks não é managed+store) — o controller traduz para 501.
|
|
18
|
+
* A política vem do runtime settings; sem settings, usa defaults (rotação off).
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildKeysStatus(svc: {
|
|
21
|
+
keystoreAgeDays(): Promise<number | null>;
|
|
22
|
+
listManagedKeys(): Promise<ManagedKeyInfo[]>;
|
|
23
|
+
}, settings: SettingsCapability | null): Promise<KeysStatus | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Rotaciona a chave agora. Valida `keep` (>= 1, default 2) e `retire` do body.
|
|
26
|
+
* A aplicação ao vivo + auditoria (keys.rotated) acontecem dentro de svc.rotateKeys.
|
|
27
|
+
*/
|
|
28
|
+
export declare function rotateNow(svc: {
|
|
29
|
+
rotateKeys(keep: number, retire?: boolean): Promise<{
|
|
30
|
+
newKid: string;
|
|
31
|
+
retiredKids: string[];
|
|
32
|
+
keptKids: string[];
|
|
33
|
+
}>;
|
|
34
|
+
}, body: {
|
|
35
|
+
retire?: boolean;
|
|
36
|
+
keep?: number;
|
|
37
|
+
} | undefined): Promise<{
|
|
38
|
+
rotated: true;
|
|
39
|
+
newKid: string;
|
|
40
|
+
retiredKids: string[];
|
|
41
|
+
keptKids: string[];
|
|
42
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveEffectiveKeyRotation } from './key_rotation.js';
|
|
2
|
+
/**
|
|
3
|
+
* Status da chave de assinatura managed. Retorna null quando o serviço não tem
|
|
4
|
+
* keystore gerenciável (jwks não é managed+store) — o controller traduz para 501.
|
|
5
|
+
* A política vem do runtime settings; sem settings, usa defaults (rotação off).
|
|
6
|
+
*/
|
|
7
|
+
export async function buildKeysStatus(svc, settings) {
|
|
8
|
+
const ageDays = await svc.keystoreAgeDays();
|
|
9
|
+
if (ageDays === null)
|
|
10
|
+
return null;
|
|
11
|
+
const policy = settings
|
|
12
|
+
? await resolveEffectiveKeyRotation(settings)
|
|
13
|
+
: { enabled: false, maxAgeDays: 90, keep: 2 };
|
|
14
|
+
const nextRotationInDays = policy.enabled ? Math.max(0, policy.maxAgeDays - ageDays) : null;
|
|
15
|
+
const keys = await svc.listManagedKeys();
|
|
16
|
+
return { ageDays, policy, nextRotationInDays, keys };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Rotaciona a chave agora. Valida `keep` (>= 1, default 2) e `retire` do body.
|
|
20
|
+
* A aplicação ao vivo + auditoria (keys.rotated) acontecem dentro de svc.rotateKeys.
|
|
21
|
+
*/
|
|
22
|
+
export async function rotateNow(svc, body) {
|
|
23
|
+
const retire = body?.retire === true;
|
|
24
|
+
const keep = typeof body?.keep === 'number' && body.keep >= 1 ? Math.floor(body.keep) : 2;
|
|
25
|
+
const res = await svc.rotateKeys(keep, retire);
|
|
26
|
+
return { rotated: true, ...res };
|
|
27
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { AuthAccount } from '../accounts/account_store.js';
|
|
2
|
+
import type { AuditSink } from '../audit/audit_sink.js';
|
|
3
|
+
import type { ResolvedServerConfig } from '../define_config.js';
|
|
4
|
+
import type { SettingsCapability } from './runtime_settings.js';
|
|
5
|
+
/**
|
|
6
|
+
* `requireVerifiedEmail` efetivo (config ou runtime setting) E o store sabe dizer
|
|
7
|
+
* se o e-mail está verificado E a conta NÃO está verificada → bloqueia.
|
|
8
|
+
* Capability-probed: sem {@link EmailVerificationStatusCapability} a checagem é
|
|
9
|
+
* no-op (não bloqueia). Compartilhado pelos três fluxos de login (senha, magic
|
|
10
|
+
* link, passkey-first).
|
|
11
|
+
*
|
|
12
|
+
* Suporta `graceDays`: se a conta foi criada há menos de `graceDays` dias e ainda
|
|
13
|
+
* não verificou o e-mail, o login é permitido (mas a UI mostra um banner avisando).
|
|
14
|
+
*
|
|
15
|
+
* @param settings - SettingsCapability para ler o runtime setting. Quando
|
|
16
|
+
* ausente (chamadores que ainda não injetam o settings), faz fallback ao config.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isEmailUnverifiedBlock(cfg: ResolvedServerConfig, accountId: string, settings?: SettingsCapability): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Verifica se a senha da conta está vencida (password expiration).
|
|
21
|
+
* Capability-probed: sem `getPasswordChangedAt` → retorna false.
|
|
22
|
+
* Quando `password_expiration.enabled` é false ou a coluna não existe → false.
|
|
23
|
+
*
|
|
24
|
+
* @returns true se a senha expirou e deve ser trocada antes de completar o login.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isPasswordExpired(cfg: ResolvedServerConfig, accountId: string, settings: SettingsCapability): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Verifica se a conta está expirada por inatividade (account_expiration setting).
|
|
29
|
+
*
|
|
30
|
+
* "Última atividade" = timestamp do último `login.success` da conta no audit.
|
|
31
|
+
* Capability-probed: sem `audit.list` → retorna false (feature indisponível).
|
|
32
|
+
* Quando enabled=false ou sem audit queryável → false (no-op).
|
|
33
|
+
*
|
|
34
|
+
* @param audit - AuditSink. Quando ausente ou sem `list`, retorna false.
|
|
35
|
+
* @param accountId - ID da conta a verificar.
|
|
36
|
+
* @param settings - SettingsCapability para ler a setting `account_expiration`.
|
|
37
|
+
* @param logger - logger mínimo opcional. Quando presente, registra falhas de DB
|
|
38
|
+
* no caminho fail-safe (sem ele, o erro continua silencioso por compat).
|
|
39
|
+
* @returns true se a conta está expirada e o login deve ser bloqueado.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isAccountExpired(audit: AuditSink | null | undefined, accountId: string, settings: SettingsCapability, logger?: LoginAttemptLogger): Promise<boolean>;
|
|
42
|
+
/** Logger mínimo (subconjunto do logger do AdonisJS) para o caminho fail-safe. */
|
|
43
|
+
export interface LoginAttemptLogger {
|
|
44
|
+
warn(obj: unknown, msg?: string): void;
|
|
45
|
+
}
|
|
46
|
+
/** Entrada de uma tentativa de login por senha (keyed por email). */
|
|
47
|
+
export interface PasswordLoginInput {
|
|
48
|
+
email: string;
|
|
49
|
+
password: string;
|
|
50
|
+
/** IP da request (para auditoria + lockout). null quando indisponível. */
|
|
51
|
+
ip: string | null;
|
|
52
|
+
/**
|
|
53
|
+
* client_id da interaction OIDC, quando existir. Só é incluído no evento de
|
|
54
|
+
* auditoria nos fluxos que o têm (interaction); ausente no console de conta.
|
|
55
|
+
*/
|
|
56
|
+
clientId?: string | null;
|
|
57
|
+
/**
|
|
58
|
+
* SettingsCapability para ler o runtime setting de `require_verified_email`.
|
|
59
|
+
* Quando ausente, faz fallback ao valor de `cfg.login.requireVerifiedEmail`.
|
|
60
|
+
*/
|
|
61
|
+
settings?: SettingsCapability;
|
|
62
|
+
/**
|
|
63
|
+
* Logger mínimo opcional. Repassado aos caminhos fail-safe (ex.: checagem de
|
|
64
|
+
* expiração por inatividade) para que falhas de infra não fiquem silenciosas.
|
|
65
|
+
*/
|
|
66
|
+
logger?: LoginAttemptLogger;
|
|
67
|
+
}
|
|
68
|
+
/** Resultado de {@link attemptPasswordLogin}. */
|
|
69
|
+
export type PasswordLoginResult = {
|
|
70
|
+
ok: true;
|
|
71
|
+
account: AuthAccount;
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
locked: boolean;
|
|
75
|
+
retryAfterSec?: number;
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
unverified?: boolean;
|
|
78
|
+
unverifiedGraceDays?: number;
|
|
79
|
+
passwordExpired?: boolean;
|
|
80
|
+
accountExpired?: boolean;
|
|
81
|
+
account?: AuthAccount;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Sequência canônica de login por senha + bloqueio progressivo, compartilhada
|
|
85
|
+
* pelos dois fluxos que pedem senha (interaction OIDC e console de conta):
|
|
86
|
+
*
|
|
87
|
+
* 1. `lockout.isLocked(email)` — se travada, NÃO verifica a senha; devolve
|
|
88
|
+
* `{ ok: false, locked: true, retryAfterSec }` (o controller renderiza o erro).
|
|
89
|
+
* 2. `verifyCredentials(email, password)` — em falha: emite `login.failure`
|
|
90
|
+
* (com `clientId` apenas quando fornecido), registra a falha no lockout
|
|
91
|
+
* (`{ sink: cfg.audit, ip }`) e devolve `{ ok: false, locked: false }`.
|
|
92
|
+
* 3. Em sucesso: limpa o contador de falhas e devolve `{ ok: true, account }`.
|
|
93
|
+
*
|
|
94
|
+
* O evento `login.success` e a finalização da sessão/interaction ficam a cargo de
|
|
95
|
+
* cada controller (os fluxos diferem: MFA gate na interaction, sessão no console),
|
|
96
|
+
* assim como toda a renderização.
|
|
97
|
+
*/
|
|
98
|
+
export declare function attemptPasswordLogin(cfg: ResolvedServerConfig, input: PasswordLoginInput): Promise<PasswordLoginResult>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { supportsAccountStatus, supportsEmailVerificationStatus, supportsPasswordExpiration, } from '../accounts/account_store.js';
|
|
2
|
+
import { createAccountLockout } from './account_lockout.js';
|
|
3
|
+
import { resolveEffectiveRequireVerifiedEmail, resolveEffectiveRequireVerifiedEmailFull, resolveEffectivePasswordExpiration, resolveEffectiveAccountExpiration, resolveEffectiveLockout, } from './runtime_toggles.js';
|
|
4
|
+
/**
|
|
5
|
+
* `requireVerifiedEmail` efetivo (config ou runtime setting) E o store sabe dizer
|
|
6
|
+
* se o e-mail está verificado E a conta NÃO está verificada → bloqueia.
|
|
7
|
+
* Capability-probed: sem {@link EmailVerificationStatusCapability} a checagem é
|
|
8
|
+
* no-op (não bloqueia). Compartilhado pelos três fluxos de login (senha, magic
|
|
9
|
+
* link, passkey-first).
|
|
10
|
+
*
|
|
11
|
+
* Suporta `graceDays`: se a conta foi criada há menos de `graceDays` dias e ainda
|
|
12
|
+
* não verificou o e-mail, o login é permitido (mas a UI mostra um banner avisando).
|
|
13
|
+
*
|
|
14
|
+
* @param settings - SettingsCapability para ler o runtime setting. Quando
|
|
15
|
+
* ausente (chamadores que ainda não injetam o settings), faz fallback ao config.
|
|
16
|
+
*/
|
|
17
|
+
export async function isEmailUnverifiedBlock(cfg, accountId, settings) {
|
|
18
|
+
// Resolve o valor efetivo: runtime setting sobrescreve o config estático.
|
|
19
|
+
const requireVerified = settings
|
|
20
|
+
? await resolveEffectiveRequireVerifiedEmail(cfg.login?.requireVerifiedEmail ?? false, settings)
|
|
21
|
+
: (cfg.login?.requireVerifiedEmail ?? false);
|
|
22
|
+
if (!requireVerified)
|
|
23
|
+
return false;
|
|
24
|
+
if (!supportsEmailVerificationStatus(cfg.accountStore))
|
|
25
|
+
return false;
|
|
26
|
+
const verified = await cfg.accountStore.isEmailVerified(accountId);
|
|
27
|
+
if (verified)
|
|
28
|
+
return false;
|
|
29
|
+
// Grace period: se a setting tem graceDays e a conta foi criada recentemente,
|
|
30
|
+
// permite o login.
|
|
31
|
+
if (settings) {
|
|
32
|
+
const full = await resolveEffectiveRequireVerifiedEmailFull(cfg.login?.requireVerifiedEmail ?? false, settings);
|
|
33
|
+
if (full.graceDays > 0) {
|
|
34
|
+
// Busca a conta para checar created_at (se o store suportar).
|
|
35
|
+
const account = await cfg.accountStore.findById(accountId);
|
|
36
|
+
if (account) {
|
|
37
|
+
// Tenta ler `created_at` via duck-typing (não é parte do contrato AccountStore).
|
|
38
|
+
const raw = account;
|
|
39
|
+
const createdAt = raw.createdAt instanceof Date
|
|
40
|
+
? raw.createdAt
|
|
41
|
+
: typeof raw.createdAt === 'string'
|
|
42
|
+
? new Date(raw.createdAt)
|
|
43
|
+
: null;
|
|
44
|
+
if (createdAt) {
|
|
45
|
+
const graceCutoff = new Date(createdAt.getTime() + full.graceDays * 24 * 60 * 60 * 1000);
|
|
46
|
+
if (new Date() <= graceCutoff) {
|
|
47
|
+
return false; // dentro da janela de graça → não bloqueia
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Verifica se a senha da conta está vencida (password expiration).
|
|
57
|
+
* Capability-probed: sem `getPasswordChangedAt` → retorna false.
|
|
58
|
+
* Quando `password_expiration.enabled` é false ou a coluna não existe → false.
|
|
59
|
+
*
|
|
60
|
+
* @returns true se a senha expirou e deve ser trocada antes de completar o login.
|
|
61
|
+
*/
|
|
62
|
+
export async function isPasswordExpired(cfg, accountId, settings) {
|
|
63
|
+
const expiration = await resolveEffectivePasswordExpiration(settings);
|
|
64
|
+
if (!expiration.enabled)
|
|
65
|
+
return false;
|
|
66
|
+
if (!supportsPasswordExpiration(cfg.accountStore))
|
|
67
|
+
return false;
|
|
68
|
+
const changedAt = await cfg.accountStore.getPasswordChangedAt(accountId);
|
|
69
|
+
if (!changedAt) {
|
|
70
|
+
// Senha nunca trocada (coluna NULL = conta legacy) → considera vencida quando
|
|
71
|
+
// expiration está ligada (seguro: força a troca na 1ª vez).
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const maxAgeMs = expiration.maxAgeDays * 24 * 60 * 60 * 1000;
|
|
75
|
+
return Date.now() - changedAt.getTime() > maxAgeMs;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Verifica se a conta está expirada por inatividade (account_expiration setting).
|
|
79
|
+
*
|
|
80
|
+
* "Última atividade" = timestamp do último `login.success` da conta no audit.
|
|
81
|
+
* Capability-probed: sem `audit.list` → retorna false (feature indisponível).
|
|
82
|
+
* Quando enabled=false ou sem audit queryável → false (no-op).
|
|
83
|
+
*
|
|
84
|
+
* @param audit - AuditSink. Quando ausente ou sem `list`, retorna false.
|
|
85
|
+
* @param accountId - ID da conta a verificar.
|
|
86
|
+
* @param settings - SettingsCapability para ler a setting `account_expiration`.
|
|
87
|
+
* @param logger - logger mínimo opcional. Quando presente, registra falhas de DB
|
|
88
|
+
* no caminho fail-safe (sem ele, o erro continua silencioso por compat).
|
|
89
|
+
* @returns true se a conta está expirada e o login deve ser bloqueado.
|
|
90
|
+
*/
|
|
91
|
+
export async function isAccountExpired(audit, accountId, settings, logger) {
|
|
92
|
+
const expiration = await resolveEffectiveAccountExpiration(settings);
|
|
93
|
+
if (!expiration.enabled)
|
|
94
|
+
return false;
|
|
95
|
+
// Sem audit queryável → feature indisponível → não bloqueia.
|
|
96
|
+
if (typeof audit?.list !== 'function')
|
|
97
|
+
return false;
|
|
98
|
+
try {
|
|
99
|
+
// Busca o último login.success da conta (ordem desc, limit 1).
|
|
100
|
+
const result = await audit.list({ type: 'login.success', subject: accountId, page: 1, limit: 1 });
|
|
101
|
+
if (result.data.length === 0) {
|
|
102
|
+
// Nunca logou → considera ativa (conta nova, não inativa).
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const lastLogin = result.data[0];
|
|
106
|
+
const createdAt = lastLogin.createdAt;
|
|
107
|
+
if (!createdAt)
|
|
108
|
+
return false;
|
|
109
|
+
const lastMs = createdAt instanceof Date ? createdAt.getTime() : Date.parse(createdAt);
|
|
110
|
+
if (!Number.isFinite(lastMs))
|
|
111
|
+
return false;
|
|
112
|
+
const cutoffMs = Date.now() - expiration.inactiveDays * 24 * 60 * 60 * 1000;
|
|
113
|
+
return lastMs < cutoffMs;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
// Fail-safe: erro de DB → não bloqueia. Loga: enquanto a query de audit estiver
|
|
117
|
+
// quebrada, a expiração por inatividade não é aplicada (admin precisa saber).
|
|
118
|
+
logger?.warn({ err, accountId }, 'authkit: account-expiration check failed (audit query error) — not blocking login (fail-safe); inactivity enforcement is disabled until the audit store recovers.');
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Sequência canônica de login por senha + bloqueio progressivo, compartilhada
|
|
124
|
+
* pelos dois fluxos que pedem senha (interaction OIDC e console de conta):
|
|
125
|
+
*
|
|
126
|
+
* 1. `lockout.isLocked(email)` — se travada, NÃO verifica a senha; devolve
|
|
127
|
+
* `{ ok: false, locked: true, retryAfterSec }` (o controller renderiza o erro).
|
|
128
|
+
* 2. `verifyCredentials(email, password)` — em falha: emite `login.failure`
|
|
129
|
+
* (com `clientId` apenas quando fornecido), registra a falha no lockout
|
|
130
|
+
* (`{ sink: cfg.audit, ip }`) e devolve `{ ok: false, locked: false }`.
|
|
131
|
+
* 3. Em sucesso: limpa o contador de falhas e devolve `{ ok: true, account }`.
|
|
132
|
+
*
|
|
133
|
+
* O evento `login.success` e a finalização da sessão/interaction ficam a cargo de
|
|
134
|
+
* cada controller (os fluxos diferem: MFA gate na interaction, sessão no console),
|
|
135
|
+
* assim como toda a renderização.
|
|
136
|
+
*/
|
|
137
|
+
export async function attemptPasswordLogin(cfg, input) {
|
|
138
|
+
const { email, password, ip } = input;
|
|
139
|
+
// Lockout efetivo: runtime setting `lockout` sobrescreve o config estático.
|
|
140
|
+
// FAIL-SAFE → cfg.lockout (resolveEffectiveLockout já trata erros internamente).
|
|
141
|
+
// `store` é infra e permanece no config estático (não vem da setting).
|
|
142
|
+
const effectiveLockout = input.settings
|
|
143
|
+
? { ...(await resolveEffectiveLockout(input.settings, cfg.lockout)), store: cfg.lockout.store }
|
|
144
|
+
: cfg.lockout;
|
|
145
|
+
const lockout = createAccountLockout(effectiveLockout);
|
|
146
|
+
// Bloqueio progressivo (keyed por email): se travada, não verifica a senha.
|
|
147
|
+
const lock = await lockout.isLocked(email);
|
|
148
|
+
if (lock.locked) {
|
|
149
|
+
return { ok: false, locked: true, retryAfterSec: lock.retryAfterSec };
|
|
150
|
+
}
|
|
151
|
+
const account = await cfg.accountStore.verifyCredentials(email, password);
|
|
152
|
+
if (!account) {
|
|
153
|
+
// `clientId` só entra no evento quando o fluxo o fornece (interaction).
|
|
154
|
+
await cfg.audit?.record(input.clientId !== undefined
|
|
155
|
+
? { type: 'login.failure', email, ip, clientId: input.clientId }
|
|
156
|
+
: { type: 'login.failure', email, ip });
|
|
157
|
+
await lockout.recordFailure(email, { sink: cfg.audit, ip });
|
|
158
|
+
return { ok: false, locked: false };
|
|
159
|
+
}
|
|
160
|
+
// Conta desabilitada: rejeita o login (mesmo com senha correta). A capacidade é
|
|
161
|
+
// opcional — só checada quando o store a implementa. Emite `login.failure` (com
|
|
162
|
+
// motivo `disabled` no metadata) e NÃO registra falha no lockout (não é tentativa
|
|
163
|
+
// de adivinhar senha).
|
|
164
|
+
if (supportsAccountStatus(cfg.accountStore) && (await cfg.accountStore.isDisabled(account.id))) {
|
|
165
|
+
await cfg.audit?.record(input.clientId !== undefined
|
|
166
|
+
? { type: 'login.failure', email, ip, clientId: input.clientId, metadata: { reason: 'disabled' } }
|
|
167
|
+
: { type: 'login.failure', email, ip, metadata: { reason: 'disabled' } });
|
|
168
|
+
return { ok: false, locked: false, disabled: true };
|
|
169
|
+
}
|
|
170
|
+
// E-mail não verificado (LGPD/compliance): rejeita o login se a política exige
|
|
171
|
+
// verificação E o store sabe responder. Não conta como falha de senha (a senha
|
|
172
|
+
// estava correta) — limpa o contador e emite login.failure com motivo `unverified`.
|
|
173
|
+
if (await isEmailUnverifiedBlock(cfg, account.id, input.settings)) {
|
|
174
|
+
await lockout.clearFailures(email);
|
|
175
|
+
await cfg.audit?.record(input.clientId !== undefined
|
|
176
|
+
? { type: 'login.failure', email, ip, clientId: input.clientId, metadata: { reason: 'unverified' } }
|
|
177
|
+
: { type: 'login.failure', email, ip, metadata: { reason: 'unverified' } });
|
|
178
|
+
return { ok: false, locked: false, unverified: true };
|
|
179
|
+
}
|
|
180
|
+
// Senha expirada (password expiration): se a senha está vencida, sinaliza ao
|
|
181
|
+
// controller para forçar a troca ANTES de completar o login.
|
|
182
|
+
// Capability-probed: sem `getPasswordChangedAt` ou sem a setting → no-op.
|
|
183
|
+
if (input.settings) {
|
|
184
|
+
const expired = await isPasswordExpired(cfg, account.id, input.settings);
|
|
185
|
+
if (expired) {
|
|
186
|
+
// Não é uma falha de credenciais — limpa o contador.
|
|
187
|
+
await lockout.clearFailures(email);
|
|
188
|
+
await cfg.audit?.record(input.clientId !== undefined
|
|
189
|
+
? { type: 'password.expired_change_forced', accountId: account.id, email, ip, clientId: input.clientId }
|
|
190
|
+
: { type: 'password.expired_change_forced', accountId: account.id, email, ip });
|
|
191
|
+
return { ok: false, locked: false, passwordExpired: true, account: account };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Conta expirada por inatividade (account_expiration setting): bloqueia o login
|
|
195
|
+
// se a conta não teve login.success há mais de `inactiveDays` dias (via audit).
|
|
196
|
+
// Capability-probed: sem audit.list ou sem setting → no-op.
|
|
197
|
+
// Reativação: fluxo de reset de senha (link exibido pelo controller na mensagem).
|
|
198
|
+
if (input.settings) {
|
|
199
|
+
const expired = await isAccountExpired(cfg.audit, account.id, input.settings, input.logger);
|
|
200
|
+
if (expired) {
|
|
201
|
+
await lockout.clearFailures(email);
|
|
202
|
+
await cfg.audit?.record(input.clientId !== undefined
|
|
203
|
+
? { type: 'account.expired_login_blocked', accountId: account.id, email, ip, clientId: input.clientId }
|
|
204
|
+
: { type: 'account.expired_login_blocked', accountId: account.id, email, ip });
|
|
205
|
+
return { ok: false, locked: false, accountExpired: true };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Senha correta: limpa o contador de falhas (o lockout protege a etapa de senha).
|
|
209
|
+
await lockout.clearFailures(email);
|
|
210
|
+
return { ok: true, account };
|
|
211
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { ResolvedServerConfig } from '../define_config.js';
|
|
3
|
+
/** Dados de um login bem-sucedido a auditar/notificar. */
|
|
4
|
+
export interface LoginSuccessInput {
|
|
5
|
+
accountId: string;
|
|
6
|
+
email?: string | null;
|
|
7
|
+
ip?: string | null;
|
|
8
|
+
clientId?: string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Sinal "dispositivo já confiável" quando o caller JÁ resolveu o cookie de
|
|
11
|
+
* confiança (ex.: o controller pulou o MFA por causa dele). Quando omitido, a
|
|
12
|
+
* notificação resolve o cookie por conta própria (best-effort). `true` =
|
|
13
|
+
* dispositivo conhecido → NÃO notifica novo dispositivo.
|
|
14
|
+
*/
|
|
15
|
+
trustedDevice?: boolean;
|
|
16
|
+
/** Metadata extra a anexar ao evento login.success (ex.: { mfa: 'totp' }). */
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Centraliza o pós-login bem-sucedido: registra o evento `login.success` e dispara
|
|
21
|
+
* (best-effort) o alerta de NOVO acesso quando o IP nunca foi visto para a conta.
|
|
22
|
+
*
|
|
23
|
+
* É fire-and-forget e FAIL-SAFE: a notificação roda DEPOIS do audit e qualquer erro
|
|
24
|
+
* é engolido — NUNCA bloqueia nem lança no caminho do login. Substitui as chamadas
|
|
25
|
+
* `cfg.audit?.record({ type: 'login.success', ... })` espalhadas pelos controllers.
|
|
26
|
+
*/
|
|
27
|
+
export declare function notifyLoginSuccess(ctx: HttpContext, cfg: ResolvedServerConfig, input: LoginSuccessInput): Promise<void>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { sendNewLoginEmail, sendNewDeviceLoginEmail } from './default_mailer.js';
|
|
2
|
+
import { TRUSTED_DEVICE_COOKIE, isTrustedDeviceValid } from './trusted_device.js';
|
|
3
|
+
/**
|
|
4
|
+
* Centraliza o pós-login bem-sucedido: registra o evento `login.success` e dispara
|
|
5
|
+
* (best-effort) o alerta de NOVO acesso quando o IP nunca foi visto para a conta.
|
|
6
|
+
*
|
|
7
|
+
* É fire-and-forget e FAIL-SAFE: a notificação roda DEPOIS do audit e qualquer erro
|
|
8
|
+
* é engolido — NUNCA bloqueia nem lança no caminho do login. Substitui as chamadas
|
|
9
|
+
* `cfg.audit?.record({ type: 'login.success', ... })` espalhadas pelos controllers.
|
|
10
|
+
*/
|
|
11
|
+
export async function notifyLoginSuccess(ctx, cfg, input) {
|
|
12
|
+
const { accountId, email, ip, clientId, metadata, trustedDevice } = input;
|
|
13
|
+
// User-agent da request: anexado ao `login.success` em `metadata.userAgent` para
|
|
14
|
+
// dar contexto de dispositivo às sessões (o admin/account o lê via join por
|
|
15
|
+
// accountId+loginTs). Sem migração — usa a coluna JSON `metadata` já existente.
|
|
16
|
+
const ua = ctx.request.header?.('user-agent') ?? null;
|
|
17
|
+
// 1) Audit do login.success (mesmo formato de antes + userAgent em metadata).
|
|
18
|
+
await cfg.audit?.record({
|
|
19
|
+
type: 'login.success',
|
|
20
|
+
accountId,
|
|
21
|
+
email: email ?? null,
|
|
22
|
+
ip: ip ?? null,
|
|
23
|
+
clientId: clientId ?? null,
|
|
24
|
+
metadata: ua ? { ...(metadata ?? {}), userAgent: ua } : metadata,
|
|
25
|
+
});
|
|
26
|
+
// 2) Sinal de DISPOSITIVO NOVO: login sem cookie de confiança válido para a
|
|
27
|
+
// conta. Auditamos `login.new_device` (mesmo sem mail configurado) e, se
|
|
28
|
+
// `notifications.newDeviceEmail`, enviamos o alerta. Resolvido aqui (síncrono
|
|
29
|
+
// com o audit) porque depende do cookie da REQUEST atual; o e-mail em si é
|
|
30
|
+
// fire-and-forget. `trustedDevice === true` (o caller já decidiu) pula tudo.
|
|
31
|
+
const onNewDevice = trustedDevice === true ? false : !hasTrustedDeviceCookie(ctx, accountId);
|
|
32
|
+
if (onNewDevice) {
|
|
33
|
+
await cfg.audit?.record({
|
|
34
|
+
type: 'login.new_device',
|
|
35
|
+
accountId,
|
|
36
|
+
email: email ?? null,
|
|
37
|
+
ip: ip ?? null,
|
|
38
|
+
clientId: clientId ?? null,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const userAgent = ua;
|
|
42
|
+
// 3) Alertas por e-mail (fire-and-forget): nunca propaga erro pro login.
|
|
43
|
+
if (!cfg.notifications.newLoginEmail && !(onNewDevice && cfg.notifications.newDeviceEmail))
|
|
44
|
+
return;
|
|
45
|
+
void (async () => {
|
|
46
|
+
// Resolve o e-mail quando o caller não o forneceu (ex.: fluxo de MFA só tem o
|
|
47
|
+
// accountId em escopo). Best-effort.
|
|
48
|
+
let resolvedEmail = email ?? null;
|
|
49
|
+
if (!resolvedEmail) {
|
|
50
|
+
resolvedEmail = (await cfg.accountStore.findById(accountId))?.email ?? null;
|
|
51
|
+
}
|
|
52
|
+
if (cfg.notifications.newLoginEmail) {
|
|
53
|
+
await maybeNotifyNewLogin(ctx, cfg, { accountId, email: resolvedEmail, ip: ip ?? null });
|
|
54
|
+
}
|
|
55
|
+
if (onNewDevice && cfg.notifications.newDeviceEmail && resolvedEmail) {
|
|
56
|
+
await notifyNewDevice(ctx, cfg, {
|
|
57
|
+
accountId,
|
|
58
|
+
email: resolvedEmail,
|
|
59
|
+
ip: ip ?? null,
|
|
60
|
+
userAgent,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
})().catch((error) => {
|
|
64
|
+
// Defensivo: o próprio handler de erro nunca pode lançar (logger pode faltar
|
|
65
|
+
// em contextos mínimos). Best-effort.
|
|
66
|
+
ctx.logger?.error?.({ err: error, accountId }, 'authkit: falha no alerta de login');
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `true` se há um cookie de dispositivo confiável VÁLIDO (estrutura íntegra, conta
|
|
71
|
+
* casa, não expirou) para `accountId`. Best-effort: qualquer erro de leitura →
|
|
72
|
+
* `false` (tratado como dispositivo novo). NÃO checa o re-enrollment de MFA
|
|
73
|
+
* (mfaEnabledAt) — o sinal de "dispositivo conhecido" é a mera presença de um
|
|
74
|
+
* cookie de confiança válido, independente do estado do MFA.
|
|
75
|
+
*/
|
|
76
|
+
function hasTrustedDeviceCookie(ctx, accountId) {
|
|
77
|
+
try {
|
|
78
|
+
const payload = ctx.request.encryptedCookie?.(TRUSTED_DEVICE_COOKIE);
|
|
79
|
+
return isTrustedDeviceValid(payload, { accountId });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Envia o alerta de NOVO DISPOSITIVO: usa o hook `mail.onNewDeviceLogin` (override
|
|
87
|
+
* do host) ou o mailer default. Best-effort — qualquer erro é engolido (já estamos
|
|
88
|
+
* dentro do fire-and-forget de {@link notifyLoginSuccess}).
|
|
89
|
+
*/
|
|
90
|
+
async function notifyNewDevice(ctx, cfg, data) {
|
|
91
|
+
const timestamp = new Date().toISOString();
|
|
92
|
+
if (cfg.mail?.onNewDeviceLogin) {
|
|
93
|
+
await cfg.mail.onNewDeviceLogin({
|
|
94
|
+
account: { id: data.accountId, email: data.email },
|
|
95
|
+
ip: data.ip,
|
|
96
|
+
userAgent: data.userAgent,
|
|
97
|
+
timestamp,
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await sendNewDeviceLoginEmail(ctx, {
|
|
102
|
+
email: data.email,
|
|
103
|
+
ip: data.ip,
|
|
104
|
+
userAgent: data.userAgent,
|
|
105
|
+
when: timestamp,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Verifica se o IP é novo para a conta (consultando o audit sink por
|
|
110
|
+
* `login.success` do subject) e, se for, envia o e-mail de alerta + audita
|
|
111
|
+
* `login.new_ip_notified`. Degrada para no-op quando: sem IP, sem e-mail, sem
|
|
112
|
+
* sink consultável (`list`), ou já houve um login.success deste IP antes.
|
|
113
|
+
*/
|
|
114
|
+
async function maybeNotifyNewLogin(ctx, cfg, data) {
|
|
115
|
+
const { accountId, email, ip } = data;
|
|
116
|
+
if (!ip || !email)
|
|
117
|
+
return;
|
|
118
|
+
// Sem consulta do histórico não dá pra decidir se o IP é novo → no-op.
|
|
119
|
+
if (typeof cfg.audit?.list !== 'function')
|
|
120
|
+
return;
|
|
121
|
+
// Lê o histórico de login.success do subject. O evento ATUAL já foi gravado por
|
|
122
|
+
// notifyLoginSuccess, então um IP visto antes aparece com count >= 2 para o IP.
|
|
123
|
+
// Buscamos uma página ampla e contamos as ocorrências deste IP.
|
|
124
|
+
const page = await cfg.audit.list({
|
|
125
|
+
type: 'login.success',
|
|
126
|
+
subject: accountId,
|
|
127
|
+
page: 1,
|
|
128
|
+
limit: 200,
|
|
129
|
+
});
|
|
130
|
+
const sameIpCount = page.data.filter((e) => e.ip === ip).length;
|
|
131
|
+
// > 1 significa que já havia um login.success deste IP antes do atual → não é novo.
|
|
132
|
+
if (sameIpCount > 1)
|
|
133
|
+
return;
|
|
134
|
+
const when = new Date().toISOString();
|
|
135
|
+
await sendNewLoginEmail(ctx, { email, ip, when });
|
|
136
|
+
await cfg.audit?.record({
|
|
137
|
+
type: 'login.new_ip_notified',
|
|
138
|
+
accountId,
|
|
139
|
+
email,
|
|
140
|
+
ip,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import '../augmentations.js';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
import type { NextFn } from '@adonisjs/core/types/http';
|
|
4
|
+
export declare const ACCOUNT_SESSION_KEY = "account_user_id";
|
|
5
|
+
export default class AccountAuthMiddleware {
|
|
6
|
+
handle(ctx: HttpContext, next: NextFn): Promise<any>;
|
|
7
|
+
}
|