@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,9 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
/** Gera um Personal Access Token opaco no formato `pat_<base64url>`. */
|
|
3
|
+
export function generatePatToken() {
|
|
4
|
+
return 'pat_' + randomBytes(32).toString('base64url');
|
|
5
|
+
}
|
|
6
|
+
/** SHA-256 (hex) do token — é o que se persiste; o token cru nunca é guardado. */
|
|
7
|
+
export function hashPatToken(token) {
|
|
8
|
+
return createHash('sha256').update(token).digest('hex');
|
|
9
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as oidc from 'oidc-provider';
|
|
2
|
+
import type { ResolvedServerConfig } from '../define_config.js';
|
|
3
|
+
export interface BuildProviderOptions {
|
|
4
|
+
/** APP_KEY do consumidor; usado p/ derivar cookies.keys se não houver. */
|
|
5
|
+
appKey: string;
|
|
6
|
+
findAccount: (ctx: any, sub: string, token?: any) => Promise<any>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Holder mutável dos TTLs de sessão OIDC. Lido de forma SÍNCRONA pelo TTL function do
|
|
10
|
+
* oidc-provider (que não aceita async). Atualizado assincronamente após write/reset
|
|
11
|
+
* da setting `session_policy` via `updateSessionTtlHolder`.
|
|
12
|
+
*
|
|
13
|
+
* Valores em SEGUNDOS. `rememberSec` é o TTL da sessão persistente (remember-me ON).
|
|
14
|
+
* `transientSec` é o TTL da sessão transiente (remember-me OFF ou rememberEnabled=false).
|
|
15
|
+
*/
|
|
16
|
+
export interface SessionTtlHolder {
|
|
17
|
+
/** TTL da sessão persistente (remember-me ON). Segundos. */
|
|
18
|
+
rememberSec: number;
|
|
19
|
+
/** TTL da sessão transiente (browser session). Segundos. */
|
|
20
|
+
transientSec: number;
|
|
21
|
+
}
|
|
22
|
+
/** Atualiza o holder mutável do TTL de sessão com os valores da setting. */
|
|
23
|
+
export declare function updateSessionTtlHolder(holder: SessionTtlHolder, policy: {
|
|
24
|
+
rememberDays: number;
|
|
25
|
+
defaultSessionHours: number;
|
|
26
|
+
}): void;
|
|
27
|
+
/**
|
|
28
|
+
* Holder mutável dos TTLs de access/id/refresh tokens OIDC. Lido de forma
|
|
29
|
+
* SÍNCRONA pelo TTL function do oidc-provider. Atualizado assincronamente após
|
|
30
|
+
* write/reset da setting `token_ttl` via `updateTokenTtlHolder`.
|
|
31
|
+
*
|
|
32
|
+
* Valores em SEGUNDOS.
|
|
33
|
+
*/
|
|
34
|
+
export interface TokenTtlHolder {
|
|
35
|
+
accessTokenSec: number;
|
|
36
|
+
idTokenSec: number;
|
|
37
|
+
refreshTokenSec: number;
|
|
38
|
+
}
|
|
39
|
+
/** Atualiza o holder mutável de TTL de tokens com os valores da setting. */
|
|
40
|
+
export declare function updateTokenTtlHolder(holder: TokenTtlHolder, ttl: {
|
|
41
|
+
accessTokenSec: number;
|
|
42
|
+
idTokenSec: number;
|
|
43
|
+
refreshTokenSec: number;
|
|
44
|
+
}): void;
|
|
45
|
+
export declare function buildProvider(config: ResolvedServerConfig, options: BuildProviderOptions, sessionTtlHolder?: SessionTtlHolder, tokenTtlHolder?: TokenTtlHolder): oidc.Provider;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import * as oidc from 'oidc-provider';
|
|
2
|
+
import { createDeviceSources } from './device_sources.js';
|
|
3
|
+
import { createLogoutSources } from './logout_sources.js';
|
|
4
|
+
/** Atualiza o holder mutável do TTL de sessão com os valores da setting. */
|
|
5
|
+
export function updateSessionTtlHolder(holder, policy) {
|
|
6
|
+
holder.rememberSec = Math.max(1, Math.floor(policy.rememberDays * 86400));
|
|
7
|
+
holder.transientSec = Math.max(1, Math.floor(policy.defaultSessionHours * 3600));
|
|
8
|
+
}
|
|
9
|
+
/** Atualiza o holder mutável de TTL de tokens com os valores da setting. */
|
|
10
|
+
export function updateTokenTtlHolder(holder, ttl) {
|
|
11
|
+
holder.accessTokenSec = Math.max(1, ttl.accessTokenSec);
|
|
12
|
+
holder.idTokenSec = Math.max(1, ttl.idTokenSec);
|
|
13
|
+
holder.refreshTokenSec = Math.max(1, ttl.refreshTokenSec);
|
|
14
|
+
}
|
|
15
|
+
export function buildProvider(config, options, sessionTtlHolder, tokenTtlHolder) {
|
|
16
|
+
const cookieKeys = config.cookieKeys.length ? config.cookieKeys : [options.appKey];
|
|
17
|
+
// OIDC Dynamic Client Registration (RFC 7591/7592). Só montamos as chaves de feature
|
|
18
|
+
// quando habilitado — desligado (default), o oidc-provider não expõe o endpoint /reg.
|
|
19
|
+
// O `initialAccessToken`: string => valida o bearer contra esse valor estático; ausente
|
|
20
|
+
// => `false` (registro ABERTO; raramente desejável em prod). Clients registrados aqui
|
|
21
|
+
// são persistidos automaticamente pelo MESMO AdapterClass, coexistindo com os estáticos.
|
|
22
|
+
const dynReg = config.dynamicRegistration;
|
|
23
|
+
const registrationFeatures = dynReg.enabled
|
|
24
|
+
? {
|
|
25
|
+
registration: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
initialAccessToken: dynReg.initialAccessToken ?? false,
|
|
28
|
+
},
|
|
29
|
+
...(dynReg.management ? { registrationManagement: { enabled: true } } : {}),
|
|
30
|
+
}
|
|
31
|
+
: {};
|
|
32
|
+
// Device Authorization Grant (RFC 8628). Quando ligado, montamos a feature com
|
|
33
|
+
// as três sources de UI i18n-izadas (entrada/confirmação/sucesso do user-code).
|
|
34
|
+
const deviceFlowFeatures = config.deviceFlow.enabled
|
|
35
|
+
? { deviceFlow: { enabled: true, ...createDeviceSources(config.messages) } }
|
|
36
|
+
: {};
|
|
37
|
+
// DPoP (RFC 9449). A chave EXATA do oidc-provider v9 é `dPoP`.
|
|
38
|
+
const dpopFeatures = config.dpop.enabled ? { dPoP: { enabled: true } } : {};
|
|
39
|
+
// PAR (RFC 9126).
|
|
40
|
+
const parFeatures = config.par.enabled
|
|
41
|
+
? {
|
|
42
|
+
pushedAuthorizationRequests: {
|
|
43
|
+
enabled: true,
|
|
44
|
+
requirePushedAuthorizationRequests: config.par.requirePushedAuthorizationRequests,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
: {};
|
|
48
|
+
// Access Tokens RFC 9068 (JWT) via Resource Indicators (RFC 8707). Só montamos a
|
|
49
|
+
// feature quando ALGUM AT deve ser JWT — caso contrário (default opaque) o
|
|
50
|
+
// oidc-provider mantém o comportamento atual (AT opaco introspecionável) intocado.
|
|
51
|
+
//
|
|
52
|
+
// Um JWT AT no oidc-provider SEMPRE exige um resource indicator com `aud`: o
|
|
53
|
+
// `defaultResource` injeta a resource default (o `audience`, default issuer) quando
|
|
54
|
+
// o client não pede `resource` explicitamente, e o `getResourceServerInfo` descreve
|
|
55
|
+
// a API (scope/audience/formato/TTL) — onde `accessTokenFormat: 'jwt'` faz o token
|
|
56
|
+
// sair como JWS `typ: at+jwt` assinado com a chave corrente do JWKS.
|
|
57
|
+
const at = config.accessTokens;
|
|
58
|
+
const allScopes = ['openid', 'profile', 'email', 'offline_access', 'roles'];
|
|
59
|
+
const resourceIndicatorFeatures = at.anyJwt
|
|
60
|
+
? {
|
|
61
|
+
resourceIndicators: {
|
|
62
|
+
enabled: true,
|
|
63
|
+
defaultResource: async (_ctx, _client, oneOf) => {
|
|
64
|
+
// Nas trocas (code/refresh/device), o provider passa `oneOf` com as
|
|
65
|
+
// resources já concedidas — devolvemos para não falhar a request.
|
|
66
|
+
if (oneOf)
|
|
67
|
+
return oneOf;
|
|
68
|
+
// Authorize/sem resource explícito: liga ao resource default (modo simples).
|
|
69
|
+
return at.audience;
|
|
70
|
+
},
|
|
71
|
+
useGrantedResource: async () => true,
|
|
72
|
+
getResourceServerInfo: (_ctx, resourceIndicator, _client) => {
|
|
73
|
+
const rc = at.resources[resourceIndicator];
|
|
74
|
+
const format = rc?.format ?? (resourceIndicator === at.audience ? at.format : 'opaque');
|
|
75
|
+
const audience = rc?.audience ?? resourceIndicator;
|
|
76
|
+
const scope = (rc?.scopes ?? allScopes).join(' ');
|
|
77
|
+
const info = {
|
|
78
|
+
scope,
|
|
79
|
+
audience,
|
|
80
|
+
accessTokenFormat: format,
|
|
81
|
+
};
|
|
82
|
+
const ttl = rc?.expiresIn;
|
|
83
|
+
if (ttl !== undefined)
|
|
84
|
+
info.accessTokenTTL = ttl;
|
|
85
|
+
if (format === 'jwt') {
|
|
86
|
+
// Assina com a chave de assinatura corrente do keystore (mesma do JWKS).
|
|
87
|
+
info.jwt = { sign: { alg: config.jwks.keys[0]?.alg ?? 'RS256' } };
|
|
88
|
+
}
|
|
89
|
+
return info;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
: {};
|
|
94
|
+
const provider = new oidc.Provider(config.issuer, {
|
|
95
|
+
adapter: config.AdapterClass,
|
|
96
|
+
clients: config.clients.map((c) => ({
|
|
97
|
+
client_id: c.clientId,
|
|
98
|
+
client_secret: c.clientSecret,
|
|
99
|
+
redirect_uris: c.redirectUris,
|
|
100
|
+
post_logout_redirect_uris: c.postLogoutRedirectUris ?? [],
|
|
101
|
+
grant_types: c.grants ?? ['authorization_code', 'refresh_token'],
|
|
102
|
+
response_types: (c.grants ?? ['authorization_code']).includes('authorization_code')
|
|
103
|
+
? ['code']
|
|
104
|
+
: [],
|
|
105
|
+
token_endpoint_auth_method: c.tokenEndpointAuthMethod ?? (c.clientSecret ? 'client_secret_basic' : 'none'),
|
|
106
|
+
// OIDC Back-Channel Logout: só envia as chaves quando o client as declara,
|
|
107
|
+
// p/ não forçar metadata vazio em clients que não usam o recurso.
|
|
108
|
+
...(c.backchannelLogoutUri ? { backchannel_logout_uri: c.backchannelLogoutUri } : {}),
|
|
109
|
+
...(c.backchannelLogoutSessionRequired !== undefined
|
|
110
|
+
? { backchannel_logout_session_required: c.backchannelLogoutSessionRequired }
|
|
111
|
+
: {}),
|
|
112
|
+
})),
|
|
113
|
+
findAccount: options.findAccount,
|
|
114
|
+
jwks: config.jwks,
|
|
115
|
+
cookies: {
|
|
116
|
+
keys: cookieKeys,
|
|
117
|
+
// O oidc-provider define o path do cookie _interaction como
|
|
118
|
+
// `/auth/interaction/<uid>` (exato) via `...cookieOptions` que
|
|
119
|
+
// SOBRESCREVE o path explícito. Sem `path: '/'` o cookie ficaria
|
|
120
|
+
// restrito ao path exato da tela de interaction e NÃO seria enviado
|
|
121
|
+
// pelo browser no POST de subpaths como `.../consent` ou `.../login`.
|
|
122
|
+
// Definir path: '/' na família "short" garante que os cookies de
|
|
123
|
+
// interaction e resume sejam enviados em todos os endpoints OIDC/auth.
|
|
124
|
+
short: { path: '/' },
|
|
125
|
+
long: { path: '/' },
|
|
126
|
+
},
|
|
127
|
+
// conformIdTokenClaims=false: caso contrário (default v9), no fluxo Authorization Code
|
|
128
|
+
// — onde também é emitido um Access Token — o oidc-provider mascara o ID token para
|
|
129
|
+
// apenas o escopo `openid` (só `sub`), removendo email/profile/roles do ID token.
|
|
130
|
+
// O client valida o ID TOKEN, então precisamos que as claims configuradas cheguem nele.
|
|
131
|
+
conformIdTokenClaims: false,
|
|
132
|
+
// DECISÃO DE SEGURANÇA (least privilege, 2026-06-09 — fix do achado L3):
|
|
133
|
+
// a claim de roles globais e as claims de org (`org_*`) ficam num escopo `roles`
|
|
134
|
+
// DEDICADO — fora do `profile`. O `profile` volta a ser só identidade (name/picture),
|
|
135
|
+
// de modo que QUALQUER client (inclusive third-party/dynamic registration futuro) que
|
|
136
|
+
// peça `scope=profile` NÃO recebe mais dados de autorização interna do usuário.
|
|
137
|
+
//
|
|
138
|
+
// Defesa em duas camadas:
|
|
139
|
+
// 1) Escopo dedicado `roles` (aqui) — roles/org_* só são candidatas a aparecer no
|
|
140
|
+
// token quando o client solicita explicitamente `scope=roles`.
|
|
141
|
+
// 2) Gate first-party no `findAccount.claims()` (ver oidc_service.ts) — mesmo pedindo
|
|
142
|
+
// `scope=roles`, um client third-party NUNCA tem essas claims emitidas. Só clients
|
|
143
|
+
// listados em `branding.firstParty` recebem.
|
|
144
|
+
//
|
|
145
|
+
// O authkit-client passa a incluir `roles` no default de scopes, então consumidores
|
|
146
|
+
// first-party continuam recebendo `hasGlobalRole`/org sem mudança de comportamento.
|
|
147
|
+
claims: {
|
|
148
|
+
openid: ['sub'],
|
|
149
|
+
profile: ['name', 'picture'],
|
|
150
|
+
email: ['email', 'email_verified'],
|
|
151
|
+
roles: [config.globalRolesClaim, 'org_id', 'org_slug', 'org_role'],
|
|
152
|
+
},
|
|
153
|
+
scopes: ['openid', 'profile', 'email', 'offline_access', 'roles'],
|
|
154
|
+
// Permite que o parametro `audience` (hint de intencao do client, ex.: 'advisor')
|
|
155
|
+
// sobreviva ao authorize e fique disponivel em interactionDetails().params.audience.
|
|
156
|
+
extraParams: ['audience'],
|
|
157
|
+
pkce: { methods: ['S256'], required: () => true },
|
|
158
|
+
rotateRefreshToken: true,
|
|
159
|
+
features: {
|
|
160
|
+
devInteractions: { enabled: false },
|
|
161
|
+
// RP-initiated logout com splash de marca que auto-confirma (sem a tela
|
|
162
|
+
// default em inglês "Do you want to sign-out from…?").
|
|
163
|
+
rpInitiatedLogout: { enabled: true, ...createLogoutSources(config.messages) },
|
|
164
|
+
// OIDC Back-Channel Logout: o oidc-provider POSTa um logout_token para o
|
|
165
|
+
// `backchannel_logout_uri` de cada RP quando a sessão/grant é encerrada.
|
|
166
|
+
backchannelLogout: { enabled: true },
|
|
167
|
+
revocation: { enabled: true },
|
|
168
|
+
introspection: { enabled: true },
|
|
169
|
+
...registrationFeatures,
|
|
170
|
+
...deviceFlowFeatures,
|
|
171
|
+
...dpopFeatures,
|
|
172
|
+
...parFeatures,
|
|
173
|
+
...resourceIndicatorFeatures,
|
|
174
|
+
},
|
|
175
|
+
// Step-up auth (acr_values): anuncia os acr suportados para que clients possam
|
|
176
|
+
// solicitá-los. A exigência efetiva do 2º fator acontece na interaction de login.
|
|
177
|
+
acrValues: config.stepUp.acrValues,
|
|
178
|
+
ttl: {
|
|
179
|
+
// AccessToken TTL: quando um TokenTtlHolder é fornecido, lê o valor atual do holder
|
|
180
|
+
// (atualizado em runtime via setting `token_ttl`). Sem holder: fallback ao config.
|
|
181
|
+
AccessToken: tokenTtlHolder
|
|
182
|
+
? () => tokenTtlHolder.accessTokenSec
|
|
183
|
+
: config.ttl.accessToken,
|
|
184
|
+
// RefreshToken TTL: idem ao AccessToken.
|
|
185
|
+
RefreshToken: tokenTtlHolder
|
|
186
|
+
? () => tokenTtlHolder.refreshTokenSec
|
|
187
|
+
: config.ttl.refreshToken,
|
|
188
|
+
// IdToken TTL: idem ao AccessToken.
|
|
189
|
+
IdToken: tokenTtlHolder
|
|
190
|
+
? () => tokenTtlHolder.idTokenSec
|
|
191
|
+
: config.ttl.idToken,
|
|
192
|
+
// Session TTL: quando um SessionTtlHolder é fornecido, usa uma função que diferencia
|
|
193
|
+
// sessões persistentes (remember-me ON → rememberSec) das transientes (remember-me
|
|
194
|
+
// OFF → transientSec). A função é SÍNCRONA (restrição do oidc-provider). O holder é
|
|
195
|
+
// atualizado de forma assíncrona após write/reset da setting `session_policy`.
|
|
196
|
+
// `session.transient` é true quando `result.login.remember === false` (o provider
|
|
197
|
+
// seta `transient: !remember` em loginAccount — ver shared/session.js do oidc-provider).
|
|
198
|
+
// Sem holder: fallback ao TTL estático do config (zero regressão).
|
|
199
|
+
Session: sessionTtlHolder
|
|
200
|
+
? (_ctx, session) => session.transient
|
|
201
|
+
? sessionTtlHolder.transientSec
|
|
202
|
+
: sessionTtlHolder.rememberSec
|
|
203
|
+
: config.ttl.session,
|
|
204
|
+
Interaction: 3600,
|
|
205
|
+
Grant: tokenTtlHolder
|
|
206
|
+
? () => tokenTtlHolder.refreshTokenSec
|
|
207
|
+
: config.ttl.refreshToken,
|
|
208
|
+
},
|
|
209
|
+
interactions: {
|
|
210
|
+
// Telas de interaction são servidas pelo CONSUMIDOR na RAIZ (fora do koa-mount do
|
|
211
|
+
// provider sob /oidc), evitando colisão com o catch-all /oidc/*. O provider redireciona
|
|
212
|
+
// o browser para cá; o consumidor chama interactionFinished, que devolve ao resume do
|
|
213
|
+
// provider em /oidc/auth/:uid (já corretamente prefixado pelo koa-mount).
|
|
214
|
+
url: (_ctx, interaction) => `/auth/interaction/${interaction.uid}`,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
provider.proxy = true;
|
|
218
|
+
return provider;
|
|
219
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AuthMessages } from '../host/i18n.js';
|
|
2
|
+
export declare function createDeviceSources(messages: AuthMessages): {
|
|
3
|
+
userCodeInputSource(_ctx: any, form: string, _out: any, err: any): Promise<void>;
|
|
4
|
+
userCodeConfirmSource(_ctx: any, form: string, _client: any, _deviceInfo: any, userCode: string): Promise<void>;
|
|
5
|
+
successSource(_ctx: any): Promise<void>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { translate } from '../host/i18n.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fontes (sources) de renderização do Device Authorization Grant (RFC 8628).
|
|
4
|
+
*
|
|
5
|
+
* O oidc-provider chama estas funções com o KOA ctx (não o HttpContext do Adonis),
|
|
6
|
+
* então elas NÃO têm acesso ao renderer Inertia/Edge do host. Emitimos HTML
|
|
7
|
+
* auto-contido e i18n-izado (mesmo idioma das demais telas), o que também silencia
|
|
8
|
+
* os avisos `shouldChange` dos defaults da lib. As três telas:
|
|
9
|
+
* - userCodeInputSource: entrada do user-code (`/device`)
|
|
10
|
+
* - userCodeConfirmSource: confirmação após o code casar
|
|
11
|
+
* - successSource: tela final pós-aprovação
|
|
12
|
+
*/
|
|
13
|
+
function esc(value) {
|
|
14
|
+
return String(value ?? '')
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"');
|
|
19
|
+
}
|
|
20
|
+
const STYLE = `
|
|
21
|
+
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#f5f5f7;margin:0;padding:48px 16px;color:#1d1d1f}
|
|
22
|
+
.card{max-width:340px;margin:0 auto;background:#fff;border-radius:14px;padding:32px;box-shadow:0 1px 4px rgba(0,0,0,.08)}
|
|
23
|
+
h1{font-size:1.4rem;font-weight:600;margin:0 0 12px;text-align:center}
|
|
24
|
+
p{font-size:.95rem;line-height:1.5;text-align:center;color:#444}
|
|
25
|
+
p.red{color:#c0392b}
|
|
26
|
+
code{display:block;font-size:1.6rem;letter-spacing:.15em;text-align:center;margin:16px 0;font-weight:600}
|
|
27
|
+
input[type=text]{width:100%;box-sizing:border-box;height:46px;font-size:1rem;text-align:center;text-transform:uppercase;border:1px solid #d2d2d7;border-radius:10px;padding:0 12px;margin-bottom:14px}
|
|
28
|
+
button{width:100%;height:44px;font-size:.95rem;font-weight:600;color:#fff;background:#0071e3;border:0;border-radius:10px;cursor:pointer}
|
|
29
|
+
button:hover{background:#0077ed}
|
|
30
|
+
.abort{margin-top:12px;background:none;color:#666;font-weight:400}
|
|
31
|
+
.abort:hover{background:none;text-decoration:underline}
|
|
32
|
+
`;
|
|
33
|
+
function page(title, inner) {
|
|
34
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(title)}</title><style>${STYLE}</style></head><body><div class="card">${inner}</div></body></html>`;
|
|
35
|
+
}
|
|
36
|
+
export function createDeviceSources(messages) {
|
|
37
|
+
const t = (key, params) => translate(messages, key, params);
|
|
38
|
+
return {
|
|
39
|
+
async userCodeInputSource(_ctx, form, _out, err) {
|
|
40
|
+
const ctx = _ctx;
|
|
41
|
+
let msg;
|
|
42
|
+
if (err && (err.userCode || err.name === 'NoCodeError')) {
|
|
43
|
+
msg = `<p class="red">${esc(t('device.input.error_invalid'))}</p>`;
|
|
44
|
+
}
|
|
45
|
+
else if (err && err.name === 'AbortedError') {
|
|
46
|
+
msg = `<p class="red">${esc(t('device.input.error_aborted'))}</p>`;
|
|
47
|
+
}
|
|
48
|
+
else if (err) {
|
|
49
|
+
msg = `<p class="red">${esc(t('device.input.error_generic'))}</p>`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
msg = `<p>${esc(t('device.input.intro'))}</p>`;
|
|
53
|
+
}
|
|
54
|
+
ctx.body = page(t('device.input.title'), `<h1>${esc(t('device.input.title'))}</h1>${msg}${form}<button type="submit" form="op.deviceInputForm">${esc(t('device.input.submit'))}</button>`);
|
|
55
|
+
},
|
|
56
|
+
async userCodeConfirmSource(_ctx, form, _client, _deviceInfo, userCode) {
|
|
57
|
+
const ctx = _ctx;
|
|
58
|
+
ctx.body = page(t('device.confirm.title'), `<h1>${esc(t('device.confirm.title'))}</h1><p>${esc(t('device.confirm.body'))}</p><code>${esc(userCode)}</code>${form}<button autofocus type="submit" form="op.deviceConfirmForm">${esc(t('device.confirm.submit'))}</button><button class="abort" type="submit" form="op.deviceConfirmForm" value="yes" name="abort">${esc(t('device.confirm.abort'))}</button>`);
|
|
59
|
+
},
|
|
60
|
+
async successSource(_ctx) {
|
|
61
|
+
const ctx = _ctx;
|
|
62
|
+
ctx.body = page(t('device.success.title'), `<h1>${esc(t('device.success.title'))}</h1><p>${esc(t('device.success.body'))}</p>`);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
export interface InteractionDeps {
|
|
3
|
+
verifyCredentials?: (email: string, password: string) => Promise<{
|
|
4
|
+
id: string;
|
|
5
|
+
} | null>;
|
|
6
|
+
}
|
|
7
|
+
/** Detalhes opcionais de login (step-up auth): acr alcançado + amr (métodos). */
|
|
8
|
+
export interface CompleteLoginExtra {
|
|
9
|
+
acr?: string;
|
|
10
|
+
amr?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* "Manter conectado" (remember-me). Mapeado para `result.login.remember` do
|
|
13
|
+
* oidc-provider. Quando `false`, a sessão se torna transiente (cookie expira ao
|
|
14
|
+
* fechar o browser); o TTL function do provider usa `session.transient` para
|
|
15
|
+
* aplicar o TTL curto (defaultSessionHours). Default: `true` (persistente).
|
|
16
|
+
*
|
|
17
|
+
* NOTA: o campo `remember` em `result.login` é NATIVO do oidc-provider v9 —
|
|
18
|
+
* ver `resume.js`: `let { remember = true, accountId, ... } = result.login`.
|
|
19
|
+
*/
|
|
20
|
+
remember?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface InteractionActions {
|
|
23
|
+
details(ctx: HttpContext): Promise<any>;
|
|
24
|
+
login(ctx: HttpContext, input: {
|
|
25
|
+
email: string;
|
|
26
|
+
password: string;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
completeLogin(ctx: HttpContext, accountId: string, extra?: CompleteLoginExtra): Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
consent(ctx: HttpContext): Promise<unknown>;
|
|
34
|
+
}
|
|
35
|
+
/** Lógica de interaction (login/consent) sobre o provider. Testável com um provider fake. */
|
|
36
|
+
export declare function createInteractionActions(provider: any, deps: InteractionDeps): InteractionActions;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Lógica de interaction (login/consent) sobre o provider. Testável com um provider fake. */
|
|
2
|
+
export function createInteractionActions(provider, deps) {
|
|
3
|
+
return {
|
|
4
|
+
async details(ctx) {
|
|
5
|
+
return provider.interactionDetails(ctx.request.request, ctx.response.response);
|
|
6
|
+
},
|
|
7
|
+
async login(ctx, { email, password }) {
|
|
8
|
+
if (!deps.verifyCredentials) {
|
|
9
|
+
throw new Error('authkit: defina `verifyCredentials` no config/authkit.ts para usar o login das interactions.');
|
|
10
|
+
}
|
|
11
|
+
const account = await deps.verifyCredentials(email, password);
|
|
12
|
+
if (!account)
|
|
13
|
+
return { ok: false };
|
|
14
|
+
await provider.interactionFinished(ctx.request.request, ctx.response.response, { login: { accountId: account.id } }, { mergeWithLastSubmission: false });
|
|
15
|
+
return { ok: true };
|
|
16
|
+
},
|
|
17
|
+
async completeLogin(ctx, accountId, extra) {
|
|
18
|
+
// acr/amr (RFC 8176): quando um step-up de MFA foi efetivamente cumprido,
|
|
19
|
+
// passamos o acr alcançado + os métodos (amr) para que o id_token os carregue.
|
|
20
|
+
const login = { accountId };
|
|
21
|
+
if (extra?.acr)
|
|
22
|
+
login.acr = extra.acr;
|
|
23
|
+
if (extra?.amr && extra.amr.length)
|
|
24
|
+
login.amr = extra.amr;
|
|
25
|
+
// remember-me: `remember: false` → sessão transiente (transient: true no provider);
|
|
26
|
+
// `remember: true` ou ausente → sessão persistente (padrão do oidc-provider).
|
|
27
|
+
// O campo `remember` é nativo do oidc-provider v9 — veja resume.js.
|
|
28
|
+
if (extra?.remember === false)
|
|
29
|
+
login.remember = false;
|
|
30
|
+
await provider.interactionFinished(ctx.request.request, ctx.response.response, { login }, { mergeWithLastSubmission: false });
|
|
31
|
+
return { ok: true };
|
|
32
|
+
},
|
|
33
|
+
async consent(ctx) {
|
|
34
|
+
const details = await provider.interactionDetails(ctx.request.request, ctx.response.response);
|
|
35
|
+
const grant = new provider.Grant({
|
|
36
|
+
accountId: details.session.accountId,
|
|
37
|
+
clientId: details.params.client_id,
|
|
38
|
+
});
|
|
39
|
+
grant.addOIDCScope(String(details.params.scope ?? 'openid'));
|
|
40
|
+
// Resource Indicators (RFC 8707): quando o authorize/token pede um `resource`
|
|
41
|
+
// (ex.: JWT Access Tokens RFC 9068), o provider sinaliza os scopes faltantes
|
|
42
|
+
// por resource em `prompt.details.missingResourceScopes`. Concedemos cada um
|
|
43
|
+
// para que o resume materialize o AT vinculado àquela API; sem isto o provider
|
|
44
|
+
// re-emite o prompt de consent num laço.
|
|
45
|
+
const missingResourceScopes = details.prompt?.details?.missingResourceScopes;
|
|
46
|
+
if (missingResourceScopes) {
|
|
47
|
+
for (const [resource, scopes] of Object.entries(missingResourceScopes)) {
|
|
48
|
+
grant.addResourceScope(resource, (scopes ?? []).join(' '));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const grantId = await grant.save();
|
|
52
|
+
return provider.interactionFinished(ctx.request.request, ctx.response.response, { consent: { grantId } }, { mergeWithLastSubmission: true });
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Housekeeping da lib: rotação de chave JWKS age-based. A cada intervalo, se a
|
|
3
|
+
* setting `key_rotation` está enabled e a chave corrente passou de `maxAgeDays`,
|
|
4
|
+
* adquire um lock single-flight (só UMA instância rotaciona) e chama `rotateKeys`,
|
|
5
|
+
* que aplica ao vivo. Re-checa a idade DENTRO do lock para não rotacionar duas
|
|
6
|
+
* vezes quando outra instância acabou de rotacionar. Fail-safe; `unref` no timer.
|
|
7
|
+
* Toda a lógica é pura+injetada (testável sem app).
|
|
8
|
+
*/
|
|
9
|
+
export interface KeyRotationSchedulerOptions {
|
|
10
|
+
policy: () => Promise<{
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
maxAgeDays: number;
|
|
13
|
+
keep: number;
|
|
14
|
+
}>;
|
|
15
|
+
ageDays: () => Promise<number | null>;
|
|
16
|
+
rotateKeys: (keep: number) => Promise<void>;
|
|
17
|
+
withLock: (fn: () => Promise<void>) => Promise<void>;
|
|
18
|
+
intervalMs: number;
|
|
19
|
+
onError?: (err: unknown) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare class KeyRotationScheduler {
|
|
22
|
+
#private;
|
|
23
|
+
private opts;
|
|
24
|
+
constructor(opts: KeyRotationSchedulerOptions);
|
|
25
|
+
tick(): Promise<void>;
|
|
26
|
+
start(): void;
|
|
27
|
+
stop(): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class KeyRotationScheduler {
|
|
2
|
+
opts;
|
|
3
|
+
#timer;
|
|
4
|
+
constructor(opts) {
|
|
5
|
+
this.opts = opts;
|
|
6
|
+
}
|
|
7
|
+
async tick() {
|
|
8
|
+
try {
|
|
9
|
+
const policy = await this.opts.policy();
|
|
10
|
+
if (!policy.enabled)
|
|
11
|
+
return;
|
|
12
|
+
const age = await this.opts.ageDays();
|
|
13
|
+
if (age === null || age < policy.maxAgeDays)
|
|
14
|
+
return;
|
|
15
|
+
await this.opts.withLock(async () => {
|
|
16
|
+
// re-check dentro do lock: outra instância pode ter rotacionado.
|
|
17
|
+
const age2 = await this.opts.ageDays();
|
|
18
|
+
if (age2 === null || age2 < policy.maxAgeDays)
|
|
19
|
+
return;
|
|
20
|
+
await this.opts.rotateKeys(policy.keep);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
this.opts.onError?.(err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
start() {
|
|
28
|
+
if (this.#timer)
|
|
29
|
+
return;
|
|
30
|
+
this.#timer = setInterval(() => { void this.tick(); }, this.opts.intervalMs);
|
|
31
|
+
if (typeof this.#timer.unref === 'function')
|
|
32
|
+
this.#timer.unref();
|
|
33
|
+
}
|
|
34
|
+
stop() {
|
|
35
|
+
if (this.#timer) {
|
|
36
|
+
clearInterval(this.#timer);
|
|
37
|
+
this.#timer = undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll de housekeeping (da lib): lê um `head` barato do cofre e dispara `reload`
|
|
3
|
+
* quando ele muda desde o último observado. Propaga rotações feitas por outro
|
|
4
|
+
* processo/instância sem restart. Fail-safe: erros viram no-op (repassados a
|
|
5
|
+
* `onError` se fornecido). `start()`/`stop()` controlam o intervalo; `tick()` é
|
|
6
|
+
* exposto p/ teste.
|
|
7
|
+
*/
|
|
8
|
+
export interface KeystoreReloadOptions {
|
|
9
|
+
head: () => Promise<string | null>;
|
|
10
|
+
reload: () => Promise<void>;
|
|
11
|
+
intervalMs: number;
|
|
12
|
+
onError?: (err: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare class KeystoreReloadPoller {
|
|
15
|
+
#private;
|
|
16
|
+
private opts;
|
|
17
|
+
constructor(opts: KeystoreReloadOptions);
|
|
18
|
+
tick(): Promise<void>;
|
|
19
|
+
start(): void;
|
|
20
|
+
stop(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class KeystoreReloadPoller {
|
|
2
|
+
opts;
|
|
3
|
+
#last;
|
|
4
|
+
#timer;
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
this.opts = opts;
|
|
7
|
+
}
|
|
8
|
+
async tick() {
|
|
9
|
+
try {
|
|
10
|
+
const head = await this.opts.head();
|
|
11
|
+
if (this.#last === undefined) {
|
|
12
|
+
this.#last = head;
|
|
13
|
+
return;
|
|
14
|
+
} // baseline, sem reload
|
|
15
|
+
if (head !== this.#last) {
|
|
16
|
+
this.#last = head;
|
|
17
|
+
await this.opts.reload();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
this.opts.onError?.(err);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
start() {
|
|
25
|
+
if (this.#timer)
|
|
26
|
+
return;
|
|
27
|
+
this.#timer = setInterval(() => {
|
|
28
|
+
void this.tick();
|
|
29
|
+
}, this.opts.intervalMs);
|
|
30
|
+
if (typeof this.#timer.unref === 'function')
|
|
31
|
+
this.#timer.unref();
|
|
32
|
+
}
|
|
33
|
+
stop() {
|
|
34
|
+
if (this.#timer) {
|
|
35
|
+
clearInterval(this.#timer);
|
|
36
|
+
this.#timer = undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AuthMessages } from '../host/i18n.js';
|
|
2
|
+
export declare function createLogoutSources(messages: AuthMessages): {
|
|
3
|
+
/**
|
|
4
|
+
* Splash de saída que auto-confirma. `form` já contém o <form> com o xsrf
|
|
5
|
+
* e a action de confirmação; injetamos o campo `logout` e submetemos.
|
|
6
|
+
*/
|
|
7
|
+
logoutSource(ctx: any, form: string): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Mostrada apenas quando NÃO há `post_logout_redirect_uri` (senão o provider
|
|
10
|
+
* redireciona o browser direto pro RP). Tela de marca em vez do default.
|
|
11
|
+
*/
|
|
12
|
+
postLogoutSuccessSource(ctx: any): Promise<void>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { translate } from '../host/i18n.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sources de RP-initiated logout (end_session).
|
|
4
|
+
*
|
|
5
|
+
* Por padrão o oidc-provider renderiza uma página em INGLÊS e sem estilo
|
|
6
|
+
* ("Do you want to sign-out from …?") com dois botões. Como o usuário já
|
|
7
|
+
* clicou em "Sair" no app, a confirmação é fricção pura: sobrescrevemos
|
|
8
|
+
* `logoutSource` por um splash de marca que AUTO-CONFIRMA (injeta o campo
|
|
9
|
+
* `logout=yes` e submete via JS), com fallback `<noscript>` acessível.
|
|
10
|
+
*
|
|
11
|
+
* Como `device_sources`, recebem o KOA ctx (sem acesso ao renderer do host),
|
|
12
|
+
* então emitem HTML auto-contido e i18n-izado.
|
|
13
|
+
*/
|
|
14
|
+
function esc(value) {
|
|
15
|
+
return String(value ?? '')
|
|
16
|
+
.replace(/&/g, '&')
|
|
17
|
+
.replace(/</g, '<')
|
|
18
|
+
.replace(/>/g, '>')
|
|
19
|
+
.replace(/"/g, '"');
|
|
20
|
+
}
|
|
21
|
+
// Papel quente neutro + tinta — alinhado ao espírito "Manuscrito Vivo" sem
|
|
22
|
+
// depender da marca de um client específico (o end_session é cross-client).
|
|
23
|
+
const STYLE = `
|
|
24
|
+
*{box-sizing:border-box}
|
|
25
|
+
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#faf6ee;margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;color:#2f1a47}
|
|
26
|
+
.card{max-width:360px;width:100%;text-align:center}
|
|
27
|
+
.spinner{width:30px;height:30px;margin:0 auto 20px;border:2.5px solid rgba(47,26,71,.15);border-top-color:#f17e42;border-radius:50%;animation:spin .7s linear infinite}
|
|
28
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
29
|
+
h1{font-family:Georgia,'Times New Roman',serif;font-size:1.5rem;font-weight:500;letter-spacing:-.01em;margin:0 0 8px}
|
|
30
|
+
p{font-size:.95rem;line-height:1.5;color:#6e5d85;margin:0}
|
|
31
|
+
button{margin-top:20px;height:44px;padding:0 22px;font-size:.95rem;font-weight:600;color:#2f1a47;background:#f17e42;border:0;border-radius:10px;cursor:pointer}
|
|
32
|
+
@media (prefers-reduced-motion:reduce){.spinner{animation:none}}
|
|
33
|
+
@media (prefers-color-scheme:dark){body{background:#1d112e;color:#f3ecdf}p{color:rgba(243,236,223,.65)}}
|
|
34
|
+
`;
|
|
35
|
+
function page(title, inner) {
|
|
36
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(title)}</title><style>${STYLE}</style></head><body><div class="card">${inner}</div></body></html>`;
|
|
37
|
+
}
|
|
38
|
+
export function createLogoutSources(messages) {
|
|
39
|
+
const t = (key, params) => translate(messages, key, params);
|
|
40
|
+
return {
|
|
41
|
+
/**
|
|
42
|
+
* Splash de saída que auto-confirma. `form` já contém o <form> com o xsrf
|
|
43
|
+
* e a action de confirmação; injetamos o campo `logout` e submetemos.
|
|
44
|
+
*/
|
|
45
|
+
async logoutSource(ctx, form) {
|
|
46
|
+
// Injeta o campo de confirmação dentro do form fornecido pela lib.
|
|
47
|
+
const confirmedForm = form.replace(/<\/form>/, '<input type="hidden" name="logout" value="yes"/></form>');
|
|
48
|
+
ctx.body = page(t('logout.title'), `<div class="spinner" aria-hidden="true"></div>` +
|
|
49
|
+
`<h1>${esc(t('logout.title'))}</h1>` +
|
|
50
|
+
`<p>${esc(t('logout.body'))}</p>` +
|
|
51
|
+
confirmedForm +
|
|
52
|
+
`<noscript><button type="submit" form="op.logoutForm">${esc(t('logout.fallback'))}</button></noscript>` +
|
|
53
|
+
`<script>(function(){var f=document.forms['op.logoutForm'];if(f){f.submit();}})();</script>`);
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* Mostrada apenas quando NÃO há `post_logout_redirect_uri` (senão o provider
|
|
57
|
+
* redireciona o browser direto pro RP). Tela de marca em vez do default.
|
|
58
|
+
*/
|
|
59
|
+
async postLogoutSuccessSource(ctx) {
|
|
60
|
+
ctx.body = page(t('logout.success.title'), `<h1>${esc(t('logout.success.title'))}</h1>` +
|
|
61
|
+
`<p>${esc(t('logout.success.body'))}</p>`);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|