├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yml │ ├── FEATURE_REQUEST.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build-frontend.yml │ ├── cli-publish.yml │ ├── codeql-analysis.yml │ ├── docker-publish.yml │ ├── e2e.yml │ ├── go.yml │ ├── release-frontend-sdk.yml │ ├── release-hanko-elements.yml │ ├── schema-generate-config.yml │ ├── schema-generate-import.yml │ ├── schema-markdown-config.yml │ └── schema-markdown-import.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── backend ├── .goreleaser.yaml ├── Dockerfile ├── Dockerfile.debug ├── README.md ├── audit_log │ └── logger.go ├── build_info │ └── build_info.go ├── cmd │ ├── cleanup │ │ └── cleanup.go │ ├── isready │ │ └── isready.go │ ├── jwk │ │ ├── create.go │ │ └── root.go │ ├── jwt │ │ ├── create.go │ │ └── root.go │ ├── migrate │ │ ├── down.go │ │ ├── root.go │ │ └── up.go │ ├── root.go │ ├── schema │ │ ├── generate.go │ │ ├── generate_config.go │ │ ├── generate_import.go │ │ ├── markdown.go │ │ ├── markdown_config.go │ │ ├── markdown_import.go │ │ └── root.go │ ├── serve │ │ ├── admin.go │ │ ├── all.go │ │ ├── public.go │ │ └── root.go │ ├── siwa │ │ └── siwa.go │ ├── user │ │ ├── export.go │ │ ├── format.go │ │ ├── format_test.go │ │ ├── generate.go │ │ ├── import.go │ │ ├── import_test.go │ │ ├── importer.go │ │ └── root.go │ └── version │ │ └── version.go ├── config │ ├── config.go │ ├── config.yaml │ ├── config_account.go │ ├── config_audit_log.go │ ├── config_database.go │ ├── config_default.go │ ├── config_email.go │ ├── config_email_delivery.go │ ├── config_emails.go │ ├── config_logger.go │ ├── config_mfa.go │ ├── config_passcode.go │ ├── config_passkey.go │ ├── config_password.go │ ├── config_privacy.go │ ├── config_rate_limiter.go │ ├── config_secrets.go │ ├── config_server.go │ ├── config_service.go │ ├── config_session.go │ ├── config_test.go │ ├── config_third_party.go │ ├── config_username.go │ ├── config_webauthn.go │ ├── config_webhook.go │ ├── config_webhook_test.go │ ├── minimal-config.yaml │ ├── passcode-smtp-config.yaml │ └── root-passcode-smtp-config.yaml ├── crypto │ ├── aes_gcm │ │ ├── aes_gcm.go │ │ └── aes_gcm_test.go │ ├── jwk │ │ ├── generator.go │ │ ├── generator_rsa.go │ │ ├── generator_test.go │ │ ├── manager.go │ │ └── manager_test.go │ ├── jwt │ │ ├── jwt.go │ │ └── jwt_test.go │ ├── passcode.go │ ├── passcode_test.go │ └── string.go ├── dto │ ├── admin │ │ ├── email.go │ │ ├── identity.go │ │ ├── metadata.go │ │ ├── otp.go │ │ ├── password.go │ │ ├── session.go │ │ ├── user.go │ │ ├── username.go │ │ ├── webauthn.go │ │ └── webhook.go │ ├── config.go │ ├── email.go │ ├── error_handler.go │ ├── intern │ │ ├── WebauthnCredential.go │ │ ├── WebauthnSessionData.go │ │ └── WebauthnUser.go │ ├── metadata.go │ ├── passcode.go │ ├── profile.go │ ├── session.go │ ├── session_test.go │ ├── thirdparty.go │ ├── user.go │ ├── username.go │ ├── validator.go │ ├── webauthn.go │ └── webhook │ │ └── email.go ├── ee │ ├── LICENSE │ ├── README.md │ └── saml │ │ ├── config │ │ ├── saml.go │ │ └── saml_test.go │ │ ├── dto │ │ └── saml.go │ │ ├── handler.go │ │ ├── provider │ │ ├── auth0.go │ │ ├── provider.go │ │ └── saml.go │ │ ├── router.go │ │ ├── service.go │ │ ├── state.go │ │ ├── state_test.go │ │ └── utils │ │ ├── response.go │ │ └── url.go ├── flow_api │ ├── flow │ │ ├── capabilities │ │ │ └── action_send_capabilities.go │ │ ├── credential_onboarding │ │ │ ├── action_continue_to_passkey.go │ │ │ ├── action_continue_to_password.go │ │ │ ├── action_register_password.go │ │ │ ├── action_skip_method_chooser.go │ │ │ ├── action_skip_passkey.go │ │ │ ├── action_skip_password.go │ │ │ ├── action_webauthn_generate_creation_options.go │ │ │ └── action_webauthn_verify_attestation_response.go │ │ ├── credential_usage │ │ │ ├── action_continue_to_passcode_confirmation.go │ │ │ ├── action_continue_to_passcode_confirmation_recovery.go │ │ │ ├── action_continue_to_password_login.go │ │ │ ├── action_continue_with_login_identifier.go │ │ │ ├── action_password_login.go │ │ │ ├── action_password_recovery.go │ │ │ ├── action_remember_me.go │ │ │ ├── action_resend_passcode.go │ │ │ ├── action_verify_passcode.go │ │ │ ├── action_webauthn_generate_request_options.go │ │ │ ├── action_webauthn_verify_assertion_response.go │ │ │ └── hook_send_passcode.go │ │ ├── device_trust │ │ │ ├── action_trust_device.go │ │ │ ├── hook_issue_trust_device_cookie.go │ │ │ └── hook_schedule_trust_device_state.go │ │ ├── flows.go │ │ ├── login │ │ │ ├── hook_create_email.go │ │ │ ├── hook_schedule_onboarding_states.go │ │ │ ├── hook_trigger_login_webhook.go │ │ │ └── hook_webauthn_generate_request_options_cond.go │ │ ├── mfa_creation │ │ │ ├── action_continue_to_otp_secret_creation.go │ │ │ ├── action_continue_to_security_key_creation.go │ │ │ ├── action_otp_code_verify.go │ │ │ ├── action_webauthn_generate_creation_options_for_security_keys.go │ │ │ ├── hook_otp_secret_generate.go │ │ │ └── skip_mfa.go │ │ ├── mfa_usage │ │ │ ├── action_continue_to_login_otp.go │ │ │ ├── action_continue_to_login_security_key.go │ │ │ ├── action_otp_code_validate.go │ │ │ └── action_webauthn_generate_request_options_security_key.go │ │ ├── profile │ │ │ ├── action_account_delete.go │ │ │ ├── action_continue_to_otp_secret_creation.go │ │ │ ├── action_continue_to_security_key_creation.go │ │ │ ├── action_email_create.go │ │ │ ├── action_email_delete.go │ │ │ ├── action_email_set_primary.go │ │ │ ├── action_email_verify.go │ │ │ ├── action_otp_secret_delete.go │ │ │ ├── action_password_create.go │ │ │ ├── action_password_delete.go │ │ │ ├── action_password_update.go │ │ │ ├── action_patch_metadata.go │ │ │ ├── action_security_key_create.go │ │ │ ├── action_security_key_delete.go │ │ │ ├── action_session_delete.go │ │ │ ├── action_username_create.go │ │ │ ├── action_username_delete.go │ │ │ ├── action_username_update.go │ │ │ ├── action_webauthn_credential_create.go │ │ │ ├── action_webauthn_credential_delete.go │ │ │ ├── action_webauthn_credential_rename.go │ │ │ ├── action_webauthn_verify_attestation_response.go │ │ │ ├── hook_get_profile_data.go │ │ │ ├── hook_get_sessions.go │ │ │ └── hook_refresh_session_user.go │ │ ├── registration │ │ │ ├── action_register_login_identifier.go │ │ │ └── hook_create_user.go │ │ ├── shared │ │ │ ├── action_back.go │ │ │ ├── action_exchange_token.go │ │ │ ├── action_skip.go │ │ │ ├── action_thirdparty_oauth.go │ │ │ ├── const_action_names.go │ │ │ ├── const_flow_names.go │ │ │ ├── const_stash_paths.go │ │ │ ├── const_state_names.go │ │ │ ├── errors.go │ │ │ ├── flow.go │ │ │ ├── hook_email_persist_verified_status.go │ │ │ ├── hook_generate_oauth_links.go │ │ │ ├── hook_get_user_data.go │ │ │ ├── hook_issue_session.go │ │ │ ├── hook_password_save.go │ │ │ ├── hook_persist_webauthn_credential.go │ │ │ ├── hook_schedule_mfa_creation_states.go │ │ │ ├── hook_verify_attestation_response.go │ │ │ └── links.go │ │ └── user_details │ │ │ ├── action_set_email.go │ │ │ ├── action_set_username.go │ │ │ ├── action_skip_email.go │ │ │ └── action_skip_username.go │ ├── handler.go │ ├── services │ │ ├── device_trust.go │ │ ├── email.go │ │ ├── passcode.go │ │ ├── password.go │ │ ├── user.go │ │ └── webauthn.go │ └── static │ │ └── generic_client.html ├── flowpilot │ ├── action_input.go │ ├── builder.go │ ├── builder_subflow.go │ ├── context.go │ ├── context_action_exec.go │ ├── context_action_init.go │ ├── context_flow.go │ ├── db.go │ ├── errors.go │ ├── flow.go │ ├── input.go │ ├── input_allowed_value.go │ ├── input_schema.go │ ├── jsonmanager │ │ └── manager.go │ ├── link.go │ ├── payload.go │ ├── query_param.go │ ├── random.go │ ├── response.go │ ├── stash.go │ ├── state_action.go │ └── state_detail.go ├── go.mod ├── go.sum ├── handler │ ├── admin_router.go │ ├── audit_log.go │ ├── email.go │ ├── email_admin.go │ ├── email_admin_test.go │ ├── email_test.go │ ├── health.go │ ├── health_test.go │ ├── helpers_test.go │ ├── metadata_admin.go │ ├── metadata_admin_test.go │ ├── otp_admin.go │ ├── otp_admin_test.go │ ├── passcode.go │ ├── passcode_test.go │ ├── password.go │ ├── password_admin.go │ ├── password_admin_test.go │ ├── password_test.go │ ├── public_router.go │ ├── session.go │ ├── session_admin.go │ ├── session_admin_test.go │ ├── status.go │ ├── thirdparty.go │ ├── thirdparty_auth_test.go │ ├── thirdparty_callback_error_test.go │ ├── thirdparty_callback_test.go │ ├── thirdparty_test.go │ ├── token.go │ ├── token_test.go │ ├── user.go │ ├── user_admin.go │ ├── user_admin_test.go │ ├── user_test.go │ ├── utils.go │ ├── webauthn.go │ ├── webauthn_credential_admin.go │ ├── webauthn_credential_admin_test.go │ ├── webauthn_test.go │ ├── webhook.go │ ├── webhook_test.go │ ├── well_known.go │ └── well_known_test.go ├── json_schema │ ├── hanko.config.json │ └── hanko.user_import.json ├── mail │ ├── locales │ │ ├── passcode.bn.yaml │ │ ├── passcode.de.yaml │ │ ├── passcode.en.yaml │ │ ├── passcode.fr.yaml │ │ ├── passcode.it.yaml │ │ ├── passcode.pt-BR.yaml │ │ └── passcode.zh-CN.yaml │ ├── mailer.go │ ├── mailer_test.go │ ├── render.go │ ├── render_test.go │ └── templates │ │ ├── email_login_attempted.html.tmpl │ │ ├── email_login_attempted.txt.tmpl │ │ ├── email_registration_attempted.html.tmpl │ │ ├── email_registration_attempted.txt.tmpl │ │ ├── email_verification.html.tmpl │ │ ├── email_verification.txt.tmpl │ │ ├── layout.html.tmpl │ │ ├── login.html.tmpl │ │ ├── login.txt.tmpl │ │ ├── recovery.html.tmpl │ │ └── recovery.txt.tmpl ├── main.go ├── mapper │ ├── aaguid.json │ └── authenticator_mapper.go ├── middleware │ ├── logger.go │ ├── session.go │ └── webhook.go ├── pagination │ ├── header.go │ └── header_test.go ├── persistence │ ├── audit_log_persister.go │ ├── email_persister.go │ ├── flow_persister.go │ ├── identity_persister.go │ ├── jwk_persister.go │ ├── migrations │ │ ├── 20220405153240_create_users.down.fizz │ │ ├── 20220405153240_create_users.up.fizz │ │ ├── 20220405153750_create_passcodes.down.fizz │ │ ├── 20220405153750_create_passcodes.up.fizz │ │ ├── 20220405154240_create_webauthn_credentials.down.fizz │ │ ├── 20220405154240_create_webauthn_credentials.up.fizz │ │ ├── 20220405154750_create_webauthn_session_data.down.fizz │ │ ├── 20220405154750_create_webauthn_session_data.up.fizz │ │ ├── 20220405155120_create_webauthn_session_data_allowed_credentials.down.fizz │ │ ├── 20220405155120_create_webauthn_session_data_allowed_credentials.up.fizz │ │ ├── 20220413152500_create_jwk.down.fizz │ │ ├── 20220413152500_create_jwk.up.fizz │ │ ├── 20220425122015_create_password_credentials.down.fizz │ │ ├── 20220425122015_create_password_credentials.up.fizz │ │ ├── 20220711121022_create_credential_transports.down.fizz │ │ ├── 20220711121022_create_credential_transports.up.fizz │ │ ├── 20220818111000_create_audit_logs.down.fizz │ │ ├── 20220818111000_create_audit_logs.up.fizz │ │ ├── 20221027104800_create_emails.down.fizz │ │ ├── 20221027104800_create_emails.up.fizz │ │ ├── 20221027104900_change_users.down.fizz │ │ ├── 20221027104900_change_users.up.fizz │ │ ├── 20221027123530_change_passcodes.down.fizz │ │ ├── 20221027123530_change_passcodes.up.fizz │ │ ├── 20221222134900_change_webauthn_credentials.down.fizz │ │ ├── 20221222134900_change_webauthn_credentials.up.fizz │ │ ├── 20230112152816_create_identities.down.fizz │ │ ├── 20230112152816_create_identities.up.fizz │ │ ├── 20230206102000_change_webauthn_credentials.down.fizz │ │ ├── 20230206102000_change_webauthn_credentials.up.fizz │ │ ├── 20230222114100_change_webauthn_credentials.down.fizz │ │ ├── 20230222114100_change_webauthn_credentials.up.fizz │ │ ├── 20230317114334_create_tokens.down.fizz │ │ ├── 20230317114334_create_tokens.up.fizz │ │ ├── 20230801124808_webauthn_session_data_add_expiry.down.fizz │ │ ├── 20230801124808_webauthn_session_data_add_expiry.up.fizz │ │ ├── 20230810173315_create_flows.down.fizz │ │ ├── 20230810173315_create_flows.up.fizz │ │ ├── 20230905102601_create_saml_state.down.fizz │ │ ├── 20230905102601_create_saml_state.up.fizz │ │ ├── 20230915111552_create_saml_certs.down.fizz │ │ ├── 20230915111552_create_saml_certs.up.fizz │ │ ├── 20231012141100_change_user_table.down.fizz │ │ ├── 20231012141100_change_user_table.up.fizz │ │ ├── 20231013113800_change_passcode_table.down.fizz │ │ ├── 20231013113800_change_passcode_table.up.fizz │ │ ├── 20240108094151_create_webhooks.down.fizz │ │ ├── 20240108094151_create_webhooks.up.fizz │ │ ├── 20240108094210_create_webhook_events.down.fizz │ │ ├── 20240108094210_create_webhook_events.up.fizz │ │ ├── 20240207150616_change_audit_logs.down.fizz │ │ ├── 20240207150616_change_audit_logs.up.fizz │ │ ├── 20240530122100_change_tokens.down.fizz │ │ ├── 20240530122100_change_tokens.up.fizz │ │ ├── 20240530145724_change_users.down.fizz │ │ ├── 20240530145724_change_users.up.fizz │ │ ├── 20240612122326_change_flows.down.fizz │ │ ├── 20240612122326_change_flows.up.fizz │ │ ├── 20240717020131_drop_transitions.down.fizz │ │ ├── 20240717020131_drop_transitions.up.fizz │ │ ├── 20240717020707_change_flows.down.fizz │ │ ├── 20240717020707_change_flows.up.fizz │ │ ├── 20240723171257_change_passcodes.down.fizz │ │ ├── 20240723171257_change_passcodes.up.fizz │ │ ├── 20240723173648_create_usernames.down.fizz │ │ ├── 20240723173648_create_usernames.up.fizz │ │ ├── 20240826132046_create_otp_secrets.down.fizz │ │ ├── 20240826132046_create_otp_secrets.up.fizz │ │ ├── 20240826133417_change_webauthn_credentials.down.fizz │ │ ├── 20240826133417_change_webauthn_credentials.up.fizz │ │ ├── 20241002113000_create_sessions.down.fizz │ │ ├── 20241002113000_create_sessions.up.fizz │ │ ├── 20241106171500_change_sessions.down.fizz │ │ ├── 20241106171500_change_sessions.up.fizz │ │ ├── 20241112181011_create_trusted_devices.down.fizz │ │ ├── 20241112181011_create_trusted_devices.up.fizz │ │ ├── 20241118114500_change_webauthn_credentials.down.fizz │ │ ├── 20241118114500_change_webauthn_credentials.up.fizz │ │ ├── 20250130154010_change_identities.down.fizz │ │ ├── 20250130154010_change_identities.up.fizz │ │ ├── 20250130170131_create_saml_identities.down.fizz │ │ ├── 20250130170131_create_saml_identities.up.fizz │ │ ├── 20250210095906_create_saml_idp_initiated_requests.down.fizz │ │ ├── 20250210095906_create_saml_idp_initiated_requests.up.fizz │ │ ├── 20250313160348_change_flows.down.fizz │ │ ├── 20250313160348_change_flows.up.fizz │ │ ├── 20250414165334_create_user_metadata.down.fizz │ │ └── 20250414165334_create_user_metadata.up.fizz │ ├── models │ │ ├── audit_log.go │ │ ├── email.go │ │ ├── flow.go │ │ ├── identity.go │ │ ├── jwk.go │ │ ├── otp_secret.go │ │ ├── passcode.go │ │ ├── password_credential.go │ │ ├── primary_email.go │ │ ├── saml_certificate.go │ │ ├── saml_identity.go │ │ ├── saml_idp_initiated_request.go │ │ ├── saml_state.go │ │ ├── session.go │ │ ├── token.go │ │ ├── trusted_device.go │ │ ├── user.go │ │ ├── user_metadata.go │ │ ├── username.go │ │ ├── webauthn_credential.go │ │ ├── webauthn_credential_transport.go │ │ ├── webauthn_credential_user_handle.go │ │ ├── webauthn_session_data.go │ │ ├── webauthn_session_data_allowed_credential.go │ │ ├── webhook.go │ │ └── webhook_event.go │ ├── otp_secret_persister.go │ ├── passcode_persister.go │ ├── password_credential_persister.go │ ├── persister.go │ ├── primary_email_persister.go │ ├── saml_certificate_persister.go │ ├── saml_identity_persister.go │ ├── saml_idp_inititated_request_persister.go │ ├── saml_state_persister.go │ ├── session_persister.go │ ├── token_persister.go │ ├── trusted_device_persister.go │ ├── user_metadata_persister.go │ ├── user_persister.go │ ├── username_persister.go │ ├── webauthn_credential_persister.go │ ├── webauthn_credential_user_handle_persister.go │ ├── webauthn_session_data_persister.go │ └── webhook_persister.go ├── rate_limiter │ ├── rate_limiter.go │ └── rate_limiter_test.go ├── server │ └── server.go ├── session │ ├── session.go │ ├── session_test.go │ ├── template.go │ └── template_test.go ├── template │ ├── template.go │ └── templates │ │ └── status.tmpl ├── test │ ├── audit_logger.go │ ├── config.go │ ├── database.go │ ├── fixtures │ │ ├── actions │ │ │ ├── get_wa_creation_options │ │ │ │ └── flows.yaml │ │ │ ├── send_capabilities │ │ │ │ └── flows.yaml │ │ │ ├── send_wa_attestation_response │ │ │ │ ├── flows.yaml │ │ │ │ └── webauthn_session_data.yaml │ │ │ ├── submit_new_password │ │ │ │ └── flows.yaml │ │ │ ├── submit_passcode │ │ │ │ ├── flows.yaml │ │ │ │ └── passcodes.yaml │ │ │ └── submit_registration_identifier │ │ │ │ ├── emails.yaml │ │ │ │ ├── flows.yaml │ │ │ │ └── users.yaml │ │ ├── email │ │ │ ├── emails.yaml │ │ │ ├── primary_emails.yaml │ │ │ └── users.yaml │ │ ├── metadata │ │ │ ├── user_metadata.yaml │ │ │ └── users.yaml │ │ ├── otp │ │ │ ├── emails.yaml │ │ │ ├── otp_secrets.yaml │ │ │ ├── primary_emails.yaml │ │ │ └── users.yaml │ │ ├── passcode │ │ │ ├── emails.yaml │ │ │ ├── primary_emails.yaml │ │ │ └── users.yaml │ │ ├── password │ │ │ ├── emails.yaml │ │ │ ├── password_credentials.yaml │ │ │ ├── primary_emails.yaml │ │ │ └── users.yaml │ │ ├── saml_state │ │ │ └── saml_states.yaml │ │ ├── sessions │ │ │ ├── emails.yaml │ │ │ ├── primary_emails.yaml │ │ │ ├── sessions.yaml │ │ │ └── users.yaml │ │ ├── thirdparty │ │ │ ├── emails.yaml │ │ │ ├── identities.yaml │ │ │ ├── primary_emails.yaml │ │ │ └── users.yaml │ │ ├── token │ │ │ ├── tokens.yaml │ │ │ └── users.yaml │ │ ├── user │ │ │ ├── emails.yaml │ │ │ └── users.yaml │ │ ├── user_admin │ │ │ ├── emails.yaml │ │ │ └── users.yaml │ │ ├── user_with_webauthn_credential │ │ │ ├── emails.yaml │ │ │ ├── users.yaml │ │ │ └── webauthn_credentials.yaml │ │ ├── webauthn │ │ │ ├── emails.yaml │ │ │ ├── primary_emails.yaml │ │ │ ├── users.yaml │ │ │ ├── webauthn_credentials.yaml │ │ │ └── webauthn_session_data.yaml │ │ ├── webauthn_registration │ │ │ ├── emails.yaml │ │ │ ├── primary_emails.yaml │ │ │ ├── users.yaml │ │ │ ├── webauthn_credentials.yaml │ │ │ └── webauthn_session_data.yaml │ │ └── webhooks │ │ │ ├── webhook_events.yaml │ │ │ └── webhooks.yaml │ ├── jwk_manager.go │ ├── mailslurper.go │ └── suite.go ├── thirdparty │ ├── error.go │ ├── helper.go │ ├── helper_test.go │ ├── linking.go │ ├── provider.go │ ├── provider_apple.go │ ├── provider_custom.go │ ├── provider_discord.go │ ├── provider_facebook.go │ ├── provider_github.go │ ├── provider_google.go │ ├── provider_linkedin.go │ ├── provider_microsoft.go │ ├── state.go │ └── state_test.go ├── utils │ ├── cookie.go │ ├── cookie_test.go │ ├── mask.go │ └── mask_test.go └── webhooks │ ├── config_hook.go │ ├── config_hook_test.go │ ├── database_hook.go │ ├── database_hook_test.go │ ├── events │ └── events.go │ ├── manager.go │ ├── manager_test.go │ ├── utils │ ├── webhook.go │ └── webhook_test.go │ ├── webhook.go │ ├── webhook_test.go │ ├── worker.go │ └── worker_test.go ├── deploy ├── docker-compose │ ├── base.yaml │ ├── config-disable-signup.yaml │ ├── config-rate-limiting.yaml │ ├── config.yaml │ ├── quickstart-with-redis.yaml │ ├── quickstart.debug.yaml │ ├── quickstart.e2e.yaml │ ├── quickstart.yaml │ ├── todo-angular.yaml │ ├── todo-nextjs.yaml │ ├── todo-react.yaml │ ├── todo-svelte.yaml │ └── todo-vue.yaml └── k8s │ ├── README.md │ ├── base │ ├── elements │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── kustomization.yaml │ │ └── service.yaml │ ├── hanko │ │ ├── config.yaml │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── kustomization.yaml │ │ ├── namespace.yaml │ │ └── services.yaml │ ├── mailhog │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── kustomization.yaml │ │ └── service.yaml │ ├── postgres │ │ ├── deployment.yaml │ │ ├── initdbscript.sh │ │ ├── kustomization.yaml │ │ ├── persistent-volume.yaml │ │ └── service.yaml │ └── quickstart │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── kustomization.yaml │ │ └── service.yaml │ └── overlays │ ├── quickstart │ └── kustomization.yaml │ └── thirdparty-x-domain │ ├── README.md │ ├── config.yaml │ ├── env-patch.yaml │ ├── ingress-patch.yaml │ └── kustomization.yaml ├── e2e ├── .eslintignore ├── .eslintrc.cjs ├── .nvmrc ├── README.md ├── fixtures │ └── Pages.ts ├── global.d.ts ├── helper │ ├── Accounts.ts │ ├── Endpoints.ts │ ├── MailSlurper.ts │ ├── Matchers.ts │ └── Setup.ts ├── package-lock.json ├── package.json ├── pages │ ├── BasePage.ts │ ├── Error.ts │ ├── LoginEmail.ts │ ├── LoginEmailNoSignUp.ts │ ├── LoginPasscode.ts │ ├── LoginPassword.ts │ ├── NoAccountFound.ts │ ├── RegisterAuthenticator.ts │ ├── RegisterConfirm.ts │ ├── RegisterPassword.ts │ └── SecuredContent.ts ├── playwright.config.ts ├── seed │ ├── Dockerfile │ ├── init.sh │ └── seed.sql ├── tests │ ├── common.spec.ts │ ├── nosignup.spec.ts │ ├── passwordless.spec.ts │ └── passwords.spec.ts └── tsconfig.json ├── frontend ├── .dockerignore ├── Dockerfile ├── Dockerfile.debug ├── elements │ ├── .dockerignore │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .nvmrc │ ├── LICENSE │ ├── README.md │ ├── babel.config.cjs │ ├── example.css │ ├── nginx │ │ └── default.conf │ ├── package.json │ ├── src │ │ ├── Elements.tsx │ │ ├── _mixins.sass │ │ ├── _preset.sass │ │ ├── _variables.sass │ │ ├── components │ │ │ ├── accordion │ │ │ │ ├── Accordion.tsx │ │ │ │ ├── AddEmailDropdown.tsx │ │ │ │ ├── AddWebauthnCredentialDropdown.tsx │ │ │ │ ├── ChangePasswordDropdown.tsx │ │ │ │ ├── ChangeUsernameDropdown.tsx │ │ │ │ ├── Dropdown.tsx │ │ │ │ ├── ListEmailsAccordion.tsx │ │ │ │ ├── ListSessionsAccordion.tsx │ │ │ │ ├── ListWebauthnCredentialsAccordion.tsx │ │ │ │ ├── ManageAuthAppDropdown.tsx │ │ │ │ └── styles.sass │ │ │ ├── error │ │ │ │ ├── ErrorBox.tsx │ │ │ │ ├── ErrorMessage.tsx │ │ │ │ └── styles.sass │ │ │ ├── form │ │ │ │ ├── Button.tsx │ │ │ │ ├── Checkbox.tsx │ │ │ │ ├── CodeInput.tsx │ │ │ │ ├── Form.tsx │ │ │ │ ├── Input.tsx │ │ │ │ ├── LastUsed.tsx │ │ │ │ └── styles.sass │ │ │ ├── headline │ │ │ │ ├── Headline1.tsx │ │ │ │ ├── Headline2.tsx │ │ │ │ └── styles.sass │ │ │ ├── icons │ │ │ │ ├── Apple.tsx │ │ │ │ ├── Checkmark.tsx │ │ │ │ ├── Copy.tsx │ │ │ │ ├── CustomProvider.tsx │ │ │ │ ├── Discord.tsx │ │ │ │ ├── ExclamationMark.tsx │ │ │ │ ├── Facebook.tsx │ │ │ │ ├── GitHub.tsx │ │ │ │ ├── Google.tsx │ │ │ │ ├── Icon.tsx │ │ │ │ ├── LinkedIn.tsx │ │ │ │ ├── LoadingSpinner.tsx │ │ │ │ ├── Mail.tsx │ │ │ │ ├── Microsoft.tsx │ │ │ │ ├── Passkey.tsx │ │ │ │ ├── Password.tsx │ │ │ │ ├── QRCodeScanner.tsx │ │ │ │ ├── SecurityKey.tsx │ │ │ │ ├── Spinner.tsx │ │ │ │ ├── icons.ts │ │ │ │ └── styles.sass │ │ │ ├── link │ │ │ │ ├── Link.tsx │ │ │ │ └── styles.sass │ │ │ ├── otp │ │ │ │ ├── OTPCreationDetails.tsx │ │ │ │ └── styles.sass │ │ │ ├── paragraph │ │ │ │ ├── Paragraph.tsx │ │ │ │ └── styles.sass │ │ │ ├── spacer │ │ │ │ ├── Divider.tsx │ │ │ │ ├── Spacer.tsx │ │ │ │ └── styles.sass │ │ │ └── wrapper │ │ │ │ ├── Clipboard.tsx │ │ │ │ ├── Container.tsx │ │ │ │ ├── Content.tsx │ │ │ │ ├── Footer.tsx │ │ │ │ └── styles.sass │ │ ├── contexts │ │ │ └── AppProvider.tsx │ │ ├── declarations.d.ts │ │ ├── example.html │ │ ├── hooks │ │ │ ├── UseFlowEffects.ts │ │ │ └── UseFlowState.ts │ │ ├── i18n │ │ │ ├── all.ts │ │ │ ├── bn.ts │ │ │ ├── de.ts │ │ │ ├── en.ts │ │ │ ├── fr.ts │ │ │ ├── it.ts │ │ │ ├── pt-BR.ts │ │ │ ├── translations.ts │ │ │ └── zh.ts │ │ ├── index.ts │ │ └── pages │ │ │ ├── CreateEmailPage.tsx │ │ │ ├── CreateOTPSecretPage.tsx │ │ │ ├── CreatePasswordPage.tsx │ │ │ ├── CreateSecurityKeyPage.tsx │ │ │ ├── CreateUsernamePage.tsx │ │ │ ├── CredentialOnboardingChooser.tsx │ │ │ ├── DeleteAccountPage.tsx │ │ │ ├── DeviceTrustPage.tsx │ │ │ ├── EditPasswordPage.tsx │ │ │ ├── ErrorPage.tsx │ │ │ ├── InitPage.tsx │ │ │ ├── LoginInitPage.tsx │ │ │ ├── LoginMethodChooser.tsx │ │ │ ├── LoginOTPPage.tsx │ │ │ ├── LoginPasswordPage.tsx │ │ │ ├── LoginSecurityKeyPage.tsx │ │ │ ├── MFAMethodChooserPage.tsx │ │ │ ├── PasscodePage.tsx │ │ │ ├── ProfilePage.tsx │ │ │ ├── RegisterPasskeyPage.tsx │ │ │ ├── RegistrationInitPage.tsx │ │ │ └── RenameWebauthnCredentialPage.tsx │ ├── tsconfig.json │ ├── webpack.config.cjs │ └── webpack.config.dev.cjs ├── examples │ ├── README.md │ ├── angular │ │ ├── .browserslistrc │ │ ├── .dockerignore │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── angular.json │ │ ├── package.json │ │ ├── src │ │ │ ├── app │ │ │ │ ├── app-routing.module.ts │ │ │ │ ├── app.component.css │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── login │ │ │ │ │ ├── login.component.html │ │ │ │ │ └── login.component.ts │ │ │ │ ├── modal │ │ │ │ │ ├── session-expired-modal.component.html │ │ │ │ │ └── session-expired-modal.component.ts │ │ │ │ ├── profile │ │ │ │ │ ├── profile.component.html │ │ │ │ │ └── profile.component.ts │ │ │ │ ├── services │ │ │ │ │ ├── hanko.services.ts │ │ │ │ │ └── todo.service.ts │ │ │ │ └── todo │ │ │ │ │ ├── todo.component.css │ │ │ │ │ ├── todo.component.html │ │ │ │ │ └── todo.component.ts │ │ │ ├── assets │ │ │ │ ├── .gitkeep │ │ │ │ └── bg.jpg │ │ │ ├── environments │ │ │ │ └── environment.ts │ │ │ ├── favicon.png │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ └── styles.css │ │ ├── tsconfig.app.json │ │ └── tsconfig.json │ ├── express │ │ ├── .dockerignore │ │ ├── .env │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ │ └── server.js │ ├── fresh │ │ ├── .env │ │ ├── .gitignore │ │ ├── .vscode │ │ │ ├── extensions.json │ │ │ └── settings.json │ │ ├── README.md │ │ ├── components │ │ │ └── TodoItem.tsx │ │ ├── config.ts │ │ ├── deno.json │ │ ├── dev.ts │ │ ├── fresh.config.ts │ │ ├── fresh.gen.ts │ │ ├── islands │ │ │ ├── Login.tsx │ │ │ ├── LogoutButton.tsx │ │ │ ├── Profile.tsx │ │ │ └── TodoList.tsx │ │ ├── main.ts │ │ ├── routes │ │ │ ├── _app.tsx │ │ │ ├── api │ │ │ │ ├── _middleware.ts │ │ │ │ └── todo │ │ │ │ │ ├── [id].ts │ │ │ │ │ └── index.ts │ │ │ ├── index.tsx │ │ │ ├── profile.tsx │ │ │ └── todo.tsx │ │ ├── static │ │ │ ├── app.css │ │ │ ├── bg.jpg │ │ │ ├── favicon.ico │ │ │ └── logo.svg │ │ ├── twind.config.ts │ │ └── types.d.ts │ ├── nextjs │ │ ├── .dockerignore │ │ ├── .env │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── assets │ │ │ └── bg.jpg │ │ ├── components │ │ │ ├── HankoAuth.tsx │ │ │ ├── HankoProfile.tsx │ │ │ └── SessionExpiredModal.tsx │ │ ├── next.config.js │ │ ├── package.json │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── index.tsx │ │ │ ├── profile.tsx │ │ │ └── todo.tsx │ │ ├── public │ │ │ └── favicon.png │ │ ├── styles │ │ │ ├── Todo.module.css │ │ │ └── index.css │ │ ├── tsconfig.json │ │ └── util │ │ │ └── TodoClient.ts │ ├── react │ │ ├── .dockerignore │ │ ├── .env │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── assets │ │ │ └── bg.jpg │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.png │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── HankoAuth.tsx │ │ │ ├── HankoProfile.tsx │ │ │ ├── SessionExpiredModal.tsx │ │ │ ├── Todo.module.css │ │ │ ├── Todo.tsx │ │ │ ├── TodoClient.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ └── react-app-env.d.ts │ │ └── tsconfig.json │ ├── svelte │ │ ├── .dockerignore │ │ ├── .env │ │ ├── .gitignore │ │ ├── .vscode │ │ │ └── extensions.json │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ │ └── favicon.png │ │ ├── src │ │ │ ├── App.svelte │ │ │ ├── app.css │ │ │ ├── assets │ │ │ │ ├── bg.jpg │ │ │ │ └── svelte.svg │ │ │ ├── lib │ │ │ │ ├── Login.svelte │ │ │ │ ├── Profile.svelte │ │ │ │ ├── SessionExpiredModal.svelte │ │ │ │ ├── Todo.svelte │ │ │ │ └── TodoClient.ts │ │ │ ├── main.ts │ │ │ └── vite-env.d.ts │ │ ├── svelte.config.js │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ └── vue │ │ ├── .dockerignore │ │ ├── .env │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── .prettierrc.json │ │ ├── .vscode │ │ └── extensions.json │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── env.d.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ └── favicon.png │ │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── base.css │ │ │ └── bg.jpg │ │ ├── components │ │ │ └── SessionExpiredModal.vue │ │ ├── main.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── utils │ │ │ └── TodoClient.ts │ │ └── views │ │ │ ├── LoginView.vue │ │ │ ├── ProfileView.vue │ │ │ └── TodoView.vue │ │ ├── tsconfig.config.json │ │ ├── tsconfig.json │ │ └── vite.config.ts ├── frontend-sdk │ ├── .dockerignore │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .nvmrc │ ├── LICENSE │ ├── README.md │ ├── jest.config.cjs │ ├── jsdoc.json │ ├── nginx │ │ └── default.conf │ ├── package.json │ ├── src │ │ ├── Hanko.ts │ │ ├── declarations.d.ts │ │ ├── index.ts │ │ └── lib │ │ │ ├── Cookie.ts │ │ │ ├── Dto.ts │ │ │ ├── Errors.ts │ │ │ ├── SessionStorage.ts │ │ │ ├── Throttle.ts │ │ │ ├── WebauthnSupport.ts │ │ │ ├── client │ │ │ ├── Client.ts │ │ │ ├── HttpClient.ts │ │ │ ├── SessionClient.ts │ │ │ └── UserClient.ts │ │ │ ├── events │ │ │ ├── CustomEvents.ts │ │ │ ├── Dispatcher.ts │ │ │ ├── Listener.ts │ │ │ ├── Relay.ts │ │ │ ├── Scheduler.ts │ │ │ ├── SessionChannel.ts │ │ │ ├── SessionState.ts │ │ │ └── WindowActivityManager.ts │ │ │ └── flow-api │ │ │ ├── State.ts │ │ │ ├── WebauthnManager.ts │ │ │ ├── auto-steps.ts │ │ │ ├── passkey-autofill-activation.ts │ │ │ └── types │ │ │ ├── action.ts │ │ │ ├── flow.ts │ │ │ ├── flowError.ts │ │ │ ├── input.ts │ │ │ ├── payload.ts │ │ │ └── state.ts │ ├── tests │ │ ├── Hanko.spec.ts │ │ ├── lib │ │ │ ├── Cookie.spec.ts │ │ │ ├── Throttle.spec.ts │ │ │ ├── WebauthnSupport.spec.ts │ │ │ ├── client │ │ │ │ ├── HttpClient.spec.ts │ │ │ │ └── UserClient.spec.ts │ │ │ ├── events │ │ │ │ ├── Dispatcher.spec.ts │ │ │ │ └── Listener.spec.ts │ │ │ └── flow-api │ │ │ │ └── State.spec.ts │ │ ├── setup.ts │ │ └── types.d.ts │ ├── tsconfig.json │ └── tsconfig.prod.json ├── package-lock.json ├── package.json └── turbo.json ├── quickstart ├── Dockerfile ├── README.md ├── go.mod ├── go.sum ├── main.go ├── middleware │ ├── cache_control.go │ └── session.go └── public │ ├── assets │ ├── css │ │ ├── common.css │ │ ├── fonts.css │ │ ├── index.css │ │ └── secured.css │ ├── fonts │ │ ├── inter-v12-latin-500.eot │ │ ├── inter-v12-latin-500.svg │ │ ├── inter-v12-latin-500.ttf │ │ ├── inter-v12-latin-500.woff │ │ ├── inter-v12-latin-500.woff2 │ │ ├── inter-v12-latin-600.eot │ │ ├── inter-v12-latin-600.svg │ │ ├── inter-v12-latin-600.ttf │ │ ├── inter-v12-latin-600.woff │ │ ├── inter-v12-latin-600.woff2 │ │ ├── inter-v12-latin-regular.eot │ │ ├── inter-v12-latin-regular.svg │ │ ├── inter-v12-latin-regular.ttf │ │ ├── inter-v12-latin-regular.woff │ │ └── inter-v12-latin-regular.woff2 │ └── img │ │ ├── Favicon_256x256.png │ │ ├── Favicon_32x32.png │ │ └── poweredBy.svg │ └── html │ ├── error.html │ ├── index.html │ ├── secured.html │ └── unauthorized.html └── skaffold.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | [**] 2 | end_of_line = lf 3 | charset = utf-8 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | 7 | [**.{ts,tsx,json,js,cjs,sass}] 8 | indent_style = space 9 | indent_size = tab 10 | tab_width = 2 11 | 12 | [{go.mod,go.sum,*.go}] 13 | indent_style = tab 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Hanko Discussions 4 | url: https://github.com/teamhanko/hanko/discussions/new?category=q-a 5 | about: 6 | If you have general questions or questions on integrating Hanko, please open up a new discussion in the Q&A section. 7 | -------------------------------------------------------------------------------- /.github/workflows/build-frontend.yml: -------------------------------------------------------------------------------- 1 | name: build-frontend 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: v20.16.0 19 | 20 | - name: Install dependencies 21 | working-directory: ./frontend 22 | run: npm ci 23 | 24 | - name: Build 25 | working-directory: ./frontend 26 | run: npm run build 27 | 28 | - name: Run tests 29 | working-directory: ./frontend 30 | run: npm test 31 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: '1.24' 20 | 21 | - name: Build 22 | working-directory: ./backend 23 | run: | 24 | go generate ./... 25 | go build -v ./... 26 | 27 | - name: Test 28 | working-directory: ./backend 29 | run: go test -v ./... 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | #env files 9 | *.env 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Generated files 18 | .generated 19 | 20 | # MacOS 21 | .DS_Store 22 | 23 | ## JetBrains 24 | .idea 25 | 26 | hanko 27 | 28 | .turbo 29 | 30 | # Dependency directories (remove the comment below to include it) 31 | node_modules 32 | dist 33 | e2e/test-results/ 34 | e2e/playwright-report/ 35 | e2e/playwright/.cache/ 36 | /backend/build_info/version.txt 37 | /backend/dist 38 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you discover a security vulnerability, we appreciate any information about it so that we can fix the problem as soon as possible. 6 | 7 | Please email us at security@hanko.io. 8 | 9 | - We will evaluate the report and respond within 3 business days 10 | - We will keep your report confidential and will not share your personal information with any third party without your consent 11 | - We will keep you informed of the progress until the problem is resolved 12 | -------------------------------------------------------------------------------- /backend/.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | - go generate ./... 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - windows 11 | - darwin 12 | 13 | archives: 14 | - format: tar.gz 15 | name_template: >- 16 | {{ .ProjectName }}_ 17 | {{- title .Os }}_ 18 | {{- if eq .Arch "amd64" }}x86_64 19 | {{- else if eq .Arch "386" }}i386 20 | {{- else }}{{ .Arch }}{{ end }} 21 | {{- if .Arm }}v{{ .Arm }}{{ end }} 22 | format_overrides: 23 | - goos: windows 24 | format: zip 25 | -------------------------------------------------------------------------------- /backend/cmd/jwk/create.go: -------------------------------------------------------------------------------- 1 | package jwk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/spf13/cobra" 7 | "github.com/teamhanko/hanko/backend/crypto/jwk" 8 | "log" 9 | ) 10 | 11 | func NewCreateCommand() *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "create", 14 | Short: "create JSON Web Key and print them in the console", 15 | Long: ``, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | fmt.Println("create called") 18 | generator := jwk.RSAKeyGenerator{} 19 | key, err := generator.Generate("key1") 20 | if err != nil { 21 | log.Panicln(err) 22 | } 23 | j, err := json.Marshal(key) 24 | if err != nil { 25 | log.Panicln(err) 26 | } 27 | fmt.Println(string(j)) 28 | }, 29 | } 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /backend/cmd/jwk/root.go: -------------------------------------------------------------------------------- 1 | package jwk 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | 8 | func NewMigrateCmd() *cobra.Command { 9 | return &cobra.Command{ 10 | Use: "jwk", 11 | Short: "Tools for handling JSON Web Keys", 12 | Long: ``, 13 | } 14 | } 15 | 16 | func RegisterCommands(parent *cobra.Command) { 17 | cmd := NewMigrateCmd() 18 | parent.AddCommand(cmd) 19 | cmd.AddCommand(NewCreateCommand()) 20 | } 21 | -------------------------------------------------------------------------------- /backend/cmd/jwt/root.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func NewJwtCmd() *cobra.Command { 8 | return &cobra.Command{ 9 | Use: "jwt", 10 | Short: "Tools for handling JSON Web Tokens", 11 | Long: ``, 12 | } 13 | } 14 | 15 | func RegisterCommands(parent *cobra.Command) { 16 | cmd := NewJwtCmd() 17 | parent.AddCommand(cmd) 18 | cmd.AddCommand(NewCreateCommand()) 19 | } 20 | -------------------------------------------------------------------------------- /backend/cmd/migrate/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Hanko GmbH 3 | 4 | */ 5 | 6 | package migrate 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | //var persister *persistence.Persister 13 | 14 | func NewMigrateCmd() *cobra.Command { 15 | return &cobra.Command{ 16 | Use: "migrate", 17 | Short: "Database migration helpers", 18 | Long: ``, 19 | } 20 | } 21 | 22 | func RegisterCommands(parent *cobra.Command) { 23 | cmd := NewMigrateCmd() 24 | parent.AddCommand(cmd) 25 | cmd.AddCommand(NewMigrateUpCommand()) 26 | cmd.AddCommand(NewMigrateDownCommand()) 27 | } 28 | -------------------------------------------------------------------------------- /backend/cmd/schema/markdown.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func NewMarkdownCommand() *cobra.Command { 6 | cmd := &cobra.Command{ 7 | Use: "markdown", 8 | Short: "Generate markdown from JSON Schema", 9 | Long: ``, 10 | } 11 | 12 | cmd.AddCommand(NewMarkdownConfigCommand()) 13 | cmd.AddCommand(NewMarkdownImportCommand()) 14 | 15 | return cmd 16 | } 17 | -------------------------------------------------------------------------------- /backend/cmd/schema/root.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func NewSchemaCommand() *cobra.Command { 8 | return &cobra.Command{ 9 | Use: "schema", 10 | Short: "JSONSchema related commands", 11 | Long: ``, 12 | } 13 | } 14 | 15 | func RegisterCommands(parent *cobra.Command) { 16 | cmd := NewSchemaCommand() 17 | cmd.AddCommand(NewGenerateCommand()) 18 | cmd.AddCommand(NewMarkdownCommand()) 19 | parent.AddCommand(cmd) 20 | } 21 | -------------------------------------------------------------------------------- /backend/cmd/serve/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Hanko GmbH 3 | 4 | */ 5 | package serve 6 | 7 | import ( 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func NewServeCommand() *cobra.Command { 12 | return &cobra.Command{ 13 | Use: "serve", 14 | Short: "Start the hanko server", 15 | Long: ``, 16 | } 17 | } 18 | 19 | func RegisterCommands(parent *cobra.Command) { 20 | cmd := NewServeCommand() 21 | parent.AddCommand(cmd) 22 | cmd.AddCommand(NewServePublicCommand()) 23 | cmd.AddCommand(NewServeAdminCommand()) 24 | cmd.AddCommand(NewServeAllCommand()) 25 | } 26 | -------------------------------------------------------------------------------- /backend/cmd/user/root.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func NewUserCommand() *cobra.Command { 8 | return &cobra.Command{ 9 | Use: "user", 10 | Short: "User import/export tools", 11 | Long: `Add the ability to import/export users into/from the hanko database.`, 12 | } 13 | } 14 | 15 | func RegisterCommands(parent *cobra.Command) { 16 | command := NewUserCommand() 17 | parent.AddCommand(command) 18 | command.AddCommand(NewImportCommand()) 19 | command.AddCommand(NewGenerateCommand()) 20 | command.AddCommand(NewExportCommand()) 21 | } 22 | -------------------------------------------------------------------------------- /backend/config/config_account.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Account struct { 4 | // `allow_deletion` determines whether users can delete their accounts. 5 | AllowDeletion bool `yaml:"allow_deletion" json:"allow_deletion,omitempty" koanf:"allow_deletion" jsonschema:"default=false"` 6 | // `allow_signup` determines whether users are able to create new accounts. 7 | AllowSignup bool `yaml:"allow_signup" json:"allow_signup,omitempty" koanf:"allow_signup" jsonschema:"default=true"` 8 | } 9 | -------------------------------------------------------------------------------- /backend/config/config_emails.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Emails struct { 4 | // Deprecated. Use `email.require_verification` instead. 5 | RequireVerification bool `yaml:"require_verification" json:"require_verification,omitempty" koanf:"require_verification" split_words:"true" jsonschema:"default=true"` 6 | // Deprecated. Use `email.limit` instead. 7 | MaxNumOfAddresses int `yaml:"max_num_of_addresses" json:"max_num_of_addresses,omitempty" koanf:"max_num_of_addresses" split_words:"true" jsonschema:"default=5"` 8 | } 9 | -------------------------------------------------------------------------------- /backend/config/config_logger.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type LoggerConfig struct { 4 | // `log_health_and_metrics` determines whether requests of the `/health` and `/metrics` endpoints are logged. 5 | LogHealthAndMetrics bool `yaml:"log_health_and_metrics,omitempty" json:"log_health_and_metrics" koanf:"log_health_and_metrics" jsonschema:"default=true"` 6 | } 7 | -------------------------------------------------------------------------------- /backend/config/config_passcode.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Passcode struct { 4 | // Deprecated. Use `email.passcode_ttl` instead. 5 | TTL int `yaml:"ttl" json:"ttl,omitempty" koanf:"ttl" jsonschema:"default=300"` 6 | } 7 | -------------------------------------------------------------------------------- /backend/config/config_service.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | type Service struct { 9 | // `name` determines the name of the service. 10 | // This value is used, e.g. in the subject header of outgoing emails. 11 | Name string `yaml:"name" json:"name,omitempty" koanf:"name"` 12 | } 13 | 14 | func (s *Service) Validate() error { 15 | if len(strings.TrimSpace(s.Name)) == 0 { 16 | return errors.New("field name must not be empty") 17 | } 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /backend/config/config_webhook_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestWebhooks_Decode(t *testing.T) { 9 | webhooks := Webhooks{} 10 | value := "{\"callback\":\"http://app.com/usercb\",\"events\":[\"user\"]};{\"callback\":\"http://app.com/callback\",\"events\":[\"email.send\"]}" 11 | err := webhooks.Decode(value) 12 | 13 | assert.NoError(t, err) 14 | assert.Len(t, webhooks, 2, "has 2 elements") 15 | for _, webhook := range webhooks { 16 | assert.IsType(t, Webhook{}, webhook) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/config/minimal-config.yaml: -------------------------------------------------------------------------------- 1 | smtp: 2 | port: "465" 3 | host: smtp.example.com 4 | user: example 5 | password: example 6 | database: 7 | url: postgres://postgres:123456@127.0.0.1:5432/dummy 8 | secrets: 9 | keys: 10 | - abcedfghijklmnopqrstuvwxyz 11 | service: 12 | name: Hanko Authentication Service 13 | 14 | -------------------------------------------------------------------------------- /backend/config/passcode-smtp-config.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | user: hanko 3 | password: hanko 4 | host: localhost 5 | port: 5432 6 | dialect: postgres 7 | passcode: 8 | email: 9 | from_address: no-reply@hanko.io 10 | secrets: 11 | keys: 12 | - abcedfghijklmnopqrstuvwxyz 13 | service: 14 | name: Hanko Authentication Service 15 | email_delivery: 16 | enabled: true 17 | convert_legacy_config: true 18 | -------------------------------------------------------------------------------- /backend/config/root-passcode-smtp-config.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | user: hanko 3 | password: hanko 4 | host: localhost 5 | port: 5432 6 | dialect: postgres 7 | smtp: 8 | host: smtp1.example.com 9 | user: example1 10 | password: example1 11 | passcode: 12 | email: 13 | from_address: no-reply@hanko.io 14 | smtp: 15 | host: smtp2.example.com 16 | user: example2 17 | password: example2 18 | secrets: 19 | keys: 20 | - abcedfghijklmnopqrstuvwxyz 21 | service: 22 | name: Hanko Authentication Service 23 | -------------------------------------------------------------------------------- /backend/crypto/jwk/generator.go: -------------------------------------------------------------------------------- 1 | package jwk 2 | 3 | import "github.com/lestrrat-go/jwx/v2/jwk" 4 | 5 | // KeyGenerator Interface for JSON Web Key Generation 6 | type KeyGenerator interface { 7 | // Generate a new JWK with a given id 8 | Generate(id string) (jwk.Key, error) 9 | } 10 | -------------------------------------------------------------------------------- /backend/crypto/passcode.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | type PasscodeGenerator interface { 10 | Generate() (string, error) 11 | } 12 | 13 | type passcodeGenerator struct { 14 | } 15 | 16 | func NewPasscodeGenerator() PasscodeGenerator { 17 | return &passcodeGenerator{} 18 | } 19 | 20 | func (g *passcodeGenerator) Generate() (string, error) { 21 | max := big.NewInt(999999) 22 | n, err := rand.Int(rand.Reader, max) 23 | if err != nil { 24 | return "", fmt.Errorf("failed to generate random number: %w", err) 25 | } 26 | return fmt.Sprintf("%06d", n), nil 27 | } 28 | -------------------------------------------------------------------------------- /backend/crypto/passcode_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestPasscodeGenerator_Generate(t *testing.T) { 9 | pg := NewPasscodeGenerator() 10 | passcode, err := pg.Generate() 11 | 12 | assert.NoError(t, err) 13 | assert.NotEmpty(t, passcode) 14 | assert.Equal(t, 6, len(passcode)) 15 | } 16 | 17 | func TestPasscodeGenerator_Generate_Different_Codes(t *testing.T) { 18 | pg := NewPasscodeGenerator() 19 | 20 | passcode1, err := pg.Generate() 21 | assert.NoError(t, err) 22 | assert.NotEmpty(t, passcode1) 23 | 24 | passcode2, err := pg.Generate() 25 | assert.NoError(t, err) 26 | assert.NotEmpty(t, passcode2) 27 | 28 | assert.NotEqual(t, passcode1, passcode2) 29 | } 30 | -------------------------------------------------------------------------------- /backend/dto/admin/identity.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/teamhanko/hanko/backend/persistence/models" 6 | "time" 7 | ) 8 | 9 | type Identity struct { 10 | ID uuid.UUID `json:"id"` 11 | ProviderID string `json:"provider_id"` 12 | ProviderName string `json:"provider_name"` 13 | EmailID uuid.UUID `json:"email_id"` 14 | CreatedAt time.Time `json:"created_at"` 15 | UpdatedAt time.Time `json:"updated_at"` 16 | } 17 | 18 | func FromIdentityModel(model models.Identity) Identity { 19 | return Identity{ 20 | ID: model.ID, 21 | ProviderID: model.ProviderUserID, 22 | ProviderName: model.ProviderID, 23 | EmailID: model.EmailID, 24 | CreatedAt: model.CreatedAt, 25 | UpdatedAt: model.UpdatedAt, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/dto/admin/otp.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "time" 6 | ) 7 | 8 | type GetOTPRequestDto struct { 9 | UserID string `param:"user_id" validate:"required,uuid"` 10 | } 11 | 12 | type OTPDto struct { 13 | ID uuid.UUID `json:"id"` 14 | CreatedAt time.Time `json:"created_at"` 15 | } 16 | -------------------------------------------------------------------------------- /backend/dto/admin/password.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "time" 6 | ) 7 | 8 | type PasswordCredential struct { 9 | ID uuid.UUID `json:"id"` 10 | CreatedAt time.Time `json:"created_at"` 11 | UpdatedAt time.Time `json:"updated_at"` 12 | } 13 | 14 | type GetPasswordCredentialRequestDto struct { 15 | UserID string `param:"user_id" validate:"required,uuid"` 16 | } 17 | 18 | type CreateOrUpdatePasswordCredentialRequestDto struct { 19 | GetPasswordCredentialRequestDto 20 | Password string `json:"password" validate:"required"` 21 | } 22 | -------------------------------------------------------------------------------- /backend/dto/admin/session.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | type CreateSessionTokenDto struct { 4 | UserID string `json:"user_id" validate:"required,uuid"` 5 | UserAgent string `json:"user_agent"` 6 | IpAddress string `json:"ip_address" validate:"omitempty,ip"` 7 | } 8 | 9 | type CreateSessionTokenResponse struct { 10 | SessionToken string `json:"session_token"` 11 | } 12 | 13 | type ListSessionsRequestDto struct { 14 | UserID string `param:"user_id" validate:"required,uuid"` 15 | } 16 | 17 | type DeleteSessionRequestDto struct { 18 | ListSessionsRequestDto 19 | SessionID string `param:"session_id" validate:"required,uuid4"` 20 | } 21 | -------------------------------------------------------------------------------- /backend/dto/admin/username.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/teamhanko/hanko/backend/persistence/models" 6 | "time" 7 | ) 8 | 9 | type Username struct { 10 | ID uuid.UUID `json:"id"` 11 | Username string `json:"username"` 12 | CreatedAt time.Time `json:"created_at"` 13 | UpdatedAt time.Time `json:"updated_at"` 14 | } 15 | 16 | // FromEmailModel Converts the DB model to a DTO object 17 | func FromUsernameModel(model *models.Username) *Username { 18 | return &Username{ 19 | ID: model.ID, 20 | Username: model.Username, 21 | CreatedAt: model.CreatedAt, 22 | UpdatedAt: model.UpdatedAt, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/dto/admin/webauthn.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | type ListWebauthnCredentialsRequestDto struct { 4 | UserID string `param:"user_id" validate:"required,uuid"` 5 | } 6 | 7 | type GetWebauthnCredentialRequestDto struct { 8 | ListWebauthnCredentialsRequestDto 9 | WebauthnCredentialID string `param:"credential_id" validate:"required"` 10 | } 11 | -------------------------------------------------------------------------------- /backend/dto/passcode.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "time" 4 | 5 | type PasscodeFinishRequest struct { 6 | Id string `json:"id" validate:"required,uuid4"` 7 | Code string `json:"code" validate:"required"` 8 | } 9 | 10 | type PasscodeInitRequest struct { 11 | UserId string `json:"user_id" validate:"required,uuid"` 12 | EmailId *string `json:"email_id"` 13 | } 14 | 15 | type PasscodeReturn struct { 16 | Id string `json:"id"` 17 | TTL int `json:"ttl"` 18 | CreatedAt time.Time `json:"created_at"` 19 | } 20 | -------------------------------------------------------------------------------- /backend/dto/username.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/teamhanko/hanko/backend/persistence/models" 6 | "time" 7 | ) 8 | 9 | type Username struct { 10 | ID uuid.UUID `json:"id"` 11 | Username string `json:"username"` 12 | CreatedAt time.Time `json:"created_at"` 13 | UpdatedAt time.Time `json:"updated_at"` 14 | } 15 | 16 | func FromUsernameModel(u *models.Username) *Username { 17 | if u == nil { 18 | return nil 19 | } 20 | return &Username{ 21 | ID: u.ID, 22 | Username: u.Username, 23 | CreatedAt: u.CreatedAt, 24 | UpdatedAt: u.UpdatedAt, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/ee/saml/dto/saml.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type SamlRequest struct { 4 | Domain string `query:"domain" validate:"required,fqdn"` 5 | } 6 | 7 | type SamlMetadataRequest struct { 8 | SamlRequest 9 | CertOnly bool `query:"cert_only" validate:"boolean"` 10 | } 11 | 12 | type SamlAuthRequest struct { 13 | SamlRequest 14 | RedirectTo string `query:"redirect_to" validate:"required,url"` 15 | } 16 | -------------------------------------------------------------------------------- /backend/ee/saml/router.go: -------------------------------------------------------------------------------- 1 | package saml 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | auditlog "github.com/teamhanko/hanko/backend/audit_log" 6 | "github.com/teamhanko/hanko/backend/session" 7 | ) 8 | 9 | func CreateSamlRoutes(e *echo.Echo, sessionManager session.Manager, auditLogger auditlog.Logger, samlService Service) { 10 | handler := NewSamlHandler(sessionManager, auditLogger, samlService) 11 | routingGroup := e.Group("saml") 12 | routingGroup.GET("/provider", handler.GetProvider) 13 | routingGroup.GET("/metadata", handler.Metadata) 14 | routingGroup.GET("/auth", handler.Auth) 15 | routingGroup.POST("/callback", handler.CallbackPost) 16 | } 17 | -------------------------------------------------------------------------------- /backend/ee/saml/utils/url.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/ee/saml/config" 5 | "strings" 6 | ) 7 | 8 | func IsAllowedRedirect(config config.Saml, redirectTo string) bool { 9 | if redirectTo == "" { 10 | return false 11 | } 12 | 13 | redirectTo = strings.TrimSuffix(redirectTo, "/") 14 | 15 | for _, pattern := range config.AllowedRedirectURLMap { 16 | if pattern.Match(redirectTo) { 17 | return true 18 | } 19 | } 20 | 21 | return false 22 | } 23 | -------------------------------------------------------------------------------- /backend/flow_api/flow/credential_onboarding/action_continue_to_passkey.go: -------------------------------------------------------------------------------- 1 | package credential_onboarding 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flow_api/flow/shared" 5 | "github.com/teamhanko/hanko/backend/flowpilot" 6 | ) 7 | 8 | type ContinueToPasskey struct { 9 | shared.Action 10 | } 11 | 12 | func (a ContinueToPasskey) GetName() flowpilot.ActionName { 13 | return shared.ActionContinueToPasskeyRegistration 14 | } 15 | 16 | func (a ContinueToPasskey) GetDescription() string { 17 | return "Register a WebAuthn credential" 18 | } 19 | 20 | func (a ContinueToPasskey) Initialize(_ flowpilot.InitializationContext) {} 21 | 22 | func (a ContinueToPasskey) Execute(c flowpilot.ExecutionContext) error { 23 | return c.Continue(shared.StateOnboardingCreatePasskey) 24 | } 25 | -------------------------------------------------------------------------------- /backend/flow_api/flow/credential_onboarding/action_continue_to_password.go: -------------------------------------------------------------------------------- 1 | package credential_onboarding 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flow_api/flow/shared" 5 | "github.com/teamhanko/hanko/backend/flowpilot" 6 | ) 7 | 8 | type ContinueToPassword struct { 9 | shared.Action 10 | } 11 | 12 | func (a ContinueToPassword) GetName() flowpilot.ActionName { 13 | return shared.ActionContinueToPasswordRegistration 14 | } 15 | 16 | func (a ContinueToPassword) GetDescription() string { 17 | return "Register a password credential" 18 | } 19 | 20 | func (a ContinueToPassword) Initialize(_ flowpilot.InitializationContext) {} 21 | 22 | func (a ContinueToPassword) Execute(c flowpilot.ExecutionContext) error { 23 | return c.Continue(shared.StatePasswordCreation) 24 | } 25 | -------------------------------------------------------------------------------- /backend/flow_api/flow/login/hook_trigger_login_webhook.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/teamhanko/hanko/backend/flow_api/flow/shared" 6 | "github.com/teamhanko/hanko/backend/flowpilot" 7 | "github.com/teamhanko/hanko/backend/webhooks/events" 8 | "github.com/teamhanko/hanko/backend/webhooks/utils" 9 | ) 10 | 11 | type TriggerLoginWebhook struct { 12 | shared.Action 13 | } 14 | 15 | func (h TriggerLoginWebhook) Execute(c flowpilot.HookExecutionContext) error { 16 | deps := h.GetDeps(c) 17 | userID := uuid.FromStringOrNil(c.Stash().Get(shared.StashPathUserID).String()) 18 | utils.NotifyUserChange(deps.HttpContext, deps.Tx, deps.Persister, events.UserLogin, userID) 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /backend/flow_api/flow/mfa_creation/skip_mfa.go: -------------------------------------------------------------------------------- 1 | package mfa_creation 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flow_api/flow/shared" 5 | "github.com/teamhanko/hanko/backend/flowpilot" 6 | ) 7 | 8 | type SkipMFA struct { 9 | shared.Action 10 | } 11 | 12 | func (a SkipMFA) GetName() flowpilot.ActionName { 13 | return shared.ActionSkip 14 | } 15 | 16 | func (a SkipMFA) GetDescription() string { 17 | return "Skip" 18 | } 19 | 20 | func (a SkipMFA) Initialize(c flowpilot.InitializationContext) { 21 | deps := a.GetDeps(c) 22 | 23 | if !deps.Cfg.MFA.Optional { 24 | c.SuspendAction() 25 | } 26 | } 27 | 28 | func (a SkipMFA) Execute(c flowpilot.ExecutionContext) error { 29 | return c.Continue() 30 | } 31 | -------------------------------------------------------------------------------- /backend/flow_api/flow/shared/action_back.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flowpilot" 5 | ) 6 | 7 | type Back struct{} 8 | 9 | func (a Back) GetName() flowpilot.ActionName { 10 | return ActionBack 11 | } 12 | 13 | func (a Back) GetDescription() string { 14 | return "Navigate one step back." 15 | } 16 | 17 | func (a Back) Initialize(c flowpilot.InitializationContext) { 18 | if !c.StateIsRevertible() { 19 | c.SuspendAction() 20 | } 21 | } 22 | 23 | func (a Back) Execute(c flowpilot.ExecutionContext) error { 24 | return c.Revert() 25 | } 26 | -------------------------------------------------------------------------------- /backend/flow_api/flow/shared/action_skip.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flowpilot" 5 | ) 6 | 7 | type Skip struct { 8 | Action 9 | } 10 | 11 | func (a Skip) GetName() flowpilot.ActionName { 12 | return ActionSkip 13 | } 14 | 15 | func (a Skip) GetDescription() string { 16 | return "Skip" 17 | } 18 | 19 | func (a Skip) Initialize(c flowpilot.InitializationContext) {} 20 | 21 | func (a Skip) Execute(c flowpilot.ExecutionContext) error { 22 | return c.Continue() 23 | } 24 | -------------------------------------------------------------------------------- /backend/flow_api/flow/shared/const_flow_names.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import "github.com/teamhanko/hanko/backend/flowpilot" 4 | 5 | const ( 6 | FlowCapabilities flowpilot.FlowName = "capabilities" 7 | FlowCredentialOnboarding flowpilot.FlowName = "credential_onboarding" 8 | FlowCredentialUsage flowpilot.FlowName = "credential_usage" 9 | FlowDeviceTrust flowpilot.FlowName = "device_trust" 10 | FlowLogin flowpilot.FlowName = "login" 11 | FlowMFACreation flowpilot.FlowName = "mfa_creation" 12 | FlowProfile flowpilot.FlowName = "profile" 13 | FlowRegistration flowpilot.FlowName = "registration" 14 | FlowUserDetails flowpilot.FlowName = "user_details" 15 | FlowMFAUsage flowpilot.FlowName = "mfa_usage" 16 | ) 17 | -------------------------------------------------------------------------------- /backend/flow_api/flow/user_details/action_skip_email.go: -------------------------------------------------------------------------------- 1 | package user_details 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flow_api/flow/shared" 5 | "github.com/teamhanko/hanko/backend/flowpilot" 6 | ) 7 | 8 | type SkipEmail struct { 9 | shared.Action 10 | } 11 | 12 | func (a SkipEmail) GetName() flowpilot.ActionName { 13 | return shared.ActionSkip 14 | } 15 | 16 | func (a SkipEmail) GetDescription() string { 17 | return "Skip" 18 | } 19 | 20 | func (a SkipEmail) Initialize(c flowpilot.InitializationContext) { 21 | deps := a.GetDeps(c) 22 | 23 | if !deps.Cfg.Email.Optional { 24 | c.SuspendAction() 25 | } 26 | } 27 | 28 | func (a SkipEmail) Execute(c flowpilot.ExecutionContext) error { 29 | return c.Continue() 30 | 31 | } 32 | -------------------------------------------------------------------------------- /backend/flow_api/flow/user_details/action_skip_username.go: -------------------------------------------------------------------------------- 1 | package user_details 2 | 3 | import ( 4 | "github.com/teamhanko/hanko/backend/flow_api/flow/shared" 5 | "github.com/teamhanko/hanko/backend/flowpilot" 6 | ) 7 | 8 | type SkipUsername struct { 9 | shared.Action 10 | } 11 | 12 | func (a SkipUsername) GetName() flowpilot.ActionName { 13 | return shared.ActionSkip 14 | } 15 | 16 | func (a SkipUsername) GetDescription() string { 17 | return "Skip" 18 | } 19 | 20 | func (a SkipUsername) Initialize(c flowpilot.InitializationContext) { 21 | deps := a.GetDeps(c) 22 | 23 | if !deps.Cfg.Username.Optional { 24 | c.SuspendAction() 25 | } 26 | } 27 | func (a SkipUsername) Execute(c flowpilot.ExecutionContext) error { 28 | return c.Continue() 29 | } 30 | -------------------------------------------------------------------------------- /backend/flowpilot/payload.go: -------------------------------------------------------------------------------- 1 | package flowpilot 2 | 3 | import "github.com/teamhanko/hanko/backend/flowpilot/jsonmanager" 4 | 5 | type payload interface { 6 | jsonmanager.JSONManager 7 | } 8 | 9 | // newPayload creates a new instance of Payload with empty JSON data. 10 | func newPayload() payload { 11 | return jsonmanager.NewJSONManager() 12 | } 13 | -------------------------------------------------------------------------------- /backend/flowpilot/state_action.go: -------------------------------------------------------------------------------- 1 | package flowpilot 2 | 3 | type actionDetail interface { 4 | getAction() Action 5 | getFlowName() FlowName 6 | } 7 | 8 | type defaultActionDetail struct { 9 | action Action 10 | flowName FlowName 11 | } 12 | 13 | // actions represents a list of action 14 | type defaultActionDetails []actionDetail 15 | 16 | func (ad *defaultActionDetail) getAction() Action { 17 | return ad.action 18 | } 19 | 20 | func (ad *defaultActionDetail) getFlowName() FlowName { 21 | return ad.flowName 22 | } 23 | -------------------------------------------------------------------------------- /backend/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "net/http" 6 | ) 7 | 8 | type HealthHandler struct{} 9 | 10 | func NewHealthHandler() *HealthHandler { 11 | return &HealthHandler{} 12 | } 13 | 14 | func (handler *HealthHandler) Ready(c echo.Context) error { 15 | return c.JSON(http.StatusOK, map[string]bool{"ready": true}) 16 | } 17 | 18 | func (handler *HealthHandler) Alive(c echo.Context) error { 19 | return c.JSON(http.StatusOK, map[string]bool{"alive": true}) 20 | } 21 | -------------------------------------------------------------------------------- /backend/handler/status.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/teamhanko/hanko/backend/persistence" 6 | "net/http" 7 | ) 8 | 9 | type StatusHandler struct { 10 | persister persistence.Persister 11 | } 12 | 13 | func NewStatusHandler(persister persistence.Persister) *StatusHandler { 14 | return &StatusHandler{ 15 | persister: persister, 16 | } 17 | } 18 | 19 | func (h *StatusHandler) Status(c echo.Context) error { 20 | // random query to check DB connectivity 21 | _, err := h.persister.GetJwkPersister().GetAll() 22 | if err != nil { 23 | return c.Render(http.StatusInternalServerError, "status", map[string]bool{"dbError": true}) 24 | } 25 | 26 | return c.Render(http.StatusOK, "status", nil) 27 | } 28 | -------------------------------------------------------------------------------- /backend/mail/mailer.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "fmt" 5 | "github.com/teamhanko/hanko/backend/config" 6 | "gopkg.in/gomail.v2" 7 | "strconv" 8 | ) 9 | 10 | type Mailer interface { 11 | Send(message *gomail.Message) error 12 | } 13 | 14 | type mailer struct { 15 | dialer *gomail.Dialer 16 | } 17 | 18 | func NewMailer(config config.SMTP) (Mailer, error) { 19 | port, err := strconv.Atoi(config.Port) 20 | if err != nil { 21 | return nil, fmt.Errorf("failed to parse SMTP port: %w", err) 22 | } 23 | d := gomail.NewDialer(config.Host, port, config.User, config.Password) 24 | return &mailer{ 25 | dialer: d, 26 | }, nil 27 | } 28 | 29 | func (m *mailer) Send(message *gomail.Message) error { 30 | return m.dialer.DialAndSend(message) 31 | } 32 | -------------------------------------------------------------------------------- /backend/mail/templates/email_login_attempted.html.tmpl: -------------------------------------------------------------------------------- 1 | {{define "content"}} 2 | {{t "email_login_attempted_text" .}} 3 | {{end}} 4 | -------------------------------------------------------------------------------- /backend/mail/templates/email_login_attempted.txt.tmpl: -------------------------------------------------------------------------------- 1 | {{t "email_login_attempted_text" .}} 2 | -------------------------------------------------------------------------------- /backend/mail/templates/email_registration_attempted.html.tmpl: -------------------------------------------------------------------------------- 1 | {{define "content"}} 2 | {{t "email_registration_attempted_text" .}} 3 | {{end}} 4 | -------------------------------------------------------------------------------- /backend/mail/templates/email_registration_attempted.txt.tmpl: -------------------------------------------------------------------------------- 1 | {{t "email_registration_attempted_text" .}} 2 | -------------------------------------------------------------------------------- /backend/mail/templates/email_verification.html.tmpl: -------------------------------------------------------------------------------- 1 | {{define "content"}} 2 | {{t "email_verification_text" .}} 3 | 4 | {{template "code" .Code}} 5 | 6 | {{t "ttl_text" .}} 7 | {{end}} 8 | -------------------------------------------------------------------------------- /backend/mail/templates/email_verification.txt.tmpl: -------------------------------------------------------------------------------- 1 | {{t "email_verification_text" .}} 2 | 3 | {{ .Code }} 4 | 5 | {{t "ttl_text" .}} 6 | -------------------------------------------------------------------------------- /backend/mail/templates/login.html.tmpl: -------------------------------------------------------------------------------- 1 | {{define "content"}} 2 | {{t "login_text" .}} 3 | 4 | {{template "code" .Code}} 5 | 6 | {{t "ttl_text" .}} 7 | {{end}} 8 | -------------------------------------------------------------------------------- /backend/mail/templates/login.txt.tmpl: -------------------------------------------------------------------------------- 1 | {{t "login_text" .}} 2 | 3 | {{ .Code }} 4 | 5 | {{t "ttl_text" .}} 6 | -------------------------------------------------------------------------------- /backend/mail/templates/recovery.html.tmpl: -------------------------------------------------------------------------------- 1 | {{define "content"}} 2 | {{t "recovery_text" .}} 3 | 4 | {{template "code" .Code}} 5 | 6 | {{t "ttl_text" .}} 7 | {{end}} 8 | -------------------------------------------------------------------------------- /backend/mail/templates/recovery.txt.tmpl: -------------------------------------------------------------------------------- 1 | {{t "recovery_text" .}} 2 | 3 | {{ .Code }} 4 | 5 | {{t "ttl_text" .}} 6 | -------------------------------------------------------------------------------- /backend/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Hanko GmbH 3 | */ 4 | package main 5 | 6 | import ( 7 | "github.com/teamhanko/hanko/backend/cmd" 8 | ) 9 | 10 | func main() { 11 | cmd.Execute() 12 | } 13 | -------------------------------------------------------------------------------- /backend/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/labstack/echo/v4/middleware" 6 | ) 7 | 8 | func GetLoggerMiddleware() echo.MiddlewareFunc { 9 | return middleware.LoggerWithConfig(middleware.LoggerConfig{ 10 | Format: `{"time":"${time_rfc3339_nano}","time_unix":"${time_unix}","id":"${id}","remote_ip":"${remote_ip}",` + 11 | `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + 12 | `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + 13 | `,"bytes_in":${bytes_in},"bytes_out":${bytes_out},"referer":"${referer}"}` + "\n", 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /backend/middleware/webhook.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/teamhanko/hanko/backend/config" 6 | hankoJwk "github.com/teamhanko/hanko/backend/crypto/jwk" 7 | "github.com/teamhanko/hanko/backend/persistence" 8 | "github.com/teamhanko/hanko/backend/webhooks" 9 | ) 10 | 11 | func WebhookMiddleware(cfg *config.Config, jwkManager hankoJwk.Manager, persister persistence.Persister) echo.MiddlewareFunc { 12 | return func(next echo.HandlerFunc) echo.HandlerFunc { 13 | return func(ctx echo.Context) error { 14 | 15 | manager, err := webhooks.NewManager(cfg, persister, jwkManager, ctx.Logger()) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | ctx.Set("webhook_manager", manager) 21 | 22 | return next(ctx) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405153240_create_users.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("users") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405153240_create_users.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("users") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("email", "string", {}) 4 | t.Column("verified", "bool", {}) 5 | t.Timestamps() 6 | t.Index("email", {"unique": true}) 7 | } 8 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405153750_create_passcodes.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("passcodes") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405153750_create_passcodes.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("passcodes") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("user_id", "uuid", {}) 4 | t.Column("ttl", "integer", {}) 5 | t.Column("code", "string", {}) 6 | t.Column("try_count", "integer", {}) 7 | t.Timestamps() 8 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405154240_create_webauthn_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("webauthn_credentials") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405154240_create_webauthn_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("webauthn_credentials") { 2 | t.Column("id", "string", {primary: true}) 3 | t.Column("user_id", "uuid", {}) 4 | t.Column("public_key", "text", {}) 5 | t.Column("attestation_type", "string", {}) 6 | t.Column("aaguid", "uuid", {}) 7 | t.Column("sign_count", "integer", {}) 8 | t.Timestamps() 9 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 10 | } 11 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405154750_create_webauthn_session_data.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("webauthn_session_data") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405154750_create_webauthn_session_data.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("webauthn_session_data") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("challenge", "string", {}) 4 | t.Column("user_id", "uuid", {}) 5 | t.Column("user_verification", "string", {}) 6 | t.Column("operation", "string", {}) 7 | t.Timestamps() 8 | t.Index("challenge", {"unique": true}) 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405155120_create_webauthn_session_data_allowed_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("webauthn_session_data_allowed_credentials") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220405155120_create_webauthn_session_data_allowed_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("webauthn_session_data_allowed_credentials") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("credential_id", "string", {}) 4 | t.Column("webauthn_session_data_id", "uuid", {}) 5 | t.Timestamps() 6 | t.ForeignKey("webauthn_session_data_id", {"webauthn_session_data": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 7 | } 8 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220413152500_create_jwk.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("jwks") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220413152500_create_jwk.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("jwks") { 2 | t.Column("id", "int", {primary: true}) 3 | t.Column("key_data", "text", {}) 4 | t.Column("created_at", "timestamp", {}) 5 | t.DisableTimestamps() 6 | } 7 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220425122015_create_password_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("password_credentials") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220425122015_create_password_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("password_credentials") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("user_id", "uuid", {}) 4 | t.Column("password", "string", {}) 5 | t.Timestamps() 6 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 7 | t.Index("user_id", {"unique": true}) 8 | } 9 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220711121022_create_credential_transports.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("webauthn_credential_transports") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220711121022_create_credential_transports.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("webauthn_credential_transports") { 2 | t.Column("id", "string", {"primary": true}) 3 | t.Column("name", "string", {}) 4 | t.Column("webauthn_credential_id", "string", {}) 5 | t.DisableTimestamps() 6 | t.ForeignKey("webauthn_credential_id", {"webauthn_credentials": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 7 | t.Index(["name", "webauthn_credential_id"], {"unique": true}) 8 | } 9 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220818111000_create_audit_logs.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("audit_logs") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20220818111000_create_audit_logs.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("audit_logs") { 2 | t.Column("id", "uuid", {"primary": true}) 3 | t.Column("type", "string", {}) 4 | t.Column("error", "string", {"null": true}) 5 | t.Column("meta_http_request_id", "string", {}) 6 | t.Column("meta_source_ip", "string", {}) 7 | t.Column("meta_user_agent", "string", {}) 8 | t.Column("actor_user_id", "uuid", {"null": true}) 9 | t.Column("actor_email", "string", {"null": true}) 10 | t.Timestamps() 11 | t.Index("type") 12 | t.Index("actor_user_id") 13 | t.Index("actor_email") 14 | t.Index("meta_http_request_id") 15 | t.Index("meta_source_ip") 16 | } 17 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221027104800_create_emails.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("primary_emails") 2 | drop_table("emails") 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221027104900_change_users.down.fizz: -------------------------------------------------------------------------------- 1 | add_column("users", "email", "string", { "null": true }) 2 | add_column("users", "verified", "bool", { "null": true }) 3 | 4 | sql(" 5 | UPDATE users u 6 | SET email = ( 7 | SELECT e.address 8 | FROM emails e 9 | JOIN primary_emails pe 10 | ON e.id = pe.email_id AND e.user_id = u.id 11 | LIMIT 1 12 | ), 13 | verified = ( 14 | SELECT e.verified 15 | FROM emails e 16 | JOIN primary_emails pe 17 | ON e.id = pe.email_id AND e.user_id = u.id 18 | LIMIT 1 19 | )") 20 | 21 | change_column("users", "email", "string", { "null": false, "unique": true }) 22 | change_column("users", "verified", "bool", { "null": false }) 23 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221027104900_change_users.up.fizz: -------------------------------------------------------------------------------- 1 | drop_column("users", "email") 2 | drop_column("users", "verified") 3 | 4 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221027123530_change_passcodes.down.fizz: -------------------------------------------------------------------------------- 1 | drop_foreign_key("passcodes", "passcodes_emails_id_fk", {"if_exists": false}) 2 | drop_column("passcodes", "email_id") 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221027123530_change_passcodes.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("passcodes", "email_id", "uuid", { "null": true }) 2 | add_foreign_key("passcodes", "email_id", {"emails": ["id"]}, { 3 | "on_delete": "cascade", 4 | "on_update": "cascade", 5 | }) 6 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221222134900_change_webauthn_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("webauthn_credentials", "name") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20221222134900_change_webauthn_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("webauthn_credentials", "name", "string", { "null": true }) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230112152816_create_identities.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("identities") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230112152816_create_identities.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("identities") { 2 | t.Column("id", "uuid", {}) 3 | t.Column("provider_id", "string", {}) 4 | t.Column("provider_name", "string", {}) 5 | t.Column("data", "text", {"null": true}) 6 | t.Column("email_id", "uuid", {}) 7 | t.Timestamps() 8 | t.PrimaryKey("id") 9 | t.ForeignKey("email_id", {"emails": ["id"]}, {"on_delete": "cascade"}) 10 | t.Index(["provider_id", "provider_name"], {"unique": true}) 11 | } 12 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230206102000_change_webauthn_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("webauthn_credentials", "backup_eligible") 2 | drop_column("webauthn_credentials", "backup_state") 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230206102000_change_webauthn_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("webauthn_credentials", "backup_eligible", "bool", {"default": false}) 2 | add_column("webauthn_credentials", "backup_state", "bool", {"default": false}) 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230222114100_change_webauthn_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("webauthn_credentials", "last_used_at") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230222114100_change_webauthn_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("webauthn_credentials", "last_used_at", "timestamp", { "null": true }) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230317114334_create_tokens.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("tokens") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230317114334_create_tokens.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("tokens") { 2 | t.Column("id", "uuid", {}) 3 | t.Column("user_id", "uuid", {}) 4 | t.Column("value", "string", {}) 5 | t.Column("expires_at", "timestamp", {}) 6 | t.Timestamps() 7 | t.PrimaryKey("id") 8 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade"}) 9 | t.Index("value", {"unique": true}) 10 | } 11 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230801124808_webauthn_session_data_add_expiry.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("webauthn_session_data", "expires_at") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230801124808_webauthn_session_data_add_expiry.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("webauthn_session_data", "expires_at", "timestamp", {"null": true}) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230810173315_create_flows.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("transitions") 2 | drop_table("flows") 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230810173315_create_flows.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("flows") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("current_state", "string") 4 | t.Column("stash_data", "string", {"size": 4096}) 5 | t.Column("version", "int") 6 | t.Column("expires_at", "timestamp") 7 | t.Timestamps() 8 | } 9 | 10 | create_table("transitions") { 11 | t.Column("id", "uuid", {primary: true}) 12 | t.Column("flow_id", "uuid") 13 | t.Column("action", "string") 14 | t.Column("from_state", "string") 15 | t.Column("to_state", "string") 16 | t.Column("input_data", "string") 17 | t.Column("error_code", "string", {"null": true}) 18 | t.ForeignKey("flow_id", {"flows": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 19 | t.Timestamps() 20 | } 21 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230905102601_create_saml_state.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("saml_states") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230905102601_create_saml_state.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("saml_states") { 2 | t.Column("id", "uuid", {}) 3 | t.Column("nonce", "string", {}) 4 | t.Column("state", "varchar(500)", {}) 5 | t.Column("expires_at", "timestamp", {}) 6 | t.Timestamps() 7 | t.PrimaryKey("id") 8 | t.Index("nonce", {"unique": true}) 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230915111552_create_saml_certs.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("saml_certificates") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20230915111552_create_saml_certs.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("saml_certificates") { 2 | t.Column("id", "uuid", {}) 3 | t.Column("cert_data", "text", {}) 4 | t.Column("cert_key", "text", {}) 5 | t.Column("encryption_key", "string", {}) 6 | t.Timestamps() 7 | 8 | t.PrimaryKey("id") 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20231012141100_change_user_table.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("users", "username") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20231012141100_change_user_table.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("users", "username", "string", { "null": true }) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20231013113800_change_passcode_table.down.fizz: -------------------------------------------------------------------------------- 1 | sql("DELETE FROM passcodes WHERE user_id IS NULL") 2 | change_column("passcodes", "user_id", "uuid", {}) 3 | 4 | drop_foreign_key("passcodes", "passcodes_flows_id_fk", {"if_exists": false}) 5 | drop_column("passcodes", "flow_id") 6 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20231013113800_change_passcode_table.up.fizz: -------------------------------------------------------------------------------- 1 | change_column("passcodes", "user_id", "uuid", {"null": true}) 2 | 3 | add_column("passcodes", "flow_id", "uuid", {"null":true}) 4 | add_foreign_key("passcodes", "flow_id", {"flows": ["id"]}, { 5 | "on_delete": "cascade", 6 | "on_update": "cascade", 7 | }) 8 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240108094151_create_webhooks.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("webhooks") -------------------------------------------------------------------------------- /backend/persistence/migrations/20240108094151_create_webhooks.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("webhooks") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("callback", "string", {}) 4 | t.Column("enabled", "bool", { "default": true }) 5 | t.Column("failures", "int", { default: 0 }) 6 | t.Column("expires_at", "timestamp", {}) 7 | 8 | t.Timestamps() 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240108094210_create_webhook_events.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("webhook_events") -------------------------------------------------------------------------------- /backend/persistence/migrations/20240108094210_create_webhook_events.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("webhook_events") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("webhook_id", "uuid", { "null": false }) 4 | t.Column("event", "string", { "null": false }) 5 | 6 | t.Timestamps() 7 | 8 | t.Index(["webhook_id", "event"], { "unique": true }) 9 | t.ForeignKey("webhook_id", {"webhooks": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 10 | } 11 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240207150616_change_audit_logs.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("audit_logs", "details") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240207150616_change_audit_logs.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("audit_logs", "details", "text", {"null":true}) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240530122100_change_tokens.down.fizz: -------------------------------------------------------------------------------- 1 | drop_foreign_key("tokens", "tokens_identities_id_fk") 2 | drop_column("tokens", "identity_id") 3 | drop_column("tokens", "is_flow") 4 | drop_column("tokens", "user_created") 5 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240530122100_change_tokens.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("tokens", "identity_id", "uuid", {"null":true}) 2 | add_column("tokens", "is_flow", "bool", {"default":false}) 3 | add_column("tokens", "user_created", "bool", {"default":false}) 4 | 5 | add_foreign_key("tokens", "identity_id", {"identities": ["id"]}, { 6 | "on_delete": "cascade", 7 | "on_update": "cascade", 8 | }) 9 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240530145724_change_users.down.fizz: -------------------------------------------------------------------------------- 1 | drop_index("users", "users_username_idx") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240530145724_change_users.up.fizz: -------------------------------------------------------------------------------- 1 | add_index("users", "username", {"unique": true}) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240612122326_change_flows.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("flows", "csrf_token") 2 | drop_column("flows", "previous_state") 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240612122326_change_flows.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("flows", "csrf_token", "string", { "size": 32 }) 2 | add_column("flows", "previous_state", "string", { "null": true }) 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240717020131_drop_transitions.down.fizz: -------------------------------------------------------------------------------- 1 | create_table("transitions") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("flow_id", "uuid") 4 | t.Column("action", "string") 5 | t.Column("from_state", "string") 6 | t.Column("to_state", "string") 7 | t.Column("input_data", "string") 8 | t.Column("error_code", "string", {"null": true}) 9 | t.ForeignKey("flow_id", {"flows": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 10 | t.Timestamps() 11 | } 12 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240717020131_drop_transitions.up.fizz: -------------------------------------------------------------------------------- 1 | drop_table("transitions") 2 | 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240717020707_change_flows.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("flows", "data") 2 | add_column("flows", "stash_data", "string", {"size": 4096}) 3 | add_column("flows", "current_state", "string") 4 | add_column("flows", "previous_state", "string") 5 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240717020707_change_flows.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("flows", "data", "text") 2 | drop_column("flows", "stash_data") 3 | drop_column("flows", "current_state") 4 | drop_column("flows", "previous_state") 5 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240723171257_change_passcodes.down.fizz: -------------------------------------------------------------------------------- 1 | add_column("passcodes", "flow_id", "uuid", {"null":true}) 2 | add_foreign_key("passcodes", "flow_id", {"flows": ["id"]}, { 3 | "on_delete": "cascade", 4 | "on_update": "cascade", 5 | }) 6 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240723171257_change_passcodes.up.fizz: -------------------------------------------------------------------------------- 1 | drop_foreign_key("passcodes", "passcodes_flows_id_fk") 2 | drop_column("passcodes", "flow_id") 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240723173648_create_usernames.down.fizz: -------------------------------------------------------------------------------- 1 | add_column("users", "username", "string", { "null": true }) 2 | add_index("users", "username", {"unique": true}) 3 | drop_table("usernames") 4 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240723173648_create_usernames.up.fizz: -------------------------------------------------------------------------------- 1 | drop_column("users", "username") 2 | create_table("usernames") { 3 | t.Column("id", "uuid", {primary: true}) 4 | t.Column("user_id", "uuid", { "null": false }) 5 | t.Column("username", "string", { "null": false }) 6 | t.Timestamps() 7 | t.Index("username", { "unique": true }) 8 | t.Index("user_id", { "unique": true }) 9 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 10 | } 11 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240826132046_create_otp_secrets.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("otp_secrets") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240826132046_create_otp_secrets.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("otp_secrets") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("user_id", "uuid", {"null": false}) 4 | t.Column("secret", "string", {"null": false}) 5 | t.Timestamps() 6 | t.Index("user_id", {"unique": true}) 7 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 8 | } 9 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240826133417_change_webauthn_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_column("webauthn_credentials", "mfa_only") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20240826133417_change_webauthn_credentials.up.fizz: -------------------------------------------------------------------------------- 1 | add_column("webauthn_credentials", "mfa_only", "bool", {"default": false} ) 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241002113000_create_sessions.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("sessions") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241002113000_create_sessions.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("sessions") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("user_id", "uuid", { "null": false }) 4 | t.Column("user_agent", "string", { "null": false }) 5 | t.Column("ip_address", "string", { "null": false }) 6 | t.Column("expires_at", "timestamp", { "null": true }) 7 | t.Column("last_used", "timestamp", { "null": false }) 8 | t.Timestamps() 9 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 10 | } 11 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241106171500_change_sessions.down.fizz: -------------------------------------------------------------------------------- 1 | change_column("sessions", "user_agent", "string", {"null": false}) 2 | change_column("sessions", "ip_address", "string", {"null": false}) 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241106171500_change_sessions.up.fizz: -------------------------------------------------------------------------------- 1 | change_column("sessions", "user_agent", "string", {"null": true}) 2 | change_column("sessions", "ip_address", "string", {"null": true}) 3 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241112181011_create_trusted_devices.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("trusted_devices") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241112181011_create_trusted_devices.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("trusted_devices") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("user_id", "uuid", { "null": false }) 4 | t.Column("device_token", "string", { "null": false, "size": 128 }) 5 | t.Column("expires_at", "timestamp", {}) 6 | t.Timestamps() 7 | t.Index("device_token") 8 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20241118114500_change_webauthn_credentials.down.fizz: -------------------------------------------------------------------------------- 1 | drop_foreign_key("webauthn_credentials", "webauthn_credential_user_handle_fkey") 2 | drop_foreign_key("webauthn_credentials", "webauthn_credentials_webauthn_credential_user_handles_id_fk") 3 | drop_column("webauthn_credentials", "user_handle_id") 4 | drop_table("webauthn_credential_user_handles") 5 | 6 | 7 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250130154010_change_identities.down.fizz: -------------------------------------------------------------------------------- 1 | drop_index("identities", "identities_provider_user_id_provider_id_idx") 2 | 3 | rename_column("identities", "provider_id", "provider_name") 4 | rename_column("identities", "provider_user_id", "provider_id") 5 | 6 | add_index("identities", ["provider_id", "provider_name"], {unique: true}) 7 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250130154010_change_identities.up.fizz: -------------------------------------------------------------------------------- 1 | drop_index("identities", "identities_provider_id_provider_name_idx") 2 | 3 | rename_column("identities", "provider_id", "provider_user_id") 4 | rename_column("identities", "provider_name", "provider_id") 5 | 6 | add_index("identities", ["provider_user_id", "provider_id"], {unique: true}) 7 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250130170131_create_saml_identities.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("saml_identities") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250130170131_create_saml_identities.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("saml_identities") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("identity_id", "uuid", { "null": false }) 4 | t.Column("domain", "string", { "null": false }) 5 | t.Timestamps() 6 | t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade", "on_update": "cascade"}) 7 | t.Index(["identity_id", "domain"], {"unique": true}) 8 | } 9 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250210095906_create_saml_idp_initiated_requests.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("saml_idp_initiated_requests") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250210095906_create_saml_idp_initiated_requests.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("saml_idp_initiated_requests") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("response_id", "string", { "null": false }) 4 | t.Column("issuer", "string", { "null": false }) 5 | t.Column("expires_at", "timestamp", { "null": false }) 6 | t.Column("created_at", "timestamp", { "null": false }) 7 | t.DisableTimestamps() 8 | t.Index(["response_id", "issuer"], {"unique": true}) 9 | } 10 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250313160348_change_flows.down.fizz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/backend/persistence/migrations/20250313160348_change_flows.down.fizz -------------------------------------------------------------------------------- /backend/persistence/migrations/20250313160348_change_flows.up.fizz: -------------------------------------------------------------------------------- 1 | change_column("flows", "data", "text") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250414165334_create_user_metadata.down.fizz: -------------------------------------------------------------------------------- 1 | drop_table("user_metadata") 2 | -------------------------------------------------------------------------------- /backend/persistence/migrations/20250414165334_create_user_metadata.up.fizz: -------------------------------------------------------------------------------- 1 | create_table("user_metadata") { 2 | t.Column("id", "uuid", {primary: true}) 3 | t.Column("user_id", "uuid", {}) 4 | t.Column("public_metadata", "string", {"null": true, size: 3000}) 5 | t.Column("private_metadata", "string", {"null": true, size: 3000}) 6 | t.Column("unsafe_metadata", "string", {"null": true, size: 3000}) 7 | t.Column("created_at", "timestamp", {}) 8 | t.Column("updated_at", "timestamp", {}) 9 | 10 | t.ForeignKey("user_id", {"users": ["id"]}, {"on_delete": "CASCADE"}) 11 | t.Index("user_id", {unique: true}) 12 | } 13 | -------------------------------------------------------------------------------- /backend/persistence/models/jwk.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/gobuffalo/pop/v6" 5 | "github.com/gobuffalo/validate/v3" 6 | "github.com/gobuffalo/validate/v3/validators" 7 | "time" 8 | ) 9 | 10 | type Jwk struct { 11 | ID int `db:"id"` 12 | KeyData string `db:"key_data"` 13 | CreatedAt time.Time `db:"created_at"` 14 | } 15 | 16 | // Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method. 17 | func (jwk *Jwk) Validate(tx *pop.Connection) (*validate.Errors, error) { 18 | return validate.Validate( 19 | &validators.StringIsPresent{Name: "KeyData", Field: jwk.KeyData}, 20 | &validators.TimeIsPresent{Name: "CreatedAt", Field: jwk.CreatedAt}, 21 | ), nil 22 | } 23 | -------------------------------------------------------------------------------- /backend/template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "embed" 5 | "github.com/labstack/echo/v4" 6 | "html/template" 7 | "io" 8 | ) 9 | 10 | //go:embed templates/* 11 | var templateFS embed.FS 12 | 13 | type Template struct { 14 | templates *template.Template 15 | } 16 | 17 | func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 18 | return t.templates.ExecuteTemplate(w, name, data) 19 | } 20 | 21 | func NewTemplateRenderer() *Template { 22 | return &Template{ 23 | templates: template.Must(template.ParseFS(templateFS, "templates/*.tmpl")), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/template/templates/status.tmpl: -------------------------------------------------------------------------------- 1 | {{define "status"}} 2 | 3 | 4 | 5 | API Integration Status 6 | 7 | 8 |

API Status

9 | {{if .dbError}} 10 |

❌ API is not functioning properly due to database connectivity problems.

11 | {{else}} 12 |

✅ API is running.

13 |

Check integration guides on docs.hanko.io

14 | {{end}} 15 | 16 | 17 | {{end}} 18 | -------------------------------------------------------------------------------- /backend/test/audit_logger.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/gobuffalo/pop/v6" 5 | "github.com/labstack/echo/v4" 6 | "github.com/teamhanko/hanko/backend/audit_log" 7 | "github.com/teamhanko/hanko/backend/persistence/models" 8 | ) 9 | 10 | func NewAuditLogger() auditlog.Logger { 11 | return &auditLogger{} 12 | } 13 | 14 | type auditLogger struct { 15 | } 16 | 17 | func (a *auditLogger) Create(context echo.Context, logType models.AuditLogType, user *models.User, err error, opts ...auditlog.DetailOption) error { 18 | return nil 19 | } 20 | 21 | func (a *auditLogger) CreateWithConnection(tx *pop.Connection, context echo.Context, logType models.AuditLogType, user *models.User, err error, opts ...auditlog.DetailOption) error { 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /backend/test/fixtures/actions/send_capabilities/flows.yaml: -------------------------------------------------------------------------------- 1 | - id: 0b41f4dd-8e46-4a7c-bb4d-d60843113431 2 | current_state: registration_preflight 3 | stash_data: {} 4 | version: 0 5 | expires_at: 2099-12-31 23:59:59 6 | created_at: 2023-01-01 00:00:00 7 | updated_at: 2023-01-01 00:00:00 8 | 9 | -------------------------------------------------------------------------------- /backend/test/fixtures/actions/send_wa_attestation_response/webauthn_session_data.yaml: -------------------------------------------------------------------------------- 1 | - id: adce0002-35bc-c60a-648b-0b25f1f05503 2 | challenge: "tOrNDCD2xQf4zFjEjwxaP8fOErP3zz08rMoTlJGtnKU" 3 | user_id: "ec4ef049-5b88-4321-a173-21b0eff06a04" 4 | user_verification: "required" 5 | operation: "registration" 6 | created_at: 2020-12-31 23:59:59 7 | updated_at: 2020-12-31 23:59:59 8 | 9 | # expired session data 10 | - id: 65f13ce2-d118-44f0-a38b-8e3ee918c6f3 11 | challenge: "FeMc7sR9ElehwEU5TtEWFi7rPP3-kdZXgnwLtlb3ChY" 12 | user_id: "ec4ef049-5b88-4321-a173-21b0eff06a04" 13 | user_verification: "required" 14 | operation: "registration" 15 | created_at: 2020-12-31 23:59:59 16 | updated_at: 2020-12-31 23:59:59 17 | expires_at: 2020-12-31 23:59:59 18 | -------------------------------------------------------------------------------- /backend/test/fixtures/actions/submit_new_password/flows.yaml: -------------------------------------------------------------------------------- 1 | - id: 0b41f4dd-8e46-4a7c-bb4d-d60843113431 2 | current_state: password_creation 3 | stash_data: "{\"webauthn_available\":true}" 4 | version: 0 5 | expires_at: 2099-12-31 23:59:59 6 | created_at: 2023-01-01 00:00:00 7 | updated_at: 2023-01-01 00:00:00 8 | - id: 8a2cf90d-dea5-4678-9dca-6707dab6af77 9 | current_state: password_creation 10 | stash_data: "{\"webauthn_available\":false}" 11 | version: 0 12 | expires_at: 2099-12-31 23:59:59 13 | created_at: 2023-01-01 00:00:00 14 | updated_at: 2023-01-01 00:00:00 15 | -------------------------------------------------------------------------------- /backend/test/fixtures/actions/submit_registration_identifier/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 7c8a09a9-5418-4e01-9918-968e58982a3a 2 | user_id: b7fe9f28-bc2d-44fc-819e-bcb478656b94 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2023-01-01 00:00:00 6 | updated_at: 2023-01-01 00:00:00 7 | - id: c23941fc-382f-46a3-bf0e-ff0f4258c58b 8 | user_id: 0f813887-5479-42d8-b8d7-3e7a2f426516 9 | address: jane.doe@example.com 10 | verified: true 11 | created_at: 2023-01-01 00:00:00 12 | updated_at: 2023-01-01 00:00:00 13 | -------------------------------------------------------------------------------- /backend/test/fixtures/actions/submit_registration_identifier/flows.yaml: -------------------------------------------------------------------------------- 1 | - id: 0b41f4dd-8e46-4a7c-bb4d-d60843113431 2 | current_state: registration_init 3 | stash_data: {} 4 | version: 0 5 | expires_at: 2099-12-31 23:59:59 6 | created_at: 2023-01-01 00:00:00 7 | updated_at: 2023-01-01 00:00:00 8 | -------------------------------------------------------------------------------- /backend/test/fixtures/actions/submit_registration_identifier/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b7fe9f28-bc2d-44fc-819e-bcb478656b94 2 | username: john.doe 3 | created_at: 2023-01-01 00:00:00 4 | updated_at: 2023-01-01 00:00:00 5 | - id: 0f813887-5479-42d8-b8d7-3e7a2f426516 6 | created_at: 2023-01-01 00:00:00 7 | updated_at: 2023-01-01 00:00:00 8 | - id: 9ee61f69-952b-400a-adc9-2800aef967fa 9 | username: max.mustermann 10 | created_at: 2023-01-01 00:00:00 11 | updated_at: 2023-01-01 00:00:00 12 | -------------------------------------------------------------------------------- /backend/test/fixtures/email/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8fe72e5f-edb6-40e7-83a7-a7e858c2c62d 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | - id: 3a5f340f-07f7-40dc-a507-d5919915e11d 7 | email_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | -------------------------------------------------------------------------------- /backend/test/fixtures/email/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: e0282f3f-b211-4f0e-b777-6fabc69287c9 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | - id: d41df4b7-c055-45e6-9faf-61aa92a4032e 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | 14 | -------------------------------------------------------------------------------- /backend/test/fixtures/metadata/user_metadata.yaml: -------------------------------------------------------------------------------- 1 | - id: 7c9e6679-7425-40de-944b-e07fc1f90ae7 2 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 3 | public_metadata: {"existing_public_str":"data","existing_public_num":1,"existing_public_bool":true} 4 | private_metadata: {"existing_private_str":"data","existing_private_arr":["existing_private_arr_0","existing_private_arr_1"]} 5 | unsafe_metadata: {"existing_unsafe_str":"data","existing_unsafe_obj":{"existing_unsafe_obj_key":"existing_unsafe_obj_value"}} 6 | created_at: 2020-12-31 23:59:59 7 | updated_at: 2020-12-31 23:59:59 8 | -------------------------------------------------------------------------------- /backend/test/fixtures/metadata/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 -------------------------------------------------------------------------------- /backend/test/fixtures/otp/otp_secrets.yaml: -------------------------------------------------------------------------------- 1 | - id: f28b15df-6e09-4ac0-b49f-e4e2d274f939 2 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 3 | secret: RANDOMOTPSECRET 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | -------------------------------------------------------------------------------- /backend/test/fixtures/otp/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8fe72e5f-edb6-40e7-83a7-a7e858c2c62d 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | - id: 3a5f340f-07f7-40dc-a507-d5919915e11d 7 | email_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | -------------------------------------------------------------------------------- /backend/test/fixtures/otp/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: e0282f3f-b211-4f0e-b777-6fabc69287c9 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | - id: d41df4b7-c055-45e6-9faf-61aa92a4032e 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | 14 | -------------------------------------------------------------------------------- /backend/test/fixtures/passcode/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8fe72e5f-edb6-40e7-83a7-a7e858c2c62d 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | - id: 3a5f340f-07f7-40dc-a507-d5919915e11d 7 | email_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | -------------------------------------------------------------------------------- /backend/test/fixtures/passcode/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: e0282f3f-b211-4f0e-b777-6fabc69287c9 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | - id: d41df4b7-c055-45e6-9faf-61aa92a4032e 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | 14 | -------------------------------------------------------------------------------- /backend/test/fixtures/password/password_credentials.yaml: -------------------------------------------------------------------------------- 1 | - id: 6a565180-2366-45b1-8785-39f7902c7f2e 2 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 3 | password: $2a$12$Cf7k.dG6pznTUJ5u2u1pgu6I4VXH5.9O0NZsDk8TwWwyBkZovYVli 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | -------------------------------------------------------------------------------- /backend/test/fixtures/password/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8fe72e5f-edb6-40e7-83a7-a7e858c2c62d 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | - id: 3a5f340f-07f7-40dc-a507-d5919915e11d 7 | email_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | -------------------------------------------------------------------------------- /backend/test/fixtures/password/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: e0282f3f-b211-4f0e-b777-6fabc69287c9 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | - id: d41df4b7-c055-45e6-9faf-61aa92a4032e 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | 14 | -------------------------------------------------------------------------------- /backend/test/fixtures/sessions/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 2 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | address: jane.doe@example.com 10 | verified: true 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | - id: 1215f04b-0fad-4c54-899e-a4f7230fcc63 14 | user_id: 46626836-f2db-4ec0-8752-858b544cbc78 15 | address: test@example.com 16 | verified: true 17 | created_at: 2020-12-31 23:59:59 18 | updated_at: 2020-12-31 23:59:59 19 | -------------------------------------------------------------------------------- /backend/test/fixtures/sessions/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8eaaa61b-ad65-45ac-a5b8-d7c6d301d29e 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | - id: df8b4c07-f97e-48aa-8895-6c8e54d5b749 7 | email_id: 1215f04b-0fad-4c54-899e-a4f7230fcc63 8 | user_id: 46626836-f2db-4ec0-8752-858b544cbc78 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | -------------------------------------------------------------------------------- /backend/test/fixtures/sessions/users.yaml: -------------------------------------------------------------------------------- 1 | - id: ec4ef049-5b88-4321-a173-21b0eff06a04 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: 46626836-f2db-4ec0-8752-858b544cbc78 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | -------------------------------------------------------------------------------- /backend/test/fixtures/token/tokens.yaml: -------------------------------------------------------------------------------- 1 | # Expired token 2 | - id: c125c6b5-37bf-446b-a1c7-6ee24eecd63e 3 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 4 | created_at: 2022-03-20T15:13:10.168902Z 5 | updated_at: 2022-03-20T15:12:01.639187Z 6 | expires_at: 2022-03-20T15:18:10.168902Z 7 | value: "Trkauhl3q7XVxw5JcDH80lTe1KxzydIw0OcizH7umWk=" 8 | user_created: false 9 | is_flow: false 10 | -------------------------------------------------------------------------------- /backend/test/fixtures/token/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | -------------------------------------------------------------------------------- /backend/test/fixtures/user/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 2 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | 8 | -------------------------------------------------------------------------------- /backend/test/fixtures/user/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | -------------------------------------------------------------------------------- /backend/test/fixtures/user_admin/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 2 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | address: john.doe+1@example.com 10 | verified: true 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | - id: e0282f3f-b211-4f0e-b777-6fabc69287c9 14 | user_id: e0282f3f-b211-4f0e-b777-6fabc69287c9 15 | address: john.doe+2@example.com 16 | verified: true 17 | created_at: 2020-12-31 23:59:59 18 | updated_at: 2020-12-31 23:59:59 19 | 20 | -------------------------------------------------------------------------------- /backend/test/fixtures/user_admin/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: e0282f3f-b211-4f0e-b777-6fabc69287c9 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | - id: d41df4b7-c055-45e6-9faf-61aa92a4032e 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | 14 | -------------------------------------------------------------------------------- /backend/test/fixtures/user_with_webauthn_credential/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 2 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | 8 | -------------------------------------------------------------------------------- /backend/test/fixtures/user_with_webauthn_credential/users.yaml: -------------------------------------------------------------------------------- 1 | - id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | -------------------------------------------------------------------------------- /backend/test/fixtures/user_with_webauthn_credential/webauthn_credentials.yaml: -------------------------------------------------------------------------------- 1 | - id: P8fcQ6U8zxJRzhI0yuUCOxcA_UyAs0jbauO5ektj4SM 2 | user_id: b5dd5267-b462-48be-b70d-bcd6f1bbe7a5 3 | public_key: public_key 4 | attestation_type: none 5 | aaguid: 00000000-0000-0000-0000-000000000000 6 | sign_count: 0 7 | backup_eligible: false 8 | backup_state: false 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | 12 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 2 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | address: jane.doe@example.com 10 | verified: true 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | - id: 1215f04b-0fad-4c54-899e-a4f7230fcc63 14 | user_id: 46626836-f2db-4ec0-8752-858b544cbc78 15 | address: test@example.com 16 | verified: true 17 | created_at: 2020-12-31 23:59:59 18 | updated_at: 2020-12-31 23:59:59 19 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8eaaa61b-ad65-45ac-a5b8-d7c6d301d29e 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | - id: df8b4c07-f97e-48aa-8895-6c8e54d5b749 7 | email_id: 1215f04b-0fad-4c54-899e-a4f7230fcc63 8 | user_id: 46626836-f2db-4ec0-8752-858b544cbc78 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn/users.yaml: -------------------------------------------------------------------------------- 1 | - id: ec4ef049-5b88-4321-a173-21b0eff06a04 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: 46626836-f2db-4ec0-8752-858b544cbc78 8 | created_at: 2020-12-31 23:59:59 9 | updated_at: 2020-12-31 23:59:59 10 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn/webauthn_session_data.yaml: -------------------------------------------------------------------------------- 1 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 2 | challenge: "gKJKmh90vOpYO55oHpqaHX_oMCq4oTZt-D0b6teIzrE" 3 | user_id: "00000000-0000-0000-0000-000000000000" 4 | user_verification: "required" 5 | operation: "authentication" 6 | created_at: 2020-12-31 23:59:59 7 | updated_at: 2020-12-31 23:59:59 8 | 9 | # expired session data, explicitly set user_id because session data expiry is only checked for non-discoverable login 10 | - id: afcfeb0f-44e8-4d44-9fab-088459d228f5 11 | challenge: "AUgNkV8nmVwv-rl8-aK1ZEX5FlenYCsaTY8vdEcJKJQ" 12 | user_id: "46626836-f2db-4ec0-8752-858b544cbc78" 13 | user_verification: "required" 14 | operation: "authentication" 15 | created_at: 2020-12-31 23:59:59 16 | updated_at: 2020-12-31 23:59:59 17 | expires_at: 2020-12-31 23:59:59 18 | 19 | 20 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn_registration/emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 2 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 3 | address: john.doe@example.com 4 | verified: true 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 8 | user_id: 38bf5a00-d7ea-40a5-a5de-48722c148925 9 | address: jane.doe@example.com 10 | verified: true 11 | created_at: 2020-12-31 23:59:59 12 | updated_at: 2020-12-31 23:59:59 13 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn_registration/primary_emails.yaml: -------------------------------------------------------------------------------- 1 | - id: 8eaaa61b-ad65-45ac-a5b8-d7c6d301d29e 2 | email_id: 51b7c175-ceb6-45ba-aae6-0092221c1b84 3 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 4 | created_at: 2020-12-31 23:59:59 5 | updated_at: 2020-12-31 23:59:59 6 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn_registration/users.yaml: -------------------------------------------------------------------------------- 1 | - id: ec4ef049-5b88-4321-a173-21b0eff06a04 2 | created_at: 2020-12-31 23:59:59 3 | updated_at: 2020-12-31 23:59:59 4 | - id: 38bf5a00-d7ea-40a5-a5de-48722c148925 5 | created_at: 2020-12-31 23:59:59 6 | updated_at: 2020-12-31 23:59:59 7 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn_registration/webauthn_credentials.yaml: -------------------------------------------------------------------------------- 1 | - id: AaFdkcD4SuPjF-jwUoRwH8-ZHuY5RW46fsZmEvBX6RNKHaGtVzpATs06KQVheIOjYz-YneG4cmQOedzl0e0jF951ukx17Hl9jeGgWz5_DKZCO12p2-2LlzjK 2 | user_id: ec4ef049-5b88-4321-a173-21b0eff06a04 3 | public_key: pQECAyYgASFYIPG9WtGAri-mevonFPH4p-lI3JBS29zjuvKvJmaP4_mRIlggOjHw31sdAGvE35vmRep-aPcbAAlbuc0KHxQ9u6zcHoj 4 | attestation_type: none 5 | aaguid: adce0002-35bc-c60a-648b-0b25f1f05503 6 | sign_count: 1650958750 7 | backup_eligible: false 8 | backup_state: false 9 | created_at: 2020-12-31 23:59:59 10 | updated_at: 2020-12-31 23:59:59 11 | 12 | -------------------------------------------------------------------------------- /backend/test/fixtures/webauthn_registration/webauthn_session_data.yaml: -------------------------------------------------------------------------------- 1 | - id: adce0002-35bc-c60a-648b-0b25f1f05503 2 | challenge: "tOrNDCD2xQf4zFjEjwxaP8fOErP3zz08rMoTlJGtnKU" 3 | user_id: "ec4ef049-5b88-4321-a173-21b0eff06a04" 4 | user_verification: "required" 5 | operation: "registration" 6 | created_at: 2020-12-31 23:59:59 7 | updated_at: 2020-12-31 23:59:59 8 | 9 | # expired session data 10 | - id: 65f13ce2-d118-44f0-a38b-8e3ee918c6f3 11 | challenge: "FeMc7sR9ElehwEU5TtEWFi7rPP3-kdZXgnwLtlb3ChY" 12 | user_id: "ec4ef049-5b88-4321-a173-21b0eff06a04" 13 | user_verification: "required" 14 | operation: "registration" 15 | created_at: 2020-12-31 23:59:59 16 | updated_at: 2020-12-31 23:59:59 17 | expires_at: 2020-12-31 23:59:59 18 | -------------------------------------------------------------------------------- /backend/webhooks/config_hook.go: -------------------------------------------------------------------------------- 1 | package webhooks 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/teamhanko/hanko/backend/config" 6 | "time" 7 | ) 8 | 9 | type ConfigHook struct { 10 | BaseWebhook 11 | } 12 | 13 | func NewConfigHook(cfgHook config.Webhook, logger echo.Logger) Webhook { 14 | return &ConfigHook{ 15 | BaseWebhook{ 16 | Logger: logger, 17 | Callback: cfgHook.Callback, 18 | Events: cfgHook.Events, 19 | }, 20 | } 21 | } 22 | 23 | func (ch *ConfigHook) DisableOnExpiryDate(_ time.Time) error { 24 | return nil 25 | } 26 | 27 | func (ch *ConfigHook) DisableOnFailure() error { 28 | return nil 29 | } 30 | 31 | func (ch *ConfigHook) Reset() error { 32 | return nil 33 | } 34 | 35 | func (ch *ConfigHook) IsEnabled() bool { 36 | return true 37 | } 38 | -------------------------------------------------------------------------------- /deploy/docker-compose/config-disable-signup.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | user: hanko 3 | password: hanko 4 | host: postgresd 5 | port: 5432 6 | dialect: postgres 7 | smtp: 8 | host: "mailslurper" 9 | port: "2500" 10 | passcode: 11 | email: 12 | from_address: no-reply@hanko.io 13 | secrets: 14 | keys: 15 | - abcedfghijklmnopqrstuvwxyz 16 | service: 17 | name: Hanko Authentication Service 18 | webauthn: 19 | relying_party: 20 | origins: 21 | - "http://localhost:8888" 22 | session: 23 | cookie: 24 | secure: false # is needed for safari, because safari does not store secure cookies on localhost 25 | server: 26 | public: 27 | cors: 28 | allow_origins: 29 | - "http://localhost:8888" 30 | account: 31 | allow_signup: false 32 | -------------------------------------------------------------------------------- /deploy/docker-compose/config-rate-limiting.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | user: hanko 3 | password: hanko 4 | host: postgresd 5 | port: 5432 6 | dialect: postgres 7 | smtp: 8 | host: "mailslurper" 9 | port: "2500" 10 | passcode: 11 | email: 12 | from_address: no-reply@hanko.io 13 | secrets: 14 | keys: 15 | - abcedfghijklmnopqrstuvwxyz 16 | service: 17 | name: Hanko Authentication Service 18 | webauthn: 19 | relying_party: 20 | origins: 21 | - "http://localhost:8888" 22 | session: 23 | cookie: 24 | secure: false # is needed for safari, because safari does not store secure cookies on localhost 25 | rate_limiter: 26 | enabled: true 27 | store: "redis" 28 | redis_config: 29 | address: "redis:6379" 30 | password: 31 | enabled: true 32 | server: 33 | cors: 34 | allow_origins: 35 | - "http://localhost:8888" 36 | -------------------------------------------------------------------------------- /deploy/docker-compose/config.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | user: hanko 3 | password: hanko 4 | host: postgresd 5 | port: 5432 6 | dialect: postgres 7 | email_delivery: 8 | smtp: 9 | host: "mailslurper" 10 | port: "2500" 11 | from_address: noreply@hanko.io 12 | secrets: 13 | keys: 14 | - abcedfghijklmnopqrstuvwxyz 15 | service: 16 | name: Hanko Authentication Service 17 | webauthn: 18 | relying_party: 19 | origins: 20 | - "http://localhost:8888" 21 | session: 22 | cookie: 23 | secure: false # is needed for safari, because safari does not store secure cookies on localhost 24 | server: 25 | public: 26 | cors: 27 | allow_origins: 28 | - "http://localhost:8888" 29 | -------------------------------------------------------------------------------- /deploy/docker-compose/todo-angular.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | todo-backend: 3 | build: ../../frontend/examples/express 4 | ports: 5 | - "8002:8002" 6 | environment: 7 | - HANKO_API_URL=http://hanko:8000 8 | networks: 9 | - intranet 10 | todo-frontend: 11 | build: ../../frontend/examples/angular 12 | ports: 13 | - "8888:8888" 14 | networks: 15 | - intranet 16 | -------------------------------------------------------------------------------- /deploy/docker-compose/todo-nextjs.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | todo-backend: 3 | build: ../../frontend/examples/express 4 | ports: 5 | - "8002:8002" 6 | environment: 7 | - HANKO_API_URL=http://hanko:8000 8 | networks: 9 | - intranet 10 | todo-frontend: 11 | build: ../../frontend/examples/nextjs 12 | ports: 13 | - "8888:8888" 14 | networks: 15 | - intranet 16 | -------------------------------------------------------------------------------- /deploy/docker-compose/todo-react.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | todo-backend: 3 | build: ../../frontend/examples/express 4 | ports: 5 | - "8002:8002" 6 | environment: 7 | - HANKO_API_URL=http://hanko:8000 8 | networks: 9 | - intranet 10 | todo-frontend: 11 | build: ../../frontend/examples/react 12 | ports: 13 | - "8888:8888" 14 | networks: 15 | - intranet 16 | -------------------------------------------------------------------------------- /deploy/docker-compose/todo-svelte.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | todo-backend: 3 | build: ../../frontend/examples/express 4 | ports: 5 | - "8002:8002" 6 | environment: 7 | - HANKO_API_URL=http://hanko:8000 8 | networks: 9 | - intranet 10 | todo-frontend: 11 | build: ../../frontend/examples/svelte 12 | ports: 13 | - "8888:8888" 14 | networks: 15 | - intranet 16 | -------------------------------------------------------------------------------- /deploy/docker-compose/todo-vue.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | todo-backend: 3 | build: ../../frontend/examples/express 4 | ports: 5 | - "8002:8002" 6 | environment: 7 | - HANKO_API_URL=http://hanko:8000 8 | networks: 9 | - intranet 10 | todo-frontend: 11 | build: ../../frontend/examples/vue 12 | ports: 13 | - "8888:8888" 14 | networks: 15 | - intranet 16 | -------------------------------------------------------------------------------- /deploy/k8s/README.md: -------------------------------------------------------------------------------- 1 | # k8s deploy 2 | This folder is used internaly by hanko to test hanko in a kubernetes setup using [skaffold](https://skaffold.dev/) and [kustomize](https://github.com/kubernetes-sigs/kustomize) 3 | running in a [kind cluster](https://kind.sigs.k8s.io/) that got set up by [gentle](https://github.com/like-a-bause/gentle). 4 | 5 | While this is a "hanko tailored" setup you can use these yamls as a base for your own kubernetes deployment. 6 | -------------------------------------------------------------------------------- /deploy/k8s/base/elements/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: hanko-elements 5 | namespace: hanko-tenant 6 | annotations: 7 | kubernetes.io/ingress.class: "nginx" 8 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 9 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 10 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 11 | labels: 12 | fqdn: elements.quickstart.test 13 | spec: 14 | tls: 15 | - hosts: 16 | - $(ELEMENTS_FQDN) 17 | secretName: elements-tls 18 | rules: 19 | - host: $(ELEMENTS_FQDN) 20 | http: 21 | paths: 22 | - path: / 23 | pathType: Prefix 24 | backend: 25 | service: 26 | name: elements 27 | port: 28 | name: http 29 | -------------------------------------------------------------------------------- /deploy/k8s/base/elements/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - ingress.yaml 5 | vars: 6 | - fieldref: 7 | fieldpath: metadata.labels.fqdn 8 | name: ELEMENTS_FQDN 9 | objref: 10 | apiVersion: networking.k8s.io/v1 11 | kind: Ingress 12 | name: hanko-elements 13 | -------------------------------------------------------------------------------- /deploy/k8s/base/elements/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: elements 6 | namespace: hanko-tenant 7 | spec: 8 | selector: 9 | app: hanko-elements 10 | ports: 11 | - port: 80 12 | targetPort: http-public 13 | protocol: TCP 14 | name: http 15 | -------------------------------------------------------------------------------- /deploy/k8s/base/hanko/config.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | user: hanko 3 | password: hanko 4 | host: postgres 5 | port: 5432 6 | dialect: postgres 7 | passcode: 8 | email: 9 | from_address: no-reply@hanko.io 10 | smtp: 11 | host: "mailhog" 12 | port: "2500" 13 | secrets: 14 | keys: 15 | - abcedfghijklmnopqrstuvwxyz 16 | service: 17 | name: Hanko Authentication Service 18 | server: 19 | public: 20 | cors: 21 | enabled: true 22 | allow_credentials: true 23 | allow_origins: 24 | - 'https://app.quickstart.test' 25 | webauthn: 26 | relying_party: 27 | origin: "https://app.quickstart.test" 28 | -------------------------------------------------------------------------------- /deploy/k8s/base/hanko/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | namespace: hanko-tenant 5 | name: hanko 6 | annotations: 7 | kubernetes.io/ingress.class: "nginx" 8 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 9 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 10 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 11 | labels: 12 | fqdn: hanko.quickstart.test 13 | spec: 14 | tls: 15 | - hosts: 16 | - $(HANKO_FQDN) 17 | secretName: hanko-tls 18 | rules: 19 | - host: $(HANKO_FQDN) 20 | http: 21 | paths: 22 | - path: / 23 | pathType: Prefix 24 | backend: 25 | service: 26 | name: hanko-public 27 | port: 28 | name: http 29 | -------------------------------------------------------------------------------- /deploy/k8s/base/hanko/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - namespace.yaml 3 | - deployment.yaml 4 | - services.yaml 5 | - ingress.yaml 6 | namespace: hanko-tenant 7 | configMapGenerator: 8 | - files: 9 | - config.yaml 10 | name: hanko 11 | vars: 12 | - fieldref: 13 | fieldpath: metadata.labels.fqdn 14 | name: HANKO_FQDN 15 | objref: 16 | apiVersion: networking.k8s.io/v1 17 | kind: Ingress 18 | name: hanko 19 | -------------------------------------------------------------------------------- /deploy/k8s/base/hanko/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: hanko-tenant 5 | -------------------------------------------------------------------------------- /deploy/k8s/base/hanko/services.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hanko-public 5 | namespace: hanko-tenant 6 | spec: 7 | selector: 8 | app: hanko 9 | ports: 10 | - port: 80 11 | targetPort: http-public 12 | protocol: TCP 13 | name: http 14 | --- 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: hanko-admin 19 | namespace: hanko-tenant 20 | spec: 21 | selector: 22 | app: hanko 23 | ports: 24 | - port: 80 25 | targetPort: http-admin 26 | protocol: TCP 27 | name: http 28 | -------------------------------------------------------------------------------- /deploy/k8s/base/mailhog/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mailhog 5 | namespace: hanko 6 | spec: 7 | replicas: 1 8 | template: 9 | metadata: 10 | labels: 11 | app: mailhog 12 | spec: 13 | containers: 14 | - name: mailhog 15 | image: mailhog/mailhog:latest 16 | ports: 17 | - containerPort: 8025 18 | name: mailhog-ui 19 | - containerPort: 1025 20 | name: smtp 21 | selector: 22 | matchLabels: 23 | app: mailhog 24 | -------------------------------------------------------------------------------- /deploy/k8s/base/mailhog/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | namespace: hanko-tenant 5 | name: mailhog 6 | annotations: 7 | kubernetes.io/ingress.class: "nginx" 8 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 9 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 10 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 11 | labels: 12 | fqdn: mail.quickstart.test 13 | spec: 14 | tls: 15 | - hosts: 16 | - $(MAILHOG_FQDN) 17 | secretName: mail-tls 18 | rules: 19 | - host: $(MAILHOG_FQDN) 20 | http: 21 | paths: 22 | - path: / 23 | pathType: Prefix 24 | backend: 25 | service: 26 | name: mailhog 27 | port: 28 | name: mailhog-ui 29 | -------------------------------------------------------------------------------- /deploy/k8s/base/mailhog/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - ingress.yaml 5 | vars: 6 | - fieldref: 7 | fieldpath: metadata.labels.fqdn 8 | name: MAILHOG_FQDN 9 | objref: 10 | apiVersion: networking.k8s.io/v1 11 | kind: Ingress 12 | name: mailhog 13 | -------------------------------------------------------------------------------- /deploy/k8s/base/mailhog/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mailhog 5 | namespace: hanko 6 | spec: 7 | ports: 8 | - port: 8080 9 | name: mailhog-ui 10 | targetPort: mailhog-ui 11 | - port: 8085 12 | name: service-port 13 | targetPort: service-port 14 | - port: 2500 15 | name: smtp 16 | targetPort: smtp 17 | selector: 18 | app: mailhog 19 | -------------------------------------------------------------------------------- /deploy/k8s/base/postgres/initdbscript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL 4 | CREATE DATABASE hanko; 5 | GRANT ALL PRIVILEGES ON DATABASE hanko TO $POSTGRES_USER; 6 | EOSQL -------------------------------------------------------------------------------- /deploy/k8s/base/postgres/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - persistent-volume.yaml 5 | configMapGenerator: 6 | - name: initdb 7 | files: 8 | - initdbscript.sh 9 | -------------------------------------------------------------------------------- /deploy/k8s/base/postgres/persistent-volume.yaml: -------------------------------------------------------------------------------- 1 | # Persistence Volume definition removed because it somehow made problems with the postgres container. 2 | # Volume now gets created dynamically (based on the claim). 3 | --- 4 | apiVersion: v1 5 | kind: PersistentVolumeClaim 6 | metadata: 7 | labels: 8 | app: postgres 9 | name: postgres-pv-claim 10 | namespace: hanko 11 | spec: 12 | storageClassName: standard 13 | accessModes: 14 | - ReadWriteOnce 15 | resources: 16 | requests: 17 | storage: 100M 18 | -------------------------------------------------------------------------------- /deploy/k8s/base/postgres/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: postgres 5 | namespace: hanko 6 | spec: 7 | ports: 8 | - port: 5432 9 | selector: 10 | app: postgres -------------------------------------------------------------------------------- /deploy/k8s/base/quickstart/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | namespace: hanko-tenant 5 | name: hanko-quickstart 6 | annotations: 7 | kubernetes.io/ingress.class: "nginx" 8 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 9 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 10 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 11 | labels: 12 | fqdn: app.quickstart.test 13 | spec: 14 | tls: 15 | - hosts: 16 | - $(QUICKSTART_FQDN) 17 | secretName: quickstart-tls 18 | rules: 19 | - host: $(QUICKSTART_FQDN) 20 | http: 21 | paths: 22 | - path: / 23 | pathType: Prefix 24 | backend: 25 | service: 26 | name: quickstart 27 | port: 28 | name: http 29 | -------------------------------------------------------------------------------- /deploy/k8s/base/quickstart/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - ingress.yaml 5 | vars: 6 | - fieldref: 7 | fieldpath: metadata.labels.fqdn 8 | name: QUICKSTART_FQDN 9 | objref: 10 | apiVersion: networking.k8s.io/v1 11 | kind: Ingress 12 | name: hanko-quickstart 13 | -------------------------------------------------------------------------------- /deploy/k8s/base/quickstart/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: quickstart 6 | namespace: hanko-tenant 7 | spec: 8 | selector: 9 | app: hanko-quickstart 10 | ports: 11 | - port: 80 12 | targetPort: http-public 13 | protocol: TCP 14 | name: http 15 | -------------------------------------------------------------------------------- /deploy/k8s/overlays/quickstart/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: hanko-quickstart 2 | resources: 3 | - ../../base/postgres 4 | - ../../base/hanko 5 | - ../../base/elements 6 | - ../../base/quickstart 7 | - ../../base/mailhog 8 | -------------------------------------------------------------------------------- /deploy/k8s/overlays/thirdparty-x-domain/README.md: -------------------------------------------------------------------------------- 1 | # Adding OIDC Clients 2 | To successfully test this you need to add OIDC Clients as Secrets: 3 | 4 | Create a github.env and a google.env of the form: 5 | ``` 6 | client_id=your-id 7 | client_secret=your-secret 8 | ``` 9 | 10 | Run 11 | > skaffold run -p thirdparty-x-domain 12 | 13 | to build and deploy to local cluster. 14 | 15 | The quickstart app should then be running on **https://app.domain-app.grocery** 16 | -------------------------------------------------------------------------------- /deploy/k8s/overlays/thirdparty-x-domain/ingress-patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: hanko 5 | namespace: hanko-tenant 6 | labels: 7 | fqdn: hanko.domain-hanko.grocery 8 | --- 9 | apiVersion: networking.k8s.io/v1 10 | kind: Ingress 11 | metadata: 12 | name: hanko-elements 13 | namespace: hanko-tenant 14 | labels: 15 | fqdn: elements.domain-app.grocery 16 | --- 17 | apiVersion: networking.k8s.io/v1 18 | kind: Ingress 19 | metadata: 20 | name: hanko-quickstart 21 | namespace: hanko-tenant 22 | labels: 23 | fqdn: app.domain-app.grocery 24 | --- 25 | apiVersion: networking.k8s.io/v1 26 | kind: Ingress 27 | metadata: 28 | name: mailhog 29 | namespace: hanko-tenant 30 | labels: 31 | fqdn: mail.domain-app.grocery 32 | -------------------------------------------------------------------------------- /deploy/k8s/overlays/thirdparty-x-domain/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: hanko-thirdparty-x-domain 2 | resources: 3 | - ../../base/postgres 4 | - ../../base/hanko 5 | - ../../base/elements 6 | - ../../base/quickstart 7 | - ../../base/mailhog 8 | patchesStrategicMerge: 9 | - ingress-patch.yaml 10 | - env-patch.yaml 11 | configMapGenerator: 12 | - files: 13 | - config.yaml 14 | name: hanko 15 | behavior: replace 16 | secretGenerator: 17 | - name: github 18 | envs: 19 | - github.env 20 | - name: google 21 | envs: 22 | - google.env 23 | - name: apple 24 | envs: 25 | - apple.env 26 | - name: facebook 27 | envs: 28 | - facebook.env 29 | -------------------------------------------------------------------------------- /e2e/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /e2e/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:playwright/playwright-test", 11 | "plugin:prettier/recommended", 12 | ], 13 | parser: "@typescript-eslint/parser", 14 | parserOptions: { 15 | ecmaVersion: "latest", 16 | sourceType: "module", 17 | }, 18 | plugins: ["@typescript-eslint"], 19 | rules: { 20 | "@typescript-eslint/no-non-null-assertion": "off" 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /e2e/.nvmrc: -------------------------------------------------------------------------------- 1 | v18.6.0 2 | -------------------------------------------------------------------------------- /e2e/global.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | namespace PlaywrightTest { 5 | interface Matchers { 6 | toHaveCookie(name?: string): R; 7 | toHaveLocalStorageEntry(origin?: string, name?: string): R; 8 | toHaveLocalStorageEntryForUserWithCredential( 9 | userId: string, 10 | credentialId: string, 11 | origin?: string, 12 | name?: string 13 | ): R; 14 | toHaveLocalStorageEntryForUserWithPasscode( 15 | userId: string, 16 | passcodeId: string, 17 | origin?: string, 18 | name?: string 19 | ): R; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /e2e/helper/Accounts.ts: -------------------------------------------------------------------------------- 1 | const Accounts = { 2 | test: { 3 | email: "test@example.com" 4 | } 5 | }; 6 | 7 | export default Accounts; 8 | -------------------------------------------------------------------------------- /e2e/helper/Endpoints.ts: -------------------------------------------------------------------------------- 1 | const Endpoints = { 2 | API: { 3 | ME: "**/me", 4 | USER: "**/user", 5 | USERS: "**/users", 6 | USERS_PARAM: "**/users/*", 7 | PASSWORD: "**/password", 8 | PASSWORD_LOGIN: "**/password/login", 9 | WEBAUTHN_LOGIN_INITIALIZE: "**/webauthn/login/initialize", 10 | WEBAUTHN_LOGIN_FINALIZE: "**/webauthn/login/finalize", 11 | WEBAUTHN_REGISTRATION_INITIALIZE: "**/webauthn/registration/initialize", 12 | WEBAUTHN_REGISTRATION_FINALIZE: "**/webauthn/registration/finalize", 13 | PASSCODE_LOGIN_INITIALIZE: "**/passcode/login/initialize", 14 | PASSCODE_LOGIN_FINALIZE: "**/passcode/login/finalize", 15 | WELL_KNOWN_CONFIG: "**/.well-known/config", 16 | }, 17 | APP: { 18 | LOGOUT: "**/logout", 19 | SECURED_CONTENT: "**/secured", 20 | }, 21 | }; 22 | 23 | export default Endpoints; 24 | -------------------------------------------------------------------------------- /e2e/pages/Error.ts: -------------------------------------------------------------------------------- 1 | import { BasePage } from "./BasePage.js"; 2 | import type { Locator, Page } from "@playwright/test"; 3 | import Endpoints from "../helper/Endpoints.js"; 4 | 5 | export class Error extends BasePage { 6 | readonly headline: Locator; 7 | readonly continueButton: Locator; 8 | 9 | constructor(page: Page) { 10 | super(page); 11 | this.headline = page.locator("h1", { 12 | hasText: "An error has occurred", 13 | }); 14 | this.continueButton = page.locator("button[type=submit]", { 15 | hasText: "Continue", 16 | }); 17 | } 18 | 19 | async continue() { 20 | await Promise.all([ 21 | this.page.waitForResponse(Endpoints.API.WELL_KNOWN_CONFIG), 22 | this.continueButton.click(), 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /e2e/pages/SecuredContent.ts: -------------------------------------------------------------------------------- 1 | import type { Locator, Page } from "@playwright/test"; 2 | import { BasePage } from "./BasePage.js"; 3 | import Endpoints from "../helper/Endpoints.js"; 4 | import { expect } from "../fixtures/Pages.js"; 5 | 6 | export class SecuredContent extends BasePage { 7 | readonly logoutLink: Locator; 8 | 9 | constructor(page: Page) { 10 | super(page); 11 | this.logoutLink = page.locator("a", { hasText: "Logout" }); 12 | } 13 | 14 | async logout() { 15 | await Promise.all([ 16 | this.page.waitForResponse(Endpoints.APP.LOGOUT), 17 | this.logoutLink.click(), 18 | ]); 19 | 20 | await expect( 21 | this, 22 | "Logging out should clear the cookie" 23 | ).not.toHaveCookie(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /e2e/seed/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:12-alpine 2 | 3 | 4 | ARG POSTGRES_HOST 5 | ARG POSTGRES_PORT 6 | ARG POSTGRES_USER 7 | ARG POSTGRES_DB 8 | ARG PGPASSWORD 9 | 10 | COPY seed.sql ./seed.sql 11 | COPY init.sh ./init.sh 12 | 13 | RUN chmod +x ./init.sh 14 | 15 | CMD ./init.sh 16 | -------------------------------------------------------------------------------- /e2e/seed/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U $POSTGRES_USER -d $POSTGRES_DB -a -w -f seed.sql 4 | -------------------------------------------------------------------------------- /e2e/seed/seed.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO users 2 | (id, created_at, updated_at) 3 | VALUES 4 | ('357461f1-458a-42c8-abf3-05eabfc36ffd', current_timestamp, current_timestamp); 5 | 6 | INSERT INTO emails 7 | (id, user_id, address, verified, created_at, updated_at) 8 | VALUES 9 | ('47c082da-b70a-4ccc-bc5f-1481b3499273', '357461f1-458a-42c8-abf3-05eabfc36ffd', 'test@example.com', true, current_timestamp, current_timestamp); 10 | 11 | INSERT INTO primary_emails 12 | (id, email_id, user_id, created_at, updated_at) 13 | VALUES 14 | ('8de035cd-3d21-415c-8844-644fe40d7d74', '47c082da-b70a-4ccc-bc5f-1481b3499273', '357461f1-458a-42c8-abf3-05eabfc36ffd', current_timestamp, current_timestamp); 15 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM node:18.14-alpine AS build 2 | RUN apk add --no-cache libc6-compat 3 | RUN apk update 4 | 5 | RUN npm install turbo --global 6 | 7 | WORKDIR /app 8 | ENV PATH=/app/node_modules/.bin:$PATH 9 | 10 | COPY package.json ./ 11 | COPY package-lock.json ./ 12 | COPY ./frontend-sdk/package.json ./frontend-sdk/package.json 13 | COPY ./elements/package.json ./elements/package.json 14 | 15 | RUN npm ci --silent 16 | 17 | COPY . . 18 | RUN npm run build:elements 19 | 20 | FROM nginx:stable-alpine 21 | COPY --from=build /app/elements/dist/elements.js /usr/share/nginx/html 22 | COPY --from=build /app/frontend-sdk/dist/sdk.* /usr/share/nginx/html 23 | 24 | COPY elements/nginx/default.conf /etc/nginx/conf.d/default.conf 25 | EXPOSE 80 26 | CMD ["nginx", "-g", "daemon off;"] 27 | -------------------------------------------------------------------------------- /frontend/Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM node:18.14-alpine AS build 2 | RUN apk add --no-cache libc6-compat 3 | RUN apk update 4 | 5 | RUN npm install turbo --global 6 | 7 | WORKDIR /app 8 | ENV PATH=/app/node_modules/.bin:$PATH 9 | 10 | COPY package.json ./ 11 | COPY package-lock.json ./ 12 | COPY ./frontend-sdk/package.json ./frontend-sdk/package.json 13 | COPY ./elements/package.json ./elements/package.json 14 | 15 | RUN npm ci --silent 16 | 17 | COPY . . 18 | RUN npm run build:elements:dev 19 | 20 | FROM nginx:stable-alpine 21 | COPY --from=build /app/elements/dist/elements.js /usr/share/nginx/html 22 | COPY --from=build /app/frontend-sdk/dist/sdk.* /usr/share/nginx/html 23 | 24 | COPY elements/nginx/default.conf /etc/nginx/conf.d/default.conf 25 | EXPOSE 80 26 | CMD ["nginx", "-g", "daemon off;"] 27 | -------------------------------------------------------------------------------- /frontend/elements/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /frontend/elements/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/elements/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true, 5 | 'node': true, 6 | }, 7 | 'extends': [ 8 | 'eslint:recommended', 9 | 'google', 10 | 'preact', 11 | 'plugin:promise/recommended', 12 | // "plugin:@typescript-eslint/recommended", 13 | // "plugin:@typescript-eslint/recommended-requiring-type-checking", 14 | 'plugin:prettier/recommended', 15 | ], 16 | 'parser': '@typescript-eslint/parser', 17 | 'parserOptions': { 18 | 'ecmaVersion': 'latest', 19 | 'sourceType': 'module', 20 | "project": "tsconfig.json", 21 | "tsconfigRootDir": ".", 22 | }, 23 | 'plugins': [ 24 | '@typescript-eslint' 25 | ], 26 | 'rules': { 27 | 'no-unused-vars': ['error', { 'args': 'none' }] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /frontend/elements/.nvmrc: -------------------------------------------------------------------------------- 1 | v20.16.0 2 | -------------------------------------------------------------------------------- /frontend/elements/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | } 10 | ], 11 | '@babel/preset-typescript', 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/elements/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | server_tokens off; 6 | 7 | add_header Access-Control-Allow-Origin *; 8 | 9 | location / { 10 | root /usr/share/nginx/html; 11 | # index index.html; 12 | try_files $uri $uri/ @index; 13 | } 14 | 15 | location @index { 16 | # add_header Cache-Control no-cache; 17 | server_tokens off; 18 | expires -1; 19 | root /usr/share/nginx/html; 20 | try_files /index.html =404; 21 | } 22 | 23 | # redirect server error pages to the static page /50x.html 24 | # 25 | error_page 500 502 503 504 /50x.html; 26 | location = /50x.html { 27 | root /usr/share/nginx/html; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/elements/src/_mixins.sass: -------------------------------------------------------------------------------- 1 | @use 'variables' 2 | 3 | @mixin font 4 | font-weight: variables.$font-weight 5 | font-size: variables.$font-size 6 | font-family: variables.$font-family 7 | line-height: variables.$line-height 8 | 9 | @mixin border 10 | border-radius: variables.$border-radius 11 | border-style: variables.$border-style 12 | border-width: variables.$border-width 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/elements/src/components/error/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.sass"; 2 | import { Fragment, useContext } from "preact/compat"; 3 | import { TranslateContext } from "@denysvuika/preact-translate"; 4 | import { FlowError } from "@teamhanko/hanko-frontend-sdk"; 5 | 6 | interface Props { 7 | flowError?: FlowError; 8 | } 9 | 10 | const ErrorMessage = ({ flowError }: Props) => { 11 | const { t } = useContext(TranslateContext); 12 | return ( 13 | 14 | {flowError ? ( 15 |
16 | {t(`flowErrors.${flowError?.code}`)} 17 |
18 | ) : null} 19 |
20 | ); 21 | }; 22 | 23 | export default ErrorMessage; 24 | -------------------------------------------------------------------------------- /frontend/elements/src/components/error/styles.sass: -------------------------------------------------------------------------------- 1 | @use '../../variables' 2 | @use '../../mixins' 3 | 4 | .errorBox 5 | @include mixins.font 6 | @include mixins.border 7 | 8 | color: variables.$error-color 9 | background: variables.$background-color 10 | margin: variables.$item-margin 11 | 12 | display: flex 13 | align-items: start 14 | box-sizing: border-box 15 | 16 | line-height: 1.5rem 17 | padding: .25em 18 | gap: .2em 19 | 20 | &>span 21 | display: inline-flex 22 | 23 | &>span:first-child 24 | padding: .25em 0 .25em .19em 25 | 26 | &[hidden] 27 | display: none 28 | 29 | .errorMessage 30 | color: variables.$error-color 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/elements/src/components/form/LastUsed.tsx: -------------------------------------------------------------------------------- 1 | import cx from "classnames"; 2 | import styles from "./styles.sass"; 3 | import { useContext } from "preact/compat"; 4 | import { TranslateContext } from "@denysvuika/preact-translate"; 5 | 6 | const LastUsed = () => { 7 | const { t } = useContext(TranslateContext); 8 | return {t("labels.lastUsed")}; 9 | }; 10 | 11 | export default LastUsed; 12 | -------------------------------------------------------------------------------- /frontend/elements/src/components/headline/Headline1.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from "preact"; 2 | 3 | import cx from "classnames"; 4 | 5 | import styles from "./styles.sass"; 6 | 7 | type Props = { 8 | children: ComponentChildren; 9 | }; 10 | 11 | const Headline1 = ({ children }: Props) => { 12 | return ( 13 |

14 | {children} 15 |

16 | ); 17 | }; 18 | 19 | export default Headline1; 20 | -------------------------------------------------------------------------------- /frontend/elements/src/components/headline/Headline2.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from "preact"; 2 | 3 | import cx from "classnames"; 4 | 5 | import styles from "./styles.sass"; 6 | 7 | type Props = { 8 | children: ComponentChildren; 9 | }; 10 | 11 | const Headline2 = ({ children }: Props) => { 12 | return ( 13 |

14 | {children} 15 |

16 | ); 17 | }; 18 | 19 | export default Headline2; 20 | -------------------------------------------------------------------------------- /frontend/elements/src/components/headline/styles.sass: -------------------------------------------------------------------------------- 1 | @use '../../variables' 2 | @use '../../mixins' 3 | 4 | .headline 5 | color: variables.$color 6 | font-family: variables.$font-family 7 | text-align: left 8 | letter-spacing: 0 9 | font-style: normal 10 | line-height: 1.1 11 | 12 | &.grade1 13 | font-size: variables.$headline1-font-size 14 | font-weight: variables.$headline1-font-weight 15 | margin: variables.$headline1-margin 16 | 17 | &.grade2 18 | font-size: variables.$headline2-font-size 19 | font-weight: variables.$headline2-font-weight 20 | margin: variables.$headline2-margin 21 | -------------------------------------------------------------------------------- /frontend/elements/src/components/icons/Copy.tsx: -------------------------------------------------------------------------------- 1 | import { IconProps } from "./Icon"; 2 | import cx from "classnames"; 3 | import styles from "./styles.sass"; 4 | 5 | const Copy = ({ size, secondary, disabled }: IconProps) => { 6 | return ( 7 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default Copy; 24 | -------------------------------------------------------------------------------- /frontend/elements/src/components/icons/ExclamationMark.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.sass"; 2 | import { IconProps } from "./Icon"; 3 | import cx from "classnames"; 4 | 5 | const ExclamationMark = ({ size, secondary, disabled }: IconProps) => { 6 | return ( 7 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default ExclamationMark; 25 | -------------------------------------------------------------------------------- /frontend/elements/src/components/icons/Icon.tsx: -------------------------------------------------------------------------------- 1 | import * as icons from "./icons"; 2 | 3 | export type IconName = keyof typeof icons; 4 | 5 | export type IconProps = { 6 | secondary?: boolean; 7 | fadeOut?: boolean; 8 | disabled?: boolean; 9 | size?: number; 10 | }; 11 | 12 | type Props = IconProps & { 13 | name: IconName; 14 | }; 15 | 16 | const Icon = ({ name, secondary, size = 18, fadeOut, disabled }: Props) => { 17 | const Ico = icons[name]; 18 | 19 | return ( 20 | 26 | ); 27 | }; 28 | 29 | export default Icon; 30 | -------------------------------------------------------------------------------- /frontend/elements/src/components/icons/Mail.tsx: -------------------------------------------------------------------------------- 1 | import { IconProps } from "./Icon"; 2 | import cx from "classnames"; 3 | import styles from "./styles.sass"; 4 | 5 | const Mail = ({ size, secondary, disabled }: IconProps) => { 6 | return ( 7 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default Mail; 25 | -------------------------------------------------------------------------------- /frontend/elements/src/components/link/styles.sass: -------------------------------------------------------------------------------- 1 | @use "../../variables" 2 | @use "../../mixins" 3 | 4 | .link 5 | @include mixins.font 6 | 7 | color: variables.$link-color 8 | text-decoration: variables.$link-text-decoration 9 | cursor: pointer 10 | background: none!important 11 | border: none 12 | padding: 0!important 13 | transition: all .1s 14 | 15 | &:hover 16 | text-decoration: variables.$link-text-decoration-hover 17 | 18 | &:disabled 19 | color: variables.$color!important 20 | pointer-events: none 21 | cursor: default 22 | 23 | &.danger 24 | color: variables.$error-color 25 | 26 | .linkWrapper 27 | display: inline-flex 28 | flex-direction: row 29 | justify-content: space-between 30 | align-items: center 31 | overflow: hidden 32 | 33 | &.reverse 34 | flex-direction: row-reverse 35 | 36 | -------------------------------------------------------------------------------- /frontend/elements/src/components/otp/OTPCreationDetails.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import styles from "./styles.sass"; 3 | 4 | import Clipboard from "../wrapper/Clipboard"; 5 | import Spacer from "../spacer/Spacer"; 6 | import { useContext } from "preact/compat"; 7 | import { TranslateContext } from "@denysvuika/preact-translate"; 8 | 9 | type Props = { 10 | src: string; 11 | secret: string; 12 | }; 13 | 14 | const OTPCreationDetails = ({ src, secret }: Props) => { 15 | const { t } = useContext(TranslateContext); 16 | return ( 17 |
18 | {"QR-Code"} 19 | 20 | {t("texts.otpSecretKey")} 21 |
{secret}
22 |
23 | ); 24 | }; 25 | 26 | export default OTPCreationDetails; 27 | -------------------------------------------------------------------------------- /frontend/elements/src/components/otp/styles.sass: -------------------------------------------------------------------------------- 1 | @use "../../variables" 2 | @use "../../mixins" 3 | 4 | .otpCreationDetails 5 | @include mixins.font 6 | 7 | color: variables.$color 8 | margin: variables.$item-margin 9 | display: flex 10 | justify-content: center 11 | align-items: center 12 | flex-direction: column 13 | font-size: smaller 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/elements/src/components/paragraph/Paragraph.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from "preact"; 2 | 3 | import styles from "./styles.sass"; 4 | 5 | type Props = { 6 | hidden?: boolean; 7 | children: ComponentChildren; 8 | }; 9 | 10 | const Paragraph = ({ children, hidden }: Props) => { 11 | return !hidden ? ( 12 |

13 | {children} 14 |

15 | ) : null; 16 | }; 17 | 18 | export default Paragraph; 19 | -------------------------------------------------------------------------------- /frontend/elements/src/components/paragraph/styles.sass: -------------------------------------------------------------------------------- 1 | @use "../../variables" 2 | @use "../../mixins" 3 | 4 | .paragraph 5 | @include mixins.font 6 | 7 | color: variables.$color 8 | margin: variables.$item-margin 9 | 10 | text-align: left 11 | word-break: break-word 12 | 13 | -------------------------------------------------------------------------------- /frontend/elements/src/components/spacer/Divider.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from "preact"; 2 | 3 | import styles from "./styles.sass"; 4 | 5 | interface Props { 6 | children?: ComponentChildren; 7 | hidden?: boolean; 8 | } 9 | 10 | const Divider = ({ children, hidden }: Props) => { 11 | return !hidden ? ( 12 |
13 |
14 | {children ? ( 15 |
16 | {children} 17 |
18 | ) : null} 19 |
20 |
21 | ) : null; 22 | }; 23 | 24 | export default Divider; 25 | -------------------------------------------------------------------------------- /frontend/elements/src/components/spacer/Spacer.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.sass"; 2 | 3 | const Spacer = () => { 4 | return
; 5 | }; 6 | 7 | export default Spacer; 8 | -------------------------------------------------------------------------------- /frontend/elements/src/components/spacer/styles.sass: -------------------------------------------------------------------------------- 1 | @use '../../variables' 2 | @use '../../mixins' 3 | 4 | .spacer 5 | height: 1em 6 | 7 | .divider 8 | @include mixins.font 9 | 10 | display: flex 11 | visibility: variables.$divider-visibility 12 | color: variables.$color-shade-1 13 | margin: variables.$item-margin 14 | padding: .5em 0 15 | 16 | .line 17 | border-bottom-style: variables.$border-style 18 | border-bottom-width: variables.$border-width 19 | 20 | color: inherit 21 | font: inherit 22 | 23 | width: 100% 24 | 25 | .text 26 | font: inherit 27 | color: inherit 28 | background: variables.$background-color 29 | padding: variables.$divider-padding 30 | line-height: .1em 31 | -------------------------------------------------------------------------------- /frontend/elements/src/components/wrapper/Content.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from "preact"; 2 | 3 | import styles from "./styles.sass"; 4 | 5 | type Props = { 6 | children: ComponentChildren; 7 | }; 8 | 9 | const Content = ({ children }: Props) => { 10 | return
{children}
; 11 | }; 12 | 13 | export default Content; 14 | -------------------------------------------------------------------------------- /frontend/elements/src/components/wrapper/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from "preact"; 2 | 3 | import styles from "./styles.sass"; 4 | 5 | interface Props { 6 | hidden?: boolean; 7 | children?: ComponentChildren; 8 | } 9 | 10 | const Footer = ({ children, hidden = false }: Props) => { 11 | return !hidden ? ( 12 |
{children}
13 | ) : null; 14 | }; 15 | 16 | export default Footer; 17 | -------------------------------------------------------------------------------- /frontend/elements/src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.sass"; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | interface Window { 5 | _hankoStyle: HTMLStyleElement; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/elements/src/hooks/UseFlowState.ts: -------------------------------------------------------------------------------- 1 | import { State, StateName } from "@teamhanko/hanko-frontend-sdk"; 2 | import { useEffect, useState } from "preact/compat"; 3 | 4 | export const useFlowState = ( 5 | initialFlowState: State, 6 | ) => { 7 | const [flowState, setFlowState] = useState>(initialFlowState); 8 | 9 | useEffect(() => { 10 | if (initialFlowState) { 11 | setFlowState(initialFlowState); 12 | } 13 | }, [initialFlowState]); 14 | 15 | return { flowState }; 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/elements/src/i18n/all.ts: -------------------------------------------------------------------------------- 1 | import { Translations } from "./translations"; 2 | import { bn } from "./bn"; 3 | import { de } from "./de"; 4 | import { en } from "./en"; 5 | import { fr } from "./fr"; 6 | import { it } from "./it"; 7 | import { ptBR } from "./pt-BR"; 8 | import { zh } from "./zh"; 9 | export const all: Translations = { bn, de, en, fr, it, ptBR, zh }; 10 | -------------------------------------------------------------------------------- /frontend/elements/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Elements"; 2 | export * from "@teamhanko/hanko-frontend-sdk"; 3 | import { Translation } from "./i18n/translations"; 4 | export type { Translation }; 5 | -------------------------------------------------------------------------------- /frontend/elements/src/pages/InitPage.tsx: -------------------------------------------------------------------------------- 1 | import LoadingSpinner from "../components/icons/LoadingSpinner"; 2 | 3 | const InitPage = () => { 4 | return ; 5 | }; 6 | 7 | export default InitPage; 8 | -------------------------------------------------------------------------------- /frontend/elements/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*" 4 | ], 5 | "compilerOptions": { 6 | "paths": { 7 | "*": [ 8 | "./*" 9 | ] 10 | }, 11 | "esModuleInterop": true, 12 | "baseUrl": "./src", 13 | "moduleResolution": "node", 14 | "allowSyntheticDefaultImports": true, 15 | "isolatedModules": true, 16 | "resolveJsonModule": true, 17 | "jsx": "react-jsx", 18 | "jsxImportSource": "preact", 19 | "noResolve": false, 20 | "declaration": true, 21 | "diagnostics": true, 22 | "outDir": "./dist", 23 | "lib":["ES2015", "ES2016", "dom"], 24 | "sourceMap": true, 25 | "noImplicitAny": true, 26 | "target": "es6", 27 | "module": "es2020" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/elements/webpack.config.dev.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require("./webpack.config.cjs"); 2 | 3 | baseConfig.module.rules.push({ 4 | test: /\.c?js$/, 5 | enforce: "pre", 6 | use: ["source-map-loader"], 7 | }) 8 | 9 | module.exports = { 10 | devtool: 'eval-source-map', 11 | ...baseConfig 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/examples/angular/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /frontend/examples/angular/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /frontend/examples/angular/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/ 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /frontend/examples/angular/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:16-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | RUN npm install 13 | 14 | # add app 15 | COPY . ./ 16 | 17 | # start app 18 | CMD ["npm", "run", "start-docker"] 19 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | width: 100%; 3 | padding: 10px; 4 | opacity: 0.9; 5 | } 6 | 7 | .button { 8 | font-size: 1rem; 9 | border: none; 10 | background: none; 11 | cursor: pointer; 12 | } 13 | 14 | .button:disabled { 15 | color: grey!important; 16 | cursor: default; 17 | text-decoration: none!important; 18 | } 19 | 20 | .nav .button:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | .nav .button { 25 | color: white; 26 | float: right; 27 | } 28 | 29 | .content { 30 | padding: 24px; 31 | border-radius: 17px; 32 | color: black; 33 | background-color: white; 34 | width: 100%; 35 | max-width: 500px; 36 | min-width: 330px; 37 | margin: 10vh auto; 38 | } 39 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'], 7 | }) 8 | export class AppComponent { 9 | title = 'angular'; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ error?.message }}
3 | 4 |
5 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { HankoService } from "../services/hanko.services"; 3 | import { Router } from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'app-login', 7 | templateUrl: './login.component.html', 8 | styleUrls: ['../app.component.css'] 9 | }) 10 | export class LoginComponent { 11 | error?: Error; 12 | 13 | constructor(private hankoService: HankoService, private router: Router) {} 14 | 15 | redirectToTodos() { 16 | this.router.navigate(['/todo']).catch((e) => (this.error = e)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/modal/session-expired-modal.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
{{ error?.message }}
4 | Please login again.

5 | 6 |
7 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/modal/session-expired-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, ViewChild } from "@angular/core"; 2 | import { Router } from '@angular/router'; 3 | import { HankoService } from "../services/hanko.services"; 4 | 5 | @Component({ 6 | selector: 'app-session-expired-modal', 7 | templateUrl: './session-expired-modal.component.html', 8 | styleUrls: ['../app.component.css'], 9 | }) 10 | export class SessionExpiredModalComponent { 11 | @ViewChild('modal') modal?: ElementRef; 12 | error?: Error; 13 | 14 | constructor(private hankoService: HankoService, private router: Router) {} 15 | 16 | redirectToLogin() { 17 | this.router.navigate(['/']).catch((e) => (this.error = e)); 18 | } 19 | 20 | show() { 21 | this.modal?.nativeElement.showModal(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/profile/profile.component.html: -------------------------------------------------------------------------------- 1 | 2 | 7 |
8 |
{{ error?.message }}
9 | 10 |
11 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/app/services/hanko.services.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { environment } from "../../environments/environment"; 3 | import { Hanko, register } from "@teamhanko/hanko-elements"; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class HankoService { 9 | api = environment.hankoApi 10 | client = new Hanko(this.api) 11 | 12 | constructor() { 13 | register(this.api).catch(console.error); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/angular/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/examples/angular/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/angular/src/assets/bg.jpg -------------------------------------------------------------------------------- /frontend/examples/angular/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | hankoApi: 'http://localhost:8000', 8 | todoApi: 'http://localhost:8002', 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/angular/src/favicon.png -------------------------------------------------------------------------------- /frontend/examples/angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /frontend/examples/angular/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | body { 3 | color: white; 4 | margin: 0; 5 | background: url("assets/bg.jpg") no-repeat center center fixed; 6 | background-size: cover; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | hanko-auth::part(form-item) { 19 | min-width: 100%; /* input fields and buttons are on top of each other */ 20 | } 21 | 22 | dialog { 23 | overflow: hidden; 24 | border-radius: 5px; 25 | box-shadow: 0 0 30px 7px #111; 26 | } 27 | -------------------------------------------------------------------------------- /frontend/examples/angular/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/examples/express/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/express/.env: -------------------------------------------------------------------------------- 1 | HANKO_API_URL=http://localhost:8000 2 | -------------------------------------------------------------------------------- /frontend/examples/express/.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/express/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:16-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | RUN npm install --silent 13 | RUN npm install react-scripts@3.4.1 -g --silent 14 | 15 | # add app 16 | COPY . ./ 17 | 18 | # start app 19 | CMD ["npm", "start"] 20 | -------------------------------------------------------------------------------- /frontend/examples/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-express", 3 | "private": true, 4 | "scripts": { 5 | "start": "node src/server.js" 6 | }, 7 | "dependencies": { 8 | "cookie-parser": "^1.4.6", 9 | "cors": "^2.8.5", 10 | "dotenv": "^16.0.2", 11 | "express": "^4.21.1", 12 | "express-jwt": "^8.3.0", 13 | "jwks-rsa": "^3.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/examples/fresh/.env: -------------------------------------------------------------------------------- 1 | HANKO_API_URL=http://localhost:8000 2 | -------------------------------------------------------------------------------- /frontend/examples/fresh/.gitignore: -------------------------------------------------------------------------------- 1 | # dotenv environment variable files 2 | .env 3 | .env.development.local 4 | .env.test.local 5 | .env.production.local 6 | .env.local 7 | 8 | # Fresh build directory 9 | _fresh/ 10 | -------------------------------------------------------------------------------- /frontend/examples/fresh/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "denoland.vscode-deno", 4 | "sastan.twind-intellisense" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /frontend/examples/fresh/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "editor.defaultFormatter": "denoland.vscode-deno", 5 | "[typescriptreact]": { 6 | "editor.defaultFormatter": "denoland.vscode-deno" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "denoland.vscode-deno" 10 | }, 11 | "[javascriptreact]": { 12 | "editor.defaultFormatter": "denoland.vscode-deno" 13 | }, 14 | "[javascript]": { 15 | "editor.defaultFormatter": "denoland.vscode-deno" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/examples/fresh/README.md: -------------------------------------------------------------------------------- 1 | # Hanko Fresh example 2 | 3 | This is a [Fresh](fresh.deno.dev/) project. 4 | 5 | ## Starting the app 6 | 7 | ### Prerequisites 8 | 9 | - a running Hanko API (see the instructions on how to run the API [in Docker](../../../backend/README.md#Docker) or [from Source](../../../backend/README.md#from-source)) 10 | - a `Deno` installation 11 | 12 | ### Set up environment variables 13 | 14 | In the `config.ts` file set up the correct variables: 15 | 16 | - `HANKO_API_URL`: this is the URL of the Hanko API (default: `http://localhost:8000`) 17 | 18 | ### Run development server 19 | 20 | Run `deno task start` for a development server. Navigate to `http://localhost:8888/`. This will watch the project directory and restart as necessary. 21 | -------------------------------------------------------------------------------- /frontend/examples/fresh/config.ts: -------------------------------------------------------------------------------- 1 | export const HANKO_API_URL = "http://localhost:8000"; 2 | -------------------------------------------------------------------------------- /frontend/examples/fresh/dev.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run -A --watch=static/,routes/ 2 | 3 | import dev from "$fresh/dev.ts"; 4 | import config from "./fresh.config.ts"; 5 | 6 | import "$std/dotenv/load.ts"; 7 | 8 | await dev(import.meta.url, "./main.ts", config); 9 | -------------------------------------------------------------------------------- /frontend/examples/fresh/fresh.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "$fresh/server.ts"; 2 | import twindPlugin from "$fresh/plugins/twind.ts"; 3 | import twindConfig from "./twind.config.ts"; 4 | export default defineConfig({ 5 | port: 8888, 6 | plugins: [twindPlugin(twindConfig)], 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/examples/fresh/islands/Login.tsx: -------------------------------------------------------------------------------- 1 | import { HANKO_API_URL } from "../config.ts"; 2 | 3 | const code = ` 4 | import { register } from 'https://esm.sh/@teamhanko/hanko-elements@2.1.0'; 5 | 6 | const {hanko} = await register('${HANKO_API_URL}', { shadow: true }); 7 | hanko.onSessionCreated((event) => { 8 | document.location.href = '/todo'; 9 | }); 10 | `; 11 | 12 | export default function Login() { 13 | return ( 14 |
15 | 16 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /frontend/examples/fresh/islands/LogoutButton.tsx: -------------------------------------------------------------------------------- 1 | import { HANKO_API_URL } from "../config.ts"; 2 | 3 | const code = ` 4 | import { register, Hanko } from 'https://esm.sh/@teamhanko/hanko-elements@2.1.0'; 5 | 6 | register('${HANKO_API_URL}', { shadow: true }); 7 | window.addEventListener('logout', () => { 8 | const hanko = new Hanko('${HANKO_API_URL}'); 9 | hanko.logout().then(() => { 10 | window.location.href = '/'; 11 | }) 12 | .catch((error) => { 13 | alert(error); 14 | }); 15 | }); 16 | `; 17 | 18 | export default function Profile() { 19 | const logout = () => { 20 | window.dispatchEvent(new Event("logout")); 21 | }; 22 | 23 | return ( 24 | <> 25 | 26 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /frontend/examples/fresh/islands/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { HANKO_API_URL } from "../config.ts"; 2 | 3 | const code = ` 4 | import { register } from 'https://esm.sh/@teamhanko/hanko-elements@2.1.0'; 5 | register('${HANKO_API_URL}', { shadow: true }); 6 | `; 7 | 8 | export default function Profile() { 9 | return ( 10 |
11 | 12 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /frontend/examples/fresh/main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import "$std/dotenv/load.ts"; 8 | 9 | import { start } from "$fresh/server.ts"; 10 | import manifest from "./fresh.gen.ts"; 11 | import config from "./fresh.config.ts"; 12 | 13 | await start(manifest, config); 14 | -------------------------------------------------------------------------------- /frontend/examples/fresh/routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "$fresh/server.ts"; 2 | 3 | export default function App({ Component }: AppProps) { 4 | return ( 5 | 6 | 7 | 8 | 9 | Hanko App 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/examples/fresh/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import Login from "../islands/Login.tsx"; 2 | 3 | export default function Home() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/examples/fresh/routes/profile.tsx: -------------------------------------------------------------------------------- 1 | import Profile from "../islands/Profile.tsx"; 2 | import LogoutButton from "../islands/LogoutButton.tsx"; 3 | 4 | export default function UserProfile() { 5 | return ( 6 | <> 7 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /frontend/examples/fresh/routes/todo.tsx: -------------------------------------------------------------------------------- 1 | import LogoutButton from "../islands/LogoutButton.tsx"; 2 | import TodoList from "../islands/TodoList.tsx"; 3 | 4 | export default function Todo() { 5 | return ( 6 | <> 7 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /frontend/examples/fresh/static/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/fresh/static/bg.jpg -------------------------------------------------------------------------------- /frontend/examples/fresh/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/fresh/static/favicon.ico -------------------------------------------------------------------------------- /frontend/examples/fresh/twind.config.ts: -------------------------------------------------------------------------------- 1 | import { Options } from "$fresh/plugins/twind.ts"; 2 | 3 | export default { 4 | selfURL: import.meta.url, 5 | } as Options; 6 | -------------------------------------------------------------------------------- /frontend/examples/fresh/types.d.ts: -------------------------------------------------------------------------------- 1 | import * as jose from "https://deno.land/x/jose@v4.14.4/index.ts"; 2 | 3 | declare global { 4 | type Todo = { todoID: string; description: string; checked: boolean }; 5 | type Store = Map>; 6 | 7 | interface AppState { 8 | store: Store; 9 | auth: jose.JWTPayload; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_HANKO_API=http://localhost:8000 2 | NEXT_PUBLIC_TODO_API=http://localhost:8002 3 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:18.17-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | RUN npm install 13 | 14 | # add app 15 | COPY . ./ 16 | 17 | # start app 18 | CMD ["npm", "start"] 19 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/nextjs/assets/bg.jpg -------------------------------------------------------------------------------- /frontend/examples/nextjs/components/HankoProfile.tsx: -------------------------------------------------------------------------------- 1 | import { register } from "@teamhanko/hanko-elements"; 2 | import { useEffect } from "react"; 3 | 4 | const api = process.env.NEXT_PUBLIC_HANKO_API!; 5 | 6 | interface Props { 7 | setError(error: Error): void; 8 | } 9 | 10 | function HankoProfile({ setError }: Props) { 11 | useEffect(() => { 12 | register(api).catch(setError); 13 | }, [setError]); 14 | 15 | return ; 16 | } 17 | 18 | export default HankoProfile; 19 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/components/SessionExpiredModal.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useCallback } from "react"; 2 | import { useRouter } from "next/router"; 3 | 4 | export const SessionExpiredModal = forwardRef( 5 | (props, ref) => { 6 | const router = useRouter(); 7 | 8 | const redirectToLogin = useCallback(() => { 9 | router.push("/"); 10 | }, [router]); 11 | 12 | return ( 13 | 14 | Please login again. 15 |
16 |
17 | 18 |
19 | ); 20 | } 21 | ); 22 | 23 | SessionExpiredModal.displayName = "SessionExpiredModal" 24 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-nextjs", 3 | "private": true, 4 | "scripts": { 5 | "start": "next dev -p 8888", 6 | "build": "next build" 7 | }, 8 | "dependencies": { 9 | "@teamhanko/hanko-elements": "^2.1.0", 10 | "next": "^15.1.6", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^22.5.1", 16 | "@types/react": "^18.2.32", 17 | "@types/react-dom": "^18.2.7", 18 | "eslint": "^8.52.0", 19 | "eslint-config-next": "^14.0.3", 20 | "typescript": "^4.8.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/index.css"; 2 | import Head from 'next/head'; 3 | import type { AppProps } from "next/app"; 4 | 5 | function MyApp({ Component, pageProps }: AppProps) { 6 | return <> 7 | 8 | Hanko Next.js Example 9 | 10 | 11 | 12 | 13 | } 14 | 15 | export default MyApp; 16 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import styles from "../styles/Todo.module.css"; 3 | import React, { useState } from "react"; 4 | import HankoAuth from "../components/HankoAuth"; 5 | 6 | 7 | const Home: NextPage = () => { 8 | const [error, setError] = useState(null); 9 | 10 | return ( 11 |
12 |
{error?.message}
13 | 14 |
15 | ); 16 | }; 17 | 18 | export default Home; 19 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/nextjs/public/favicon.png -------------------------------------------------------------------------------- /frontend/examples/nextjs/styles/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: white; 3 | margin: 0; 4 | background: url("../assets/bg.jpg") no-repeat center center fixed; 5 | background-size: cover; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | } 16 | 17 | hanko-auth::part(form-item) { 18 | min-width: 100%; /* input fields and buttons are on top of each other */ 19 | } 20 | -------------------------------------------------------------------------------- /frontend/examples/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "downlevelIteration": true, 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /frontend/examples/react/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/react/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_HANKO_API=http://localhost:8000 2 | REACT_APP_TODO_API=http://localhost:8002 3 | -------------------------------------------------------------------------------- /frontend/examples/react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/examples/react/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:16-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | RUN npm install 13 | RUN npm install react-scripts@3.4.1 -g --silent 14 | 15 | # add app 16 | COPY . ./ 17 | 18 | # start app 19 | CMD ["npm", "start"] 20 | -------------------------------------------------------------------------------- /frontend/examples/react/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/react/assets/bg.jpg -------------------------------------------------------------------------------- /frontend/examples/react/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/react/public/favicon.png -------------------------------------------------------------------------------- /frontend/examples/react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Hanko React Example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /frontend/examples/react/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/examples/react/src/SessionExpiredModal.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useCallback } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | export const SessionExpiredModal = forwardRef( 5 | (props, ref) => { 6 | const navigate = useNavigate(); 7 | 8 | const redirectToLogin = useCallback(() => { 9 | navigate("/"); 10 | }, [navigate]); 11 | 12 | return ( 13 | 14 | Please login again. 15 |
16 |
17 | 18 |
19 | ); 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /frontend/examples/react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: white; 3 | margin: 0; 4 | background: url("../assets/bg.jpg") no-repeat center center fixed; 5 | background-size: cover; 6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 7 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | } 16 | 17 | hanko-auth::part(form-item) { 18 | min-width: 100%; /* input fields and buttons are on top of each other */ 19 | } 20 | -------------------------------------------------------------------------------- /frontend/examples/react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 4 | import HankoAuth from "./HankoAuth"; 5 | import Todo from "./Todo"; 6 | import "./index.css"; 7 | import HankoProfile from "./HankoProfile"; 8 | 9 | const root = ReactDOM.createRoot( 10 | document.getElementById("root") as HTMLElement 11 | ); 12 | 13 | root.render( 14 | 15 | 16 | 17 | } /> 18 | } /> 19 | } /> 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /frontend/examples/react/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /frontend/examples/svelte/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/svelte/.env: -------------------------------------------------------------------------------- 1 | VITE_HANKO_API=http://localhost:8000 2 | VITE_TODO_API=http://localhost:8002 3 | -------------------------------------------------------------------------------- /frontend/examples/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/examples/svelte/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/examples/svelte/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:16-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | RUN npm install 13 | 14 | # add app 15 | COPY . ./ 16 | 17 | # start app 18 | CMD ["npm", "start"] 19 | -------------------------------------------------------------------------------- /frontend/examples/svelte/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hanko Svelte Example 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/examples/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-svelte", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "vite --port 8888 --host", 7 | "build": "vite build" 8 | }, 9 | "devDependencies": { 10 | "@sveltejs/vite-plugin-svelte": "^2.4.1", 11 | "@tsconfig/svelte": "^3.0.0", 12 | "svelte": "^4.2.19", 13 | "svelte-check": "^3.8.6", 14 | "svelte-preprocess": "^5.1.0", 15 | "tslib": "^2.6.2", 16 | "typescript": "^4.6.4", 17 | "vite": "^4.5.6" 18 | }, 19 | "dependencies": { 20 | "@teamhanko/hanko-elements": "^2.1.0", 21 | "svelte-navigator": "^3.2.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/examples/svelte/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/svelte/public/favicon.png -------------------------------------------------------------------------------- /frontend/examples/svelte/src/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: white; 3 | margin: 0; 4 | background: url("assets/bg.jpg") no-repeat center center fixed; 5 | background-size: cover; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | } 16 | 17 | hanko-auth::part(form-item) { 18 | min-width: 100%; /* input fields and buttons are on top of each other */ 19 | } 20 | -------------------------------------------------------------------------------- /frontend/examples/svelte/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/svelte/src/assets/bg.jpg -------------------------------------------------------------------------------- /frontend/examples/svelte/src/lib/Login.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | {#if error} 19 |
{ error?.message }
20 | {/if} 21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /frontend/examples/svelte/src/lib/SessionExpiredModal.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | Please login again.

20 | 21 |
22 | -------------------------------------------------------------------------------- /frontend/examples/svelte/src/main.ts: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import App from './App.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app') 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /frontend/examples/svelte/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /frontend/examples/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import sveltePreprocess from 'svelte-preprocess' 2 | 3 | export default { 4 | // Consult https://github.com/sveltejs/svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: sveltePreprocess() 7 | } 8 | -------------------------------------------------------------------------------- /frontend/examples/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | "baseUrl": ".", 9 | /** 10 | * Typecheck JS in `.svelte` and `.js` files by default. 11 | * Disable checkJs if you'd like to use dynamic types in JS. 12 | * Note that setting allowJs false does not prevent the use 13 | * of JS in `.svelte` files. 14 | */ 15 | "allowJs": true, 16 | "checkJs": true, 17 | "isolatedModules": true 18 | }, 19 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /frontend/examples/svelte/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /frontend/examples/svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | server: { 7 | host: '0.0.0.0' 8 | }, 9 | plugins: [svelte()] 10 | }) 11 | -------------------------------------------------------------------------------- /frontend/examples/vue/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/examples/vue/.env: -------------------------------------------------------------------------------- 1 | VITE_HANKO_API=http://localhost:8000 2 | VITE_TODO_API=http://localhost:8002 3 | -------------------------------------------------------------------------------- /frontend/examples/vue/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | root: true, 6 | extends: [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript", 10 | "@vue/eslint-config-prettier", 11 | ], 12 | parserOptions: { 13 | ecmaVersion: "latest", 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/examples/vue/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /frontend/examples/vue/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /frontend/examples/vue/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/examples/vue/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:16-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | RUN npm install 13 | 14 | # add app 15 | COPY . ./ 16 | 17 | # start app 18 | CMD ["npm", "start"] 19 | -------------------------------------------------------------------------------- /frontend/examples/vue/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hanko Vue Example 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-vue", 3 | "scripts": { 4 | "start": "vite --port 8888 --host", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "@teamhanko/hanko-elements": "^2.1.0", 9 | "vue": "^3.3.8", 10 | "vue-router": "^4.1.6" 11 | }, 12 | "devDependencies": { 13 | "@rushstack/eslint-patch": "^1.3.2", 14 | "@types/node": "^22.5.1", 15 | "@vitejs/plugin-vue": "^5.1.3", 16 | "@vue/eslint-config-prettier": "^7.0.0", 17 | "@vue/eslint-config-typescript": "^12.0.0", 18 | "@vue/tsconfig": "^0.1.3", 19 | "eslint": "^8.52.0", 20 | "eslint-plugin-vue": "^9.15.1", 21 | "npm-run-all": "^4.1.5", 22 | "prettier": "^2.7.1", 23 | "typescript": "~4.7.4", 24 | "vite": "^4.5.6", 25 | "vue-tsc": "^1.8.22" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/examples/vue/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/vue/public/favicon.png -------------------------------------------------------------------------------- /frontend/examples/vue/src/assets/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: white; 3 | margin: 0; 4 | background: url("bg.jpg") no-repeat center center fixed; 5 | background-size: cover; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | } 16 | 17 | hanko-auth::part(form-item) { 18 | min-width: 100%; /* input fields and buttons are on top of each other */ 19 | } 20 | -------------------------------------------------------------------------------- /frontend/examples/vue/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/frontend/examples/vue/src/assets/bg.jpg -------------------------------------------------------------------------------- /frontend/examples/vue/src/components/SessionExpiredModal.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 30 | -------------------------------------------------------------------------------- /frontend/examples/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | 5 | import "./assets/base.css"; 6 | 7 | const app = createApp(App); 8 | 9 | app.use(router); 10 | 11 | app.mount("#app"); 12 | -------------------------------------------------------------------------------- /frontend/examples/vue/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | import LoginView from "../views/LoginView.vue"; 3 | import TodoView from "../views/TodoView.vue"; 4 | import ProfileView from "../views/ProfileView.vue"; 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(import.meta.env.BASE_URL), 8 | routes: [ 9 | { 10 | path: "/", 11 | name: "login", 12 | component: LoginView, 13 | }, 14 | { 15 | path: "/todo", 16 | name: "todo", 17 | component: TodoView, 18 | }, 19 | { 20 | path: "/profile", 21 | name: "profile", 22 | component: ProfileView, 23 | }, 24 | ], 25 | }); 26 | 27 | export default router; 28 | -------------------------------------------------------------------------------- /frontend/examples/vue/src/views/LoginView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /frontend/examples/vue/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/examples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "./tsconfig.config.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /frontend/examples/vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue({ 10 | template: { 11 | compilerOptions: { isCustomElement: (tag) => tag.startsWith("hanko-") }, 12 | }, 13 | }), 14 | ], 15 | resolve: { 16 | alias: { 17 | "@": fileURLToPath(new URL("./src", import.meta.url)), 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true, 5 | 'node': true, 6 | }, 7 | 'extends': [ 8 | 'eslint:recommended', 9 | 'google', 10 | 'preact', 11 | 'plugin:promise/recommended', 12 | 'plugin:prettier/recommended', 13 | ], 14 | 'parser': '@typescript-eslint/parser', 15 | 'parserOptions': { 16 | 'ecmaVersion': 'latest', 17 | 'sourceType': 'module', 18 | "project": "tsconfig.json", 19 | "tsconfigRootDir": ".", 20 | }, 21 | 'plugins': [ 22 | '@typescript-eslint' 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/.nvmrc: -------------------------------------------------------------------------------- 1 | v20.16.0 2 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/jest.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | setupFilesAfterEnv: ['/tests/setup.ts'], 6 | coverageProvider: "v8", 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "template": "node_modules/better-docs" 4 | }, 5 | "tags": { 6 | "allowUnknownTags": ["category", "subcategory"], 7 | "dictionaries": ["jsdoc", "closure"] 8 | }, 9 | "plugins": [ 10 | "node_modules/better-docs/typescript", 11 | "node_modules/better-docs/category" 12 | ], 13 | "source": { 14 | "include": ["./src"], 15 | "includePattern": ".(ts)$" 16 | }, 17 | "templates": { 18 | "cleverLinks": true 19 | }, 20 | "sourceType": "module" 21 | } 22 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | server_tokens off; 6 | 7 | location / { 8 | root /usr/share/nginx/html; 9 | # index index.html; 10 | try_files $uri $uri/ @index; 11 | } 12 | 13 | location @index { 14 | # add_header Cache-Control no-cache; 15 | server_tokens off; 16 | expires -1; 17 | root /usr/share/nginx/html; 18 | try_files /index.html =404; 19 | } 20 | 21 | # redirect server error pages to the static page /50x.html 22 | # 23 | error_page 500 502 503 504 /50x.html; 24 | location = /50x.html { 25 | root /usr/share/nginx/html; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CustomEventWithDetail, 3 | SessionDetail, 4 | sessionCreatedType, 5 | sessionExpiredType, 6 | userLoggedOutType, 7 | userDeletedType, 8 | flowErrorType, 9 | } from "./lib/events/CustomEvents"; 10 | 11 | declare global { 12 | // eslint-disable-next-line no-unused-vars 13 | interface DocumentEventMap { 14 | [sessionCreatedType]: CustomEventWithDetail; 15 | [sessionExpiredType]: CustomEventWithDetail; 16 | [userLoggedOutType]: CustomEventWithDetail; 17 | [userDeletedType]: CustomEventWithDetail; 18 | [flowErrorType]: CustomEventWithDetail; 19 | } 20 | } 21 | 22 | export {}; 23 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/src/lib/client/Client.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpClientOptions } from "./HttpClient"; 2 | 3 | /** 4 | * A class to be extended by the other client classes. 5 | * 6 | * @abstract 7 | * @category SDK 8 | * @subcategory Internal 9 | * @param {string} api - The URL of your Hanko API instance 10 | * @param {HttpClientOptions} options - The options that can be used 11 | */ 12 | abstract class Client { 13 | client: HttpClient; 14 | 15 | // eslint-disable-next-line require-jsdoc 16 | constructor(api: string, options: HttpClientOptions) { 17 | /** 18 | * @public 19 | * @type {HttpClient} 20 | */ 21 | this.client = new HttpClient(api, options); 22 | } 23 | } 24 | 25 | export { Client }; 26 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/src/lib/flow-api/types/flowError.ts: -------------------------------------------------------------------------------- 1 | export interface FlowError { 2 | code: string; 3 | message: string; 4 | cause?: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/tests/Hanko.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, Relay, Hanko } from "../src"; 2 | 3 | describe("class hanko", () => { 4 | it("should hold instances of available Hanko API clients", async () => { 5 | const hanko = new Hanko("http://api.test"); 6 | 7 | expect(hanko.client).toBeInstanceOf(HttpClient); 8 | expect(hanko.relay).toBeInstanceOf(Relay); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/tests/types.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | // eslint-disable-next-line no-unused-vars 3 | interface PublicKeyCredential { 4 | isExternalCTAP2SecurityKeySupported: () => Promise; 5 | isConditionalMediationAvailable: () => Promise; 6 | } 7 | } 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*", 4 | "tests/**/*" 5 | ], 6 | "compilerOptions": { 7 | "paths": { 8 | "*": [ 9 | "./*" 10 | ] 11 | }, 12 | "esModuleInterop": true, 13 | "baseUrl": "src", 14 | "moduleResolution": "node", 15 | "allowSyntheticDefaultImports": true, 16 | "isolatedModules": true, 17 | "resolveJsonModule": true, 18 | "noResolve": false, 19 | "declaration": true, 20 | "diagnostics": true, 21 | "outDir": "./dist", 22 | "lib":["ES2015", "ES2016", "dom"], 23 | "sourceMap": true, 24 | "noImplicitAny": true, 25 | "target": "es6", 26 | "module": "commonjs", 27 | "types": ["jest", "node", "@types/jest"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/frontend-sdk/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "tests/**/*" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hanko-frontend", 3 | "description": "hanko frontend parts", 4 | "private": true, 5 | "workspaces": [ 6 | "frontend-sdk", 7 | "elements", 8 | "examples/*" 9 | ], 10 | "scripts": { 11 | "build": "turbo run build", 12 | "lint": "turbo run lint", 13 | "test": "turbo run test", 14 | "start": "turbo run start", 15 | "build:elements": "turbo build --filter=./frontend-sdk --filter=./elements", 16 | "build:elements:dev": "turbo build:dev --filter=./elements" 17 | }, 18 | "devDependencies": { 19 | "turbo": "^1.10.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**"] 7 | }, 8 | "build:dev": { 9 | "dependsOn": ["^build"], 10 | "outputs": ["dist/**"] 11 | }, 12 | "start": { 13 | "dependsOn": ["^build"], 14 | "outputs": ["dist/**"] 15 | }, 16 | "test": { 17 | "dependsOn": ["build"], 18 | "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"] 19 | }, 20 | "lint": {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /quickstart/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the quickstart binary 2 | FROM --platform=$BUILDPLATFORM golang:1.24 AS builder 3 | 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | COPY . . 8 | 9 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -a -o quickstart main.go 10 | 11 | # Use distroless as minimal base image to package quickstart binary 12 | # See https://github.com/GoogleContainerTools/distroless for details 13 | FROM gcr.io/distroless/static:nonroot 14 | WORKDIR / 15 | COPY --from=builder /workspace/quickstart . 16 | COPY /public /public 17 | USER 65532:65532 18 | 19 | ENTRYPOINT ["/quickstart"] 20 | -------------------------------------------------------------------------------- /quickstart/middleware/cache_control.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | ) 6 | 7 | func CacheControlMiddleware() echo.MiddlewareFunc { 8 | return func(next echo.HandlerFunc) echo.HandlerFunc { 9 | return func(c echo.Context) error { 10 | 11 | c.Response().Header().Set(echo.HeaderCacheControl, "no-store") 12 | 13 | return next(c) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /quickstart/public/assets/css/index.css: -------------------------------------------------------------------------------- 1 | @import url("common.css"); 2 | 3 | .main { 4 | margin-top: 10%; 5 | } 6 | 7 | .auth-container { 8 | max-width: 360px; 9 | min-width: 200px; 10 | margin: auto; 11 | border-radius: 16px; 12 | padding: 25px; 13 | } 14 | 15 | @media screen and (max-width: 470px) { 16 | .auth-container { 17 | padding: 10px; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /quickstart/public/assets/css/secured.css: -------------------------------------------------------------------------------- 1 | @import url("common.css"); 2 | 3 | .profile { 4 | margin-bottom: 20px; 5 | } 6 | 7 | .profile-container { 8 | max-width: 700px; 9 | min-width: 200px; 10 | margin: auto; 11 | border-radius: 16px; 12 | padding: 25px; 13 | } 14 | 15 | hanko-profile { 16 | --headline1-margin: 2em 0 1em; 17 | --input-min-width: 20em; 18 | } 19 | 20 | @media screen and (max-width: 420px) { 21 | hanko-profile { 22 | --input-min-width: 14em; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-500.eot -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-500.ttf -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-500.woff -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-500.woff2 -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-600.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-600.eot -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-600.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-600.ttf -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-600.woff -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-600.woff2 -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-regular.eot -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-regular.ttf -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-regular.woff -------------------------------------------------------------------------------- /quickstart/public/assets/fonts/inter-v12-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/fonts/inter-v12-latin-regular.woff2 -------------------------------------------------------------------------------- /quickstart/public/assets/img/Favicon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/img/Favicon_256x256.png -------------------------------------------------------------------------------- /quickstart/public/assets/img/Favicon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamhanko/hanko/891cc2cce3e4d5e40ff6695baad885dbb825bef7/quickstart/public/assets/img/Favicon_32x32.png -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta29 2 | kind: Config 3 | metadata: 4 | name: hanko-tenant 5 | build: 6 | artifacts: 7 | - image: ghcr.io/teamhanko/hanko 8 | context: backend 9 | docker: 10 | dockerfile: Dockerfile 11 | - image: ghcr.io/teamhanko/hanko/quickstart 12 | context: quickstart 13 | docker: 14 | dockerfile: Dockerfile 15 | - image: ghcr.io/teamhanko/hanko/elements 16 | context: frontend 17 | docker: 18 | dockerfile: Dockerfile 19 | deploy: 20 | kustomize: 21 | paths: 22 | - deploy/k8s/overlays/quickstart 23 | profiles: 24 | - name: quickstart 25 | deploy: 26 | kustomize: 27 | paths: 28 | - deploy/k8s/overlays/quickstart 29 | - name: thirdparty-x-domain 30 | deploy: 31 | kustomize: 32 | paths: 33 | - deploy/k8s/overlays/thirdparty-x-domain 34 | --------------------------------------------------------------------------------