Repository: ory/kratos Branch: master Commit: 82d9700311a6 Files: 7814 Total size: 15.4 MB Directory structure: gitextract_41u59ogb/ ├── .docker/ │ ├── Dockerfile-alpine │ ├── Dockerfile-build │ ├── Dockerfile-debug │ ├── Dockerfile-distroless-static │ └── docker-compose.template.dbg ├── .dockerignore ├── .editorconfig ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── BUG-REPORT.yml │ │ ├── DESIGN-DOC.yml │ │ ├── FEATURE-REQUEST.yml │ │ └── config.yml │ ├── auto_assign.yml │ ├── codeql/ │ │ └── codeql-config.yml │ ├── config.yml │ ├── conventional_commits.json │ ├── labels.json │ ├── pull_request_template.md │ └── workflows/ │ ├── ci.yaml │ ├── closed_references.yml │ ├── codeql-analysis.yml │ ├── conventional_commits.yml │ ├── cve-scan.yaml │ ├── format.yml │ ├── labels.yml │ ├── milestone.yml │ ├── pm.yml │ └── stale.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .grype.yaml ├── .mailmap ├── .nancy-ignore ├── .nvmrc ├── .orycli.yml ├── .prettierignore ├── .reference-ignore ├── .reports/ │ └── dep-licenses.csv ├── .schema/ │ ├── api.openapi.json │ ├── openapi/ │ │ ├── gen.go.yml │ │ ├── gen.typescript.yml │ │ └── patches/ │ │ ├── common.yaml │ │ ├── courier.yaml │ │ ├── generic_error.yaml │ │ ├── identity.yaml │ │ ├── meta.yaml │ │ ├── nulls.yaml │ │ ├── schema.yaml │ │ ├── security.yaml │ │ ├── selfservice.yaml │ │ └── session.yaml │ ├── openapi.json │ └── version.schema.json ├── .schemastore/ │ ├── README.md │ └── config.schema.json ├── .trivyignore ├── .vscode/ │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── AUTHORS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOP.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── anonymous_sessions.md ├── buf.gen.yaml ├── buf.yaml ├── cipher/ │ ├── aes.go │ ├── chacha20.go │ ├── cipher.go │ ├── cipher_test.go │ └── noop.go ├── cmd/ │ ├── cleanup/ │ │ ├── root.go │ │ ├── sql.go │ │ └── sql_test.go │ ├── cliclient/ │ │ ├── cleanup.go │ │ ├── client.go │ │ └── migrate.go │ ├── clidoc/ │ │ ├── main.go │ │ └── main_test.go │ ├── courier/ │ │ ├── root.go │ │ ├── watch.go │ │ └── watch_test.go │ ├── daemon/ │ │ ├── serve.go │ │ └── serve_test.go │ ├── hashers/ │ │ ├── argon2/ │ │ │ ├── calibrate.go │ │ │ ├── hash.go │ │ │ ├── loadtest.go │ │ │ └── root.go │ │ └── root.go │ ├── identities/ │ │ ├── definitions.go │ │ ├── delete.go │ │ ├── delete_test.go │ │ ├── get.go │ │ ├── get_test.go │ │ ├── helpers.go │ │ ├── helpers_test.go │ │ ├── import.go │ │ ├── import_test.go │ │ ├── list.go │ │ ├── list_test.go │ │ ├── stubs/ │ │ │ └── identity.schema.json │ │ ├── validate.go │ │ └── validate_test.go │ ├── jsonnet/ │ │ ├── format.go │ │ ├── lint.go │ │ └── root.go │ ├── migrate/ │ │ ├── root.go │ │ └── sql.go │ ├── remote/ │ │ ├── root.go │ │ ├── status.go │ │ └── version.go │ ├── root.go │ ├── root_test.go │ └── serve/ │ ├── root.go │ ├── root_test.go │ └── stub/ │ ├── identity.schema.json │ └── kratos.yml ├── codecov.yml ├── continuity/ │ ├── container.go │ ├── container_test.go │ ├── manager.go │ ├── manager_cookie.go │ ├── manager_options_test.go │ ├── manager_test.go │ ├── persistence.go │ └── test/ │ └── persistence.go ├── contrib/ │ └── quickstart/ │ ├── .dockerignore │ ├── kratos/ │ │ ├── all-strategies/ │ │ │ ├── identity.schema.json │ │ │ └── kratos.yml │ │ ├── cloud/ │ │ │ ├── Caddyfile │ │ │ ├── identity.schema.json │ │ │ ├── kratos.yml │ │ │ └── quickstart.yml │ │ ├── email-password/ │ │ │ ├── identity.schema.json │ │ │ └── kratos.yml │ │ ├── passkey/ │ │ │ ├── identity.schema.json │ │ │ └── kratos.yml │ │ ├── phone-password/ │ │ │ ├── identity.schema.json │ │ │ └── kratos.yml │ │ └── webauthn/ │ │ ├── identity.schema.json │ │ └── kratos.yml │ └── oathkeeper/ │ ├── access-rules.yml │ ├── id_token.jwks.json │ └── oathkeeper.yml ├── corpx/ │ └── faker.go ├── courier/ │ ├── .snapshots/ │ │ ├── TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=admin.json │ │ ├── TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=public.json │ │ ├── TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=admin.json │ │ └── TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=public.json │ ├── channel.go │ ├── courier.go │ ├── courier_dispatcher.go │ ├── courier_dispatcher_test.go │ ├── email_templates.go │ ├── email_templates_test.go │ ├── handler.go │ ├── handler_test.go │ ├── http_channel.go │ ├── http_test.go │ ├── message.go │ ├── message_dispatch.go │ ├── message_test.go │ ├── persistence.go │ ├── sms.go │ ├── sms_templates.go │ ├── sms_templates_test.go │ ├── sms_test.go │ ├── smtp.go │ ├── smtp_channel.go │ ├── smtp_test.go │ ├── stub/ │ │ ├── request.config.mailer.jsonnet │ │ └── request.config.twilio.jsonnet │ ├── template/ │ │ ├── courier/ │ │ │ └── builtin/ │ │ │ └── templates/ │ │ │ ├── login_code/ │ │ │ │ └── valid/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ ├── email.subject.gotmpl │ │ │ │ └── sms.body.gotmpl │ │ │ ├── otp/ │ │ │ │ └── sms.body.gotmpl │ │ │ ├── recovery/ │ │ │ │ ├── invalid/ │ │ │ │ │ ├── email.body.gotmpl │ │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ │ └── email.subject.gotmpl │ │ │ │ └── valid/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ └── email.subject.gotmpl │ │ │ ├── recovery_code/ │ │ │ │ ├── invalid/ │ │ │ │ │ ├── email.body.gotmpl │ │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ │ └── email.subject.gotmpl │ │ │ │ └── valid/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ ├── email.subject.gotmpl │ │ │ │ └── sms.body.gotmpl │ │ │ ├── registration_code/ │ │ │ │ └── valid/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ ├── email.subject.gotmpl │ │ │ │ └── sms.body.gotmpl │ │ │ ├── test_stub/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.html.en_US.gotmpl │ │ │ │ ├── email.body.html.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ ├── email.body.sprig.gotmpl │ │ │ │ └── email.subject.gotmpl │ │ │ ├── verification/ │ │ │ │ ├── invalid/ │ │ │ │ │ ├── email.body.gotmpl │ │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ │ └── email.subject.gotmpl │ │ │ │ └── valid/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ └── email.subject.gotmpl │ │ │ └── verification_code/ │ │ │ ├── invalid/ │ │ │ │ ├── email.body.gotmpl │ │ │ │ ├── email.body.plaintext.gotmpl │ │ │ │ └── email.subject.gotmpl │ │ │ └── valid/ │ │ │ ├── email.body.gotmpl │ │ │ ├── email.body.plaintext.gotmpl │ │ │ ├── email.subject.gotmpl │ │ │ └── sms.body.gotmpl │ │ ├── email/ │ │ │ ├── login_code_valid.go │ │ │ ├── login_code_valid_test.go │ │ │ ├── recovery_code_invalid.go │ │ │ ├── recovery_code_invalid_test.go │ │ │ ├── recovery_code_valid.go │ │ │ ├── recovery_code_valid_test.go │ │ │ ├── recovery_invalid.go │ │ │ ├── recovery_invalid_test.go │ │ │ ├── recovery_valid.go │ │ │ ├── recovery_valid_test.go │ │ │ ├── registration_code_valid.go │ │ │ ├── registration_code_valid_test.go │ │ │ ├── stub.go │ │ │ ├── verification_code_invalid.go │ │ │ ├── verification_code_invalid_test.go │ │ │ ├── verification_code_valid.go │ │ │ ├── verification_code_valid_test.go │ │ │ ├── verification_invalid.go │ │ │ ├── verification_invalid_test.go │ │ │ ├── verification_valid.go │ │ │ └── verification_valid_test.go │ │ ├── load_template.go │ │ ├── load_template_test.go │ │ ├── sms/ │ │ │ ├── login_code_valid.go │ │ │ ├── login_code_valid_test.go │ │ │ ├── recovery_code.go │ │ │ ├── registration_code_valid.go │ │ │ ├── registration_code_valid_test.go │ │ │ ├── stub.go │ │ │ ├── verification_code.go │ │ │ └── verification_code_test.go │ │ ├── template.go │ │ ├── testhelpers/ │ │ │ └── testhelpers.go │ │ └── type.go │ └── test/ │ └── persistence.go ├── docs/ │ ├── README.md │ └── sidebar.json ├── driver/ │ ├── config/ │ │ ├── .snapshots/ │ │ │ ├── TestCourierEmailHTTP-case=configs_set.json │ │ │ ├── TestCourierSMS-case=configs_set.json │ │ │ ├── TestCourierSMS-case=defaults.json │ │ │ └── TestDefaultWebhookHeaderAllowlist.json │ │ ├── buildinfo.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── handler.go │ │ ├── handler_test.go │ │ └── stub/ │ │ ├── .defaults-password.yml │ │ ├── .defaults-verification.yml │ │ ├── .defaults.yml │ │ ├── .identity.invalid.json │ │ ├── .identity.other.json │ │ ├── .identity.test.json │ │ ├── .kratos.courier.channels.yaml │ │ ├── .kratos.courier.email.http.yaml │ │ ├── .kratos.courier.message_retries.yaml │ │ ├── .kratos.courier.remote.invalid.subject.yaml │ │ ├── .kratos.courier.remote.partial.templates.yaml │ │ ├── .kratos.courier.remote.templates.yaml │ │ ├── .kratos.courier.sms.yaml │ │ ├── .kratos.invalid.identities.yaml │ │ ├── .kratos.mock.identities.yaml │ │ ├── .kratos.notify-unknown-recipients.yml │ │ ├── .kratos.oauth2_provider.yaml │ │ ├── .kratos.webauthn.invalid.yaml │ │ ├── .kratos.webauthn.origin.yaml │ │ ├── .kratos.webauthn.origins.yaml │ │ └── .kratos.yaml │ ├── factory.go │ ├── factory_test.go │ ├── registry.go │ ├── registry_default.go │ ├── registry_default_hooks.go │ ├── registry_default_login.go │ ├── registry_default_recovery.go │ ├── registry_default_registration.go │ ├── registry_default_schemas.go │ ├── registry_default_schemas_test.go │ ├── registry_default_sessiontokenexchange.go │ ├── registry_default_settings.go │ ├── registry_default_test.go │ └── registry_default_verification.go ├── embedx/ │ ├── config.schema.json │ ├── embedx.go │ ├── embedx_test.go │ ├── identity_extension.schema.json │ ├── identity_meta.schema.json │ └── testdata/ │ ├── identity_meta.no_traits.invalid.schema.json │ ├── identity_meta.simple.valid.schema.json │ └── identity_meta.verification_format.invalid.schema.json ├── examples/ │ └── go/ │ ├── identity/ │ │ ├── create/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── delete/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── get/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ └── update/ │ │ ├── main.go │ │ └── main_test.go │ ├── pkg/ │ │ ├── common.go │ │ ├── resources.go │ │ └── stub/ │ │ ├── identity.schema.json │ │ └── kratos.yaml │ ├── selfservice/ │ │ ├── error/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── login/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── logout/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── recovery/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── registration/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── settings/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ └── verification/ │ │ ├── main.go │ │ └── main_test.go │ └── session/ │ └── tosession/ │ ├── main.go │ └── main_test.go ├── gen/ │ └── oidc/ │ └── v1/ │ └── state.pb.go ├── go.mod ├── go.sum ├── hash/ │ ├── hash_comparator.go │ ├── hasher.go │ ├── hasher_argon2.go │ ├── hasher_bcrypt.go │ ├── hasher_pbkdf2.go │ ├── hasher_scrypt.go │ └── hasher_test.go ├── hydra/ │ ├── fake.go │ ├── hydra.go │ └── hydra_test.go ├── identity/ │ ├── .snapshots/ │ │ ├── TestHandler-case=PATCH_should_allow_to_update_credential_password-endpoint=admin.json │ │ ├── TestHandler-case=PATCH_should_allow_to_update_credential_password-endpoint=public.json │ │ ├── TestHandler-case=PATCH_should_allow_to_update_credential_password.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_cleartext_password_and_oidc_credentials.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=SSHA.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=SSHA256.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=SSHA512.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=argon2i.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=argon2id.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=bcrypt2.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=hmac.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=md5.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=pkbdf2.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=scrypt.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_not-normalized_email.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_organization_oidc_and_saml_credentials.json │ │ ├── TestHandler-case=should_be_able_to_import_users-with_password_migration_hook_enabled.json │ │ ├── TestHandler-case=should_be_able_to_import_users-without_any_credentials.json │ │ ├── TestHandler-case=should_be_able_to_import_users-without_traits.json │ │ ├── TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_and_multiple_fido_mfa_type-admin.json │ │ ├── TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_and_multiple_fido_mfa_type-public.json │ │ ├── TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_type-admin.json │ │ ├── TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_type-public.json │ │ ├── TestHandler-case=should_list_all_identities_with_credentials-include_credential=oidc_should_include_OIDC_credentials_config.json │ │ ├── TestHandler-suite=PATCH_identities-case=success-assert=identity_0.json │ │ ├── TestHandler-suite=PATCH_identities-case=success-assert=identity_1.json │ │ ├── TestHandler-suite=PATCH_identities-case=success-assert=identity_2.json │ │ ├── TestHandler-suite=PATCH_identities-case=success-assert=identity_3.json │ │ ├── TestHandler-suite=create_and_update-case=should_get_identity_with_credentials-case=should_get_identity_with_password_and_webauthn_credentials_included.json │ │ ├── TestHandler-suite=create_and_update-case=should_get_identity_with_credentials-case=should_get_identity_with_password_credentials_included.json │ │ ├── TestHandler-suite=create_and_update-case=should_get_identity_with_credentials-case=should_get_identity_without_credentials_included.json │ │ ├── TestImportCredentials-OIDC_new_credential_with_organization.json │ │ ├── TestImportCredentials-OIDC_new_credential_without_organization.json │ │ ├── TestImportCredentials-OIDC_update_credential_with_organization.json │ │ ├── TestImportCredentials-OIDC_update_credential_without_organization.json │ │ ├── TestImportCredentials-OIDC_update_with_multiple_providers.json │ │ ├── TestImportCredentials-SAML_new_credential_with_organization.json │ │ ├── TestImportCredentials-SAML_new_credential_without_organization.json │ │ ├── TestImportCredentials-SAML_update_credential_with_organization.json │ │ ├── TestImportCredentials-SAML_update_credential_without_organization.json │ │ ├── TestImportCredentials-SAML_update_with_multiple_providers.json │ │ ├── TestMarshalIdentityWithAll.json │ │ ├── TestSchemaExtensionCredentials-case=0.json │ │ ├── TestSchemaExtensionCredentials-case=1.json │ │ ├── TestSchemaExtensionCredentials-case=10.json │ │ ├── TestSchemaExtensionCredentials-case=11.json │ │ ├── TestSchemaExtensionCredentials-case=12.json │ │ ├── TestSchemaExtensionCredentials-case=13.json │ │ ├── TestSchemaExtensionCredentials-case=2.json │ │ ├── TestSchemaExtensionCredentials-case=3.json │ │ ├── TestSchemaExtensionCredentials-case=4.json │ │ ├── TestSchemaExtensionCredentials-case=5.json │ │ ├── TestSchemaExtensionCredentials-case=6.json │ │ ├── TestSchemaExtensionCredentials-case=7.json │ │ ├── TestSchemaExtensionCredentials-case=8.json │ │ ├── TestSchemaExtensionCredentials-case=9.json │ │ ├── TestToNode.json │ │ ├── TestUpgradeCredentials-empty_credentials.json │ │ ├── TestUpgradeCredentials-type=code-from=v0_with_correct_value.json │ │ ├── TestUpgradeCredentials-type=code-from=v0_with_email_empty_space_value-with_one_identifier.json │ │ ├── TestUpgradeCredentials-type=code-from=v0_with_email_empty_space_value-with_two_identifiers.json │ │ ├── TestUpgradeCredentials-type=code-from=v0_with_empty_value.json │ │ ├── TestUpgradeCredentials-type=code-from=v0_with_unknown_value.json │ │ ├── TestUpgradeCredentials-type=code-from=v2_with_empty_value.json │ │ ├── TestUpgradeCredentials-type=webauthn-from=v0.json │ │ ├── TestUpgradeCredentials-type=webauthn-from=v1.json │ │ ├── TestWithDeclassifiedCredentials-case=include-multi-credential=oidc.json │ │ ├── TestWithDeclassifiedCredentials-case=include-multi-credential=password.json │ │ ├── TestWithDeclassifiedCredentials-case=include-multi-credential=saml.json │ │ ├── TestWithDeclassifiedCredentials-case=include-multi-credential=webauthn.json │ │ ├── TestWithDeclassifiedCredentials-case=include-webauthn-credential=oidc.json │ │ ├── TestWithDeclassifiedCredentials-case=include-webauthn-credential=password.json │ │ ├── TestWithDeclassifiedCredentials-case=include-webauthn-credential=saml.json │ │ ├── TestWithDeclassifiedCredentials-case=include-webauthn-credential=webauthn.json │ │ ├── TestWithDeclassifiedCredentials-case=no-include-credential=oidc.json │ │ ├── TestWithDeclassifiedCredentials-case=no-include-credential=password.json │ │ ├── TestWithDeclassifiedCredentials-case=no-include-credential=saml.json │ │ ├── TestWithDeclassifiedCredentials-case=no-include-credential=webauthn.json │ │ ├── TestWithDeclassifiedCredentials-case=oidc-credential=oidc.json │ │ ├── TestWithDeclassifiedCredentials-case=oidc-credential=password.json │ │ ├── TestWithDeclassifiedCredentials-case=oidc-credential=saml.json │ │ ├── TestWithDeclassifiedCredentials-case=oidc-credential=webauthn.json │ │ ├── TestWithDeclassifiedCredentials-case=saml-credential=oidc.json │ │ ├── TestWithDeclassifiedCredentials-case=saml-credential=password.json │ │ ├── TestWithDeclassifiedCredentials-case=saml-credential=saml.json │ │ └── TestWithDeclassifiedCredentials-case=saml-credential=webauthn.json │ ├── address.go │ ├── credentials.go │ ├── credentials_code.go │ ├── credentials_code_test.go │ ├── credentials_lookup.go │ ├── credentials_lookup_test.go │ ├── credentials_migrate.go │ ├── credentials_migrate_test.go │ ├── credentials_oidc.go │ ├── credentials_oidc_test.go │ ├── credentials_password.go │ ├── credentials_password_test.go │ ├── credentials_test.go │ ├── credentials_totp.go │ ├── credentials_webauthn.go │ ├── credentials_webauthn_test.go │ ├── error_test.go │ ├── expandables.go │ ├── extension_credentials.go │ ├── extension_credentials_test.go │ ├── extension_recovery.go │ ├── extension_recovery_test.go │ ├── extension_verification.go │ ├── extension_verification_test.go │ ├── handler.go │ ├── handler_import.go │ ├── handler_import_test.go │ ├── handler_test.go │ ├── identity.go │ ├── identity_recovery.go │ ├── identity_recovery_test.go │ ├── identity_test.go │ ├── identity_verification.go │ ├── identity_verification_test.go │ ├── manager.go │ ├── manager_test.go │ ├── pool.go │ ├── registry.go │ ├── stub/ │ │ ├── aal.json │ │ ├── expand.schema.json │ │ ├── extension/ │ │ │ ├── credentials/ │ │ │ │ ├── code-phone-email.schema.json │ │ │ │ ├── code.schema.json │ │ │ │ ├── email.schema.json │ │ │ │ ├── multi.schema.json │ │ │ │ ├── schema.json │ │ │ │ └── webauthn.schema.json │ │ │ ├── recovery/ │ │ │ │ ├── email.schema.json │ │ │ │ ├── schema.json │ │ │ │ └── sms.schema.json │ │ │ └── verify/ │ │ │ ├── email.schema.json │ │ │ ├── legacy-email-missing-format.schema.json │ │ │ ├── missing-format.schema.json │ │ │ ├── no-validate.schema.json │ │ │ └── phone.schema.json │ │ ├── extension.schema.json │ │ ├── handler/ │ │ │ ├── customer.schema.json │ │ │ ├── employee.schema.json │ │ │ └── multiple_emails.schema.json │ │ ├── identity-2.schema.json │ │ ├── identity.schema.json │ │ ├── localhost-ref.schema.json │ │ ├── manager.schema.json │ │ └── webauthn/ │ │ ├── v0.json │ │ └── v1.json │ ├── test/ │ │ └── pool.go │ ├── validator.go │ └── validator_test.go ├── install.sh ├── main.go ├── openapitools.json ├── oryx/ │ ├── .gitignore │ ├── .goimportsignore │ ├── .golangci.yml │ ├── .nancy-ignore │ ├── .prettierignore │ ├── .reference-ignore │ ├── .schemas/ │ │ ├── corsx/ │ │ │ └── viper.schema.json │ │ ├── logrusx/ │ │ │ └── viper.schema.json │ │ ├── profilingx/ │ │ │ └── viper.schema.json │ │ └── tlsx/ │ │ └── viper.schema.json │ ├── LICENSE │ ├── Makefile │ ├── assertx/ │ │ └── assertx.go │ ├── cachex/ │ │ └── ristretto.go │ ├── castx/ │ │ └── castx.go │ ├── clidoc/ │ │ ├── generate.go │ │ ├── md_docs.go │ │ ├── testdata/ │ │ │ ├── hydra-client-admin.md │ │ │ ├── hydra-client-public.md │ │ │ ├── hydra-client.md │ │ │ ├── hydra-serve.md │ │ │ └── hydra.md │ │ └── util.go │ ├── cmdx/ │ │ ├── args.go │ │ ├── env.go │ │ ├── helper.go │ │ ├── http.go │ │ ├── noise_printer.go │ │ ├── output.go │ │ ├── pagination.go │ │ ├── printing.go │ │ ├── usage.go │ │ ├── user_input.go │ │ └── version.go │ ├── configx/ │ │ ├── .snapshots/ │ │ │ └── TestKoanfSchemaDefaults.json │ │ ├── context.go │ │ ├── cors.go │ │ ├── cors.schema.json │ │ ├── error.go │ │ ├── helpers.go │ │ ├── koanf_confmap.go │ │ ├── koanf_env.go │ │ ├── koanf_file.go │ │ ├── koanf_full_merge.go │ │ ├── koanf_memory.go │ │ ├── koanf_schema_defaults.go │ │ ├── options.go │ │ ├── permission.go │ │ ├── pflag.go │ │ ├── provider.go │ │ ├── schema.go │ │ ├── schema_cache.go │ │ ├── schema_path_cache.go │ │ ├── serve.go │ │ ├── serve.schema.json │ │ ├── span.go │ │ ├── stub/ │ │ │ ├── benchmark/ │ │ │ │ ├── benchmark.yaml │ │ │ │ └── schema.config.json │ │ │ ├── domain-aliases/ │ │ │ │ └── config.schema.json │ │ │ ├── from-files/ │ │ │ │ ├── a.yaml │ │ │ │ ├── b.yaml │ │ │ │ ├── config.schema.json │ │ │ │ └── expected.json │ │ │ ├── hydra/ │ │ │ │ ├── config.schema.json │ │ │ │ ├── expected.json │ │ │ │ └── hydra.yaml │ │ │ ├── kratos/ │ │ │ │ ├── config.schema.json │ │ │ │ ├── expected.json │ │ │ │ └── kratos.yaml │ │ │ ├── multi/ │ │ │ │ ├── a.yaml │ │ │ │ ├── b.yaml │ │ │ │ ├── config.schema.json │ │ │ │ └── expected.json │ │ │ ├── nested-array/ │ │ │ │ ├── config.schema.json │ │ │ │ ├── expected.json │ │ │ │ └── kratos.yaml │ │ │ └── watch/ │ │ │ └── config.schema.json │ │ └── tls.schema.json │ ├── contextx/ │ │ ├── contextual.go │ │ ├── contextual_mock.go │ │ ├── default.go │ │ ├── testhelpers.go │ │ └── tree.go │ ├── corsx/ │ │ ├── check_origin.go │ │ ├── cmd.go │ │ ├── defaults.go │ │ ├── middleware.go │ │ └── normalize.go │ ├── crdbx/ │ │ ├── readonly.go │ │ └── staleness.go │ ├── dbal/ │ │ ├── canonicalize.go │ │ ├── driver.go │ │ ├── dsn.go │ │ └── testhelpers.go │ ├── decoderx/ │ │ ├── http.go │ │ └── stub/ │ │ ├── consent.json │ │ ├── dynamic-object.json │ │ ├── nested.json │ │ ├── person.json │ │ ├── required-defaults.json │ │ └── schema.json │ ├── errorsx/ │ │ └── errors.go │ ├── fetcher/ │ │ └── fetcher.go │ ├── flagx/ │ │ └── flagx.go │ ├── fsx/ │ │ ├── dirhash.go │ │ └── merge.go │ ├── go.mod │ ├── go.sum │ ├── hasherx/ │ │ ├── hash_comparator.go │ │ ├── hasher.go │ │ ├── hasher_argon2.go │ │ ├── hasher_bcrypt.go │ │ └── hasher_pbkdf2.go │ ├── healthx/ │ │ ├── doc.go │ │ ├── handler.go │ │ └── openapi/ │ │ └── patch.yaml │ ├── httprouterx/ │ │ └── router.go │ ├── httpx/ │ │ ├── assert.go │ │ ├── chan_handler.go │ │ ├── client_info.go │ │ ├── content_type.go │ │ ├── gzip_server.go │ │ ├── private_ip_validator.go │ │ ├── request.go │ │ ├── resilient_client.go │ │ ├── ssrf.go │ │ ├── transports.go │ │ ├── url.go │ │ └── wait_for.go │ ├── ioutilx/ │ │ └── pkger.go │ ├── ipx/ │ │ ├── cidr.go │ │ └── ip_validator.go │ ├── josex/ │ │ ├── encoding.go │ │ ├── generate.go │ │ ├── public.go │ │ └── utils.go │ ├── jsonnetsecure/ │ │ ├── cmd/ │ │ │ └── root.go │ │ ├── cmd.go │ │ ├── jsonnet.go │ │ ├── jsonnet_pool.go │ │ ├── limit_unix.go │ │ ├── limit_windows.go │ │ ├── null.go │ │ ├── provider.go │ │ └── stub/ │ │ └── import.jsonnet │ ├── jsonnetx/ │ │ ├── format.go │ │ ├── lint.go │ │ └── root.go │ ├── jsonschemax/ │ │ ├── .snapshots/ │ │ │ ├── TestListPaths-case=0.json │ │ │ ├── TestListPaths-case=1.json │ │ │ ├── TestListPaths-case=2.json │ │ │ ├── TestListPaths-case=3.json │ │ │ ├── TestListPaths-case=4.json │ │ │ ├── TestListPaths-case=5.json │ │ │ ├── TestListPaths-case=6.json │ │ │ ├── TestListPaths-case=7.json │ │ │ ├── TestListPaths-case=8.json │ │ │ ├── TestListPaths-case=9.json │ │ │ └── TestListPathsWithRecursion-case=0.json │ │ ├── README.md │ │ ├── error.go │ │ ├── keys.go │ │ ├── pointer.go │ │ ├── print.go │ │ └── stub/ │ │ ├── .config.yaml │ │ ├── .oathkeeper.schema.json │ │ ├── config.schema.json │ │ ├── json/ │ │ │ └── .project-stub-name.json │ │ ├── nested-array.schema.json │ │ ├── nested-simple-array.schema.json │ │ ├── toml/ │ │ │ └── .project-stub-name.toml │ │ ├── yaml/ │ │ │ └── .project-stub-name.yaml │ │ └── yml/ │ │ └── .project-stub-name.yml │ ├── jsonx/ │ │ ├── .snapshots/ │ │ │ ├── TestEmbedSources-fixtures-fixture=1.json.json │ │ │ ├── TestEmbedSources-fixtures-fixture=2.json.json │ │ │ ├── TestEmbedSources-fixtures-fixture=3.json.json │ │ │ ├── TestEmbedSources-fixtures-fixture=4.json.json │ │ │ ├── TestEmbedSources-fixtures-fixture=5.json.json │ │ │ ├── TestEmbedSources-fixtures-fixture=6.json.json │ │ │ └── TestEmbedSources-only_embeds_base64.json │ │ ├── debug.go │ │ ├── decoder.go │ │ ├── embed.go │ │ ├── fixture/ │ │ │ └── embed/ │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ ├── 3.json │ │ │ ├── 4.json │ │ │ ├── 5.json │ │ │ └── 6.json │ │ ├── flatten.go │ │ ├── get.go │ │ ├── helpers.go │ │ ├── patch.go │ │ └── stub/ │ │ └── random.json │ ├── jwksx/ │ │ ├── .snapshots/ │ │ │ ├── TestFetcherNext-case=resolve_multiple_source_urls-case=succeeds_with_forced_kid.json │ │ │ ├── TestFetcherNext-case=resolve_single_source_url-case=with_cache.json │ │ │ ├── TestFetcherNext-case=resolve_single_source_url-case=with_cache_and_TTL.json │ │ │ ├── TestFetcherNext-case=resolve_single_source_url-case=with_forced_key.json │ │ │ └── TestFetcherNext-case=resolve_single_source_url-case=without_cache.json │ │ ├── fetcher.go │ │ ├── fetcher_v2.go │ │ └── generator.go │ ├── jwtmiddleware/ │ │ ├── middleware.go │ │ └── stub/ │ │ └── jwks.json │ ├── jwtx/ │ │ └── claims.go │ ├── logrusx/ │ │ ├── config.schema.json │ │ ├── helper.go │ │ └── logrus.go │ ├── mapx/ │ │ └── type_assert.go │ ├── metricsx/ │ │ ├── metrics.go │ │ └── middleware.go │ ├── migratest/ │ │ ├── refresh.go │ │ ├── run.go │ │ └── strict.go │ ├── networkx/ │ │ ├── listener.go │ │ ├── manager.go │ │ ├── migrations/ │ │ │ ├── sql/ │ │ │ │ ├── 20150100000001000000_networks.cockroach.down.sql │ │ │ │ ├── 20150100000001000000_networks.cockroach.up.sql │ │ │ │ ├── 20150100000001000000_networks.mysql.down.sql │ │ │ │ ├── 20150100000001000000_networks.mysql.up.sql │ │ │ │ ├── 20150100000001000000_networks.postgres.down.sql │ │ │ │ ├── 20150100000001000000_networks.postgres.up.sql │ │ │ │ ├── 20150100000001000000_networks.sqlite3.down.sql │ │ │ │ └── 20150100000001000000_networks.sqlite3.up.sql │ │ │ └── templates/ │ │ │ ├── 20150100000001_networks.down.fizz │ │ │ └── 20150100000001_networks.up.fizz │ │ └── network.go │ ├── openapix/ │ │ ├── doc.go │ │ ├── jsonpatch.go │ │ └── pagination.go │ ├── osx/ │ │ ├── env.go │ │ ├── file.go │ │ └── stub/ │ │ └── text.txt │ ├── otelx/ │ │ ├── attribute.go │ │ ├── config.go │ │ ├── config.schema.json │ │ ├── jaeger.go │ │ ├── middleware.go │ │ ├── otel.go │ │ ├── otlp.go │ │ ├── semconv/ │ │ │ ├── context.go │ │ │ ├── deprecated.go │ │ │ ├── events.go │ │ │ └── warning.go │ │ ├── withspan.go │ │ └── zipkin.go │ ├── package.go │ ├── package.json │ ├── pagination/ │ │ ├── README.md │ │ ├── header.go │ │ ├── items.go │ │ ├── keysetpagination/ │ │ │ ├── header.go │ │ │ ├── page_token.go │ │ │ ├── paginator.go │ │ │ └── parse_header.go │ │ ├── keysetpagination_v2/ │ │ │ ├── .snapshots/ │ │ │ │ └── TestPageToken-Marshal_snapshot.json │ │ │ ├── page_token.go │ │ │ ├── paginator.go │ │ │ ├── parse_header.go │ │ │ ├── query_builder.go │ │ │ └── request_params.go │ │ ├── limit.go │ │ ├── migrationpagination/ │ │ │ ├── .snapshots/ │ │ │ │ ├── TestPaginationHeader-Create_next_and_last,_but_not_previous_or_first_if_at_the_beginning.json │ │ │ │ ├── TestPaginationHeader-Create_only_first_if_the_limits_provided_exceeds_the_number_of_clients_found.json │ │ │ │ ├── TestPaginationHeader-Create_previous,_next,_first,_and_last_if_in_the_middle.json │ │ │ │ ├── TestPaginationHeader-Create_previous,_next,_first,_but_not_last_if_in_the_middle_and_no_total_was_provided.json │ │ │ │ ├── TestPaginationHeader-Create_previous_and_first_but_not_next_or_last_if_at_the_end.json │ │ │ │ └── TestPaginationHeader-Header_should_default_limit_to_1_no_limit_was_provided.json │ │ │ ├── header.go │ │ │ └── pagination.go │ │ ├── pagepagination/ │ │ │ ├── header.go │ │ │ └── pagination.go │ │ ├── paginationplanner/ │ │ │ └── planner.go │ │ ├── parse.go │ │ └── tokenpagination/ │ │ ├── .snapshots/ │ │ │ ├── TestPaginationHeader-Create_next_and_last,_but_not_previous_or_first_if_at_the_beginning.json │ │ │ ├── TestPaginationHeader-Create_only_first_if_the_limits_provided_exceeds_the_number_of_clients_found.json │ │ │ ├── TestPaginationHeader-Create_previous,_next,_first,_and_last_if_in_the_middle.json │ │ │ ├── TestPaginationHeader-Create_previous,_next,_first,_but_not_last_if_in_the_middle_and_no_total_was_provided.json │ │ │ ├── TestPaginationHeader-Create_previous_and_first_but_not_next_or_last_if_at_the_end.json │ │ │ └── TestPaginationHeader-Header_should_default_limit_to_1_no_limit_was_provided.json │ │ ├── header.go │ │ └── pagination.go │ ├── pointerx/ │ │ └── pointerx.go │ ├── popx/ │ │ ├── .snapshots/ │ │ │ ├── TestMigrateSQLUp-final_status.txt │ │ │ ├── TestMigrateSQLUp-migrate_down_but_do_not_confirm.txt │ │ │ ├── TestMigrateSQLUp-migrate_down_but_no_steps.txt │ │ │ ├── TestMigrateSQLUp-migrate_down_four_steps.txt │ │ │ ├── TestMigrateSQLUp-migrate_down_two_steps.txt │ │ │ ├── TestMigrateSQLUp-migrate_rollbacks_up_again.txt │ │ │ ├── TestMigrateSQLUp-migrate_rollbacks_up_without_confirm.txt │ │ │ ├── TestMigrateSQLUp-migrate_up.txt │ │ │ ├── TestMigrateSQLUp-status_migrated.txt │ │ │ ├── TestMigrateSQLUp-status_pre.txt │ │ │ ├── TestMigrateSQLUp-status_two_steps_rolled_back.txt │ │ │ └── TestMigrateSQLUp-status_two_versions_rolled_back.txt │ │ ├── cmd.go │ │ ├── db_columns.go │ │ ├── loggers.go │ │ ├── match.go │ │ ├── migration_box.go │ │ ├── migration_content.go │ │ ├── migration_info.go │ │ ├── migrator.go │ │ ├── span.go │ │ ├── sql_template_funcs.go │ │ ├── stub/ │ │ │ └── migrations/ │ │ │ ├── check/ │ │ │ │ └── valid/ │ │ │ │ ├── 123_a.down.sql │ │ │ │ ├── 123_a.mysql.up.sql │ │ │ │ └── 123_a.postgres.up.sql │ │ │ ├── legacy/ │ │ │ │ ├── 20191100000001_identities.cockroach.down.sql │ │ │ │ ├── 20191100000001_identities.cockroach.up.sql │ │ │ │ ├── 20191100000001_identities.mysql.down.sql │ │ │ │ ├── 20191100000001_identities.mysql.up.sql │ │ │ │ ├── 20191100000001_identities.postgres.down.sql │ │ │ │ ├── 20191100000001_identities.postgres.up.sql │ │ │ │ ├── 20191100000001_identities.sqlite3.down.sql │ │ │ │ ├── 20191100000001_identities.sqlite3.up.sql │ │ │ │ ├── 20191100000002_requests.cockroach.down.sql │ │ │ │ ├── 20191100000002_requests.cockroach.up.sql │ │ │ │ ├── 20191100000002_requests.mysql.down.sql │ │ │ │ ├── 20191100000002_requests.mysql.up.sql │ │ │ │ ├── 20191100000002_requests.postgres.down.sql │ │ │ │ ├── 20191100000002_requests.postgres.up.sql │ │ │ │ ├── 20191100000002_requests.sqlite3.down.sql │ │ │ │ ├── 20191100000002_requests.sqlite3.up.sql │ │ │ │ ├── 20191100000003_sessions.cockroach.down.sql │ │ │ │ ├── 20191100000003_sessions.cockroach.up.sql │ │ │ │ ├── 20191100000003_sessions.mysql.down.sql │ │ │ │ ├── 20191100000003_sessions.mysql.up.sql │ │ │ │ ├── 20191100000003_sessions.postgres.down.sql │ │ │ │ ├── 20191100000003_sessions.postgres.up.sql │ │ │ │ ├── 20191100000003_sessions.sqlite3.down.sql │ │ │ │ ├── 20191100000003_sessions.sqlite3.up.sql │ │ │ │ ├── 20191100000004_errors.cockroach.down.sql │ │ │ │ ├── 20191100000004_errors.cockroach.up.sql │ │ │ │ ├── 20191100000004_errors.mysql.down.sql │ │ │ │ ├── 20191100000004_errors.mysql.up.sql │ │ │ │ ├── 20191100000004_errors.postgres.down.sql │ │ │ │ ├── 20191100000004_errors.postgres.up.sql │ │ │ │ ├── 20191100000004_errors.sqlite3.down.sql │ │ │ │ ├── 20191100000004_errors.sqlite3.up.sql │ │ │ │ ├── 20191100000005_identities.mysql.down.sql │ │ │ │ ├── 20191100000005_identities.mysql.up.sql │ │ │ │ ├── 20191100000006_courier.cockroach.down.sql │ │ │ │ ├── 20191100000006_courier.cockroach.up.sql │ │ │ │ ├── 20191100000006_courier.mysql.down.sql │ │ │ │ ├── 20191100000006_courier.mysql.up.sql │ │ │ │ ├── 20191100000006_courier.postgres.down.sql │ │ │ │ ├── 20191100000006_courier.postgres.up.sql │ │ │ │ ├── 20191100000006_courier.sqlite3.down.sql │ │ │ │ ├── 20191100000006_courier.sqlite3.up.sql │ │ │ │ ├── 20191100000007_errors.cockroach.down.sql │ │ │ │ ├── 20191100000007_errors.cockroach.up.sql │ │ │ │ ├── 20191100000007_errors.mysql.down.sql │ │ │ │ ├── 20191100000007_errors.mysql.up.sql │ │ │ │ ├── 20191100000007_errors.postgres.down.sql │ │ │ │ ├── 20191100000007_errors.postgres.up.sql │ │ │ │ ├── 20191100000007_errors.sqlite3.down.sql │ │ │ │ ├── 20191100000007_errors.sqlite3.up.sql │ │ │ │ ├── 20191100000008_selfservice_verification.cockroach.down.sql │ │ │ │ ├── 20191100000008_selfservice_verification.cockroach.up.sql │ │ │ │ ├── 20191100000008_selfservice_verification.mysql.down.sql │ │ │ │ ├── 20191100000008_selfservice_verification.mysql.up.sql │ │ │ │ ├── 20191100000008_selfservice_verification.postgres.down.sql │ │ │ │ ├── 20191100000008_selfservice_verification.postgres.up.sql │ │ │ │ ├── 20191100000008_selfservice_verification.sqlite3.down.sql │ │ │ │ ├── 20191100000008_selfservice_verification.sqlite3.up.sql │ │ │ │ ├── 20191100000009_verification.mysql.down.sql │ │ │ │ ├── 20191100000009_verification.mysql.up.sql │ │ │ │ ├── 20191100000010_errors.cockroach.down.sql │ │ │ │ ├── 20191100000010_errors.cockroach.up.sql │ │ │ │ ├── 20191100000010_errors.mysql.down.sql │ │ │ │ ├── 20191100000010_errors.mysql.up.sql │ │ │ │ ├── 20191100000010_errors.postgres.down.sql │ │ │ │ ├── 20191100000010_errors.postgres.up.sql │ │ │ │ ├── 20191100000010_errors.sqlite3.down.sql │ │ │ │ ├── 20191100000010_errors.sqlite3.up.sql │ │ │ │ ├── 20191100000011_courier_body_type.cockroach.up.sql │ │ │ │ ├── 20191100000011_courier_body_type.mysql.up.sql │ │ │ │ ├── 20191100000011_courier_body_type.postgres.up.sql │ │ │ │ ├── 20191100000011_courier_body_type.sqlite3.up.sql │ │ │ │ ├── 20191100000012_login_request_forced.cockroach.down.sql │ │ │ │ ├── 20191100000012_login_request_forced.cockroach.up.sql │ │ │ │ ├── 20191100000012_login_request_forced.mysql.down.sql │ │ │ │ ├── 20191100000012_login_request_forced.mysql.up.sql │ │ │ │ ├── 20191100000012_login_request_forced.postgres.down.sql │ │ │ │ ├── 20191100000012_login_request_forced.postgres.up.sql │ │ │ │ ├── 20191100000012_login_request_forced.sqlite3.down.sql │ │ │ │ ├── 20191100000012_login_request_forced.sqlite3.up.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.cockroach.down.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.cockroach.up.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.mysql.down.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.mysql.up.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.postgres.down.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.postgres.up.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.sqlite3.down.sql │ │ │ │ ├── 20200317160354_create_profile_request_forms.sqlite3.up.sql │ │ │ │ ├── 20200401183443_continuity_containers.cockroach.down.sql │ │ │ │ ├── 20200401183443_continuity_containers.cockroach.up.sql │ │ │ │ ├── 20200401183443_continuity_containers.mysql.down.sql │ │ │ │ ├── 20200401183443_continuity_containers.mysql.up.sql │ │ │ │ ├── 20200401183443_continuity_containers.postgres.down.sql │ │ │ │ ├── 20200401183443_continuity_containers.postgres.up.sql │ │ │ │ ├── 20200401183443_continuity_containers.sqlite3.down.sql │ │ │ │ ├── 20200401183443_continuity_containers.sqlite3.up.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.cockroach.down.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.cockroach.up.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.mysql.down.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.mysql.up.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.postgres.down.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.postgres.up.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.sqlite3.down.sql │ │ │ │ ├── 20200402142539_rename_profile_flows.sqlite3.up.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.cockroach.down.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.cockroach.up.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.mysql.down.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.mysql.up.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.postgres.down.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.postgres.up.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.sqlite3.down.sql │ │ │ │ ├── 20200519101057_create_recovery_addresses.sqlite3.up.sql │ │ │ │ ├── 20200519101058_create_recovery_addresses.mysql.down.sql │ │ │ │ ├── 20200519101058_create_recovery_addresses.mysql.up.sql │ │ │ │ ├── 20200601101000_create_messages.cockroach.down.sql │ │ │ │ ├── 20200601101000_create_messages.cockroach.up.sql │ │ │ │ ├── 20200601101000_create_messages.mysql.down.sql │ │ │ │ ├── 20200601101000_create_messages.mysql.up.sql │ │ │ │ ├── 20200601101000_create_messages.postgres.down.sql │ │ │ │ ├── 20200601101000_create_messages.postgres.up.sql │ │ │ │ ├── 20200601101000_create_messages.sqlite3.down.sql │ │ │ │ ├── 20200601101000_create_messages.sqlite3.up.sql │ │ │ │ ├── 20200601101001_verification.mysql.down.sql │ │ │ │ ├── 20200601101001_verification.mysql.up.sql │ │ │ │ ├── 20200605111551_messages.cockroach.down.sql │ │ │ │ ├── 20200605111551_messages.cockroach.up.sql │ │ │ │ ├── 20200605111551_messages.mysql.down.sql │ │ │ │ ├── 20200605111551_messages.mysql.up.sql │ │ │ │ ├── 20200605111551_messages.postgres.down.sql │ │ │ │ ├── 20200605111551_messages.postgres.up.sql │ │ │ │ ├── 20200605111551_messages.sqlite3.down.sql │ │ │ │ ├── 20200605111551_messages.sqlite3.up.sql │ │ │ │ ├── 20200607165100_settings.cockroach.down.sql │ │ │ │ ├── 20200607165100_settings.cockroach.up.sql │ │ │ │ ├── 20200607165100_settings.mysql.down.sql │ │ │ │ ├── 20200607165100_settings.mysql.up.sql │ │ │ │ ├── 20200607165100_settings.postgres.down.sql │ │ │ │ ├── 20200607165100_settings.postgres.up.sql │ │ │ │ ├── 20200607165100_settings.sqlite3.down.sql │ │ │ │ ├── 20200607165100_settings.sqlite3.up.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.cockroach.down.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.cockroach.up.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.mysql.down.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.mysql.up.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.postgres.down.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.postgres.up.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.sqlite3.down.sql │ │ │ │ ├── 20200705105359_rename_identities_schema.sqlite3.up.sql │ │ │ │ ├── 20200810141652_flow_type.cockroach.down.sql │ │ │ │ ├── 20200810141652_flow_type.cockroach.up.sql │ │ │ │ ├── 20200810141652_flow_type.mysql.down.sql │ │ │ │ ├── 20200810141652_flow_type.mysql.up.sql │ │ │ │ ├── 20200810141652_flow_type.postgres.down.sql │ │ │ │ ├── 20200810141652_flow_type.postgres.up.sql │ │ │ │ ├── 20200810141652_flow_type.sqlite3.down.sql │ │ │ │ ├── 20200810141652_flow_type.sqlite3.up.sql │ │ │ │ ├── 20200810161022_flow_rename.cockroach.down.sql │ │ │ │ ├── 20200810161022_flow_rename.cockroach.up.sql │ │ │ │ ├── 20200810161022_flow_rename.mysql.down.sql │ │ │ │ ├── 20200810161022_flow_rename.mysql.up.sql │ │ │ │ ├── 20200810161022_flow_rename.postgres.down.sql │ │ │ │ ├── 20200810161022_flow_rename.postgres.up.sql │ │ │ │ ├── 20200810161022_flow_rename.sqlite3.down.sql │ │ │ │ ├── 20200810161022_flow_rename.sqlite3.up.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.cockroach.down.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.cockroach.up.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.mysql.down.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.mysql.up.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.postgres.down.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.postgres.up.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.sqlite3.down.sql │ │ │ │ ├── 20200810162450_flow_fields_rename.sqlite3.up.sql │ │ │ │ ├── 20200812124254_add_session_token.cockroach.down.sql │ │ │ │ ├── 20200812124254_add_session_token.cockroach.up.sql │ │ │ │ ├── 20200812124254_add_session_token.mysql.down.sql │ │ │ │ ├── 20200812124254_add_session_token.mysql.up.sql │ │ │ │ ├── 20200812124254_add_session_token.postgres.down.sql │ │ │ │ ├── 20200812124254_add_session_token.postgres.up.sql │ │ │ │ ├── 20200812124254_add_session_token.sqlite3.down.sql │ │ │ │ ├── 20200812124254_add_session_token.sqlite3.up.sql │ │ │ │ ├── 20200812160551_add_session_revoke.cockroach.down.sql │ │ │ │ ├── 20200812160551_add_session_revoke.cockroach.up.sql │ │ │ │ ├── 20200812160551_add_session_revoke.mysql.down.sql │ │ │ │ ├── 20200812160551_add_session_revoke.mysql.up.sql │ │ │ │ ├── 20200812160551_add_session_revoke.postgres.down.sql │ │ │ │ ├── 20200812160551_add_session_revoke.postgres.up.sql │ │ │ │ ├── 20200812160551_add_session_revoke.sqlite3.down.sql │ │ │ │ ├── 20200812160551_add_session_revoke.sqlite3.up.sql │ │ │ │ ├── 20200830121710_update_recovery_token.cockroach.down.sql │ │ │ │ ├── 20200830121710_update_recovery_token.cockroach.up.sql │ │ │ │ ├── 20200830121710_update_recovery_token.mysql.down.sql │ │ │ │ ├── 20200830121710_update_recovery_token.mysql.up.sql │ │ │ │ ├── 20200830121710_update_recovery_token.postgres.down.sql │ │ │ │ ├── 20200830121710_update_recovery_token.postgres.up.sql │ │ │ │ ├── 20200830121710_update_recovery_token.sqlite3.down.sql │ │ │ │ ├── 20200830121710_update_recovery_token.sqlite3.up.sql │ │ │ │ ├── 20200830130642_add_verification_methods.cockroach.down.sql │ │ │ │ ├── 20200830130642_add_verification_methods.cockroach.up.sql │ │ │ │ ├── 20200830130642_add_verification_methods.mysql.down.sql │ │ │ │ ├── 20200830130642_add_verification_methods.mysql.up.sql │ │ │ │ ├── 20200830130642_add_verification_methods.postgres.down.sql │ │ │ │ ├── 20200830130642_add_verification_methods.postgres.up.sql │ │ │ │ ├── 20200830130642_add_verification_methods.sqlite3.down.sql │ │ │ │ ├── 20200830130642_add_verification_methods.sqlite3.up.sql │ │ │ │ ├── 20200830130643_add_verification_methods.cockroach.up.sql │ │ │ │ ├── 20200830130643_add_verification_methods.mysql.up.sql │ │ │ │ ├── 20200830130643_add_verification_methods.postgres.up.sql │ │ │ │ ├── 20200830130643_add_verification_methods.sqlite3.up.sql │ │ │ │ ├── 20200830130644_add_verification_methods.cockroach.up.sql │ │ │ │ ├── 20200830130644_add_verification_methods.mysql.up.sql │ │ │ │ ├── 20200830130644_add_verification_methods.postgres.up.sql │ │ │ │ ├── 20200830130644_add_verification_methods.sqlite3.up.sql │ │ │ │ ├── 20200830130645_add_verification_methods.cockroach.up.sql │ │ │ │ ├── 20200830130645_add_verification_methods.mysql.up.sql │ │ │ │ ├── 20200830130645_add_verification_methods.postgres.up.sql │ │ │ │ ├── 20200830130645_add_verification_methods.sqlite3.up.sql │ │ │ │ ├── 20200830130646_add_verification_methods.cockroach.up.sql │ │ │ │ ├── 20200830130646_add_verification_methods.mysql.up.sql │ │ │ │ ├── 20200830130646_add_verification_methods.postgres.up.sql │ │ │ │ ├── 20200830130646_add_verification_methods.sqlite3.up.sql │ │ │ │ ├── 20200830154602_add_verification_token.cockroach.down.sql │ │ │ │ ├── 20200830154602_add_verification_token.cockroach.up.sql │ │ │ │ ├── 20200830154602_add_verification_token.mysql.down.sql │ │ │ │ ├── 20200830154602_add_verification_token.mysql.up.sql │ │ │ │ ├── 20200830154602_add_verification_token.postgres.down.sql │ │ │ │ ├── 20200830154602_add_verification_token.postgres.up.sql │ │ │ │ ├── 20200830154602_add_verification_token.sqlite3.down.sql │ │ │ │ ├── 20200830154602_add_verification_token.sqlite3.up.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.cockroach.down.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.cockroach.up.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.mysql.down.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.mysql.up.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.postgres.down.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.postgres.up.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.sqlite3.down.sql │ │ │ │ ├── 20200830172221_recovery_token_expires.sqlite3.up.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ │ ├── 20201201161451_credential_types_values.cockroach.down.sql │ │ │ │ ├── 20201201161451_credential_types_values.cockroach.up.sql │ │ │ │ ├── 20201201161451_credential_types_values.mysql.down.sql │ │ │ │ ├── 20201201161451_credential_types_values.mysql.up.sql │ │ │ │ ├── 20201201161451_credential_types_values.postgres.down.sql │ │ │ │ ├── 20201201161451_credential_types_values.postgres.up.sql │ │ │ │ ├── 20201201161451_credential_types_values.sqlite3.down.sql │ │ │ │ └── 20201201161451_credential_types_values.sqlite3.up.sql │ │ │ ├── notx/ │ │ │ │ ├── 20241031_notx.autocommit.down.sql │ │ │ │ └── 20241031_notx.autocommit.up.sql │ │ │ ├── source/ │ │ │ │ ├── 20191100000001_identities.down.fizz │ │ │ │ ├── 20191100000001_identities.up.fizz │ │ │ │ ├── 20191100000002_requests.down.fizz │ │ │ │ ├── 20191100000002_requests.up.fizz │ │ │ │ ├── 20191100000003_sessions.down.fizz │ │ │ │ ├── 20191100000003_sessions.up.fizz │ │ │ │ ├── 20191100000004_errors.down.fizz │ │ │ │ ├── 20191100000004_errors.up.fizz │ │ │ │ ├── 20191100000005_identities.mysql.down.sql │ │ │ │ ├── 20191100000005_identities.mysql.up.sql │ │ │ │ ├── 20191100000006_courier.down.fizz │ │ │ │ ├── 20191100000006_courier.up.fizz │ │ │ │ ├── 20191100000007_errors.down.fizz │ │ │ │ ├── 20191100000007_errors.up.fizz │ │ │ │ ├── 20191100000008_selfservice_verification.down.fizz │ │ │ │ ├── 20191100000008_selfservice_verification.up.fizz │ │ │ │ ├── 20191100000009_verification.mysql.down.sql │ │ │ │ ├── 20191100000009_verification.mysql.up.sql │ │ │ │ ├── 20191100000010_errors.down.fizz │ │ │ │ ├── 20191100000010_errors.up.fizz │ │ │ │ ├── 20191100000011_courier_body_type.down.fizz │ │ │ │ ├── 20191100000011_courier_body_type.up.fizz │ │ │ │ ├── 20191100000012_login_request_forced.down.fizz │ │ │ │ ├── 20191100000012_login_request_forced.up.fizz │ │ │ │ ├── 20200317160354_create_profile_request_forms.down.fizz │ │ │ │ ├── 20200317160354_create_profile_request_forms.up.fizz │ │ │ │ ├── 20200401183443_continuity_containers.down.fizz │ │ │ │ ├── 20200401183443_continuity_containers.up.fizz │ │ │ │ ├── 20200402142539_rename_profile_flows.down.fizz │ │ │ │ ├── 20200402142539_rename_profile_flows.up.fizz │ │ │ │ ├── 20200519101057_create_recovery_addresses.down.fizz │ │ │ │ ├── 20200519101057_create_recovery_addresses.up.fizz │ │ │ │ ├── 20200519101058_create_recovery_addresses.mysql.down.sql │ │ │ │ ├── 20200519101058_create_recovery_addresses.mysql.up.sql │ │ │ │ ├── 20200601101000_create_messages.down.fizz │ │ │ │ ├── 20200601101000_create_messages.up.fizz │ │ │ │ ├── 20200601101001_verification.mysql.down.sql │ │ │ │ ├── 20200601101001_verification.mysql.up.sql │ │ │ │ ├── 20200605111551_messages.down.fizz │ │ │ │ ├── 20200605111551_messages.up.fizz │ │ │ │ ├── 20200607165100_settings.down.fizz │ │ │ │ ├── 20200607165100_settings.up.fizz │ │ │ │ ├── 20200705105359_rename_identities_schema.down.fizz │ │ │ │ ├── 20200705105359_rename_identities_schema.up.fizz │ │ │ │ ├── 20200810141652_flow_type.down.fizz │ │ │ │ ├── 20200810141652_flow_type.up.fizz │ │ │ │ ├── 20200810161022_flow_rename.down.fizz │ │ │ │ ├── 20200810161022_flow_rename.up.fizz │ │ │ │ ├── 20200810162450_flow_fields_rename.down.fizz │ │ │ │ ├── 20200810162450_flow_fields_rename.up.fizz │ │ │ │ ├── 20200812124254_add_session_token.down.fizz │ │ │ │ ├── 20200812124254_add_session_token.up.fizz │ │ │ │ ├── 20200812160551_add_session_revoke.down.fizz │ │ │ │ ├── 20200812160551_add_session_revoke.up.fizz │ │ │ │ ├── 20200830121710_update_recovery_token.down.fizz │ │ │ │ ├── 20200830121710_update_recovery_token.up.fizz │ │ │ │ ├── 20200830130642_add_verification_methods.down.fizz │ │ │ │ ├── 20200830130642_add_verification_methods.up.fizz │ │ │ │ ├── 20200830130643_add_verification_methods.down.fizz │ │ │ │ ├── 20200830130643_add_verification_methods.up.fizz │ │ │ │ ├── 20200830130644_add_verification_methods.down.fizz │ │ │ │ ├── 20200830130644_add_verification_methods.up.fizz │ │ │ │ ├── 20200830130645_add_verification_methods.down.fizz │ │ │ │ ├── 20200830130645_add_verification_methods.up.fizz │ │ │ │ ├── 20200830130646_add_verification_methods.down.fizz │ │ │ │ ├── 20200830130646_add_verification_methods.up.fizz │ │ │ │ ├── 20200830154602_add_verification_token.down.fizz │ │ │ │ ├── 20200830154602_add_verification_token.up.fizz │ │ │ │ ├── 20200830172221_recovery_token_expires.down.fizz │ │ │ │ ├── 20200830172221_recovery_token_expires.up.fizz │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.down.fizz │ │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.up.fizz │ │ │ │ ├── 20201201161451_credential_types_values.down.fizz │ │ │ │ └── 20201201161451_credential_types_values.up.fizz │ │ │ ├── templating/ │ │ │ │ ├── 0_sql_create_tablename_template.down.sql │ │ │ │ └── 0_sql_create_tablename_template.up.sql │ │ │ ├── testdata/ │ │ │ │ ├── 20220513_testdata.invalid │ │ │ │ ├── 20220513_testdata.sql │ │ │ │ ├── 20220514_testdata.sql │ │ │ │ ├── invalid │ │ │ │ └── invalid_testdata.sql │ │ │ ├── testdata_migrations/ │ │ │ │ ├── 20220513_create_table.down.sql │ │ │ │ └── 20220513_create_table.up.sql │ │ │ └── transactional/ │ │ │ ├── 20191100000001000000_identities.cockroach.down.sql │ │ │ ├── 20191100000001000000_identities.cockroach.up.sql │ │ │ ├── 20191100000001000000_identities.mysql.down.sql │ │ │ ├── 20191100000001000000_identities.mysql.up.sql │ │ │ ├── 20191100000001000000_identities.postgres.down.sql │ │ │ ├── 20191100000001000000_identities.postgres.up.sql │ │ │ ├── 20191100000001000000_identities.sqlite3.down.sql │ │ │ ├── 20191100000001000000_identities.sqlite3.up.sql │ │ │ ├── 20191100000001000001_identities.cockroach.down.sql │ │ │ ├── 20191100000001000001_identities.cockroach.up.sql │ │ │ ├── 20191100000001000001_identities.mysql.down.sql │ │ │ ├── 20191100000001000001_identities.mysql.up.sql │ │ │ ├── 20191100000001000001_identities.postgres.down.sql │ │ │ ├── 20191100000001000001_identities.postgres.up.sql │ │ │ ├── 20191100000001000001_identities.sqlite3.down.sql │ │ │ ├── 20191100000001000001_identities.sqlite3.up.sql │ │ │ ├── 20191100000001000002_identities.cockroach.down.sql │ │ │ ├── 20191100000001000002_identities.cockroach.up.sql │ │ │ ├── 20191100000001000002_identities.mysql.down.sql │ │ │ ├── 20191100000001000002_identities.mysql.up.sql │ │ │ ├── 20191100000001000002_identities.postgres.down.sql │ │ │ ├── 20191100000001000002_identities.postgres.up.sql │ │ │ ├── 20191100000001000002_identities.sqlite3.down.sql │ │ │ ├── 20191100000001000002_identities.sqlite3.up.sql │ │ │ ├── 20191100000001000003_identities.cockroach.down.sql │ │ │ ├── 20191100000001000003_identities.cockroach.up.sql │ │ │ ├── 20191100000001000003_identities.mysql.down.sql │ │ │ ├── 20191100000001000003_identities.mysql.up.sql │ │ │ ├── 20191100000001000003_identities.postgres.down.sql │ │ │ ├── 20191100000001000003_identities.postgres.up.sql │ │ │ ├── 20191100000001000003_identities.sqlite3.down.sql │ │ │ ├── 20191100000001000003_identities.sqlite3.up.sql │ │ │ ├── 20191100000001000004_identities.cockroach.down.sql │ │ │ ├── 20191100000001000004_identities.cockroach.up.sql │ │ │ ├── 20191100000001000004_identities.mysql.down.sql │ │ │ ├── 20191100000001000004_identities.mysql.up.sql │ │ │ ├── 20191100000001000004_identities.postgres.down.sql │ │ │ ├── 20191100000001000004_identities.postgres.up.sql │ │ │ ├── 20191100000001000004_identities.sqlite3.down.sql │ │ │ ├── 20191100000001000004_identities.sqlite3.up.sql │ │ │ ├── 20191100000001000005_identities.cockroach.down.sql │ │ │ ├── 20191100000001000005_identities.cockroach.up.sql │ │ │ ├── 20191100000001000005_identities.mysql.down.sql │ │ │ ├── 20191100000001000005_identities.mysql.up.sql │ │ │ ├── 20191100000001000005_identities.postgres.down.sql │ │ │ ├── 20191100000001000005_identities.postgres.up.sql │ │ │ ├── 20191100000001000005_identities.sqlite3.down.sql │ │ │ ├── 20191100000001000005_identities.sqlite3.up.sql │ │ │ ├── 20191100000002000000_requests.cockroach.down.sql │ │ │ ├── 20191100000002000000_requests.cockroach.up.sql │ │ │ ├── 20191100000002000000_requests.mysql.down.sql │ │ │ ├── 20191100000002000000_requests.mysql.up.sql │ │ │ ├── 20191100000002000000_requests.postgres.down.sql │ │ │ ├── 20191100000002000000_requests.postgres.up.sql │ │ │ ├── 20191100000002000000_requests.sqlite3.down.sql │ │ │ ├── 20191100000002000000_requests.sqlite3.up.sql │ │ │ ├── 20191100000002000001_requests.cockroach.down.sql │ │ │ ├── 20191100000002000001_requests.cockroach.up.sql │ │ │ ├── 20191100000002000001_requests.mysql.down.sql │ │ │ ├── 20191100000002000001_requests.mysql.up.sql │ │ │ ├── 20191100000002000001_requests.postgres.down.sql │ │ │ ├── 20191100000002000001_requests.postgres.up.sql │ │ │ ├── 20191100000002000001_requests.sqlite3.down.sql │ │ │ ├── 20191100000002000001_requests.sqlite3.up.sql │ │ │ ├── 20191100000002000002_requests.cockroach.down.sql │ │ │ ├── 20191100000002000002_requests.cockroach.up.sql │ │ │ ├── 20191100000002000002_requests.mysql.down.sql │ │ │ ├── 20191100000002000002_requests.mysql.up.sql │ │ │ ├── 20191100000002000002_requests.postgres.down.sql │ │ │ ├── 20191100000002000002_requests.postgres.up.sql │ │ │ ├── 20191100000002000002_requests.sqlite3.down.sql │ │ │ ├── 20191100000002000002_requests.sqlite3.up.sql │ │ │ ├── 20191100000002000003_requests.cockroach.down.sql │ │ │ ├── 20191100000002000003_requests.cockroach.up.sql │ │ │ ├── 20191100000002000003_requests.mysql.down.sql │ │ │ ├── 20191100000002000003_requests.mysql.up.sql │ │ │ ├── 20191100000002000003_requests.postgres.down.sql │ │ │ ├── 20191100000002000003_requests.postgres.up.sql │ │ │ ├── 20191100000002000003_requests.sqlite3.down.sql │ │ │ ├── 20191100000002000003_requests.sqlite3.up.sql │ │ │ ├── 20191100000002000004_requests.cockroach.down.sql │ │ │ ├── 20191100000002000004_requests.cockroach.up.sql │ │ │ ├── 20191100000002000004_requests.mysql.down.sql │ │ │ ├── 20191100000002000004_requests.mysql.up.sql │ │ │ ├── 20191100000002000004_requests.postgres.down.sql │ │ │ ├── 20191100000002000004_requests.postgres.up.sql │ │ │ ├── 20191100000002000004_requests.sqlite3.down.sql │ │ │ ├── 20191100000002000004_requests.sqlite3.up.sql │ │ │ ├── 20191100000003000000_sessions.cockroach.down.sql │ │ │ ├── 20191100000003000000_sessions.cockroach.up.sql │ │ │ ├── 20191100000003000000_sessions.mysql.down.sql │ │ │ ├── 20191100000003000000_sessions.mysql.up.sql │ │ │ ├── 20191100000003000000_sessions.postgres.down.sql │ │ │ ├── 20191100000003000000_sessions.postgres.up.sql │ │ │ ├── 20191100000003000000_sessions.sqlite3.down.sql │ │ │ ├── 20191100000003000000_sessions.sqlite3.up.sql │ │ │ ├── 20191100000004000000_errors.cockroach.down.sql │ │ │ ├── 20191100000004000000_errors.cockroach.up.sql │ │ │ ├── 20191100000004000000_errors.mysql.down.sql │ │ │ ├── 20191100000004000000_errors.mysql.up.sql │ │ │ ├── 20191100000004000000_errors.postgres.down.sql │ │ │ ├── 20191100000004000000_errors.postgres.up.sql │ │ │ ├── 20191100000004000000_errors.sqlite3.down.sql │ │ │ ├── 20191100000004000000_errors.sqlite3.up.sql │ │ │ ├── 20191100000005000000_identities.mysql.down.sql │ │ │ ├── 20191100000005000000_identities.mysql.up.sql │ │ │ ├── 20191100000005000001_identities.mysql.down.sql │ │ │ ├── 20191100000005000001_identities.mysql.up.sql │ │ │ ├── 20191100000006000000_courier.cockroach.down.sql │ │ │ ├── 20191100000006000000_courier.cockroach.up.sql │ │ │ ├── 20191100000006000000_courier.mysql.down.sql │ │ │ ├── 20191100000006000000_courier.mysql.up.sql │ │ │ ├── 20191100000006000000_courier.postgres.down.sql │ │ │ ├── 20191100000006000000_courier.postgres.up.sql │ │ │ ├── 20191100000006000000_courier.sqlite3.down.sql │ │ │ ├── 20191100000006000000_courier.sqlite3.up.sql │ │ │ ├── 20191100000007000000_errors.cockroach.down.sql │ │ │ ├── 20191100000007000000_errors.cockroach.up.sql │ │ │ ├── 20191100000007000000_errors.mysql.down.sql │ │ │ ├── 20191100000007000000_errors.mysql.up.sql │ │ │ ├── 20191100000007000000_errors.postgres.down.sql │ │ │ ├── 20191100000007000000_errors.postgres.up.sql │ │ │ ├── 20191100000007000000_errors.sqlite3.down.sql │ │ │ ├── 20191100000007000000_errors.sqlite3.up.sql │ │ │ ├── 20191100000007000001_errors.sqlite3.down.sql │ │ │ ├── 20191100000007000001_errors.sqlite3.up.sql │ │ │ ├── 20191100000007000002_errors.sqlite3.down.sql │ │ │ ├── 20191100000007000002_errors.sqlite3.up.sql │ │ │ ├── 20191100000007000003_errors.sqlite3.down.sql │ │ │ ├── 20191100000007000003_errors.sqlite3.up.sql │ │ │ ├── 20191100000008000000_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008000000_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008000000_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008000000_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008000000_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008000000_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008000000_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008000000_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000008000001_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008000001_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008000001_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008000001_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008000001_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008000001_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008000001_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008000001_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000008000002_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008000002_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008000002_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008000002_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008000002_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008000002_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008000002_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008000002_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000008000003_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008000003_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008000003_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008000003_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008000003_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008000003_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008000003_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008000003_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000008000004_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008000004_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008000004_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008000004_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008000004_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008000004_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008000004_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008000004_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000008000005_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008000005_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008000005_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008000005_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008000005_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008000005_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008000005_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008000005_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000009000000_verification.mysql.down.sql │ │ │ ├── 20191100000009000000_verification.mysql.up.sql │ │ │ ├── 20191100000009000001_verification.mysql.down.sql │ │ │ ├── 20191100000009000001_verification.mysql.up.sql │ │ │ ├── 20191100000010000000_errors.cockroach.down.sql │ │ │ ├── 20191100000010000000_errors.cockroach.up.sql │ │ │ ├── 20191100000010000000_errors.mysql.down.sql │ │ │ ├── 20191100000010000000_errors.mysql.up.sql │ │ │ ├── 20191100000010000000_errors.postgres.down.sql │ │ │ ├── 20191100000010000000_errors.postgres.up.sql │ │ │ ├── 20191100000010000000_errors.sqlite3.down.sql │ │ │ ├── 20191100000010000000_errors.sqlite3.up.sql │ │ │ ├── 20191100000010000001_errors.cockroach.down.sql │ │ │ ├── 20191100000010000001_errors.cockroach.up.sql │ │ │ ├── 20191100000010000001_errors.mysql.down.sql │ │ │ ├── 20191100000010000001_errors.mysql.up.sql │ │ │ ├── 20191100000010000001_errors.postgres.down.sql │ │ │ ├── 20191100000010000001_errors.postgres.up.sql │ │ │ ├── 20191100000010000001_errors.sqlite3.down.sql │ │ │ ├── 20191100000010000001_errors.sqlite3.up.sql │ │ │ ├── 20191100000010000002_errors.cockroach.down.sql │ │ │ ├── 20191100000010000002_errors.cockroach.up.sql │ │ │ ├── 20191100000010000002_errors.sqlite3.down.sql │ │ │ ├── 20191100000010000002_errors.sqlite3.up.sql │ │ │ ├── 20191100000010000003_errors.cockroach.down.sql │ │ │ ├── 20191100000010000003_errors.cockroach.up.sql │ │ │ ├── 20191100000010000003_errors.sqlite3.down.sql │ │ │ ├── 20191100000010000003_errors.sqlite3.up.sql │ │ │ ├── 20191100000010000004_errors.cockroach.down.sql │ │ │ ├── 20191100000010000004_errors.cockroach.up.sql │ │ │ ├── 20191100000010000004_errors.sqlite3.down.sql │ │ │ ├── 20191100000010000004_errors.sqlite3.up.sql │ │ │ ├── 20191100000011000000_courier_body_type.cockroach.down.sql │ │ │ ├── 20191100000011000000_courier_body_type.cockroach.up.sql │ │ │ ├── 20191100000011000000_courier_body_type.mysql.down.sql │ │ │ ├── 20191100000011000000_courier_body_type.mysql.up.sql │ │ │ ├── 20191100000011000000_courier_body_type.postgres.down.sql │ │ │ ├── 20191100000011000000_courier_body_type.postgres.up.sql │ │ │ ├── 20191100000011000000_courier_body_type.sqlite3.down.sql │ │ │ ├── 20191100000011000000_courier_body_type.sqlite3.up.sql │ │ │ ├── 20191100000011000001_courier_body_type.cockroach.down.sql │ │ │ ├── 20191100000011000001_courier_body_type.cockroach.up.sql │ │ │ ├── 20191100000011000001_courier_body_type.sqlite3.down.sql │ │ │ ├── 20191100000011000001_courier_body_type.sqlite3.up.sql │ │ │ ├── 20191100000011000002_courier_body_type.cockroach.down.sql │ │ │ ├── 20191100000011000002_courier_body_type.cockroach.up.sql │ │ │ ├── 20191100000011000002_courier_body_type.sqlite3.down.sql │ │ │ ├── 20191100000011000002_courier_body_type.sqlite3.up.sql │ │ │ ├── 20191100000011000003_courier_body_type.cockroach.down.sql │ │ │ ├── 20191100000011000003_courier_body_type.cockroach.up.sql │ │ │ ├── 20191100000011000003_courier_body_type.sqlite3.down.sql │ │ │ ├── 20191100000011000003_courier_body_type.sqlite3.up.sql │ │ │ ├── 20191100000011000004_courier_body_type.cockroach.down.sql │ │ │ ├── 20191100000011000004_courier_body_type.cockroach.up.sql │ │ │ ├── 20191100000012000000_login_request_forced.cockroach.down.sql │ │ │ ├── 20191100000012000000_login_request_forced.cockroach.up.sql │ │ │ ├── 20191100000012000000_login_request_forced.mysql.down.sql │ │ │ ├── 20191100000012000000_login_request_forced.mysql.up.sql │ │ │ ├── 20191100000012000000_login_request_forced.postgres.down.sql │ │ │ ├── 20191100000012000000_login_request_forced.postgres.up.sql │ │ │ ├── 20191100000012000000_login_request_forced.sqlite3.down.sql │ │ │ ├── 20191100000012000000_login_request_forced.sqlite3.up.sql │ │ │ ├── 20191100000012000001_login_request_forced.sqlite3.down.sql │ │ │ ├── 20191100000012000001_login_request_forced.sqlite3.up.sql │ │ │ ├── 20191100000012000002_login_request_forced.sqlite3.down.sql │ │ │ ├── 20191100000012000002_login_request_forced.sqlite3.up.sql │ │ │ ├── 20191100000012000003_login_request_forced.sqlite3.down.sql │ │ │ ├── 20191100000012000003_login_request_forced.sqlite3.up.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.cockroach.down.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.cockroach.up.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.mysql.down.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.mysql.up.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.postgres.down.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.postgres.up.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000000_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.cockroach.down.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.cockroach.up.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.mysql.down.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.mysql.up.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.postgres.down.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.postgres.up.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000001_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.cockroach.down.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.cockroach.up.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.mysql.down.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.mysql.up.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.postgres.down.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.postgres.up.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000002_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.cockroach.down.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.cockroach.up.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.mysql.down.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.mysql.up.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.postgres.down.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.postgres.up.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000003_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200317160354000004_create_profile_request_forms.mysql.down.sql │ │ │ ├── 20200317160354000004_create_profile_request_forms.mysql.up.sql │ │ │ ├── 20200317160354000004_create_profile_request_forms.postgres.down.sql │ │ │ ├── 20200317160354000004_create_profile_request_forms.postgres.up.sql │ │ │ ├── 20200317160354000004_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000004_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200317160354000005_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000005_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200317160354000006_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354000006_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200401183443000000_continuity_containers.cockroach.down.sql │ │ │ ├── 20200401183443000000_continuity_containers.cockroach.up.sql │ │ │ ├── 20200401183443000000_continuity_containers.mysql.down.sql │ │ │ ├── 20200401183443000000_continuity_containers.mysql.up.sql │ │ │ ├── 20200401183443000000_continuity_containers.postgres.down.sql │ │ │ ├── 20200401183443000000_continuity_containers.postgres.up.sql │ │ │ ├── 20200401183443000000_continuity_containers.sqlite3.down.sql │ │ │ ├── 20200401183443000000_continuity_containers.sqlite3.up.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.cockroach.down.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.cockroach.up.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.mysql.down.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.mysql.up.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.postgres.down.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.postgres.up.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.sqlite3.down.sql │ │ │ ├── 20200402142539000000_rename_profile_flows.sqlite3.up.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.cockroach.down.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.cockroach.up.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.mysql.down.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.mysql.up.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.postgres.down.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.postgres.up.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.sqlite3.down.sql │ │ │ ├── 20200402142539000001_rename_profile_flows.sqlite3.up.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.cockroach.down.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.cockroach.up.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.mysql.down.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.mysql.up.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.postgres.down.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.postgres.up.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.sqlite3.down.sql │ │ │ ├── 20200402142539000002_rename_profile_flows.sqlite3.up.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000000_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000001_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000002_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000003_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000004_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000005_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000006_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057000007_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101058000000_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101058000000_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101058000001_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101058000001_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200601101000000000_create_messages.cockroach.down.sql │ │ │ ├── 20200601101000000000_create_messages.cockroach.up.sql │ │ │ ├── 20200601101000000000_create_messages.mysql.down.sql │ │ │ ├── 20200601101000000000_create_messages.mysql.up.sql │ │ │ ├── 20200601101000000000_create_messages.postgres.down.sql │ │ │ ├── 20200601101000000000_create_messages.postgres.up.sql │ │ │ ├── 20200601101000000000_create_messages.sqlite3.down.sql │ │ │ ├── 20200601101000000000_create_messages.sqlite3.up.sql │ │ │ ├── 20200601101000000001_create_messages.sqlite3.down.sql │ │ │ ├── 20200601101000000001_create_messages.sqlite3.up.sql │ │ │ ├── 20200601101000000002_create_messages.sqlite3.down.sql │ │ │ ├── 20200601101000000002_create_messages.sqlite3.up.sql │ │ │ ├── 20200601101000000003_create_messages.sqlite3.down.sql │ │ │ ├── 20200601101000000003_create_messages.sqlite3.up.sql │ │ │ ├── 20200601101001000000_verification.mysql.down.sql │ │ │ ├── 20200601101001000000_verification.mysql.up.sql │ │ │ ├── 20200601101001000001_verification.mysql.down.sql │ │ │ ├── 20200601101001000001_verification.mysql.up.sql │ │ │ ├── 20200605111551000000_messages.cockroach.down.sql │ │ │ ├── 20200605111551000000_messages.cockroach.up.sql │ │ │ ├── 20200605111551000000_messages.mysql.down.sql │ │ │ ├── 20200605111551000000_messages.mysql.up.sql │ │ │ ├── 20200605111551000000_messages.postgres.down.sql │ │ │ ├── 20200605111551000000_messages.postgres.up.sql │ │ │ ├── 20200605111551000000_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000000_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000001_messages.cockroach.down.sql │ │ │ ├── 20200605111551000001_messages.cockroach.up.sql │ │ │ ├── 20200605111551000001_messages.mysql.down.sql │ │ │ ├── 20200605111551000001_messages.mysql.up.sql │ │ │ ├── 20200605111551000001_messages.postgres.down.sql │ │ │ ├── 20200605111551000001_messages.postgres.up.sql │ │ │ ├── 20200605111551000001_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000001_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000002_messages.cockroach.down.sql │ │ │ ├── 20200605111551000002_messages.cockroach.up.sql │ │ │ ├── 20200605111551000002_messages.mysql.down.sql │ │ │ ├── 20200605111551000002_messages.mysql.up.sql │ │ │ ├── 20200605111551000002_messages.postgres.down.sql │ │ │ ├── 20200605111551000002_messages.postgres.up.sql │ │ │ ├── 20200605111551000002_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000002_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000003_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000003_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000004_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000004_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000005_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000005_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000006_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000006_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000007_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000007_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000008_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000008_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000009_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000009_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000010_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000010_messages.sqlite3.up.sql │ │ │ ├── 20200605111551000011_messages.sqlite3.down.sql │ │ │ ├── 20200605111551000011_messages.sqlite3.up.sql │ │ │ ├── 20200607165100000000_settings.cockroach.down.sql │ │ │ ├── 20200607165100000000_settings.cockroach.up.sql │ │ │ ├── 20200607165100000000_settings.mysql.down.sql │ │ │ ├── 20200607165100000000_settings.mysql.up.sql │ │ │ ├── 20200607165100000000_settings.postgres.down.sql │ │ │ ├── 20200607165100000000_settings.postgres.up.sql │ │ │ ├── 20200607165100000000_settings.sqlite3.down.sql │ │ │ ├── 20200607165100000000_settings.sqlite3.up.sql │ │ │ ├── 20200607165100000001_settings.cockroach.down.sql │ │ │ ├── 20200607165100000001_settings.cockroach.up.sql │ │ │ ├── 20200607165100000001_settings.mysql.down.sql │ │ │ ├── 20200607165100000001_settings.mysql.up.sql │ │ │ ├── 20200607165100000001_settings.postgres.down.sql │ │ │ ├── 20200607165100000001_settings.postgres.up.sql │ │ │ ├── 20200607165100000001_settings.sqlite3.down.sql │ │ │ ├── 20200607165100000001_settings.sqlite3.up.sql │ │ │ ├── 20200607165100000002_settings.sqlite3.down.sql │ │ │ ├── 20200607165100000002_settings.sqlite3.up.sql │ │ │ ├── 20200607165100000003_settings.sqlite3.down.sql │ │ │ ├── 20200607165100000003_settings.sqlite3.up.sql │ │ │ ├── 20200607165100000004_settings.sqlite3.down.sql │ │ │ ├── 20200607165100000004_settings.sqlite3.up.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.cockroach.down.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.cockroach.up.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.mysql.down.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.mysql.up.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.postgres.down.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.postgres.up.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.sqlite3.down.sql │ │ │ ├── 20200705105359000000_rename_identities_schema.sqlite3.up.sql │ │ │ ├── 20200810141652000000_flow_type.cockroach.down.sql │ │ │ ├── 20200810141652000000_flow_type.cockroach.up.sql │ │ │ ├── 20200810141652000000_flow_type.mysql.down.sql │ │ │ ├── 20200810141652000000_flow_type.mysql.up.sql │ │ │ ├── 20200810141652000000_flow_type.postgres.down.sql │ │ │ ├── 20200810141652000000_flow_type.postgres.up.sql │ │ │ ├── 20200810141652000000_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000000_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000001_flow_type.cockroach.down.sql │ │ │ ├── 20200810141652000001_flow_type.cockroach.up.sql │ │ │ ├── 20200810141652000001_flow_type.mysql.down.sql │ │ │ ├── 20200810141652000001_flow_type.mysql.up.sql │ │ │ ├── 20200810141652000001_flow_type.postgres.down.sql │ │ │ ├── 20200810141652000001_flow_type.postgres.up.sql │ │ │ ├── 20200810141652000001_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000001_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000002_flow_type.cockroach.down.sql │ │ │ ├── 20200810141652000002_flow_type.cockroach.up.sql │ │ │ ├── 20200810141652000002_flow_type.mysql.down.sql │ │ │ ├── 20200810141652000002_flow_type.mysql.up.sql │ │ │ ├── 20200810141652000002_flow_type.postgres.down.sql │ │ │ ├── 20200810141652000002_flow_type.postgres.up.sql │ │ │ ├── 20200810141652000002_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000002_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000003_flow_type.cockroach.down.sql │ │ │ ├── 20200810141652000003_flow_type.cockroach.up.sql │ │ │ ├── 20200810141652000003_flow_type.mysql.down.sql │ │ │ ├── 20200810141652000003_flow_type.mysql.up.sql │ │ │ ├── 20200810141652000003_flow_type.postgres.down.sql │ │ │ ├── 20200810141652000003_flow_type.postgres.up.sql │ │ │ ├── 20200810141652000003_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000003_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000004_flow_type.cockroach.down.sql │ │ │ ├── 20200810141652000004_flow_type.cockroach.up.sql │ │ │ ├── 20200810141652000004_flow_type.mysql.down.sql │ │ │ ├── 20200810141652000004_flow_type.mysql.up.sql │ │ │ ├── 20200810141652000004_flow_type.postgres.down.sql │ │ │ ├── 20200810141652000004_flow_type.postgres.up.sql │ │ │ ├── 20200810141652000004_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000004_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000005_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000005_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000006_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000006_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000007_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000007_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000008_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000008_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000009_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000009_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000010_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000010_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000011_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000011_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000012_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000012_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000013_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000013_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000014_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000014_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000015_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000015_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000016_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000016_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000017_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000017_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000018_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000018_flow_type.sqlite3.up.sql │ │ │ ├── 20200810141652000019_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652000019_flow_type.sqlite3.up.sql │ │ │ ├── 20200810161022000000_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000000_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000000_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000000_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000000_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000000_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000000_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000000_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000001_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000001_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000001_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000001_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000001_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000001_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000001_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000001_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000002_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000002_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000002_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000002_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000002_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000002_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000002_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000002_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000003_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000003_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000003_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000003_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000003_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000003_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000003_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000003_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000004_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000004_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000004_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000004_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000004_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000004_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000004_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000004_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000005_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000005_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000005_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000005_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000005_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000005_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000005_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000005_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000006_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000006_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000006_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000006_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000006_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000006_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000006_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000006_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000007_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000007_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000007_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000007_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000007_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000007_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000007_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000007_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810161022000008_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022000008_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022000008_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022000008_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022000008_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022000008_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022000008_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022000008_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.cockroach.down.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.cockroach.up.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.mysql.down.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.mysql.up.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.postgres.down.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.postgres.up.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.sqlite3.down.sql │ │ │ ├── 20200810162450000000_flow_fields_rename.sqlite3.up.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.cockroach.down.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.cockroach.up.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.mysql.down.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.mysql.up.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.postgres.down.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.postgres.up.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.sqlite3.down.sql │ │ │ ├── 20200810162450000001_flow_fields_rename.sqlite3.up.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.cockroach.down.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.cockroach.up.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.mysql.down.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.mysql.up.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.postgres.down.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.postgres.up.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.sqlite3.down.sql │ │ │ ├── 20200810162450000002_flow_fields_rename.sqlite3.up.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.cockroach.down.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.cockroach.up.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.mysql.down.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.mysql.up.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.postgres.down.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.postgres.up.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.sqlite3.down.sql │ │ │ ├── 20200810162450000003_flow_fields_rename.sqlite3.up.sql │ │ │ ├── 20200812124254000000_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000000_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000000_add_session_token.mysql.down.sql │ │ │ ├── 20200812124254000000_add_session_token.mysql.up.sql │ │ │ ├── 20200812124254000000_add_session_token.postgres.down.sql │ │ │ ├── 20200812124254000000_add_session_token.postgres.up.sql │ │ │ ├── 20200812124254000000_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000000_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000001_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000001_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000001_add_session_token.mysql.down.sql │ │ │ ├── 20200812124254000001_add_session_token.mysql.up.sql │ │ │ ├── 20200812124254000001_add_session_token.postgres.down.sql │ │ │ ├── 20200812124254000001_add_session_token.postgres.up.sql │ │ │ ├── 20200812124254000001_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000001_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000002_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000002_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000002_add_session_token.mysql.down.sql │ │ │ ├── 20200812124254000002_add_session_token.mysql.up.sql │ │ │ ├── 20200812124254000002_add_session_token.postgres.down.sql │ │ │ ├── 20200812124254000002_add_session_token.postgres.up.sql │ │ │ ├── 20200812124254000002_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000002_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000003_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000003_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000003_add_session_token.mysql.down.sql │ │ │ ├── 20200812124254000003_add_session_token.mysql.up.sql │ │ │ ├── 20200812124254000003_add_session_token.postgres.down.sql │ │ │ ├── 20200812124254000003_add_session_token.postgres.up.sql │ │ │ ├── 20200812124254000003_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000003_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000004_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000004_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000004_add_session_token.mysql.down.sql │ │ │ ├── 20200812124254000004_add_session_token.mysql.up.sql │ │ │ ├── 20200812124254000004_add_session_token.postgres.down.sql │ │ │ ├── 20200812124254000004_add_session_token.postgres.up.sql │ │ │ ├── 20200812124254000004_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000004_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000005_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000005_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000005_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000005_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000006_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000006_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000006_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000006_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812124254000007_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254000007_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254000007_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254000007_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812160551000000_add_session_revoke.cockroach.down.sql │ │ │ ├── 20200812160551000000_add_session_revoke.cockroach.up.sql │ │ │ ├── 20200812160551000000_add_session_revoke.mysql.down.sql │ │ │ ├── 20200812160551000000_add_session_revoke.mysql.up.sql │ │ │ ├── 20200812160551000000_add_session_revoke.postgres.down.sql │ │ │ ├── 20200812160551000000_add_session_revoke.postgres.up.sql │ │ │ ├── 20200812160551000000_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000000_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000001_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000001_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000002_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000002_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000003_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000003_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000004_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000004_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000005_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000005_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000006_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000006_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200812160551000007_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551000007_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200830121710000000_update_recovery_token.cockroach.down.sql │ │ │ ├── 20200830121710000000_update_recovery_token.cockroach.up.sql │ │ │ ├── 20200830121710000000_update_recovery_token.mysql.down.sql │ │ │ ├── 20200830121710000000_update_recovery_token.mysql.up.sql │ │ │ ├── 20200830121710000000_update_recovery_token.postgres.down.sql │ │ │ ├── 20200830121710000000_update_recovery_token.postgres.up.sql │ │ │ ├── 20200830121710000000_update_recovery_token.sqlite3.down.sql │ │ │ ├── 20200830121710000000_update_recovery_token.sqlite3.up.sql │ │ │ ├── 20200830130642000000_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642000000_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642000000_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000000_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000000_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000000_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000000_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000000_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000001_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642000001_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642000001_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000001_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000001_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000001_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000001_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000001_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000002_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642000002_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642000002_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000002_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000002_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000002_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000002_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000002_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000003_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642000003_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642000003_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000003_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000003_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000003_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000003_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000003_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000004_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642000004_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642000004_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000004_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000004_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000004_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000004_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000004_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000005_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642000005_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642000005_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000005_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000005_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000005_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000005_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000005_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000006_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000006_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000006_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000006_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000006_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000006_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000007_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642000007_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642000007_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642000007_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642000007_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000007_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000008_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000008_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000009_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000009_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130642000010_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642000010_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130643000000_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130643000000_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130643000000_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130643000000_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130643000000_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130643000000_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130643000000_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130643000000_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130644000000_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130644000000_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130644000000_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130644000000_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130644000000_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130644000000_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130644000000_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130644000000_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130644000001_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130644000001_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130644000001_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130644000001_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130644000001_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130644000001_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130644000001_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130644000001_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130645000000_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130645000000_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130645000000_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130645000000_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130645000000_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130645000000_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130645000000_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130645000000_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000000_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130646000000_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130646000000_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130646000000_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130646000000_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130646000000_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130646000000_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000000_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000001_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130646000001_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130646000001_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130646000001_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130646000001_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130646000001_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130646000001_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000001_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000002_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130646000002_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130646000002_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130646000002_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130646000002_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130646000002_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130646000002_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000002_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000003_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000003_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000004_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000004_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000005_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000005_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000006_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000006_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000007_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000007_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000008_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000008_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000009_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000009_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000010_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000010_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646000011_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130646000011_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830154602000000_add_verification_token.cockroach.down.sql │ │ │ ├── 20200830154602000000_add_verification_token.cockroach.up.sql │ │ │ ├── 20200830154602000000_add_verification_token.mysql.down.sql │ │ │ ├── 20200830154602000000_add_verification_token.mysql.up.sql │ │ │ ├── 20200830154602000000_add_verification_token.postgres.down.sql │ │ │ ├── 20200830154602000000_add_verification_token.postgres.up.sql │ │ │ ├── 20200830154602000000_add_verification_token.sqlite3.down.sql │ │ │ ├── 20200830154602000000_add_verification_token.sqlite3.up.sql │ │ │ ├── 20200830154602000001_add_verification_token.cockroach.down.sql │ │ │ ├── 20200830154602000001_add_verification_token.cockroach.up.sql │ │ │ ├── 20200830154602000001_add_verification_token.mysql.down.sql │ │ │ ├── 20200830154602000001_add_verification_token.mysql.up.sql │ │ │ ├── 20200830154602000001_add_verification_token.postgres.down.sql │ │ │ ├── 20200830154602000001_add_verification_token.postgres.up.sql │ │ │ ├── 20200830154602000001_add_verification_token.sqlite3.down.sql │ │ │ ├── 20200830154602000001_add_verification_token.sqlite3.up.sql │ │ │ ├── 20200830154602000002_add_verification_token.cockroach.down.sql │ │ │ ├── 20200830154602000002_add_verification_token.cockroach.up.sql │ │ │ ├── 20200830154602000002_add_verification_token.mysql.down.sql │ │ │ ├── 20200830154602000002_add_verification_token.mysql.up.sql │ │ │ ├── 20200830154602000002_add_verification_token.postgres.down.sql │ │ │ ├── 20200830154602000002_add_verification_token.postgres.up.sql │ │ │ ├── 20200830154602000002_add_verification_token.sqlite3.down.sql │ │ │ ├── 20200830154602000002_add_verification_token.sqlite3.up.sql │ │ │ ├── 20200830154602000003_add_verification_token.cockroach.down.sql │ │ │ ├── 20200830154602000003_add_verification_token.cockroach.up.sql │ │ │ ├── 20200830154602000003_add_verification_token.mysql.down.sql │ │ │ ├── 20200830154602000003_add_verification_token.mysql.up.sql │ │ │ ├── 20200830154602000003_add_verification_token.postgres.down.sql │ │ │ ├── 20200830154602000003_add_verification_token.postgres.up.sql │ │ │ ├── 20200830154602000003_add_verification_token.sqlite3.down.sql │ │ │ ├── 20200830154602000003_add_verification_token.sqlite3.up.sql │ │ │ ├── 20200830154602000004_add_verification_token.cockroach.down.sql │ │ │ ├── 20200830154602000004_add_verification_token.cockroach.up.sql │ │ │ ├── 20200830154602000004_add_verification_token.mysql.down.sql │ │ │ ├── 20200830154602000004_add_verification_token.mysql.up.sql │ │ │ ├── 20200830154602000004_add_verification_token.postgres.down.sql │ │ │ ├── 20200830154602000004_add_verification_token.postgres.up.sql │ │ │ ├── 20200830154602000004_add_verification_token.sqlite3.down.sql │ │ │ ├── 20200830154602000004_add_verification_token.sqlite3.up.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.mysql.down.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.mysql.up.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.postgres.down.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.postgres.up.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000000_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.mysql.down.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.mysql.up.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.postgres.down.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.postgres.up.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000001_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.mysql.down.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.mysql.up.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.postgres.down.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.postgres.up.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000002_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.mysql.down.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.mysql.up.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.postgres.down.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.postgres.up.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000003_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000004_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000004_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000004_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000004_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000005_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000005_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000005_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000005_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000006_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000006_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000006_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000006_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000007_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000007_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000007_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000007_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000008_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000008_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000008_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000008_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000009_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221000009_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221000009_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000009_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000010_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000010_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000011_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000011_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000012_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000012_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000013_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000013_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000014_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000014_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000015_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000015_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000016_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000016_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000017_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000017_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000018_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000018_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000019_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000019_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000020_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000020_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000021_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000021_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000022_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000022_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000023_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000023_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200830172221000024_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221000024_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000015_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000015_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000016_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000016_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000017_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000017_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000018_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000018_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000019_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000019_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000020_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000020_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20200831110752000021_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752000021_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20201201161451000000_credential_types_values.cockroach.down.sql │ │ │ ├── 20201201161451000000_credential_types_values.cockroach.up.sql │ │ │ ├── 20201201161451000000_credential_types_values.mysql.down.sql │ │ │ ├── 20201201161451000000_credential_types_values.mysql.up.sql │ │ │ ├── 20201201161451000000_credential_types_values.postgres.down.sql │ │ │ ├── 20201201161451000000_credential_types_values.postgres.up.sql │ │ │ ├── 20201201161451000000_credential_types_values.sqlite3.down.sql │ │ │ ├── 20201201161451000000_credential_types_values.sqlite3.up.sql │ │ │ ├── 20201201161451000001_credential_types_values.cockroach.down.sql │ │ │ ├── 20201201161451000001_credential_types_values.cockroach.up.sql │ │ │ ├── 20201201161451000001_credential_types_values.mysql.down.sql │ │ │ ├── 20201201161451000001_credential_types_values.mysql.up.sql │ │ │ ├── 20201201161451000001_credential_types_values.postgres.down.sql │ │ │ ├── 20201201161451000001_credential_types_values.postgres.up.sql │ │ │ ├── 20201201161451000001_credential_types_values.sqlite3.down.sql │ │ │ └── 20201201161451000001_credential_types_values.sqlite3.up.sql │ │ └── transaction.go │ ├── profilex/ │ │ └── profiling.go │ ├── prometheusx/ │ │ ├── handler.go │ │ └── metrics.go │ ├── proxy/ │ │ ├── proxy.go │ │ ├── rewrites.go │ │ └── stubs/ │ │ └── auth.example.com.json │ ├── randx/ │ │ ├── README.md │ │ ├── sequence.go │ │ └── strength/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── reqlog/ │ │ ├── LICENSE │ │ ├── external_latency.go │ │ └── middleware.go │ ├── resilience/ │ │ └── retry.go │ ├── safecast/ │ │ └── safecast.go │ ├── serverx/ │ │ ├── 404.go │ │ ├── 404.html │ │ └── 404.json │ ├── servicelocatorx/ │ │ └── options.go │ ├── snapshotx/ │ │ ├── .snapshots/ │ │ │ ├── TestDeleteMatches-file=1.json-fn.json │ │ │ ├── TestDeleteMatches-file=2.json-fn.json │ │ │ └── TestDeleteMatches-file=3.json-fn.json │ │ ├── fixtures/ │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ └── 3.json │ │ └── snapshot.go │ ├── sqlcon/ │ │ ├── connector.go │ │ ├── dockertest/ │ │ │ ├── cockroach.go │ │ │ └── test_helper.go │ │ ├── error.go │ │ ├── error_nosqlite.go │ │ ├── error_sqlite.go │ │ ├── message.go │ │ └── parse_opts.go │ ├── sqlxx/ │ │ ├── batch/ │ │ │ ├── .snapshots/ │ │ │ │ ├── Test_buildInsertQueryArgs-case=cockroach.json │ │ │ │ ├── Test_buildInsertQueryArgs-case=testModel.json │ │ │ │ └── Test_buildInsertQueryValues-case=testModel-case=cockroach.json │ │ │ └── create.go │ │ ├── expand.go │ │ ├── sqlxx.go │ │ └── types.go │ ├── stringslice/ │ │ └── unique.go │ ├── stringsx/ │ │ ├── case.go │ │ ├── split.go │ │ ├── switch_case.go │ │ └── truncate.go │ ├── swaggerx/ │ │ └── error.go │ ├── testingx/ │ │ └── helpers.go │ ├── tlsx/ │ │ ├── cert.go │ │ └── termination.go │ ├── urlx/ │ │ ├── copy.go │ │ ├── extract.go │ │ ├── join.go │ │ ├── parse.go │ │ ├── path.go │ │ └── path_windows.go │ ├── uuidx/ │ │ └── uuid.go │ └── watcherx/ │ ├── definitions.go │ ├── directory.go │ ├── event.go │ ├── file.go │ ├── integrationtest/ │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── README.md │ │ ├── configmap.yml │ │ ├── event_logger.yml │ │ ├── eventlog_snapshot │ │ └── main.go │ └── test_helpers.go ├── otp/ │ └── otp.go ├── package.json ├── persistence/ │ ├── reference.go │ └── sql/ │ ├── .soda.yml │ ├── README.md │ ├── batch/ │ │ ├── .snapshots/ │ │ │ ├── Test_buildInsertQueryArgs-case=Identities.json │ │ │ ├── Test_buildInsertQueryArgs-case=RecoveryAddress#01.json │ │ │ ├── Test_buildInsertQueryArgs-case=RecoveryAddress.json │ │ │ ├── Test_buildInsertQueryArgs-case=cockroach.json │ │ │ ├── Test_buildInsertQueryArgs-case=testModel.json │ │ │ └── Test_buildInsertQueryValues-case=testModel-case=cockroach.json │ │ ├── create.go │ │ ├── create_test.go │ │ └── test_persister.go │ ├── devices/ │ │ └── persister_devices.go │ ├── identity/ │ │ └── persister_identity.go │ ├── migratest/ │ │ ├── fixtures/ │ │ │ ├── identity/ │ │ │ │ ├── 0149ce5f-76a8-4efe-b2e3-431b8c6cceb6.json │ │ │ │ ├── 0149ce5f-76a8-4efe-b2e3-431b8c6cceb7.json │ │ │ │ ├── 196d8c1e-4f04-40f0-94b3-5ec43996b28a.json │ │ │ │ ├── 28ff0031-190b-4253-bd15-14308dec013e.json │ │ │ │ ├── 2ae6a5a7-2983-49e7-a4d8-7740b37c88cb.json │ │ │ │ ├── 308929d3-41a2-43fe-a33c-75308539d841.json │ │ │ │ ├── 359963ec-b09b-4ea0-aece-fb4dd95f304a.json │ │ │ │ ├── 5ff66179-c240-4703-b0d8-494592cefff5.json │ │ │ │ ├── a251ebc2-880c-4f76-a8f3-38e6940eab0e.json │ │ │ │ ├── d7b9addb-ac15-4bc2-9fa5-562e0bf48755.json │ │ │ │ └── ed253b2c-48ed-4c58-9b6f-1dc963c30a66.json │ │ │ ├── identity_recovery_address/ │ │ │ │ └── b8293f1c-010f-45d9-b809-f3fc5365ba80.json │ │ │ ├── identity_verification_address/ │ │ │ │ ├── 45e867e9-2745-4f16-8dd4-84334a252b61.json │ │ │ │ ├── b2d59320-8564-4400-a39f-a22a497a23f1.json │ │ │ │ ├── c2427b6d-312b-46d9-9285-536db7ae11fd.json │ │ │ │ └── d4718a67-aec2-418d-8173-6ebc7bde3b86.json │ │ │ ├── login_code/ │ │ │ │ └── bd292366-af32-4ba6-bdf0-11d6d1a217f3.json │ │ │ ├── login_flow/ │ │ │ │ ├── 00b1517f-2467-4aaf-b0a5-82b4a27dcaf5.json │ │ │ │ ├── 0bc96cc9-dda4-4700-9e42-35731f2af91e.json │ │ │ │ ├── 1fb23c75-b809-42cc-8984-6ca2d0a1192f.json │ │ │ │ ├── 202c1981-1e25-47f0-8764-75ad506c2bec.json │ │ │ │ ├── 349c945a-60f8-436a-a301-7a42c92604f9.json │ │ │ │ ├── 38caf592-b042-4551-b92f-8d5223c2a4e2.json │ │ │ │ ├── 3a9ea34f-0f12-469b-9417-3ae5795a7baa.json │ │ │ │ ├── 43c99182-bb67-47e1-b564-bb23bd8d4393.json │ │ │ │ ├── 47edd3a8-0998-4779-9469-f4b8ee4430df.json │ │ │ │ ├── 56d94e8b-8a5d-4b7f-8a6e-3259d2b2903e.json │ │ │ │ ├── 6d387820-f2f4-4f9f-9980-a90d89e7811f.json │ │ │ │ ├── 916ded11-aa64-4a27-b06e-96e221a509d7.json │ │ │ │ ├── 99974ce6-388c-4669-a95a-7757ee724020.json │ │ │ │ ├── b1fac7fb-d016-4a06-a7fe-e4eab2a0429f.json │ │ │ │ ├── cccccccc-dda4-4700-9e42-35731f2af911.json │ │ │ │ ├── cccccccc-dda4-4700-9e42-35731f2af91e.json │ │ │ │ └── d6aa1f23-88c9-4b9b-a850-392f48c7f9e8.json │ │ │ ├── recovery_code/ │ │ │ │ └── 8f75f5d9-9cb4-4848-9a73-9344f686f8a6.json │ │ │ ├── recovery_flow/ │ │ │ │ ├── 0d14427f-e16d-43a5-8695-8278bf85d4eb.json │ │ │ │ ├── 13178936-095a-466b-abe0-36d977d3dc18.json │ │ │ │ ├── 4963f305-e874-4a68-8424-a00bec679e7b.json │ │ │ │ ├── 68fb4010-84a9-4d1e-9f92-2705978ee891.json │ │ │ │ ├── 68fb4010-84a9-4d1e-9f92-2705978ee89e.json │ │ │ │ └── 87e871e1-a45f-4ed0-ba4e-a03063c774dc.json │ │ │ ├── recovery_token/ │ │ │ │ ├── 1b667e6d-8fda-4194-a765-08185185d7e4.json │ │ │ │ ├── 5529d454-2946-404e-b681-d950f8657fd0.json │ │ │ │ └── 77ca3f5c-cd39-488b-9f1d-cc7166d14bdc.json │ │ │ ├── registration_code/ │ │ │ │ └── f1f66a69-ce02-4a12-9591-9e02dda30a0d.json │ │ │ ├── registration_flow/ │ │ │ │ ├── 05a7f09d-4ef3-41fb-958a-6ad74584b36a.json │ │ │ │ ├── 22d58184-b97d-44a5-bbaf-0aa8b4000d81.json │ │ │ │ ├── 2bf132e0-5d40-4df9-9a11-9106e5333735.json │ │ │ │ ├── 696e7022-c466-44f6-89c6-8cf93c06a62a.json │ │ │ │ ├── 69c80296-36cd-4afc-921a-15369cac5bf0.json │ │ │ │ ├── 87fa3f43-5155-42b4-a1ad-174c2595fdaf.json │ │ │ │ ├── 8ef215a9-e8d5-43b3-9aa3-cb4333562e36.json │ │ │ │ ├── 8f32efdc-f6fc-4c27-a3c2-579d109eff60.json │ │ │ │ ├── 9edcf051-1cd0-44cc-bd2f-6ac21f0c24dd.json │ │ │ │ ├── e2150cdc-23ac-4940-a240-6c79c27ab029.json │ │ │ │ ├── ef18b06e-4700-4021-9949-ef783cd86be1.json │ │ │ │ ├── ef18b06e-4700-4021-9949-ef783cd86be8.json │ │ │ │ └── f1b5ed18-113a-4a98-aae7-d4eba007199c.json │ │ │ ├── session/ │ │ │ │ ├── 068f6bb6-d15f-436d-94f7-b3fd0489c9ef.json │ │ │ │ ├── 7458af86-c1d8-401c-978a-8da89133f78b.json │ │ │ │ ├── 7458af86-c1d8-401c-978a-8da89133f98b.json │ │ │ │ ├── 8571e374-38f2-4f46-8ad3-b9d914e174d3.json │ │ │ │ ├── dcde5aaa-f789-4d3d-ae1f-76da8d57e67c.json │ │ │ │ └── f38cdebe-e567-42c9-a562-1bd4dee40998.json │ │ │ ├── settings_flow/ │ │ │ │ ├── 194c5b05-0487-4a11-bcbc-f301c9ff9678.json │ │ │ │ ├── 19ede218-928c-4e02-ab49-b76e12b34f31.json │ │ │ │ ├── 19ede218-928c-4e02-ab49-b76e12b34f32.json │ │ │ │ ├── 21c5f714-3089-49d2-b387-f244d4dd9e00.json │ │ │ │ ├── 74fd6c53-7651-453e-90b8-2c5adbf911bb.json │ │ │ │ ├── 77fe4fb3-2d4e-4532-b568-c44b0aece0aa.json │ │ │ │ ├── 8248bb5d-8ef7-45e3-8e07-9e2003dd5352.json │ │ │ │ ├── 90b4f970-b9ae-42bc-a0a7-73ec750e0aa1.json │ │ │ │ ├── a79bfcf1-68ae-49de-8b23-4f96921b8341.json │ │ │ │ ├── aeba85bd-1a8c-44bf-8fc3-3be83c01a3dc.json │ │ │ │ └── cdfd1eed-34a4-491d-ad0a-7579d3a0a7ba.json │ │ │ ├── verification_flow/ │ │ │ │ ├── 29b2c16e-2955-4faa-bd16-33af098cdf83.json │ │ │ │ ├── 3631e880-ce59-4cbd-a705-0d825eea590d.json │ │ │ │ ├── 42f31e47-65e1-4be9-80ea-e5d8ed64b236.json │ │ │ │ ├── 5385c962-0295-4575-9b1b-d7eef13c0eda.json │ │ │ │ ├── 6aae3159-b880-4cfb-a863-03b114b1371b.json │ │ │ │ ├── 7be6c72c-c868-4b61-a1f0-1130603665d1.json │ │ │ │ ├── 7be6c72c-c868-4b61-a1f0-1130603665d8.json │ │ │ │ ├── 81f74e5d-1fa5-4e1b-a9bf-e95119260471.json │ │ │ │ ├── 81f74e5d-1fa5-4e1b-a9bf-e9511926047c.json │ │ │ │ ├── a8e2b810-a561-4a9e-bbd8-37f649bc26fa.json │ │ │ │ └── b37d34c2-4290-4be4-9e3a-c6263ee77082.json │ │ │ └── verification_token/ │ │ │ ├── ee56574d-2f0c-43f6-8d26-0062938ae330.json │ │ │ ├── ee56574d-2f1c-43f6-8d26-0062938ae330.json │ │ │ └── f81fd924-23bb-4cdf-8fa0-56253eff6cc9.json │ │ ├── migration_test.go │ │ ├── stub/ │ │ │ └── default.schema.json │ │ └── testdata/ │ │ ├── 20150100000001_testdata.sql │ │ ├── 20191100000001_testdata.sql │ │ ├── 20191100000002_testdata.sql │ │ ├── 20191100000003_testdata.sql │ │ ├── 20191100000004_testdata.sql │ │ ├── 20191100000005_testdata.mysql.sql │ │ ├── 20191100000006_testdata.sql │ │ ├── 20191100000007_testdata.sql │ │ ├── 20191100000008_testdata.sql │ │ ├── 20191100000009_testdata.mysql.sql │ │ ├── 20191100000010_testdata.sql │ │ ├── 20191100000011_testdata.sql │ │ ├── 20191100000012_testdata.sql │ │ ├── 20200317160354_testdata.sql │ │ ├── 20200401183443_testdata.sql │ │ ├── 20200402142539_testdata.sql │ │ ├── 20200519101057_testdata.sql │ │ ├── 20200519101058_testdata.mysql.sql │ │ ├── 20200601101000_testdata.sql │ │ ├── 20200601101001_testdata.mysql.sql │ │ ├── 20200605111551_testdata.sql │ │ ├── 20200607165100_testdata.sql │ │ ├── 20200705105359_testdata.sql │ │ ├── 20200810141652_testdata.sql │ │ ├── 20200810161022_testdata.sql │ │ ├── 20200810162450_testdata.sql │ │ ├── 20200812124254_testdata.sql │ │ ├── 20200812160551_testdata.sql │ │ ├── 20200830121710_testdata.sql │ │ ├── 20200830130642_testdata.sql │ │ ├── 20200830130643_testdata.sql │ │ ├── 20200830130644_testdata.sql │ │ ├── 20200830130645_testdata.sql │ │ ├── 20200830130646_testdata.sql │ │ ├── 20200830154602_testdata.sql │ │ ├── 20200830172221_testdata.sql │ │ ├── 20200831110752_testdata.sql │ │ ├── 20201201161451_testdata.sql │ │ ├── 20210118113234_testdata.sql │ │ ├── 20210126114619_testdata.sql │ │ ├── 20210307130558_testdata.sql │ │ ├── 20210307130559_testdata.sql │ │ ├── 20210311102338_testdata.sql │ │ ├── 20210410175418_testdata.sql │ │ ├── 20210504121624_testdata.sql │ │ ├── 20210618103120_testdata.sql │ │ ├── 20210805112414_testdata.sql │ │ ├── 20210805122535_testdata.sql │ │ ├── 20210810153530_testdata.sql │ │ ├── 20210813150152_testdata.sql │ │ ├── 20210816113956_testdata.sql │ │ ├── 20210816142650_testdata.sql │ │ ├── 20210817181232_testdata.sql │ │ ├── 20210829131458_testdata.sql │ │ ├── 20210913095309_testdata.sql │ │ ├── 20220118104539_testdata.sql │ │ ├── 20220301102701_testdata.sql │ │ ├── 20220420102701_testdata.sql │ │ ├── 20220607000001_testdata.sql │ │ ├── 20220902141902_testdata.sql │ │ ├── 20220907132836_testdata.sql │ │ ├── 20220929124401_testdata.sql │ │ ├── 20221103120601_testdata.sql │ │ ├── 20221205095201_testdata.sql │ │ ├── 20230313141439_testdata.sql │ │ ├── 20230614205200_testdata.sql │ │ ├── 20230705000000_testdata.sql │ │ ├── 20230706000000_testdata.sql │ │ ├── 20230707133700_testdata.sql │ │ └── 20230707133701_testdata.sql │ ├── migrations/ │ │ ├── go/ │ │ │ ├── 20251105000000000000_identity_id_not_null_fks.go │ │ │ └── gomigrations.go │ │ ├── legacy/ │ │ │ ├── 20191100000001_identities.cockroach.down.sql │ │ │ ├── 20191100000001_identities.cockroach.up.sql │ │ │ ├── 20191100000001_identities.mysql.down.sql │ │ │ ├── 20191100000001_identities.mysql.up.sql │ │ │ ├── 20191100000001_identities.postgres.down.sql │ │ │ ├── 20191100000001_identities.postgres.up.sql │ │ │ ├── 20191100000001_identities.sqlite3.down.sql │ │ │ ├── 20191100000001_identities.sqlite3.up.sql │ │ │ ├── 20191100000002_requests.cockroach.down.sql │ │ │ ├── 20191100000002_requests.cockroach.up.sql │ │ │ ├── 20191100000002_requests.mysql.down.sql │ │ │ ├── 20191100000002_requests.mysql.up.sql │ │ │ ├── 20191100000002_requests.postgres.down.sql │ │ │ ├── 20191100000002_requests.postgres.up.sql │ │ │ ├── 20191100000002_requests.sqlite3.down.sql │ │ │ ├── 20191100000002_requests.sqlite3.up.sql │ │ │ ├── 20191100000003_sessions.cockroach.down.sql │ │ │ ├── 20191100000003_sessions.cockroach.up.sql │ │ │ ├── 20191100000003_sessions.mysql.down.sql │ │ │ ├── 20191100000003_sessions.mysql.up.sql │ │ │ ├── 20191100000003_sessions.postgres.down.sql │ │ │ ├── 20191100000003_sessions.postgres.up.sql │ │ │ ├── 20191100000003_sessions.sqlite3.down.sql │ │ │ ├── 20191100000003_sessions.sqlite3.up.sql │ │ │ ├── 20191100000004_errors.cockroach.down.sql │ │ │ ├── 20191100000004_errors.cockroach.up.sql │ │ │ ├── 20191100000004_errors.mysql.down.sql │ │ │ ├── 20191100000004_errors.mysql.up.sql │ │ │ ├── 20191100000004_errors.postgres.down.sql │ │ │ ├── 20191100000004_errors.postgres.up.sql │ │ │ ├── 20191100000004_errors.sqlite3.down.sql │ │ │ ├── 20191100000004_errors.sqlite3.up.sql │ │ │ ├── 20191100000005_identities.mysql.down.sql │ │ │ ├── 20191100000005_identities.mysql.up.sql │ │ │ ├── 20191100000006_courier.cockroach.down.sql │ │ │ ├── 20191100000006_courier.cockroach.up.sql │ │ │ ├── 20191100000006_courier.mysql.down.sql │ │ │ ├── 20191100000006_courier.mysql.up.sql │ │ │ ├── 20191100000006_courier.postgres.down.sql │ │ │ ├── 20191100000006_courier.postgres.up.sql │ │ │ ├── 20191100000006_courier.sqlite3.down.sql │ │ │ ├── 20191100000006_courier.sqlite3.up.sql │ │ │ ├── 20191100000007_errors.cockroach.down.sql │ │ │ ├── 20191100000007_errors.cockroach.up.sql │ │ │ ├── 20191100000007_errors.mysql.down.sql │ │ │ ├── 20191100000007_errors.mysql.up.sql │ │ │ ├── 20191100000007_errors.postgres.down.sql │ │ │ ├── 20191100000007_errors.postgres.up.sql │ │ │ ├── 20191100000007_errors.sqlite3.down.sql │ │ │ ├── 20191100000007_errors.sqlite3.up.sql │ │ │ ├── 20191100000008_selfservice_verification.cockroach.down.sql │ │ │ ├── 20191100000008_selfservice_verification.cockroach.up.sql │ │ │ ├── 20191100000008_selfservice_verification.mysql.down.sql │ │ │ ├── 20191100000008_selfservice_verification.mysql.up.sql │ │ │ ├── 20191100000008_selfservice_verification.postgres.down.sql │ │ │ ├── 20191100000008_selfservice_verification.postgres.up.sql │ │ │ ├── 20191100000008_selfservice_verification.sqlite3.down.sql │ │ │ ├── 20191100000008_selfservice_verification.sqlite3.up.sql │ │ │ ├── 20191100000009_verification.mysql.down.sql │ │ │ ├── 20191100000009_verification.mysql.up.sql │ │ │ ├── 20191100000010_errors.cockroach.down.sql │ │ │ ├── 20191100000010_errors.cockroach.up.sql │ │ │ ├── 20191100000010_errors.mysql.down.sql │ │ │ ├── 20191100000010_errors.mysql.up.sql │ │ │ ├── 20191100000010_errors.postgres.down.sql │ │ │ ├── 20191100000010_errors.postgres.up.sql │ │ │ ├── 20191100000010_errors.sqlite3.down.sql │ │ │ ├── 20191100000010_errors.sqlite3.up.sql │ │ │ ├── 20191100000011_courier_body_type.cockroach.up.sql │ │ │ ├── 20191100000011_courier_body_type.down.sql │ │ │ ├── 20191100000011_courier_body_type.mysql.up.sql │ │ │ ├── 20191100000011_courier_body_type.postgres.up.sql │ │ │ ├── 20191100000011_courier_body_type.sqlite3.up.sql │ │ │ ├── 20191100000012_login_request_forced.cockroach.down.sql │ │ │ ├── 20191100000012_login_request_forced.cockroach.up.sql │ │ │ ├── 20191100000012_login_request_forced.mysql.down.sql │ │ │ ├── 20191100000012_login_request_forced.mysql.up.sql │ │ │ ├── 20191100000012_login_request_forced.postgres.down.sql │ │ │ ├── 20191100000012_login_request_forced.postgres.up.sql │ │ │ ├── 20191100000012_login_request_forced.sqlite3.down.sql │ │ │ ├── 20191100000012_login_request_forced.sqlite3.up.sql │ │ │ ├── 20200317160354_create_profile_request_forms.cockroach.down.sql │ │ │ ├── 20200317160354_create_profile_request_forms.cockroach.up.sql │ │ │ ├── 20200317160354_create_profile_request_forms.mysql.down.sql │ │ │ ├── 20200317160354_create_profile_request_forms.mysql.up.sql │ │ │ ├── 20200317160354_create_profile_request_forms.postgres.down.sql │ │ │ ├── 20200317160354_create_profile_request_forms.postgres.up.sql │ │ │ ├── 20200317160354_create_profile_request_forms.sqlite3.down.sql │ │ │ ├── 20200317160354_create_profile_request_forms.sqlite3.up.sql │ │ │ ├── 20200401183443_continuity_containers.cockroach.down.sql │ │ │ ├── 20200401183443_continuity_containers.cockroach.up.sql │ │ │ ├── 20200401183443_continuity_containers.mysql.down.sql │ │ │ ├── 20200401183443_continuity_containers.mysql.up.sql │ │ │ ├── 20200401183443_continuity_containers.postgres.down.sql │ │ │ ├── 20200401183443_continuity_containers.postgres.up.sql │ │ │ ├── 20200401183443_continuity_containers.sqlite3.down.sql │ │ │ ├── 20200401183443_continuity_containers.sqlite3.up.sql │ │ │ ├── 20200402142539_rename_profile_flows.cockroach.down.sql │ │ │ ├── 20200402142539_rename_profile_flows.cockroach.up.sql │ │ │ ├── 20200402142539_rename_profile_flows.mysql.down.sql │ │ │ ├── 20200402142539_rename_profile_flows.mysql.up.sql │ │ │ ├── 20200402142539_rename_profile_flows.postgres.down.sql │ │ │ ├── 20200402142539_rename_profile_flows.postgres.up.sql │ │ │ ├── 20200402142539_rename_profile_flows.sqlite3.down.sql │ │ │ ├── 20200402142539_rename_profile_flows.sqlite3.up.sql │ │ │ ├── 20200519101057_create_recovery_addresses.cockroach.down.sql │ │ │ ├── 20200519101057_create_recovery_addresses.cockroach.up.sql │ │ │ ├── 20200519101057_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101057_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200519101057_create_recovery_addresses.postgres.down.sql │ │ │ ├── 20200519101057_create_recovery_addresses.postgres.up.sql │ │ │ ├── 20200519101057_create_recovery_addresses.sqlite3.down.sql │ │ │ ├── 20200519101057_create_recovery_addresses.sqlite3.up.sql │ │ │ ├── 20200519101058_create_recovery_addresses.mysql.down.sql │ │ │ ├── 20200519101058_create_recovery_addresses.mysql.up.sql │ │ │ ├── 20200601101000_create_messages.cockroach.down.sql │ │ │ ├── 20200601101000_create_messages.cockroach.up.sql │ │ │ ├── 20200601101000_create_messages.mysql.down.sql │ │ │ ├── 20200601101000_create_messages.mysql.up.sql │ │ │ ├── 20200601101000_create_messages.postgres.down.sql │ │ │ ├── 20200601101000_create_messages.postgres.up.sql │ │ │ ├── 20200601101000_create_messages.sqlite3.down.sql │ │ │ ├── 20200601101000_create_messages.sqlite3.up.sql │ │ │ ├── 20200601101001_verification.mysql.down.sql │ │ │ ├── 20200601101001_verification.mysql.up.sql │ │ │ ├── 20200605111551_messages.cockroach.down.sql │ │ │ ├── 20200605111551_messages.cockroach.up.sql │ │ │ ├── 20200605111551_messages.mysql.down.sql │ │ │ ├── 20200605111551_messages.mysql.up.sql │ │ │ ├── 20200605111551_messages.postgres.down.sql │ │ │ ├── 20200605111551_messages.postgres.up.sql │ │ │ ├── 20200605111551_messages.sqlite3.down.sql │ │ │ ├── 20200605111551_messages.sqlite3.up.sql │ │ │ ├── 20200607165100_settings.cockroach.down.sql │ │ │ ├── 20200607165100_settings.cockroach.up.sql │ │ │ ├── 20200607165100_settings.mysql.down.sql │ │ │ ├── 20200607165100_settings.mysql.up.sql │ │ │ ├── 20200607165100_settings.postgres.down.sql │ │ │ ├── 20200607165100_settings.postgres.up.sql │ │ │ ├── 20200607165100_settings.sqlite3.down.sql │ │ │ ├── 20200607165100_settings.sqlite3.up.sql │ │ │ ├── 20200705105359_rename_identities_schema.cockroach.down.sql │ │ │ ├── 20200705105359_rename_identities_schema.cockroach.up.sql │ │ │ ├── 20200705105359_rename_identities_schema.mysql.down.sql │ │ │ ├── 20200705105359_rename_identities_schema.mysql.up.sql │ │ │ ├── 20200705105359_rename_identities_schema.postgres.down.sql │ │ │ ├── 20200705105359_rename_identities_schema.postgres.up.sql │ │ │ ├── 20200705105359_rename_identities_schema.sqlite3.down.sql │ │ │ ├── 20200705105359_rename_identities_schema.sqlite3.up.sql │ │ │ ├── 20200810141652_flow_type.cockroach.down.sql │ │ │ ├── 20200810141652_flow_type.cockroach.up.sql │ │ │ ├── 20200810141652_flow_type.mysql.down.sql │ │ │ ├── 20200810141652_flow_type.mysql.up.sql │ │ │ ├── 20200810141652_flow_type.postgres.down.sql │ │ │ ├── 20200810141652_flow_type.postgres.up.sql │ │ │ ├── 20200810141652_flow_type.sqlite3.down.sql │ │ │ ├── 20200810141652_flow_type.sqlite3.up.sql │ │ │ ├── 20200810161022_flow_rename.cockroach.down.sql │ │ │ ├── 20200810161022_flow_rename.cockroach.up.sql │ │ │ ├── 20200810161022_flow_rename.mysql.down.sql │ │ │ ├── 20200810161022_flow_rename.mysql.up.sql │ │ │ ├── 20200810161022_flow_rename.postgres.down.sql │ │ │ ├── 20200810161022_flow_rename.postgres.up.sql │ │ │ ├── 20200810161022_flow_rename.sqlite3.down.sql │ │ │ ├── 20200810161022_flow_rename.sqlite3.up.sql │ │ │ ├── 20200810162450_flow_fields_rename.cockroach.down.sql │ │ │ ├── 20200810162450_flow_fields_rename.cockroach.up.sql │ │ │ ├── 20200810162450_flow_fields_rename.mysql.down.sql │ │ │ ├── 20200810162450_flow_fields_rename.mysql.up.sql │ │ │ ├── 20200810162450_flow_fields_rename.postgres.down.sql │ │ │ ├── 20200810162450_flow_fields_rename.postgres.up.sql │ │ │ ├── 20200810162450_flow_fields_rename.sqlite3.down.sql │ │ │ ├── 20200810162450_flow_fields_rename.sqlite3.up.sql │ │ │ ├── 20200812124254_add_session_token.cockroach.down.sql │ │ │ ├── 20200812124254_add_session_token.cockroach.up.sql │ │ │ ├── 20200812124254_add_session_token.mysql.down.sql │ │ │ ├── 20200812124254_add_session_token.mysql.up.sql │ │ │ ├── 20200812124254_add_session_token.postgres.down.sql │ │ │ ├── 20200812124254_add_session_token.postgres.up.sql │ │ │ ├── 20200812124254_add_session_token.sqlite3.down.sql │ │ │ ├── 20200812124254_add_session_token.sqlite3.up.sql │ │ │ ├── 20200812160551_add_session_revoke.cockroach.down.sql │ │ │ ├── 20200812160551_add_session_revoke.cockroach.up.sql │ │ │ ├── 20200812160551_add_session_revoke.mysql.down.sql │ │ │ ├── 20200812160551_add_session_revoke.mysql.up.sql │ │ │ ├── 20200812160551_add_session_revoke.postgres.down.sql │ │ │ ├── 20200812160551_add_session_revoke.postgres.up.sql │ │ │ ├── 20200812160551_add_session_revoke.sqlite3.down.sql │ │ │ ├── 20200812160551_add_session_revoke.sqlite3.up.sql │ │ │ ├── 20200830121710_update_recovery_token.cockroach.down.sql │ │ │ ├── 20200830121710_update_recovery_token.cockroach.up.sql │ │ │ ├── 20200830121710_update_recovery_token.mysql.down.sql │ │ │ ├── 20200830121710_update_recovery_token.mysql.up.sql │ │ │ ├── 20200830121710_update_recovery_token.postgres.down.sql │ │ │ ├── 20200830121710_update_recovery_token.postgres.up.sql │ │ │ ├── 20200830121710_update_recovery_token.sqlite3.down.sql │ │ │ ├── 20200830121710_update_recovery_token.sqlite3.up.sql │ │ │ ├── 20200830130642_add_verification_methods.cockroach.down.sql │ │ │ ├── 20200830130642_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130642_add_verification_methods.mysql.down.sql │ │ │ ├── 20200830130642_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130642_add_verification_methods.postgres.down.sql │ │ │ ├── 20200830130642_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130642_add_verification_methods.sqlite3.down.sql │ │ │ ├── 20200830130642_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130643_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130643_add_verification_methods.down.sql │ │ │ ├── 20200830130643_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130643_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130643_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130644_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130644_add_verification_methods.down.sql │ │ │ ├── 20200830130644_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130644_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130644_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130645_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130645_add_verification_methods.down.sql │ │ │ ├── 20200830130645_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130645_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130645_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830130646_add_verification_methods.cockroach.up.sql │ │ │ ├── 20200830130646_add_verification_methods.down.sql │ │ │ ├── 20200830130646_add_verification_methods.mysql.up.sql │ │ │ ├── 20200830130646_add_verification_methods.postgres.up.sql │ │ │ ├── 20200830130646_add_verification_methods.sqlite3.up.sql │ │ │ ├── 20200830154602_add_verification_token.cockroach.down.sql │ │ │ ├── 20200830154602_add_verification_token.cockroach.up.sql │ │ │ ├── 20200830154602_add_verification_token.mysql.down.sql │ │ │ ├── 20200830154602_add_verification_token.mysql.up.sql │ │ │ ├── 20200830154602_add_verification_token.postgres.down.sql │ │ │ ├── 20200830154602_add_verification_token.postgres.up.sql │ │ │ ├── 20200830154602_add_verification_token.sqlite3.down.sql │ │ │ ├── 20200830154602_add_verification_token.sqlite3.up.sql │ │ │ ├── 20200830172221_recovery_token_expires.cockroach.down.sql │ │ │ ├── 20200830172221_recovery_token_expires.cockroach.up.sql │ │ │ ├── 20200830172221_recovery_token_expires.mysql.down.sql │ │ │ ├── 20200830172221_recovery_token_expires.mysql.up.sql │ │ │ ├── 20200830172221_recovery_token_expires.postgres.down.sql │ │ │ ├── 20200830172221_recovery_token_expires.postgres.up.sql │ │ │ ├── 20200830172221_recovery_token_expires.sqlite3.down.sql │ │ │ ├── 20200830172221_recovery_token_expires.sqlite3.up.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.cockroach.down.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.cockroach.up.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.mysql.down.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.mysql.up.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.postgres.down.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.postgres.up.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ │ ├── 20200831110752_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ │ ├── 20201201161451_credential_types_values.cockroach.down.sql │ │ │ ├── 20201201161451_credential_types_values.cockroach.up.sql │ │ │ ├── 20201201161451_credential_types_values.mysql.down.sql │ │ │ ├── 20201201161451_credential_types_values.mysql.up.sql │ │ │ ├── 20201201161451_credential_types_values.postgres.down.sql │ │ │ ├── 20201201161451_credential_types_values.postgres.up.sql │ │ │ ├── 20201201161451_credential_types_values.sqlite3.down.sql │ │ │ └── 20201201161451_credential_types_values.sqlite3.up.sql │ │ └── sql/ │ │ ├── 20150100000001000000_networks.cockroach.down.sql │ │ ├── 20150100000001000000_networks.cockroach.up.sql │ │ ├── 20150100000001000000_networks.mysql.down.sql │ │ ├── 20150100000001000000_networks.mysql.up.sql │ │ ├── 20150100000001000000_networks.postgres.down.sql │ │ ├── 20150100000001000000_networks.postgres.up.sql │ │ ├── 20150100000001000000_networks.sqlite3.down.sql │ │ ├── 20150100000001000000_networks.sqlite3.up.sql │ │ ├── 20191100000001000000_identities.cockroach.down.sql │ │ ├── 20191100000001000000_identities.cockroach.up.sql │ │ ├── 20191100000001000000_identities.mysql.down.sql │ │ ├── 20191100000001000000_identities.mysql.up.sql │ │ ├── 20191100000001000000_identities.postgres.down.sql │ │ ├── 20191100000001000000_identities.postgres.up.sql │ │ ├── 20191100000001000000_identities.sqlite3.down.sql │ │ ├── 20191100000001000000_identities.sqlite3.up.sql │ │ ├── 20191100000001000001_identities.cockroach.down.sql │ │ ├── 20191100000001000001_identities.cockroach.up.sql │ │ ├── 20191100000001000001_identities.mysql.down.sql │ │ ├── 20191100000001000001_identities.mysql.up.sql │ │ ├── 20191100000001000001_identities.postgres.down.sql │ │ ├── 20191100000001000001_identities.postgres.up.sql │ │ ├── 20191100000001000001_identities.sqlite3.down.sql │ │ ├── 20191100000001000001_identities.sqlite3.up.sql │ │ ├── 20191100000001000002_identities.cockroach.down.sql │ │ ├── 20191100000001000002_identities.cockroach.up.sql │ │ ├── 20191100000001000002_identities.mysql.down.sql │ │ ├── 20191100000001000002_identities.mysql.up.sql │ │ ├── 20191100000001000002_identities.postgres.down.sql │ │ ├── 20191100000001000002_identities.postgres.up.sql │ │ ├── 20191100000001000002_identities.sqlite3.down.sql │ │ ├── 20191100000001000002_identities.sqlite3.up.sql │ │ ├── 20191100000001000003_identities.cockroach.down.sql │ │ ├── 20191100000001000003_identities.cockroach.up.sql │ │ ├── 20191100000001000003_identities.mysql.down.sql │ │ ├── 20191100000001000003_identities.mysql.up.sql │ │ ├── 20191100000001000003_identities.postgres.down.sql │ │ ├── 20191100000001000003_identities.postgres.up.sql │ │ ├── 20191100000001000003_identities.sqlite3.down.sql │ │ ├── 20191100000001000003_identities.sqlite3.up.sql │ │ ├── 20191100000001000004_identities.cockroach.down.sql │ │ ├── 20191100000001000004_identities.cockroach.up.sql │ │ ├── 20191100000001000004_identities.mysql.down.sql │ │ ├── 20191100000001000004_identities.mysql.up.sql │ │ ├── 20191100000001000004_identities.postgres.down.sql │ │ ├── 20191100000001000004_identities.postgres.up.sql │ │ ├── 20191100000001000004_identities.sqlite3.down.sql │ │ ├── 20191100000001000004_identities.sqlite3.up.sql │ │ ├── 20191100000001000005_identities.cockroach.down.sql │ │ ├── 20191100000001000005_identities.cockroach.up.sql │ │ ├── 20191100000001000005_identities.mysql.down.sql │ │ ├── 20191100000001000005_identities.mysql.up.sql │ │ ├── 20191100000001000005_identities.postgres.down.sql │ │ ├── 20191100000001000005_identities.postgres.up.sql │ │ ├── 20191100000001000005_identities.sqlite3.down.sql │ │ ├── 20191100000001000005_identities.sqlite3.up.sql │ │ ├── 20191100000002000000_requests.cockroach.down.sql │ │ ├── 20191100000002000000_requests.cockroach.up.sql │ │ ├── 20191100000002000000_requests.mysql.down.sql │ │ ├── 20191100000002000000_requests.mysql.up.sql │ │ ├── 20191100000002000000_requests.postgres.down.sql │ │ ├── 20191100000002000000_requests.postgres.up.sql │ │ ├── 20191100000002000000_requests.sqlite3.down.sql │ │ ├── 20191100000002000000_requests.sqlite3.up.sql │ │ ├── 20191100000002000001_requests.cockroach.down.sql │ │ ├── 20191100000002000001_requests.cockroach.up.sql │ │ ├── 20191100000002000001_requests.mysql.down.sql │ │ ├── 20191100000002000001_requests.mysql.up.sql │ │ ├── 20191100000002000001_requests.postgres.down.sql │ │ ├── 20191100000002000001_requests.postgres.up.sql │ │ ├── 20191100000002000001_requests.sqlite3.down.sql │ │ ├── 20191100000002000001_requests.sqlite3.up.sql │ │ ├── 20191100000002000002_requests.cockroach.down.sql │ │ ├── 20191100000002000002_requests.cockroach.up.sql │ │ ├── 20191100000002000002_requests.mysql.down.sql │ │ ├── 20191100000002000002_requests.mysql.up.sql │ │ ├── 20191100000002000002_requests.postgres.down.sql │ │ ├── 20191100000002000002_requests.postgres.up.sql │ │ ├── 20191100000002000002_requests.sqlite3.down.sql │ │ ├── 20191100000002000002_requests.sqlite3.up.sql │ │ ├── 20191100000002000003_requests.cockroach.down.sql │ │ ├── 20191100000002000003_requests.cockroach.up.sql │ │ ├── 20191100000002000003_requests.mysql.down.sql │ │ ├── 20191100000002000003_requests.mysql.up.sql │ │ ├── 20191100000002000003_requests.postgres.down.sql │ │ ├── 20191100000002000003_requests.postgres.up.sql │ │ ├── 20191100000002000003_requests.sqlite3.down.sql │ │ ├── 20191100000002000003_requests.sqlite3.up.sql │ │ ├── 20191100000002000004_requests.cockroach.down.sql │ │ ├── 20191100000002000004_requests.cockroach.up.sql │ │ ├── 20191100000002000004_requests.mysql.down.sql │ │ ├── 20191100000002000004_requests.mysql.up.sql │ │ ├── 20191100000002000004_requests.postgres.down.sql │ │ ├── 20191100000002000004_requests.postgres.up.sql │ │ ├── 20191100000002000004_requests.sqlite3.down.sql │ │ ├── 20191100000002000004_requests.sqlite3.up.sql │ │ ├── 20191100000003000000_sessions.cockroach.down.sql │ │ ├── 20191100000003000000_sessions.cockroach.up.sql │ │ ├── 20191100000003000000_sessions.mysql.down.sql │ │ ├── 20191100000003000000_sessions.mysql.up.sql │ │ ├── 20191100000003000000_sessions.postgres.down.sql │ │ ├── 20191100000003000000_sessions.postgres.up.sql │ │ ├── 20191100000003000000_sessions.sqlite3.down.sql │ │ ├── 20191100000003000000_sessions.sqlite3.up.sql │ │ ├── 20191100000004000000_errors.cockroach.down.sql │ │ ├── 20191100000004000000_errors.cockroach.up.sql │ │ ├── 20191100000004000000_errors.mysql.down.sql │ │ ├── 20191100000004000000_errors.mysql.up.sql │ │ ├── 20191100000004000000_errors.postgres.down.sql │ │ ├── 20191100000004000000_errors.postgres.up.sql │ │ ├── 20191100000004000000_errors.sqlite3.down.sql │ │ ├── 20191100000004000000_errors.sqlite3.up.sql │ │ ├── 20191100000005000000_identities.mysql.down.sql │ │ ├── 20191100000005000000_identities.mysql.up.sql │ │ ├── 20191100000005000001_identities.mysql.down.sql │ │ ├── 20191100000005000001_identities.mysql.up.sql │ │ ├── 20191100000006000000_courier.cockroach.down.sql │ │ ├── 20191100000006000000_courier.cockroach.up.sql │ │ ├── 20191100000006000000_courier.mysql.down.sql │ │ ├── 20191100000006000000_courier.mysql.up.sql │ │ ├── 20191100000006000000_courier.postgres.down.sql │ │ ├── 20191100000006000000_courier.postgres.up.sql │ │ ├── 20191100000006000000_courier.sqlite3.down.sql │ │ ├── 20191100000006000000_courier.sqlite3.up.sql │ │ ├── 20191100000007000000_errors.cockroach.down.sql │ │ ├── 20191100000007000000_errors.cockroach.up.sql │ │ ├── 20191100000007000000_errors.mysql.down.sql │ │ ├── 20191100000007000000_errors.mysql.up.sql │ │ ├── 20191100000007000000_errors.postgres.down.sql │ │ ├── 20191100000007000000_errors.postgres.up.sql │ │ ├── 20191100000007000000_errors.sqlite3.down.sql │ │ ├── 20191100000007000000_errors.sqlite3.up.sql │ │ ├── 20191100000007000001_errors.sqlite3.down.sql │ │ ├── 20191100000007000001_errors.sqlite3.up.sql │ │ ├── 20191100000007000002_errors.sqlite3.down.sql │ │ ├── 20191100000007000002_errors.sqlite3.up.sql │ │ ├── 20191100000007000003_errors.sqlite3.down.sql │ │ ├── 20191100000007000003_errors.sqlite3.up.sql │ │ ├── 20191100000008000000_selfservice_verification.cockroach.down.sql │ │ ├── 20191100000008000000_selfservice_verification.cockroach.up.sql │ │ ├── 20191100000008000000_selfservice_verification.mysql.down.sql │ │ ├── 20191100000008000000_selfservice_verification.mysql.up.sql │ │ ├── 20191100000008000000_selfservice_verification.postgres.down.sql │ │ ├── 20191100000008000000_selfservice_verification.postgres.up.sql │ │ ├── 20191100000008000000_selfservice_verification.sqlite3.down.sql │ │ ├── 20191100000008000000_selfservice_verification.sqlite3.up.sql │ │ ├── 20191100000008000001_selfservice_verification.cockroach.down.sql │ │ ├── 20191100000008000001_selfservice_verification.cockroach.up.sql │ │ ├── 20191100000008000001_selfservice_verification.mysql.down.sql │ │ ├── 20191100000008000001_selfservice_verification.mysql.up.sql │ │ ├── 20191100000008000001_selfservice_verification.postgres.down.sql │ │ ├── 20191100000008000001_selfservice_verification.postgres.up.sql │ │ ├── 20191100000008000001_selfservice_verification.sqlite3.down.sql │ │ ├── 20191100000008000001_selfservice_verification.sqlite3.up.sql │ │ ├── 20191100000008000002_selfservice_verification.cockroach.down.sql │ │ ├── 20191100000008000002_selfservice_verification.cockroach.up.sql │ │ ├── 20191100000008000002_selfservice_verification.mysql.down.sql │ │ ├── 20191100000008000002_selfservice_verification.mysql.up.sql │ │ ├── 20191100000008000002_selfservice_verification.postgres.down.sql │ │ ├── 20191100000008000002_selfservice_verification.postgres.up.sql │ │ ├── 20191100000008000002_selfservice_verification.sqlite3.down.sql │ │ ├── 20191100000008000002_selfservice_verification.sqlite3.up.sql │ │ ├── 20191100000008000003_selfservice_verification.cockroach.down.sql │ │ ├── 20191100000008000003_selfservice_verification.cockroach.up.sql │ │ ├── 20191100000008000003_selfservice_verification.mysql.down.sql │ │ ├── 20191100000008000003_selfservice_verification.mysql.up.sql │ │ ├── 20191100000008000003_selfservice_verification.postgres.down.sql │ │ ├── 20191100000008000003_selfservice_verification.postgres.up.sql │ │ ├── 20191100000008000003_selfservice_verification.sqlite3.down.sql │ │ ├── 20191100000008000003_selfservice_verification.sqlite3.up.sql │ │ ├── 20191100000008000004_selfservice_verification.cockroach.down.sql │ │ ├── 20191100000008000004_selfservice_verification.cockroach.up.sql │ │ ├── 20191100000008000004_selfservice_verification.mysql.down.sql │ │ ├── 20191100000008000004_selfservice_verification.mysql.up.sql │ │ ├── 20191100000008000004_selfservice_verification.postgres.down.sql │ │ ├── 20191100000008000004_selfservice_verification.postgres.up.sql │ │ ├── 20191100000008000004_selfservice_verification.sqlite3.down.sql │ │ ├── 20191100000008000004_selfservice_verification.sqlite3.up.sql │ │ ├── 20191100000008000005_selfservice_verification.cockroach.down.sql │ │ ├── 20191100000008000005_selfservice_verification.cockroach.up.sql │ │ ├── 20191100000008000005_selfservice_verification.mysql.down.sql │ │ ├── 20191100000008000005_selfservice_verification.mysql.up.sql │ │ ├── 20191100000008000005_selfservice_verification.postgres.down.sql │ │ ├── 20191100000008000005_selfservice_verification.postgres.up.sql │ │ ├── 20191100000008000005_selfservice_verification.sqlite3.down.sql │ │ ├── 20191100000008000005_selfservice_verification.sqlite3.up.sql │ │ ├── 20191100000009000000_verification.mysql.down.sql │ │ ├── 20191100000009000000_verification.mysql.up.sql │ │ ├── 20191100000009000001_verification.mysql.down.sql │ │ ├── 20191100000009000001_verification.mysql.up.sql │ │ ├── 20191100000010000000_errors.cockroach.down.sql │ │ ├── 20191100000010000000_errors.cockroach.up.sql │ │ ├── 20191100000010000000_errors.mysql.down.sql │ │ ├── 20191100000010000000_errors.mysql.up.sql │ │ ├── 20191100000010000000_errors.postgres.down.sql │ │ ├── 20191100000010000000_errors.postgres.up.sql │ │ ├── 20191100000010000000_errors.sqlite3.down.sql │ │ ├── 20191100000010000000_errors.sqlite3.up.sql │ │ ├── 20191100000010000001_errors.cockroach.down.sql │ │ ├── 20191100000010000001_errors.cockroach.up.sql │ │ ├── 20191100000010000001_errors.mysql.down.sql │ │ ├── 20191100000010000001_errors.mysql.up.sql │ │ ├── 20191100000010000001_errors.postgres.down.sql │ │ ├── 20191100000010000001_errors.postgres.up.sql │ │ ├── 20191100000010000001_errors.sqlite3.down.sql │ │ ├── 20191100000010000001_errors.sqlite3.up.sql │ │ ├── 20191100000010000002_errors.cockroach.down.sql │ │ ├── 20191100000010000002_errors.cockroach.up.sql │ │ ├── 20191100000010000002_errors.sqlite3.down.sql │ │ ├── 20191100000010000002_errors.sqlite3.up.sql │ │ ├── 20191100000010000003_errors.cockroach.down.sql │ │ ├── 20191100000010000003_errors.cockroach.up.sql │ │ ├── 20191100000010000003_errors.sqlite3.down.sql │ │ ├── 20191100000010000003_errors.sqlite3.up.sql │ │ ├── 20191100000010000004_errors.cockroach.down.sql │ │ ├── 20191100000010000004_errors.cockroach.up.sql │ │ ├── 20191100000010000004_errors.sqlite3.down.sql │ │ ├── 20191100000010000004_errors.sqlite3.up.sql │ │ ├── 20191100000011000000_courier_body_type.cockroach.down.sql │ │ ├── 20191100000011000000_courier_body_type.cockroach.up.sql │ │ ├── 20191100000011000000_courier_body_type.mysql.down.sql │ │ ├── 20191100000011000000_courier_body_type.mysql.up.sql │ │ ├── 20191100000011000000_courier_body_type.postgres.down.sql │ │ ├── 20191100000011000000_courier_body_type.postgres.up.sql │ │ ├── 20191100000011000000_courier_body_type.sqlite3.down.sql │ │ ├── 20191100000011000000_courier_body_type.sqlite3.up.sql │ │ ├── 20191100000011000001_courier_body_type.cockroach.down.sql │ │ ├── 20191100000011000001_courier_body_type.cockroach.up.sql │ │ ├── 20191100000011000001_courier_body_type.sqlite3.down.sql │ │ ├── 20191100000011000001_courier_body_type.sqlite3.up.sql │ │ ├── 20191100000011000002_courier_body_type.cockroach.down.sql │ │ ├── 20191100000011000002_courier_body_type.cockroach.up.sql │ │ ├── 20191100000011000002_courier_body_type.sqlite3.down.sql │ │ ├── 20191100000011000002_courier_body_type.sqlite3.up.sql │ │ ├── 20191100000011000003_courier_body_type.cockroach.down.sql │ │ ├── 20191100000011000003_courier_body_type.cockroach.up.sql │ │ ├── 20191100000011000003_courier_body_type.sqlite3.down.sql │ │ ├── 20191100000011000003_courier_body_type.sqlite3.up.sql │ │ ├── 20191100000011000004_courier_body_type.cockroach.down.sql │ │ ├── 20191100000011000004_courier_body_type.cockroach.up.sql │ │ ├── 20191100000012000000_login_request_forced.cockroach.down.sql │ │ ├── 20191100000012000000_login_request_forced.cockroach.up.sql │ │ ├── 20191100000012000000_login_request_forced.mysql.down.sql │ │ ├── 20191100000012000000_login_request_forced.mysql.up.sql │ │ ├── 20191100000012000000_login_request_forced.postgres.down.sql │ │ ├── 20191100000012000000_login_request_forced.postgres.up.sql │ │ ├── 20191100000012000000_login_request_forced.sqlite3.down.sql │ │ ├── 20191100000012000000_login_request_forced.sqlite3.up.sql │ │ ├── 20191100000012000001_login_request_forced.sqlite3.down.sql │ │ ├── 20191100000012000001_login_request_forced.sqlite3.up.sql │ │ ├── 20191100000012000002_login_request_forced.sqlite3.down.sql │ │ ├── 20191100000012000002_login_request_forced.sqlite3.up.sql │ │ ├── 20191100000012000003_login_request_forced.sqlite3.down.sql │ │ ├── 20191100000012000003_login_request_forced.sqlite3.up.sql │ │ ├── 20200317160354000000_create_profile_request_forms.cockroach.down.sql │ │ ├── 20200317160354000000_create_profile_request_forms.cockroach.up.sql │ │ ├── 20200317160354000000_create_profile_request_forms.mysql.down.sql │ │ ├── 20200317160354000000_create_profile_request_forms.mysql.up.sql │ │ ├── 20200317160354000000_create_profile_request_forms.postgres.down.sql │ │ ├── 20200317160354000000_create_profile_request_forms.postgres.up.sql │ │ ├── 20200317160354000000_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000000_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000001_create_profile_request_forms.cockroach.down.sql │ │ ├── 20200317160354000001_create_profile_request_forms.cockroach.up.sql │ │ ├── 20200317160354000001_create_profile_request_forms.mysql.down.sql │ │ ├── 20200317160354000001_create_profile_request_forms.mysql.up.sql │ │ ├── 20200317160354000001_create_profile_request_forms.postgres.down.sql │ │ ├── 20200317160354000001_create_profile_request_forms.postgres.up.sql │ │ ├── 20200317160354000001_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000001_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000002_create_profile_request_forms.cockroach.down.sql │ │ ├── 20200317160354000002_create_profile_request_forms.cockroach.up.sql │ │ ├── 20200317160354000002_create_profile_request_forms.mysql.down.sql │ │ ├── 20200317160354000002_create_profile_request_forms.mysql.up.sql │ │ ├── 20200317160354000002_create_profile_request_forms.postgres.down.sql │ │ ├── 20200317160354000002_create_profile_request_forms.postgres.up.sql │ │ ├── 20200317160354000002_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000002_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000003_create_profile_request_forms.cockroach.down.sql │ │ ├── 20200317160354000003_create_profile_request_forms.cockroach.up.sql │ │ ├── 20200317160354000003_create_profile_request_forms.mysql.down.sql │ │ ├── 20200317160354000003_create_profile_request_forms.mysql.up.sql │ │ ├── 20200317160354000003_create_profile_request_forms.postgres.down.sql │ │ ├── 20200317160354000003_create_profile_request_forms.postgres.up.sql │ │ ├── 20200317160354000003_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000003_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000004_create_profile_request_forms.mysql.down.sql │ │ ├── 20200317160354000004_create_profile_request_forms.mysql.up.sql │ │ ├── 20200317160354000004_create_profile_request_forms.postgres.down.sql │ │ ├── 20200317160354000004_create_profile_request_forms.postgres.up.sql │ │ ├── 20200317160354000004_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000004_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000005_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000005_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000006_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000006_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000007_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000007_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000008_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000008_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000009_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000009_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200317160354000010_create_profile_request_forms.sqlite3.down.sql │ │ ├── 20200317160354000010_create_profile_request_forms.sqlite3.up.sql │ │ ├── 20200401183443000000_continuity_containers.cockroach.down.sql │ │ ├── 20200401183443000000_continuity_containers.cockroach.up.sql │ │ ├── 20200401183443000000_continuity_containers.mysql.down.sql │ │ ├── 20200401183443000000_continuity_containers.mysql.up.sql │ │ ├── 20200401183443000000_continuity_containers.postgres.down.sql │ │ ├── 20200401183443000000_continuity_containers.postgres.up.sql │ │ ├── 20200401183443000000_continuity_containers.sqlite3.down.sql │ │ ├── 20200401183443000000_continuity_containers.sqlite3.up.sql │ │ ├── 20200402142539000000_rename_profile_flows.cockroach.down.sql │ │ ├── 20200402142539000000_rename_profile_flows.cockroach.up.sql │ │ ├── 20200402142539000000_rename_profile_flows.mysql.down.sql │ │ ├── 20200402142539000000_rename_profile_flows.mysql.up.sql │ │ ├── 20200402142539000000_rename_profile_flows.postgres.down.sql │ │ ├── 20200402142539000000_rename_profile_flows.postgres.up.sql │ │ ├── 20200402142539000000_rename_profile_flows.sqlite3.down.sql │ │ ├── 20200402142539000000_rename_profile_flows.sqlite3.up.sql │ │ ├── 20200402142539000001_rename_profile_flows.cockroach.down.sql │ │ ├── 20200402142539000001_rename_profile_flows.cockroach.up.sql │ │ ├── 20200402142539000001_rename_profile_flows.mysql.down.sql │ │ ├── 20200402142539000001_rename_profile_flows.mysql.up.sql │ │ ├── 20200402142539000001_rename_profile_flows.postgres.down.sql │ │ ├── 20200402142539000001_rename_profile_flows.postgres.up.sql │ │ ├── 20200402142539000001_rename_profile_flows.sqlite3.down.sql │ │ ├── 20200402142539000001_rename_profile_flows.sqlite3.up.sql │ │ ├── 20200402142539000002_rename_profile_flows.cockroach.down.sql │ │ ├── 20200402142539000002_rename_profile_flows.cockroach.up.sql │ │ ├── 20200402142539000002_rename_profile_flows.mysql.down.sql │ │ ├── 20200402142539000002_rename_profile_flows.mysql.up.sql │ │ ├── 20200402142539000002_rename_profile_flows.postgres.down.sql │ │ ├── 20200402142539000002_rename_profile_flows.postgres.up.sql │ │ ├── 20200402142539000002_rename_profile_flows.sqlite3.down.sql │ │ ├── 20200402142539000002_rename_profile_flows.sqlite3.up.sql │ │ ├── 20200519101057000000_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000000_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000000_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000000_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000000_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000000_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000000_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000000_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000001_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000001_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000001_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000001_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000001_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000001_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000001_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000001_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000002_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000002_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000002_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000002_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000002_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000002_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000002_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000002_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000003_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000003_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000003_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000003_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000003_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000003_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000003_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000003_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000004_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000004_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000004_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000004_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000004_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000004_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000004_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000004_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000005_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000005_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000005_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000005_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000005_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000005_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000005_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000005_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000006_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000006_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000006_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000006_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000006_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000006_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000006_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000006_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101057000007_create_recovery_addresses.cockroach.down.sql │ │ ├── 20200519101057000007_create_recovery_addresses.cockroach.up.sql │ │ ├── 20200519101057000007_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101057000007_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101057000007_create_recovery_addresses.postgres.down.sql │ │ ├── 20200519101057000007_create_recovery_addresses.postgres.up.sql │ │ ├── 20200519101057000007_create_recovery_addresses.sqlite3.down.sql │ │ ├── 20200519101057000007_create_recovery_addresses.sqlite3.up.sql │ │ ├── 20200519101058000000_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101058000000_create_recovery_addresses.mysql.up.sql │ │ ├── 20200519101058000001_create_recovery_addresses.mysql.down.sql │ │ ├── 20200519101058000001_create_recovery_addresses.mysql.up.sql │ │ ├── 20200601101000000000_create_messages.cockroach.down.sql │ │ ├── 20200601101000000000_create_messages.cockroach.up.sql │ │ ├── 20200601101000000000_create_messages.mysql.down.sql │ │ ├── 20200601101000000000_create_messages.mysql.up.sql │ │ ├── 20200601101000000000_create_messages.postgres.down.sql │ │ ├── 20200601101000000000_create_messages.postgres.up.sql │ │ ├── 20200601101000000000_create_messages.sqlite3.down.sql │ │ ├── 20200601101000000000_create_messages.sqlite3.up.sql │ │ ├── 20200601101000000001_create_messages.sqlite3.down.sql │ │ ├── 20200601101000000001_create_messages.sqlite3.up.sql │ │ ├── 20200601101000000002_create_messages.sqlite3.down.sql │ │ ├── 20200601101000000002_create_messages.sqlite3.up.sql │ │ ├── 20200601101000000003_create_messages.sqlite3.down.sql │ │ ├── 20200601101000000003_create_messages.sqlite3.up.sql │ │ ├── 20200601101001000000_verification.mysql.down.sql │ │ ├── 20200601101001000000_verification.mysql.up.sql │ │ ├── 20200601101001000001_verification.mysql.down.sql │ │ ├── 20200601101001000001_verification.mysql.up.sql │ │ ├── 20200605111551000000_messages.cockroach.down.sql │ │ ├── 20200605111551000000_messages.cockroach.up.sql │ │ ├── 20200605111551000000_messages.mysql.down.sql │ │ ├── 20200605111551000000_messages.mysql.up.sql │ │ ├── 20200605111551000000_messages.postgres.down.sql │ │ ├── 20200605111551000000_messages.postgres.up.sql │ │ ├── 20200605111551000000_messages.sqlite3.down.sql │ │ ├── 20200605111551000000_messages.sqlite3.up.sql │ │ ├── 20200605111551000001_messages.cockroach.down.sql │ │ ├── 20200605111551000001_messages.cockroach.up.sql │ │ ├── 20200605111551000001_messages.mysql.down.sql │ │ ├── 20200605111551000001_messages.mysql.up.sql │ │ ├── 20200605111551000001_messages.postgres.down.sql │ │ ├── 20200605111551000001_messages.postgres.up.sql │ │ ├── 20200605111551000001_messages.sqlite3.down.sql │ │ ├── 20200605111551000001_messages.sqlite3.up.sql │ │ ├── 20200605111551000002_messages.cockroach.down.sql │ │ ├── 20200605111551000002_messages.cockroach.up.sql │ │ ├── 20200605111551000002_messages.mysql.down.sql │ │ ├── 20200605111551000002_messages.mysql.up.sql │ │ ├── 20200605111551000002_messages.postgres.down.sql │ │ ├── 20200605111551000002_messages.postgres.up.sql │ │ ├── 20200605111551000002_messages.sqlite3.down.sql │ │ ├── 20200605111551000002_messages.sqlite3.up.sql │ │ ├── 20200605111551000003_messages.sqlite3.down.sql │ │ ├── 20200605111551000003_messages.sqlite3.up.sql │ │ ├── 20200605111551000004_messages.sqlite3.down.sql │ │ ├── 20200605111551000004_messages.sqlite3.up.sql │ │ ├── 20200605111551000005_messages.sqlite3.down.sql │ │ ├── 20200605111551000005_messages.sqlite3.up.sql │ │ ├── 20200605111551000006_messages.sqlite3.down.sql │ │ ├── 20200605111551000006_messages.sqlite3.up.sql │ │ ├── 20200605111551000007_messages.sqlite3.down.sql │ │ ├── 20200605111551000007_messages.sqlite3.up.sql │ │ ├── 20200605111551000008_messages.sqlite3.down.sql │ │ ├── 20200605111551000008_messages.sqlite3.up.sql │ │ ├── 20200605111551000009_messages.sqlite3.down.sql │ │ ├── 20200605111551000009_messages.sqlite3.up.sql │ │ ├── 20200605111551000010_messages.sqlite3.down.sql │ │ ├── 20200605111551000010_messages.sqlite3.up.sql │ │ ├── 20200605111551000011_messages.sqlite3.down.sql │ │ ├── 20200605111551000011_messages.sqlite3.up.sql │ │ ├── 20200605111551000012_messages.sqlite3.down.sql │ │ ├── 20200605111551000012_messages.sqlite3.up.sql │ │ ├── 20200605111551000013_messages.sqlite3.down.sql │ │ ├── 20200605111551000013_messages.sqlite3.up.sql │ │ ├── 20200607165100000000_settings.cockroach.down.sql │ │ ├── 20200607165100000000_settings.cockroach.up.sql │ │ ├── 20200607165100000000_settings.mysql.down.sql │ │ ├── 20200607165100000000_settings.mysql.up.sql │ │ ├── 20200607165100000000_settings.postgres.down.sql │ │ ├── 20200607165100000000_settings.postgres.up.sql │ │ ├── 20200607165100000000_settings.sqlite3.down.sql │ │ ├── 20200607165100000000_settings.sqlite3.up.sql │ │ ├── 20200607165100000001_settings.cockroach.down.sql │ │ ├── 20200607165100000001_settings.cockroach.up.sql │ │ ├── 20200607165100000001_settings.mysql.down.sql │ │ ├── 20200607165100000001_settings.mysql.up.sql │ │ ├── 20200607165100000001_settings.postgres.down.sql │ │ ├── 20200607165100000001_settings.postgres.up.sql │ │ ├── 20200607165100000001_settings.sqlite3.down.sql │ │ ├── 20200607165100000001_settings.sqlite3.up.sql │ │ ├── 20200607165100000002_settings.sqlite3.down.sql │ │ ├── 20200607165100000002_settings.sqlite3.up.sql │ │ ├── 20200607165100000003_settings.sqlite3.down.sql │ │ ├── 20200607165100000003_settings.sqlite3.up.sql │ │ ├── 20200607165100000004_settings.sqlite3.down.sql │ │ ├── 20200607165100000004_settings.sqlite3.up.sql │ │ ├── 20200705105359000000_rename_identities_schema.cockroach.down.sql │ │ ├── 20200705105359000000_rename_identities_schema.cockroach.up.sql │ │ ├── 20200705105359000000_rename_identities_schema.mysql.down.sql │ │ ├── 20200705105359000000_rename_identities_schema.mysql.up.sql │ │ ├── 20200705105359000000_rename_identities_schema.postgres.down.sql │ │ ├── 20200705105359000000_rename_identities_schema.postgres.up.sql │ │ ├── 20200705105359000000_rename_identities_schema.sqlite3.down.sql │ │ ├── 20200705105359000000_rename_identities_schema.sqlite3.up.sql │ │ ├── 20200810141652000000_flow_type.cockroach.down.sql │ │ ├── 20200810141652000000_flow_type.cockroach.up.sql │ │ ├── 20200810141652000000_flow_type.mysql.down.sql │ │ ├── 20200810141652000000_flow_type.mysql.up.sql │ │ ├── 20200810141652000000_flow_type.postgres.down.sql │ │ ├── 20200810141652000000_flow_type.postgres.up.sql │ │ ├── 20200810141652000000_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000000_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000001_flow_type.cockroach.down.sql │ │ ├── 20200810141652000001_flow_type.cockroach.up.sql │ │ ├── 20200810141652000001_flow_type.mysql.down.sql │ │ ├── 20200810141652000001_flow_type.mysql.up.sql │ │ ├── 20200810141652000001_flow_type.postgres.down.sql │ │ ├── 20200810141652000001_flow_type.postgres.up.sql │ │ ├── 20200810141652000001_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000001_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000002_flow_type.cockroach.down.sql │ │ ├── 20200810141652000002_flow_type.cockroach.up.sql │ │ ├── 20200810141652000002_flow_type.mysql.down.sql │ │ ├── 20200810141652000002_flow_type.mysql.up.sql │ │ ├── 20200810141652000002_flow_type.postgres.down.sql │ │ ├── 20200810141652000002_flow_type.postgres.up.sql │ │ ├── 20200810141652000002_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000002_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000003_flow_type.cockroach.down.sql │ │ ├── 20200810141652000003_flow_type.cockroach.up.sql │ │ ├── 20200810141652000003_flow_type.mysql.down.sql │ │ ├── 20200810141652000003_flow_type.mysql.up.sql │ │ ├── 20200810141652000003_flow_type.postgres.down.sql │ │ ├── 20200810141652000003_flow_type.postgres.up.sql │ │ ├── 20200810141652000003_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000003_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000004_flow_type.cockroach.down.sql │ │ ├── 20200810141652000004_flow_type.cockroach.up.sql │ │ ├── 20200810141652000004_flow_type.mysql.down.sql │ │ ├── 20200810141652000004_flow_type.mysql.up.sql │ │ ├── 20200810141652000004_flow_type.postgres.down.sql │ │ ├── 20200810141652000004_flow_type.postgres.up.sql │ │ ├── 20200810141652000004_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000004_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000005_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000005_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000006_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000006_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000007_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000007_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000008_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000008_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000009_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000009_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000010_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000010_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000011_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000011_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000012_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000012_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000013_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000013_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000014_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000014_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000015_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000015_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000016_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000016_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000017_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000017_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000018_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000018_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000019_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000019_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000020_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000020_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000021_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000021_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000022_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000022_flow_type.sqlite3.up.sql │ │ ├── 20200810141652000023_flow_type.sqlite3.down.sql │ │ ├── 20200810141652000023_flow_type.sqlite3.up.sql │ │ ├── 20200810161022000000_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000000_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000000_flow_rename.mysql.down.sql │ │ ├── 20200810161022000000_flow_rename.mysql.up.sql │ │ ├── 20200810161022000000_flow_rename.postgres.down.sql │ │ ├── 20200810161022000000_flow_rename.postgres.up.sql │ │ ├── 20200810161022000000_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000000_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000001_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000001_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000001_flow_rename.mysql.down.sql │ │ ├── 20200810161022000001_flow_rename.mysql.up.sql │ │ ├── 20200810161022000001_flow_rename.postgres.down.sql │ │ ├── 20200810161022000001_flow_rename.postgres.up.sql │ │ ├── 20200810161022000001_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000001_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000002_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000002_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000002_flow_rename.mysql.down.sql │ │ ├── 20200810161022000002_flow_rename.mysql.up.sql │ │ ├── 20200810161022000002_flow_rename.postgres.down.sql │ │ ├── 20200810161022000002_flow_rename.postgres.up.sql │ │ ├── 20200810161022000002_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000002_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000003_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000003_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000003_flow_rename.mysql.down.sql │ │ ├── 20200810161022000003_flow_rename.mysql.up.sql │ │ ├── 20200810161022000003_flow_rename.postgres.down.sql │ │ ├── 20200810161022000003_flow_rename.postgres.up.sql │ │ ├── 20200810161022000003_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000003_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000004_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000004_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000004_flow_rename.mysql.down.sql │ │ ├── 20200810161022000004_flow_rename.mysql.up.sql │ │ ├── 20200810161022000004_flow_rename.postgres.down.sql │ │ ├── 20200810161022000004_flow_rename.postgres.up.sql │ │ ├── 20200810161022000004_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000004_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000005_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000005_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000005_flow_rename.mysql.down.sql │ │ ├── 20200810161022000005_flow_rename.mysql.up.sql │ │ ├── 20200810161022000005_flow_rename.postgres.down.sql │ │ ├── 20200810161022000005_flow_rename.postgres.up.sql │ │ ├── 20200810161022000005_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000005_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000006_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000006_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000006_flow_rename.mysql.down.sql │ │ ├── 20200810161022000006_flow_rename.mysql.up.sql │ │ ├── 20200810161022000006_flow_rename.postgres.down.sql │ │ ├── 20200810161022000006_flow_rename.postgres.up.sql │ │ ├── 20200810161022000006_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000006_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000007_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000007_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000007_flow_rename.mysql.down.sql │ │ ├── 20200810161022000007_flow_rename.mysql.up.sql │ │ ├── 20200810161022000007_flow_rename.postgres.down.sql │ │ ├── 20200810161022000007_flow_rename.postgres.up.sql │ │ ├── 20200810161022000007_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000007_flow_rename.sqlite3.up.sql │ │ ├── 20200810161022000008_flow_rename.cockroach.down.sql │ │ ├── 20200810161022000008_flow_rename.cockroach.up.sql │ │ ├── 20200810161022000008_flow_rename.mysql.down.sql │ │ ├── 20200810161022000008_flow_rename.mysql.up.sql │ │ ├── 20200810161022000008_flow_rename.postgres.down.sql │ │ ├── 20200810161022000008_flow_rename.postgres.up.sql │ │ ├── 20200810161022000008_flow_rename.sqlite3.down.sql │ │ ├── 20200810161022000008_flow_rename.sqlite3.up.sql │ │ ├── 20200810162450000000_flow_fields_rename.cockroach.down.sql │ │ ├── 20200810162450000000_flow_fields_rename.cockroach.up.sql │ │ ├── 20200810162450000000_flow_fields_rename.mysql.down.sql │ │ ├── 20200810162450000000_flow_fields_rename.mysql.up.sql │ │ ├── 20200810162450000000_flow_fields_rename.postgres.down.sql │ │ ├── 20200810162450000000_flow_fields_rename.postgres.up.sql │ │ ├── 20200810162450000000_flow_fields_rename.sqlite3.down.sql │ │ ├── 20200810162450000000_flow_fields_rename.sqlite3.up.sql │ │ ├── 20200810162450000001_flow_fields_rename.cockroach.down.sql │ │ ├── 20200810162450000001_flow_fields_rename.cockroach.up.sql │ │ ├── 20200810162450000001_flow_fields_rename.mysql.down.sql │ │ ├── 20200810162450000001_flow_fields_rename.mysql.up.sql │ │ ├── 20200810162450000001_flow_fields_rename.postgres.down.sql │ │ ├── 20200810162450000001_flow_fields_rename.postgres.up.sql │ │ ├── 20200810162450000001_flow_fields_rename.sqlite3.down.sql │ │ ├── 20200810162450000001_flow_fields_rename.sqlite3.up.sql │ │ ├── 20200810162450000002_flow_fields_rename.cockroach.down.sql │ │ ├── 20200810162450000002_flow_fields_rename.cockroach.up.sql │ │ ├── 20200810162450000002_flow_fields_rename.mysql.down.sql │ │ ├── 20200810162450000002_flow_fields_rename.mysql.up.sql │ │ ├── 20200810162450000002_flow_fields_rename.postgres.down.sql │ │ ├── 20200810162450000002_flow_fields_rename.postgres.up.sql │ │ ├── 20200810162450000002_flow_fields_rename.sqlite3.down.sql │ │ ├── 20200810162450000002_flow_fields_rename.sqlite3.up.sql │ │ ├── 20200810162450000003_flow_fields_rename.cockroach.down.sql │ │ ├── 20200810162450000003_flow_fields_rename.cockroach.up.sql │ │ ├── 20200810162450000003_flow_fields_rename.mysql.down.sql │ │ ├── 20200810162450000003_flow_fields_rename.mysql.up.sql │ │ ├── 20200810162450000003_flow_fields_rename.postgres.down.sql │ │ ├── 20200810162450000003_flow_fields_rename.postgres.up.sql │ │ ├── 20200810162450000003_flow_fields_rename.sqlite3.down.sql │ │ ├── 20200810162450000003_flow_fields_rename.sqlite3.up.sql │ │ ├── 20200812124254000000_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000000_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000000_add_session_token.mysql.down.sql │ │ ├── 20200812124254000000_add_session_token.mysql.up.sql │ │ ├── 20200812124254000000_add_session_token.postgres.down.sql │ │ ├── 20200812124254000000_add_session_token.postgres.up.sql │ │ ├── 20200812124254000000_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000000_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000001_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000001_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000001_add_session_token.mysql.down.sql │ │ ├── 20200812124254000001_add_session_token.mysql.up.sql │ │ ├── 20200812124254000001_add_session_token.postgres.down.sql │ │ ├── 20200812124254000001_add_session_token.postgres.up.sql │ │ ├── 20200812124254000001_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000001_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000002_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000002_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000002_add_session_token.mysql.down.sql │ │ ├── 20200812124254000002_add_session_token.mysql.up.sql │ │ ├── 20200812124254000002_add_session_token.postgres.down.sql │ │ ├── 20200812124254000002_add_session_token.postgres.up.sql │ │ ├── 20200812124254000002_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000002_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000003_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000003_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000003_add_session_token.mysql.down.sql │ │ ├── 20200812124254000003_add_session_token.mysql.up.sql │ │ ├── 20200812124254000003_add_session_token.postgres.down.sql │ │ ├── 20200812124254000003_add_session_token.postgres.up.sql │ │ ├── 20200812124254000003_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000003_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000004_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000004_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000004_add_session_token.mysql.down.sql │ │ ├── 20200812124254000004_add_session_token.mysql.up.sql │ │ ├── 20200812124254000004_add_session_token.postgres.down.sql │ │ ├── 20200812124254000004_add_session_token.postgres.up.sql │ │ ├── 20200812124254000004_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000004_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000005_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000005_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000005_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000005_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000006_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000006_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000006_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000006_add_session_token.sqlite3.up.sql │ │ ├── 20200812124254000007_add_session_token.cockroach.down.sql │ │ ├── 20200812124254000007_add_session_token.cockroach.up.sql │ │ ├── 20200812124254000007_add_session_token.sqlite3.down.sql │ │ ├── 20200812124254000007_add_session_token.sqlite3.up.sql │ │ ├── 20200812160551000000_add_session_revoke.cockroach.down.sql │ │ ├── 20200812160551000000_add_session_revoke.cockroach.up.sql │ │ ├── 20200812160551000000_add_session_revoke.mysql.down.sql │ │ ├── 20200812160551000000_add_session_revoke.mysql.up.sql │ │ ├── 20200812160551000000_add_session_revoke.postgres.down.sql │ │ ├── 20200812160551000000_add_session_revoke.postgres.up.sql │ │ ├── 20200812160551000000_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000000_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000001_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000001_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000002_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000002_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000003_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000003_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000004_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000004_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000005_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000005_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000006_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000006_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000007_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000007_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000008_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000008_add_session_revoke.sqlite3.up.sql │ │ ├── 20200812160551000009_add_session_revoke.sqlite3.down.sql │ │ ├── 20200812160551000009_add_session_revoke.sqlite3.up.sql │ │ ├── 20200830121710000000_update_recovery_token.cockroach.down.sql │ │ ├── 20200830121710000000_update_recovery_token.cockroach.up.sql │ │ ├── 20200830121710000000_update_recovery_token.mysql.down.sql │ │ ├── 20200830121710000000_update_recovery_token.mysql.up.sql │ │ ├── 20200830121710000000_update_recovery_token.postgres.down.sql │ │ ├── 20200830121710000000_update_recovery_token.postgres.up.sql │ │ ├── 20200830121710000000_update_recovery_token.sqlite3.down.sql │ │ ├── 20200830121710000000_update_recovery_token.sqlite3.up.sql │ │ ├── 20200830130642000000_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130642000000_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130642000000_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000000_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000000_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000000_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000000_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000000_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000001_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130642000001_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130642000001_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000001_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000001_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000001_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000001_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000001_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000002_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130642000002_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130642000002_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000002_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000002_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000002_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000002_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000002_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000003_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130642000003_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130642000003_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000003_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000003_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000003_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000003_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000003_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000004_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130642000004_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130642000004_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000004_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000004_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000004_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000004_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000004_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000005_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130642000005_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130642000005_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000005_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000005_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000005_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000005_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000005_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000006_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000006_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000006_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000006_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000006_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000006_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000007_add_verification_methods.mysql.down.sql │ │ ├── 20200830130642000007_add_verification_methods.mysql.up.sql │ │ ├── 20200830130642000007_add_verification_methods.postgres.down.sql │ │ ├── 20200830130642000007_add_verification_methods.postgres.up.sql │ │ ├── 20200830130642000007_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000007_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000008_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000008_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000009_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000009_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000010_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000010_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000011_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000011_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000012_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000012_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000013_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000013_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000014_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000014_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000015_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000015_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000016_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000016_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000017_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000017_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000018_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000018_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000019_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000019_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000020_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000020_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000021_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000021_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130642000022_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130642000022_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130643000000_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130643000000_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130643000000_add_verification_methods.mysql.down.sql │ │ ├── 20200830130643000000_add_verification_methods.mysql.up.sql │ │ ├── 20200830130643000000_add_verification_methods.postgres.down.sql │ │ ├── 20200830130643000000_add_verification_methods.postgres.up.sql │ │ ├── 20200830130643000000_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130643000000_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130644000000_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130644000000_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130644000000_add_verification_methods.mysql.down.sql │ │ ├── 20200830130644000000_add_verification_methods.mysql.up.sql │ │ ├── 20200830130644000000_add_verification_methods.postgres.down.sql │ │ ├── 20200830130644000000_add_verification_methods.postgres.up.sql │ │ ├── 20200830130644000000_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130644000000_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130644000001_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130644000001_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130644000001_add_verification_methods.mysql.down.sql │ │ ├── 20200830130644000001_add_verification_methods.mysql.up.sql │ │ ├── 20200830130644000001_add_verification_methods.postgres.down.sql │ │ ├── 20200830130644000001_add_verification_methods.postgres.up.sql │ │ ├── 20200830130644000001_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130644000001_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130645000000_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130645000000_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130645000000_add_verification_methods.mysql.down.sql │ │ ├── 20200830130645000000_add_verification_methods.mysql.up.sql │ │ ├── 20200830130645000000_add_verification_methods.postgres.down.sql │ │ ├── 20200830130645000000_add_verification_methods.postgres.up.sql │ │ ├── 20200830130645000000_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130645000000_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000000_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130646000000_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130646000000_add_verification_methods.mysql.down.sql │ │ ├── 20200830130646000000_add_verification_methods.mysql.up.sql │ │ ├── 20200830130646000000_add_verification_methods.postgres.down.sql │ │ ├── 20200830130646000000_add_verification_methods.postgres.up.sql │ │ ├── 20200830130646000000_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000000_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000001_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130646000001_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130646000001_add_verification_methods.mysql.down.sql │ │ ├── 20200830130646000001_add_verification_methods.mysql.up.sql │ │ ├── 20200830130646000001_add_verification_methods.postgres.down.sql │ │ ├── 20200830130646000001_add_verification_methods.postgres.up.sql │ │ ├── 20200830130646000001_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000001_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000002_add_verification_methods.cockroach.down.sql │ │ ├── 20200830130646000002_add_verification_methods.cockroach.up.sql │ │ ├── 20200830130646000002_add_verification_methods.mysql.down.sql │ │ ├── 20200830130646000002_add_verification_methods.mysql.up.sql │ │ ├── 20200830130646000002_add_verification_methods.postgres.down.sql │ │ ├── 20200830130646000002_add_verification_methods.postgres.up.sql │ │ ├── 20200830130646000002_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000002_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000003_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000003_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000004_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000004_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000005_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000005_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000006_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000006_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000007_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000007_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000008_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000008_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000009_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000009_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000010_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000010_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830130646000011_add_verification_methods.sqlite3.down.sql │ │ ├── 20200830130646000011_add_verification_methods.sqlite3.up.sql │ │ ├── 20200830154602000000_add_verification_token.cockroach.down.sql │ │ ├── 20200830154602000000_add_verification_token.cockroach.up.sql │ │ ├── 20200830154602000000_add_verification_token.mysql.down.sql │ │ ├── 20200830154602000000_add_verification_token.mysql.up.sql │ │ ├── 20200830154602000000_add_verification_token.postgres.down.sql │ │ ├── 20200830154602000000_add_verification_token.postgres.up.sql │ │ ├── 20200830154602000000_add_verification_token.sqlite3.down.sql │ │ ├── 20200830154602000000_add_verification_token.sqlite3.up.sql │ │ ├── 20200830154602000001_add_verification_token.cockroach.down.sql │ │ ├── 20200830154602000001_add_verification_token.cockroach.up.sql │ │ ├── 20200830154602000001_add_verification_token.mysql.down.sql │ │ ├── 20200830154602000001_add_verification_token.mysql.up.sql │ │ ├── 20200830154602000001_add_verification_token.postgres.down.sql │ │ ├── 20200830154602000001_add_verification_token.postgres.up.sql │ │ ├── 20200830154602000001_add_verification_token.sqlite3.down.sql │ │ ├── 20200830154602000001_add_verification_token.sqlite3.up.sql │ │ ├── 20200830154602000002_add_verification_token.cockroach.down.sql │ │ ├── 20200830154602000002_add_verification_token.cockroach.up.sql │ │ ├── 20200830154602000002_add_verification_token.mysql.down.sql │ │ ├── 20200830154602000002_add_verification_token.mysql.up.sql │ │ ├── 20200830154602000002_add_verification_token.postgres.down.sql │ │ ├── 20200830154602000002_add_verification_token.postgres.up.sql │ │ ├── 20200830154602000002_add_verification_token.sqlite3.down.sql │ │ ├── 20200830154602000002_add_verification_token.sqlite3.up.sql │ │ ├── 20200830154602000003_add_verification_token.cockroach.down.sql │ │ ├── 20200830154602000003_add_verification_token.cockroach.up.sql │ │ ├── 20200830154602000003_add_verification_token.mysql.down.sql │ │ ├── 20200830154602000003_add_verification_token.mysql.up.sql │ │ ├── 20200830154602000003_add_verification_token.postgres.down.sql │ │ ├── 20200830154602000003_add_verification_token.postgres.up.sql │ │ ├── 20200830154602000003_add_verification_token.sqlite3.down.sql │ │ ├── 20200830154602000003_add_verification_token.sqlite3.up.sql │ │ ├── 20200830154602000004_add_verification_token.cockroach.down.sql │ │ ├── 20200830154602000004_add_verification_token.cockroach.up.sql │ │ ├── 20200830154602000004_add_verification_token.mysql.down.sql │ │ ├── 20200830154602000004_add_verification_token.mysql.up.sql │ │ ├── 20200830154602000004_add_verification_token.postgres.down.sql │ │ ├── 20200830154602000004_add_verification_token.postgres.up.sql │ │ ├── 20200830154602000004_add_verification_token.sqlite3.down.sql │ │ ├── 20200830154602000004_add_verification_token.sqlite3.up.sql │ │ ├── 20200830172221000000_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000000_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000000_recovery_token_expires.mysql.down.sql │ │ ├── 20200830172221000000_recovery_token_expires.mysql.up.sql │ │ ├── 20200830172221000000_recovery_token_expires.postgres.down.sql │ │ ├── 20200830172221000000_recovery_token_expires.postgres.up.sql │ │ ├── 20200830172221000000_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000000_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000001_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000001_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000001_recovery_token_expires.mysql.down.sql │ │ ├── 20200830172221000001_recovery_token_expires.mysql.up.sql │ │ ├── 20200830172221000001_recovery_token_expires.postgres.down.sql │ │ ├── 20200830172221000001_recovery_token_expires.postgres.up.sql │ │ ├── 20200830172221000001_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000001_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000002_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000002_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000002_recovery_token_expires.mysql.down.sql │ │ ├── 20200830172221000002_recovery_token_expires.mysql.up.sql │ │ ├── 20200830172221000002_recovery_token_expires.postgres.down.sql │ │ ├── 20200830172221000002_recovery_token_expires.postgres.up.sql │ │ ├── 20200830172221000002_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000002_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000003_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000003_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000003_recovery_token_expires.mysql.down.sql │ │ ├── 20200830172221000003_recovery_token_expires.mysql.up.sql │ │ ├── 20200830172221000003_recovery_token_expires.postgres.down.sql │ │ ├── 20200830172221000003_recovery_token_expires.postgres.up.sql │ │ ├── 20200830172221000003_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000003_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000004_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000004_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000004_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000004_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000005_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000005_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000005_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000005_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000006_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000006_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000006_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000006_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000007_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000007_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000007_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000007_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000008_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000008_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000008_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000008_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000009_recovery_token_expires.cockroach.down.sql │ │ ├── 20200830172221000009_recovery_token_expires.cockroach.up.sql │ │ ├── 20200830172221000009_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000009_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000010_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000010_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000011_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000011_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000012_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000012_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000013_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000013_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000014_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000014_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000015_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000015_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000016_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000016_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000017_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000017_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000018_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000018_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000019_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000019_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000020_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000020_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000021_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000021_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000022_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000022_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000023_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000023_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000024_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000024_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000025_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000025_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000026_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000026_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000027_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000027_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000028_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000028_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000029_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000029_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200830172221000030_recovery_token_expires.sqlite3.down.sql │ │ ├── 20200830172221000030_recovery_token_expires.sqlite3.up.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000000_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000001_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000002_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000003_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000004_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000005_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000006_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.mysql.down.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.mysql.up.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.postgres.down.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.postgres.up.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000007_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000008_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000009_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000010_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000011_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000012_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000013_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.cockroach.down.sql │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.cockroach.up.sql │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000014_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000015_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000015_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000016_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000016_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000017_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000017_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000018_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000018_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000019_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000019_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000020_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000020_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20200831110752000021_identity_verifiable_address_remove_code.sqlite3.down.sql │ │ ├── 20200831110752000021_identity_verifiable_address_remove_code.sqlite3.up.sql │ │ ├── 20201201161451000000_credential_types_values.cockroach.down.sql │ │ ├── 20201201161451000000_credential_types_values.cockroach.up.sql │ │ ├── 20201201161451000000_credential_types_values.mysql.down.sql │ │ ├── 20201201161451000000_credential_types_values.mysql.up.sql │ │ ├── 20201201161451000000_credential_types_values.postgres.down.sql │ │ ├── 20201201161451000000_credential_types_values.postgres.up.sql │ │ ├── 20201201161451000000_credential_types_values.sqlite3.down.sql │ │ ├── 20201201161451000000_credential_types_values.sqlite3.up.sql │ │ ├── 20201201161451000001_credential_types_values.cockroach.down.sql │ │ ├── 20201201161451000001_credential_types_values.cockroach.up.sql │ │ ├── 20201201161451000001_credential_types_values.mysql.down.sql │ │ ├── 20201201161451000001_credential_types_values.mysql.up.sql │ │ ├── 20201201161451000001_credential_types_values.postgres.down.sql │ │ ├── 20201201161451000001_credential_types_values.postgres.up.sql │ │ ├── 20201201161451000001_credential_types_values.sqlite3.down.sql │ │ ├── 20201201161451000001_credential_types_values.sqlite3.up.sql │ │ ├── 20210307130558000000_courier_status_index.cockroach.down.sql │ │ ├── 20210307130558000000_courier_status_index.cockroach.up.sql │ │ ├── 20210307130558000000_courier_status_index.mysql.down.sql │ │ ├── 20210307130558000000_courier_status_index.mysql.up.sql │ │ ├── 20210307130558000000_courier_status_index.postgres.down.sql │ │ ├── 20210307130558000000_courier_status_index.postgres.up.sql │ │ ├── 20210307130558000000_courier_status_index.sqlite3.down.sql │ │ ├── 20210307130558000000_courier_status_index.sqlite3.up.sql │ │ ├── 20210307130559000000_courier_message_template.cockroach.down.sql │ │ ├── 20210307130559000000_courier_message_template.cockroach.up.sql │ │ ├── 20210307130559000000_courier_message_template.mysql.down.sql │ │ ├── 20210307130559000000_courier_message_template.mysql.up.sql │ │ ├── 20210307130559000000_courier_message_template.postgres.down.sql │ │ ├── 20210307130559000000_courier_message_template.postgres.up.sql │ │ ├── 20210307130559000000_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000000_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000001_courier_message_template.cockroach.down.sql │ │ ├── 20210307130559000001_courier_message_template.cockroach.up.sql │ │ ├── 20210307130559000001_courier_message_template.mysql.down.sql │ │ ├── 20210307130559000001_courier_message_template.mysql.up.sql │ │ ├── 20210307130559000001_courier_message_template.postgres.down.sql │ │ ├── 20210307130559000001_courier_message_template.postgres.up.sql │ │ ├── 20210307130559000001_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000001_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000002_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000002_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000003_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000003_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000004_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000004_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000005_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000005_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000006_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000006_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000007_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000007_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000008_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000008_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000009_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000009_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000010_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000010_courier_message_template.sqlite3.up.sql │ │ ├── 20210307130559000011_courier_message_template.sqlite3.down.sql │ │ ├── 20210307130559000011_courier_message_template.sqlite3.up.sql │ │ ├── 20210311102338000000_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000000_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000000_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000000_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000000_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000000_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000000_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000000_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000001_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000001_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000001_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000001_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000001_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000001_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000001_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000001_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000002_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000002_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000002_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000002_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000002_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000002_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000002_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000002_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000003_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000003_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000003_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000003_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000003_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000003_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000003_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000003_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000004_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000004_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000004_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000004_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000004_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000004_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000004_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000004_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000005_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000005_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000005_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000005_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000005_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000005_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000005_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000005_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000006_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000006_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000006_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000006_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000006_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000006_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000006_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000006_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000007_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000007_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000007_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000007_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000007_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000007_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000007_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000007_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000008_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000008_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000008_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000008_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000008_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000008_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000008_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000008_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000009_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000009_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000009_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000009_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000009_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000009_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000009_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000009_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000010_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000010_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000010_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000010_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000010_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000010_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000010_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000010_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000011_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000011_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000011_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000011_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000011_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000011_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000011_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000011_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000012_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000012_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000012_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000012_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000012_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000012_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000012_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000012_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000013_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000013_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000013_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000013_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000013_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000013_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000013_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000013_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000014_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000014_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000014_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000014_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000014_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000014_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000014_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000014_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000015_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000015_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000015_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000015_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000015_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000015_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000015_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000015_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000016_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000016_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000016_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000016_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000016_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000016_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000016_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000016_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000017_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000017_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000017_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000017_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000017_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000017_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000017_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000017_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000018_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000018_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000018_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000018_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000018_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000018_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000018_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000018_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000019_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000019_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000019_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000019_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000019_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000019_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000019_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000019_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000020_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000020_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000020_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000020_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000020_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000020_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000020_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000020_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000021_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000021_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000021_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000021_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000021_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000021_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000021_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000021_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000022_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000022_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000022_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000022_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000022_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000022_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000022_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000022_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000023_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000023_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000023_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000023_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000023_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000023_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000023_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000023_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000024_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000024_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000024_form_refactoring.mysql.down.sql │ │ ├── 20210311102338000024_form_refactoring.mysql.up.sql │ │ ├── 20210311102338000024_form_refactoring.postgres.down.sql │ │ ├── 20210311102338000024_form_refactoring.postgres.up.sql │ │ ├── 20210311102338000024_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000024_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000025_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000025_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000025_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000025_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000026_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000026_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000026_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000026_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000027_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000027_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000027_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000027_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000028_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000028_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000028_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000028_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000029_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000029_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000029_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000029_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000030_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000030_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000030_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000030_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000031_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000031_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000031_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000031_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000032_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000032_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000032_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000032_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000033_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000033_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000033_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000033_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000034_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000034_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000034_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000034_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000035_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000035_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000035_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000035_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000036_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000036_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000036_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000036_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000037_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000037_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000037_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000037_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000038_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000038_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000038_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000038_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000039_form_refactoring.cockroach.down.sql │ │ ├── 20210311102338000039_form_refactoring.cockroach.up.sql │ │ ├── 20210311102338000039_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000039_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000040_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000040_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000041_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000041_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000042_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000042_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000043_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000043_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000044_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000044_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000045_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000045_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000046_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000046_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000047_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000047_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000048_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000048_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000049_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000049_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000050_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000050_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000051_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000051_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000052_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000052_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000053_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000053_form_refactoring.sqlite3.up.sql │ │ ├── 20210311102338000054_form_refactoring.sqlite3.down.sql │ │ ├── 20210311102338000054_form_refactoring.sqlite3.up.sql │ │ ├── 20210410175418000000_network.cockroach.down.sql │ │ ├── 20210410175418000000_network.cockroach.up.sql │ │ ├── 20210410175418000000_network.mysql.down.sql │ │ ├── 20210410175418000000_network.mysql.up.sql │ │ ├── 20210410175418000000_network.postgres.down.sql │ │ ├── 20210410175418000000_network.postgres.up.sql │ │ ├── 20210410175418000000_network.sqlite3.down.sql │ │ ├── 20210410175418000000_network.sqlite3.up.sql │ │ ├── 20210410175418000001_network.cockroach.down.sql │ │ ├── 20210410175418000001_network.cockroach.up.sql │ │ ├── 20210410175418000001_network.mysql.down.sql │ │ ├── 20210410175418000001_network.mysql.up.sql │ │ ├── 20210410175418000001_network.postgres.down.sql │ │ ├── 20210410175418000001_network.postgres.up.sql │ │ ├── 20210410175418000001_network.sqlite3.down.sql │ │ ├── 20210410175418000001_network.sqlite3.up.sql │ │ ├── 20210410175418000002_network.cockroach.down.sql │ │ ├── 20210410175418000002_network.cockroach.up.sql │ │ ├── 20210410175418000002_network.mysql.down.sql │ │ ├── 20210410175418000002_network.mysql.up.sql │ │ ├── 20210410175418000002_network.postgres.down.sql │ │ ├── 20210410175418000002_network.postgres.up.sql │ │ ├── 20210410175418000002_network.sqlite3.down.sql │ │ ├── 20210410175418000002_network.sqlite3.up.sql │ │ ├── 20210410175418000003_network.cockroach.down.sql │ │ ├── 20210410175418000003_network.cockroach.up.sql │ │ ├── 20210410175418000003_network.mysql.down.sql │ │ ├── 20210410175418000003_network.mysql.up.sql │ │ ├── 20210410175418000003_network.postgres.down.sql │ │ ├── 20210410175418000003_network.postgres.up.sql │ │ ├── 20210410175418000003_network.sqlite3.down.sql │ │ ├── 20210410175418000003_network.sqlite3.up.sql │ │ ├── 20210410175418000004_network.cockroach.down.sql │ │ ├── 20210410175418000004_network.cockroach.up.sql │ │ ├── 20210410175418000004_network.mysql.down.sql │ │ ├── 20210410175418000004_network.mysql.up.sql │ │ ├── 20210410175418000004_network.postgres.down.sql │ │ ├── 20210410175418000004_network.postgres.up.sql │ │ ├── 20210410175418000004_network.sqlite3.down.sql │ │ ├── 20210410175418000004_network.sqlite3.up.sql │ │ ├── 20210410175418000005_network.cockroach.down.sql │ │ ├── 20210410175418000005_network.cockroach.up.sql │ │ ├── 20210410175418000005_network.mysql.down.sql │ │ ├── 20210410175418000005_network.mysql.up.sql │ │ ├── 20210410175418000005_network.postgres.down.sql │ │ ├── 20210410175418000005_network.postgres.up.sql │ │ ├── 20210410175418000005_network.sqlite3.down.sql │ │ ├── 20210410175418000005_network.sqlite3.up.sql │ │ ├── 20210410175418000006_network.cockroach.down.sql │ │ ├── 20210410175418000006_network.cockroach.up.sql │ │ ├── 20210410175418000006_network.mysql.down.sql │ │ ├── 20210410175418000006_network.mysql.up.sql │ │ ├── 20210410175418000006_network.postgres.down.sql │ │ ├── 20210410175418000006_network.postgres.up.sql │ │ ├── 20210410175418000006_network.sqlite3.down.sql │ │ ├── 20210410175418000006_network.sqlite3.up.sql │ │ ├── 20210410175418000007_network.cockroach.down.sql │ │ ├── 20210410175418000007_network.cockroach.up.sql │ │ ├── 20210410175418000007_network.mysql.down.sql │ │ ├── 20210410175418000007_network.mysql.up.sql │ │ ├── 20210410175418000007_network.postgres.down.sql │ │ ├── 20210410175418000007_network.postgres.up.sql │ │ ├── 20210410175418000007_network.sqlite3.down.sql │ │ ├── 20210410175418000007_network.sqlite3.up.sql │ │ ├── 20210410175418000008_network.cockroach.down.sql │ │ ├── 20210410175418000008_network.cockroach.up.sql │ │ ├── 20210410175418000008_network.mysql.down.sql │ │ ├── 20210410175418000008_network.mysql.up.sql │ │ ├── 20210410175418000008_network.postgres.down.sql │ │ ├── 20210410175418000008_network.postgres.up.sql │ │ ├── 20210410175418000008_network.sqlite3.down.sql │ │ ├── 20210410175418000008_network.sqlite3.up.sql │ │ ├── 20210410175418000009_network.cockroach.down.sql │ │ ├── 20210410175418000009_network.cockroach.up.sql │ │ ├── 20210410175418000009_network.mysql.down.sql │ │ ├── 20210410175418000009_network.mysql.up.sql │ │ ├── 20210410175418000009_network.postgres.down.sql │ │ ├── 20210410175418000009_network.postgres.up.sql │ │ ├── 20210410175418000009_network.sqlite3.down.sql │ │ ├── 20210410175418000009_network.sqlite3.up.sql │ │ ├── 20210410175418000010_network.cockroach.down.sql │ │ ├── 20210410175418000010_network.cockroach.up.sql │ │ ├── 20210410175418000010_network.mysql.down.sql │ │ ├── 20210410175418000010_network.mysql.up.sql │ │ ├── 20210410175418000010_network.postgres.down.sql │ │ ├── 20210410175418000010_network.postgres.up.sql │ │ ├── 20210410175418000010_network.sqlite3.down.sql │ │ ├── 20210410175418000010_network.sqlite3.up.sql │ │ ├── 20210410175418000011_network.cockroach.down.sql │ │ ├── 20210410175418000011_network.cockroach.up.sql │ │ ├── 20210410175418000011_network.mysql.down.sql │ │ ├── 20210410175418000011_network.mysql.up.sql │ │ ├── 20210410175418000011_network.postgres.down.sql │ │ ├── 20210410175418000011_network.postgres.up.sql │ │ ├── 20210410175418000011_network.sqlite3.down.sql │ │ ├── 20210410175418000011_network.sqlite3.up.sql │ │ ├── 20210410175418000012_network.cockroach.down.sql │ │ ├── 20210410175418000012_network.cockroach.up.sql │ │ ├── 20210410175418000012_network.mysql.down.sql │ │ ├── 20210410175418000012_network.mysql.up.sql │ │ ├── 20210410175418000012_network.postgres.down.sql │ │ ├── 20210410175418000012_network.postgres.up.sql │ │ ├── 20210410175418000012_network.sqlite3.down.sql │ │ ├── 20210410175418000012_network.sqlite3.up.sql │ │ ├── 20210410175418000013_network.cockroach.down.sql │ │ ├── 20210410175418000013_network.cockroach.up.sql │ │ ├── 20210410175418000013_network.mysql.down.sql │ │ ├── 20210410175418000013_network.mysql.up.sql │ │ ├── 20210410175418000013_network.postgres.down.sql │ │ ├── 20210410175418000013_network.postgres.up.sql │ │ ├── 20210410175418000013_network.sqlite3.down.sql │ │ ├── 20210410175418000013_network.sqlite3.up.sql │ │ ├── 20210410175418000014_network.cockroach.down.sql │ │ ├── 20210410175418000014_network.cockroach.up.sql │ │ ├── 20210410175418000014_network.mysql.down.sql │ │ ├── 20210410175418000014_network.mysql.up.sql │ │ ├── 20210410175418000014_network.postgres.down.sql │ │ ├── 20210410175418000014_network.postgres.up.sql │ │ ├── 20210410175418000014_network.sqlite3.down.sql │ │ ├── 20210410175418000014_network.sqlite3.up.sql │ │ ├── 20210410175418000015_network.cockroach.down.sql │ │ ├── 20210410175418000015_network.cockroach.up.sql │ │ ├── 20210410175418000015_network.mysql.down.sql │ │ ├── 20210410175418000015_network.mysql.up.sql │ │ ├── 20210410175418000015_network.postgres.down.sql │ │ ├── 20210410175418000015_network.postgres.up.sql │ │ ├── 20210410175418000015_network.sqlite3.down.sql │ │ ├── 20210410175418000015_network.sqlite3.up.sql │ │ ├── 20210410175418000016_network.cockroach.down.sql │ │ ├── 20210410175418000016_network.cockroach.up.sql │ │ ├── 20210410175418000016_network.mysql.down.sql │ │ ├── 20210410175418000016_network.mysql.up.sql │ │ ├── 20210410175418000016_network.postgres.down.sql │ │ ├── 20210410175418000016_network.postgres.up.sql │ │ ├── 20210410175418000016_network.sqlite3.down.sql │ │ ├── 20210410175418000016_network.sqlite3.up.sql │ │ ├── 20210410175418000017_network.cockroach.down.sql │ │ ├── 20210410175418000017_network.cockroach.up.sql │ │ ├── 20210410175418000017_network.mysql.down.sql │ │ ├── 20210410175418000017_network.mysql.up.sql │ │ ├── 20210410175418000017_network.postgres.down.sql │ │ ├── 20210410175418000017_network.postgres.up.sql │ │ ├── 20210410175418000017_network.sqlite3.down.sql │ │ ├── 20210410175418000017_network.sqlite3.up.sql │ │ ├── 20210410175418000018_network.cockroach.down.sql │ │ ├── 20210410175418000018_network.cockroach.up.sql │ │ ├── 20210410175418000018_network.mysql.down.sql │ │ ├── 20210410175418000018_network.mysql.up.sql │ │ ├── 20210410175418000018_network.postgres.down.sql │ │ ├── 20210410175418000018_network.postgres.up.sql │ │ ├── 20210410175418000018_network.sqlite3.down.sql │ │ ├── 20210410175418000018_network.sqlite3.up.sql │ │ ├── 20210410175418000019_network.cockroach.down.sql │ │ ├── 20210410175418000019_network.cockroach.up.sql │ │ ├── 20210410175418000019_network.mysql.down.sql │ │ ├── 20210410175418000019_network.mysql.up.sql │ │ ├── 20210410175418000019_network.postgres.down.sql │ │ ├── 20210410175418000019_network.postgres.up.sql │ │ ├── 20210410175418000019_network.sqlite3.down.sql │ │ ├── 20210410175418000019_network.sqlite3.up.sql │ │ ├── 20210410175418000020_network.cockroach.down.sql │ │ ├── 20210410175418000020_network.cockroach.up.sql │ │ ├── 20210410175418000020_network.mysql.down.sql │ │ ├── 20210410175418000020_network.mysql.up.sql │ │ ├── 20210410175418000020_network.postgres.down.sql │ │ ├── 20210410175418000020_network.postgres.up.sql │ │ ├── 20210410175418000020_network.sqlite3.down.sql │ │ ├── 20210410175418000020_network.sqlite3.up.sql │ │ ├── 20210410175418000021_network.cockroach.down.sql │ │ ├── 20210410175418000021_network.cockroach.up.sql │ │ ├── 20210410175418000021_network.mysql.down.sql │ │ ├── 20210410175418000021_network.mysql.up.sql │ │ ├── 20210410175418000021_network.postgres.down.sql │ │ ├── 20210410175418000021_network.postgres.up.sql │ │ ├── 20210410175418000021_network.sqlite3.down.sql │ │ ├── 20210410175418000021_network.sqlite3.up.sql │ │ ├── 20210410175418000022_network.cockroach.down.sql │ │ ├── 20210410175418000022_network.cockroach.up.sql │ │ ├── 20210410175418000022_network.mysql.down.sql │ │ ├── 20210410175418000022_network.mysql.up.sql │ │ ├── 20210410175418000022_network.postgres.down.sql │ │ ├── 20210410175418000022_network.postgres.up.sql │ │ ├── 20210410175418000022_network.sqlite3.down.sql │ │ ├── 20210410175418000022_network.sqlite3.up.sql │ │ ├── 20210410175418000023_network.cockroach.down.sql │ │ ├── 20210410175418000023_network.cockroach.up.sql │ │ ├── 20210410175418000023_network.mysql.down.sql │ │ ├── 20210410175418000023_network.mysql.up.sql │ │ ├── 20210410175418000023_network.postgres.down.sql │ │ ├── 20210410175418000023_network.postgres.up.sql │ │ ├── 20210410175418000023_network.sqlite3.down.sql │ │ ├── 20210410175418000023_network.sqlite3.up.sql │ │ ├── 20210410175418000024_network.cockroach.down.sql │ │ ├── 20210410175418000024_network.cockroach.up.sql │ │ ├── 20210410175418000024_network.mysql.down.sql │ │ ├── 20210410175418000024_network.mysql.up.sql │ │ ├── 20210410175418000024_network.postgres.down.sql │ │ ├── 20210410175418000024_network.postgres.up.sql │ │ ├── 20210410175418000024_network.sqlite3.down.sql │ │ ├── 20210410175418000024_network.sqlite3.up.sql │ │ ├── 20210410175418000025_network.cockroach.down.sql │ │ ├── 20210410175418000025_network.cockroach.up.sql │ │ ├── 20210410175418000025_network.mysql.down.sql │ │ ├── 20210410175418000025_network.mysql.up.sql │ │ ├── 20210410175418000025_network.postgres.down.sql │ │ ├── 20210410175418000025_network.postgres.up.sql │ │ ├── 20210410175418000025_network.sqlite3.down.sql │ │ ├── 20210410175418000025_network.sqlite3.up.sql │ │ ├── 20210410175418000026_network.cockroach.down.sql │ │ ├── 20210410175418000026_network.cockroach.up.sql │ │ ├── 20210410175418000026_network.mysql.down.sql │ │ ├── 20210410175418000026_network.mysql.up.sql │ │ ├── 20210410175418000026_network.postgres.down.sql │ │ ├── 20210410175418000026_network.postgres.up.sql │ │ ├── 20210410175418000026_network.sqlite3.down.sql │ │ ├── 20210410175418000026_network.sqlite3.up.sql │ │ ├── 20210410175418000027_network.cockroach.down.sql │ │ ├── 20210410175418000027_network.cockroach.up.sql │ │ ├── 20210410175418000027_network.mysql.down.sql │ │ ├── 20210410175418000027_network.mysql.up.sql │ │ ├── 20210410175418000027_network.postgres.down.sql │ │ ├── 20210410175418000027_network.postgres.up.sql │ │ ├── 20210410175418000027_network.sqlite3.down.sql │ │ ├── 20210410175418000027_network.sqlite3.up.sql │ │ ├── 20210410175418000028_network.cockroach.down.sql │ │ ├── 20210410175418000028_network.cockroach.up.sql │ │ ├── 20210410175418000028_network.mysql.down.sql │ │ ├── 20210410175418000028_network.mysql.up.sql │ │ ├── 20210410175418000028_network.postgres.down.sql │ │ ├── 20210410175418000028_network.postgres.up.sql │ │ ├── 20210410175418000028_network.sqlite3.down.sql │ │ ├── 20210410175418000028_network.sqlite3.up.sql │ │ ├── 20210410175418000029_network.cockroach.down.sql │ │ ├── 20210410175418000029_network.cockroach.up.sql │ │ ├── 20210410175418000029_network.mysql.down.sql │ │ ├── 20210410175418000029_network.mysql.up.sql │ │ ├── 20210410175418000029_network.postgres.down.sql │ │ ├── 20210410175418000029_network.postgres.up.sql │ │ ├── 20210410175418000029_network.sqlite3.down.sql │ │ ├── 20210410175418000029_network.sqlite3.up.sql │ │ ├── 20210410175418000030_network.cockroach.down.sql │ │ ├── 20210410175418000030_network.cockroach.up.sql │ │ ├── 20210410175418000030_network.mysql.down.sql │ │ ├── 20210410175418000030_network.mysql.up.sql │ │ ├── 20210410175418000030_network.postgres.down.sql │ │ ├── 20210410175418000030_network.postgres.up.sql │ │ ├── 20210410175418000030_network.sqlite3.down.sql │ │ ├── 20210410175418000030_network.sqlite3.up.sql │ │ ├── 20210410175418000031_network.cockroach.down.sql │ │ ├── 20210410175418000031_network.cockroach.up.sql │ │ ├── 20210410175418000031_network.mysql.down.sql │ │ ├── 20210410175418000031_network.mysql.up.sql │ │ ├── 20210410175418000031_network.postgres.down.sql │ │ ├── 20210410175418000031_network.postgres.up.sql │ │ ├── 20210410175418000031_network.sqlite3.down.sql │ │ ├── 20210410175418000031_network.sqlite3.up.sql │ │ ├── 20210410175418000032_network.cockroach.down.sql │ │ ├── 20210410175418000032_network.cockroach.up.sql │ │ ├── 20210410175418000032_network.mysql.down.sql │ │ ├── 20210410175418000032_network.mysql.up.sql │ │ ├── 20210410175418000032_network.postgres.down.sql │ │ ├── 20210410175418000032_network.postgres.up.sql │ │ ├── 20210410175418000032_network.sqlite3.down.sql │ │ ├── 20210410175418000032_network.sqlite3.up.sql │ │ ├── 20210410175418000033_network.cockroach.down.sql │ │ ├── 20210410175418000033_network.cockroach.up.sql │ │ ├── 20210410175418000033_network.mysql.down.sql │ │ ├── 20210410175418000033_network.mysql.up.sql │ │ ├── 20210410175418000033_network.postgres.down.sql │ │ ├── 20210410175418000033_network.postgres.up.sql │ │ ├── 20210410175418000033_network.sqlite3.down.sql │ │ ├── 20210410175418000033_network.sqlite3.up.sql │ │ ├── 20210410175418000034_network.cockroach.down.sql │ │ ├── 20210410175418000034_network.cockroach.up.sql │ │ ├── 20210410175418000034_network.mysql.down.sql │ │ ├── 20210410175418000034_network.mysql.up.sql │ │ ├── 20210410175418000034_network.postgres.down.sql │ │ ├── 20210410175418000034_network.postgres.up.sql │ │ ├── 20210410175418000034_network.sqlite3.down.sql │ │ ├── 20210410175418000034_network.sqlite3.up.sql │ │ ├── 20210410175418000035_network.cockroach.down.sql │ │ ├── 20210410175418000035_network.cockroach.up.sql │ │ ├── 20210410175418000035_network.mysql.down.sql │ │ ├── 20210410175418000035_network.mysql.up.sql │ │ ├── 20210410175418000035_network.postgres.down.sql │ │ ├── 20210410175418000035_network.postgres.up.sql │ │ ├── 20210410175418000035_network.sqlite3.down.sql │ │ ├── 20210410175418000035_network.sqlite3.up.sql │ │ ├── 20210410175418000036_network.cockroach.down.sql │ │ ├── 20210410175418000036_network.cockroach.up.sql │ │ ├── 20210410175418000036_network.mysql.down.sql │ │ ├── 20210410175418000036_network.mysql.up.sql │ │ ├── 20210410175418000036_network.postgres.down.sql │ │ ├── 20210410175418000036_network.postgres.up.sql │ │ ├── 20210410175418000036_network.sqlite3.down.sql │ │ ├── 20210410175418000036_network.sqlite3.up.sql │ │ ├── 20210410175418000037_network.cockroach.down.sql │ │ ├── 20210410175418000037_network.cockroach.up.sql │ │ ├── 20210410175418000037_network.mysql.down.sql │ │ ├── 20210410175418000037_network.mysql.up.sql │ │ ├── 20210410175418000037_network.postgres.down.sql │ │ ├── 20210410175418000037_network.postgres.up.sql │ │ ├── 20210410175418000037_network.sqlite3.down.sql │ │ ├── 20210410175418000037_network.sqlite3.up.sql │ │ ├── 20210410175418000038_network.cockroach.down.sql │ │ ├── 20210410175418000038_network.cockroach.up.sql │ │ ├── 20210410175418000038_network.mysql.down.sql │ │ ├── 20210410175418000038_network.mysql.up.sql │ │ ├── 20210410175418000038_network.postgres.down.sql │ │ ├── 20210410175418000038_network.postgres.up.sql │ │ ├── 20210410175418000038_network.sqlite3.down.sql │ │ ├── 20210410175418000038_network.sqlite3.up.sql │ │ ├── 20210410175418000039_network.cockroach.down.sql │ │ ├── 20210410175418000039_network.cockroach.up.sql │ │ ├── 20210410175418000039_network.mysql.down.sql │ │ ├── 20210410175418000039_network.mysql.up.sql │ │ ├── 20210410175418000039_network.postgres.down.sql │ │ ├── 20210410175418000039_network.postgres.up.sql │ │ ├── 20210410175418000039_network.sqlite3.down.sql │ │ ├── 20210410175418000039_network.sqlite3.up.sql │ │ ├── 20210410175418000040_network.cockroach.down.sql │ │ ├── 20210410175418000040_network.cockroach.up.sql │ │ ├── 20210410175418000040_network.mysql.down.sql │ │ ├── 20210410175418000040_network.mysql.up.sql │ │ ├── 20210410175418000040_network.postgres.down.sql │ │ ├── 20210410175418000040_network.postgres.up.sql │ │ ├── 20210410175418000040_network.sqlite3.down.sql │ │ ├── 20210410175418000040_network.sqlite3.up.sql │ │ ├── 20210410175418000041_network.cockroach.down.sql │ │ ├── 20210410175418000041_network.cockroach.up.sql │ │ ├── 20210410175418000041_network.mysql.down.sql │ │ ├── 20210410175418000041_network.mysql.up.sql │ │ ├── 20210410175418000041_network.postgres.down.sql │ │ ├── 20210410175418000041_network.postgres.up.sql │ │ ├── 20210410175418000041_network.sqlite3.down.sql │ │ ├── 20210410175418000041_network.sqlite3.up.sql │ │ ├── 20210410175418000042_network.cockroach.down.sql │ │ ├── 20210410175418000042_network.cockroach.up.sql │ │ ├── 20210410175418000042_network.mysql.down.sql │ │ ├── 20210410175418000042_network.mysql.up.sql │ │ ├── 20210410175418000042_network.postgres.down.sql │ │ ├── 20210410175418000042_network.postgres.up.sql │ │ ├── 20210410175418000042_network.sqlite3.down.sql │ │ ├── 20210410175418000042_network.sqlite3.up.sql │ │ ├── 20210410175418000043_network.cockroach.down.sql │ │ ├── 20210410175418000043_network.cockroach.up.sql │ │ ├── 20210410175418000043_network.mysql.down.sql │ │ ├── 20210410175418000043_network.mysql.up.sql │ │ ├── 20210410175418000043_network.postgres.down.sql │ │ ├── 20210410175418000043_network.postgres.up.sql │ │ ├── 20210410175418000043_network.sqlite3.down.sql │ │ ├── 20210410175418000043_network.sqlite3.up.sql │ │ ├── 20210410175418000044_network.cockroach.down.sql │ │ ├── 20210410175418000044_network.cockroach.up.sql │ │ ├── 20210410175418000044_network.mysql.down.sql │ │ ├── 20210410175418000044_network.mysql.up.sql │ │ ├── 20210410175418000044_network.postgres.down.sql │ │ ├── 20210410175418000044_network.postgres.up.sql │ │ ├── 20210410175418000044_network.sqlite3.down.sql │ │ ├── 20210410175418000044_network.sqlite3.up.sql │ │ ├── 20210410175418000045_network.cockroach.down.sql │ │ ├── 20210410175418000045_network.cockroach.up.sql │ │ ├── 20210410175418000045_network.mysql.down.sql │ │ ├── 20210410175418000045_network.mysql.up.sql │ │ ├── 20210410175418000045_network.postgres.down.sql │ │ ├── 20210410175418000045_network.postgres.up.sql │ │ ├── 20210410175418000045_network.sqlite3.down.sql │ │ ├── 20210410175418000045_network.sqlite3.up.sql │ │ ├── 20210410175418000046_network.cockroach.down.sql │ │ ├── 20210410175418000046_network.cockroach.up.sql │ │ ├── 20210410175418000046_network.mysql.down.sql │ │ ├── 20210410175418000046_network.mysql.up.sql │ │ ├── 20210410175418000046_network.postgres.down.sql │ │ ├── 20210410175418000046_network.postgres.up.sql │ │ ├── 20210410175418000046_network.sqlite3.down.sql │ │ ├── 20210410175418000046_network.sqlite3.up.sql │ │ ├── 20210410175418000047_network.cockroach.down.sql │ │ ├── 20210410175418000047_network.cockroach.up.sql │ │ ├── 20210410175418000047_network.mysql.down.sql │ │ ├── 20210410175418000047_network.mysql.up.sql │ │ ├── 20210410175418000047_network.postgres.down.sql │ │ ├── 20210410175418000047_network.postgres.up.sql │ │ ├── 20210410175418000047_network.sqlite3.down.sql │ │ ├── 20210410175418000047_network.sqlite3.up.sql │ │ ├── 20210410175418000048_network.cockroach.down.sql │ │ ├── 20210410175418000048_network.cockroach.up.sql │ │ ├── 20210410175418000048_network.mysql.down.sql │ │ ├── 20210410175418000048_network.mysql.up.sql │ │ ├── 20210410175418000048_network.postgres.down.sql │ │ ├── 20210410175418000048_network.postgres.up.sql │ │ ├── 20210410175418000048_network.sqlite3.down.sql │ │ ├── 20210410175418000048_network.sqlite3.up.sql │ │ ├── 20210410175418000049_network.cockroach.down.sql │ │ ├── 20210410175418000049_network.cockroach.up.sql │ │ ├── 20210410175418000049_network.mysql.down.sql │ │ ├── 20210410175418000049_network.mysql.up.sql │ │ ├── 20210410175418000049_network.postgres.down.sql │ │ ├── 20210410175418000049_network.postgres.up.sql │ │ ├── 20210410175418000049_network.sqlite3.down.sql │ │ ├── 20210410175418000049_network.sqlite3.up.sql │ │ ├── 20210410175418000050_network.cockroach.down.sql │ │ ├── 20210410175418000050_network.cockroach.up.sql │ │ ├── 20210410175418000050_network.mysql.down.sql │ │ ├── 20210410175418000050_network.mysql.up.sql │ │ ├── 20210410175418000050_network.postgres.down.sql │ │ ├── 20210410175418000050_network.postgres.up.sql │ │ ├── 20210410175418000050_network.sqlite3.down.sql │ │ ├── 20210410175418000050_network.sqlite3.up.sql │ │ ├── 20210410175418000051_network.cockroach.down.sql │ │ ├── 20210410175418000051_network.cockroach.up.sql │ │ ├── 20210410175418000051_network.mysql.down.sql │ │ ├── 20210410175418000051_network.mysql.up.sql │ │ ├── 20210410175418000051_network.postgres.down.sql │ │ ├── 20210410175418000051_network.postgres.up.sql │ │ ├── 20210410175418000051_network.sqlite3.down.sql │ │ ├── 20210410175418000051_network.sqlite3.up.sql │ │ ├── 20210410175418000052_network.cockroach.down.sql │ │ ├── 20210410175418000052_network.cockroach.up.sql │ │ ├── 20210410175418000052_network.mysql.down.sql │ │ ├── 20210410175418000052_network.mysql.up.sql │ │ ├── 20210410175418000052_network.postgres.down.sql │ │ ├── 20210410175418000052_network.postgres.up.sql │ │ ├── 20210410175418000052_network.sqlite3.down.sql │ │ ├── 20210410175418000052_network.sqlite3.up.sql │ │ ├── 20210410175418000053_network.cockroach.down.sql │ │ ├── 20210410175418000053_network.cockroach.up.sql │ │ ├── 20210410175418000053_network.mysql.down.sql │ │ ├── 20210410175418000053_network.mysql.up.sql │ │ ├── 20210410175418000053_network.postgres.down.sql │ │ ├── 20210410175418000053_network.postgres.up.sql │ │ ├── 20210410175418000053_network.sqlite3.down.sql │ │ ├── 20210410175418000053_network.sqlite3.up.sql │ │ ├── 20210410175418000054_network.cockroach.down.sql │ │ ├── 20210410175418000054_network.cockroach.up.sql │ │ ├── 20210410175418000054_network.mysql.down.sql │ │ ├── 20210410175418000054_network.mysql.up.sql │ │ ├── 20210410175418000054_network.postgres.down.sql │ │ ├── 20210410175418000054_network.postgres.up.sql │ │ ├── 20210410175418000054_network.sqlite3.down.sql │ │ ├── 20210410175418000054_network.sqlite3.up.sql │ │ ├── 20210410175418000055_network.cockroach.down.sql │ │ ├── 20210410175418000055_network.cockroach.up.sql │ │ ├── 20210410175418000055_network.mysql.down.sql │ │ ├── 20210410175418000055_network.mysql.up.sql │ │ ├── 20210410175418000055_network.postgres.down.sql │ │ ├── 20210410175418000055_network.postgres.up.sql │ │ ├── 20210410175418000055_network.sqlite3.down.sql │ │ ├── 20210410175418000055_network.sqlite3.up.sql │ │ ├── 20210410175418000056_network.cockroach.down.sql │ │ ├── 20210410175418000056_network.cockroach.up.sql │ │ ├── 20210410175418000056_network.mysql.down.sql │ │ ├── 20210410175418000056_network.mysql.up.sql │ │ ├── 20210410175418000056_network.postgres.down.sql │ │ ├── 20210410175418000056_network.postgres.up.sql │ │ ├── 20210410175418000056_network.sqlite3.down.sql │ │ ├── 20210410175418000056_network.sqlite3.up.sql │ │ ├── 20210410175418000057_network.cockroach.down.sql │ │ ├── 20210410175418000057_network.cockroach.up.sql │ │ ├── 20210410175418000057_network.mysql.down.sql │ │ ├── 20210410175418000057_network.mysql.up.sql │ │ ├── 20210410175418000057_network.postgres.down.sql │ │ ├── 20210410175418000057_network.postgres.up.sql │ │ ├── 20210410175418000057_network.sqlite3.down.sql │ │ ├── 20210410175418000057_network.sqlite3.up.sql │ │ ├── 20210410175418000058_network.cockroach.down.sql │ │ ├── 20210410175418000058_network.cockroach.up.sql │ │ ├── 20210410175418000058_network.mysql.down.sql │ │ ├── 20210410175418000058_network.mysql.up.sql │ │ ├── 20210410175418000058_network.postgres.down.sql │ │ ├── 20210410175418000058_network.postgres.up.sql │ │ ├── 20210410175418000058_network.sqlite3.down.sql │ │ ├── 20210410175418000058_network.sqlite3.up.sql │ │ ├── 20210410175418000059_network.cockroach.down.sql │ │ ├── 20210410175418000059_network.cockroach.up.sql │ │ ├── 20210410175418000059_network.mysql.down.sql │ │ ├── 20210410175418000059_network.mysql.up.sql │ │ ├── 20210410175418000059_network.postgres.down.sql │ │ ├── 20210410175418000059_network.postgres.up.sql │ │ ├── 20210410175418000059_network.sqlite3.down.sql │ │ ├── 20210410175418000059_network.sqlite3.up.sql │ │ ├── 20210410175418000060_network.cockroach.down.sql │ │ ├── 20210410175418000060_network.cockroach.up.sql │ │ ├── 20210410175418000060_network.mysql.down.sql │ │ ├── 20210410175418000060_network.mysql.up.sql │ │ ├── 20210410175418000060_network.postgres.down.sql │ │ ├── 20210410175418000060_network.postgres.up.sql │ │ ├── 20210410175418000060_network.sqlite3.down.sql │ │ ├── 20210410175418000060_network.sqlite3.up.sql │ │ ├── 20210410175418000061_network.cockroach.down.sql │ │ ├── 20210410175418000061_network.cockroach.up.sql │ │ ├── 20210410175418000061_network.mysql.down.sql │ │ ├── 20210410175418000061_network.mysql.up.sql │ │ ├── 20210410175418000061_network.postgres.down.sql │ │ ├── 20210410175418000061_network.postgres.up.sql │ │ ├── 20210410175418000061_network.sqlite3.down.sql │ │ ├── 20210410175418000061_network.sqlite3.up.sql │ │ ├── 20210410175418000062_network.cockroach.down.sql │ │ ├── 20210410175418000062_network.cockroach.up.sql │ │ ├── 20210410175418000062_network.mysql.down.sql │ │ ├── 20210410175418000062_network.mysql.up.sql │ │ ├── 20210410175418000062_network.postgres.down.sql │ │ ├── 20210410175418000062_network.postgres.up.sql │ │ ├── 20210410175418000062_network.sqlite3.down.sql │ │ ├── 20210410175418000062_network.sqlite3.up.sql │ │ ├── 20210410175418000063_network.cockroach.down.sql │ │ ├── 20210410175418000063_network.cockroach.up.sql │ │ ├── 20210410175418000063_network.mysql.down.sql │ │ ├── 20210410175418000063_network.mysql.up.sql │ │ ├── 20210410175418000063_network.postgres.down.sql │ │ ├── 20210410175418000063_network.postgres.up.sql │ │ ├── 20210410175418000063_network.sqlite3.down.sql │ │ ├── 20210410175418000063_network.sqlite3.up.sql │ │ ├── 20210410175418000064_network.cockroach.down.sql │ │ ├── 20210410175418000064_network.cockroach.up.sql │ │ ├── 20210410175418000064_network.mysql.down.sql │ │ ├── 20210410175418000064_network.mysql.up.sql │ │ ├── 20210410175418000064_network.postgres.down.sql │ │ ├── 20210410175418000064_network.postgres.up.sql │ │ ├── 20210410175418000064_network.sqlite3.down.sql │ │ ├── 20210410175418000064_network.sqlite3.up.sql │ │ ├── 20210410175418000065_network.cockroach.down.sql │ │ ├── 20210410175418000065_network.cockroach.up.sql │ │ ├── 20210410175418000065_network.mysql.down.sql │ │ ├── 20210410175418000065_network.mysql.up.sql │ │ ├── 20210410175418000065_network.postgres.down.sql │ │ ├── 20210410175418000065_network.postgres.up.sql │ │ ├── 20210410175418000065_network.sqlite3.down.sql │ │ ├── 20210410175418000065_network.sqlite3.up.sql │ │ ├── 20210410175418000066_network.cockroach.down.sql │ │ ├── 20210410175418000066_network.cockroach.up.sql │ │ ├── 20210410175418000066_network.mysql.down.sql │ │ ├── 20210410175418000066_network.mysql.up.sql │ │ ├── 20210410175418000066_network.postgres.down.sql │ │ ├── 20210410175418000066_network.postgres.up.sql │ │ ├── 20210410175418000066_network.sqlite3.down.sql │ │ ├── 20210410175418000066_network.sqlite3.up.sql │ │ ├── 20210410175418000067_network.cockroach.down.sql │ │ ├── 20210410175418000067_network.cockroach.up.sql │ │ ├── 20210410175418000067_network.mysql.down.sql │ │ ├── 20210410175418000067_network.mysql.up.sql │ │ ├── 20210410175418000067_network.postgres.down.sql │ │ ├── 20210410175418000067_network.postgres.up.sql │ │ ├── 20210410175418000067_network.sqlite3.down.sql │ │ ├── 20210410175418000067_network.sqlite3.up.sql │ │ ├── 20210410175418000068_network.cockroach.down.sql │ │ ├── 20210410175418000068_network.cockroach.up.sql │ │ ├── 20210410175418000068_network.mysql.down.sql │ │ ├── 20210410175418000068_network.mysql.up.sql │ │ ├── 20210410175418000068_network.postgres.down.sql │ │ ├── 20210410175418000068_network.postgres.up.sql │ │ ├── 20210410175418000068_network.sqlite3.down.sql │ │ ├── 20210410175418000068_network.sqlite3.up.sql │ │ ├── 20210410175418000069_network.cockroach.down.sql │ │ ├── 20210410175418000069_network.cockroach.up.sql │ │ ├── 20210410175418000069_network.mysql.down.sql │ │ ├── 20210410175418000069_network.mysql.up.sql │ │ ├── 20210410175418000069_network.postgres.down.sql │ │ ├── 20210410175418000069_network.postgres.up.sql │ │ ├── 20210410175418000069_network.sqlite3.down.sql │ │ ├── 20210410175418000069_network.sqlite3.up.sql │ │ ├── 20210410175418000070_network.cockroach.down.sql │ │ ├── 20210410175418000070_network.cockroach.up.sql │ │ ├── 20210410175418000070_network.mysql.down.sql │ │ ├── 20210410175418000070_network.mysql.up.sql │ │ ├── 20210410175418000070_network.postgres.down.sql │ │ ├── 20210410175418000070_network.postgres.up.sql │ │ ├── 20210410175418000070_network.sqlite3.down.sql │ │ ├── 20210410175418000070_network.sqlite3.up.sql │ │ ├── 20210410175418000071_network.cockroach.down.sql │ │ ├── 20210410175418000071_network.cockroach.up.sql │ │ ├── 20210410175418000071_network.mysql.down.sql │ │ ├── 20210410175418000071_network.mysql.up.sql │ │ ├── 20210410175418000071_network.postgres.down.sql │ │ ├── 20210410175418000071_network.postgres.up.sql │ │ ├── 20210410175418000071_network.sqlite3.down.sql │ │ ├── 20210410175418000071_network.sqlite3.up.sql │ │ ├── 20210410175418000072_network.cockroach.down.sql │ │ ├── 20210410175418000072_network.cockroach.up.sql │ │ ├── 20210410175418000072_network.mysql.down.sql │ │ ├── 20210410175418000072_network.mysql.up.sql │ │ ├── 20210410175418000072_network.postgres.down.sql │ │ ├── 20210410175418000072_network.postgres.up.sql │ │ ├── 20210410175418000072_network.sqlite3.down.sql │ │ ├── 20210410175418000072_network.sqlite3.up.sql │ │ ├── 20210410175418000073_network.cockroach.down.sql │ │ ├── 20210410175418000073_network.cockroach.up.sql │ │ ├── 20210410175418000073_network.mysql.down.sql │ │ ├── 20210410175418000073_network.mysql.up.sql │ │ ├── 20210410175418000073_network.postgres.down.sql │ │ ├── 20210410175418000073_network.postgres.up.sql │ │ ├── 20210410175418000073_network.sqlite3.down.sql │ │ ├── 20210410175418000073_network.sqlite3.up.sql │ │ ├── 20210410175418000074_network.cockroach.down.sql │ │ ├── 20210410175418000074_network.cockroach.up.sql │ │ ├── 20210410175418000074_network.mysql.down.sql │ │ ├── 20210410175418000074_network.mysql.up.sql │ │ ├── 20210410175418000074_network.postgres.down.sql │ │ ├── 20210410175418000074_network.postgres.up.sql │ │ ├── 20210410175418000074_network.sqlite3.down.sql │ │ ├── 20210410175418000074_network.sqlite3.up.sql │ │ ├── 20210410175418000075_network.cockroach.down.sql │ │ ├── 20210410175418000075_network.cockroach.up.sql │ │ ├── 20210410175418000075_network.mysql.down.sql │ │ ├── 20210410175418000075_network.mysql.up.sql │ │ ├── 20210410175418000075_network.postgres.down.sql │ │ ├── 20210410175418000075_network.postgres.up.sql │ │ ├── 20210410175418000075_network.sqlite3.down.sql │ │ ├── 20210410175418000075_network.sqlite3.up.sql │ │ ├── 20210410175418000076_network.cockroach.down.sql │ │ ├── 20210410175418000076_network.cockroach.up.sql │ │ ├── 20210410175418000076_network.mysql.down.sql │ │ ├── 20210410175418000076_network.mysql.up.sql │ │ ├── 20210410175418000076_network.postgres.down.sql │ │ ├── 20210410175418000076_network.postgres.up.sql │ │ ├── 20210410175418000076_network.sqlite3.down.sql │ │ ├── 20210410175418000076_network.sqlite3.up.sql │ │ ├── 20210410175418000077_network.cockroach.down.sql │ │ ├── 20210410175418000077_network.cockroach.up.sql │ │ ├── 20210410175418000077_network.mysql.down.sql │ │ ├── 20210410175418000077_network.mysql.up.sql │ │ ├── 20210410175418000077_network.postgres.down.sql │ │ ├── 20210410175418000077_network.postgres.up.sql │ │ ├── 20210410175418000077_network.sqlite3.down.sql │ │ ├── 20210410175418000077_network.sqlite3.up.sql │ │ ├── 20210410175418000078_network.cockroach.down.sql │ │ ├── 20210410175418000078_network.cockroach.up.sql │ │ ├── 20210410175418000078_network.mysql.down.sql │ │ ├── 20210410175418000078_network.mysql.up.sql │ │ ├── 20210410175418000078_network.postgres.down.sql │ │ ├── 20210410175418000078_network.postgres.up.sql │ │ ├── 20210410175418000078_network.sqlite3.down.sql │ │ ├── 20210410175418000078_network.sqlite3.up.sql │ │ ├── 20210410175418000079_network.cockroach.down.sql │ │ ├── 20210410175418000079_network.cockroach.up.sql │ │ ├── 20210410175418000079_network.mysql.down.sql │ │ ├── 20210410175418000079_network.mysql.up.sql │ │ ├── 20210410175418000079_network.postgres.down.sql │ │ ├── 20210410175418000079_network.postgres.up.sql │ │ ├── 20210410175418000079_network.sqlite3.down.sql │ │ ├── 20210410175418000079_network.sqlite3.up.sql │ │ ├── 20210410175418000080_network.cockroach.down.sql │ │ ├── 20210410175418000080_network.cockroach.up.sql │ │ ├── 20210410175418000080_network.mysql.down.sql │ │ ├── 20210410175418000080_network.mysql.up.sql │ │ ├── 20210410175418000080_network.postgres.down.sql │ │ ├── 20210410175418000080_network.postgres.up.sql │ │ ├── 20210410175418000080_network.sqlite3.down.sql │ │ ├── 20210410175418000080_network.sqlite3.up.sql │ │ ├── 20210410175418000081_network.cockroach.down.sql │ │ ├── 20210410175418000081_network.cockroach.up.sql │ │ ├── 20210410175418000081_network.mysql.down.sql │ │ ├── 20210410175418000081_network.mysql.up.sql │ │ ├── 20210410175418000081_network.postgres.down.sql │ │ ├── 20210410175418000081_network.postgres.up.sql │ │ ├── 20210410175418000081_network.sqlite3.down.sql │ │ ├── 20210410175418000081_network.sqlite3.up.sql │ │ ├── 20210410175418000082_network.cockroach.down.sql │ │ ├── 20210410175418000082_network.cockroach.up.sql │ │ ├── 20210410175418000082_network.mysql.down.sql │ │ ├── 20210410175418000082_network.mysql.up.sql │ │ ├── 20210410175418000082_network.postgres.down.sql │ │ ├── 20210410175418000082_network.postgres.up.sql │ │ ├── 20210410175418000082_network.sqlite3.down.sql │ │ ├── 20210410175418000082_network.sqlite3.up.sql │ │ ├── 20210410175418000083_network.cockroach.down.sql │ │ ├── 20210410175418000083_network.cockroach.up.sql │ │ ├── 20210410175418000083_network.mysql.down.sql │ │ ├── 20210410175418000083_network.mysql.up.sql │ │ ├── 20210410175418000083_network.postgres.down.sql │ │ ├── 20210410175418000083_network.postgres.up.sql │ │ ├── 20210410175418000083_network.sqlite3.down.sql │ │ ├── 20210410175418000083_network.sqlite3.up.sql │ │ ├── 20210410175418000084_network.cockroach.down.sql │ │ ├── 20210410175418000084_network.cockroach.up.sql │ │ ├── 20210410175418000084_network.mysql.down.sql │ │ ├── 20210410175418000084_network.mysql.up.sql │ │ ├── 20210410175418000084_network.postgres.down.sql │ │ ├── 20210410175418000084_network.postgres.up.sql │ │ ├── 20210410175418000084_network.sqlite3.down.sql │ │ ├── 20210410175418000084_network.sqlite3.up.sql │ │ ├── 20210410175418000085_network.cockroach.down.sql │ │ ├── 20210410175418000085_network.cockroach.up.sql │ │ ├── 20210410175418000085_network.mysql.down.sql │ │ ├── 20210410175418000085_network.mysql.up.sql │ │ ├── 20210410175418000085_network.postgres.down.sql │ │ ├── 20210410175418000085_network.postgres.up.sql │ │ ├── 20210410175418000085_network.sqlite3.down.sql │ │ ├── 20210410175418000085_network.sqlite3.up.sql │ │ ├── 20210410175418000086_network.cockroach.down.sql │ │ ├── 20210410175418000086_network.cockroach.up.sql │ │ ├── 20210410175418000086_network.mysql.down.sql │ │ ├── 20210410175418000086_network.mysql.up.sql │ │ ├── 20210410175418000086_network.postgres.down.sql │ │ ├── 20210410175418000086_network.postgres.up.sql │ │ ├── 20210410175418000086_network.sqlite3.down.sql │ │ ├── 20210410175418000086_network.sqlite3.up.sql │ │ ├── 20210410175418000087_network.cockroach.down.sql │ │ ├── 20210410175418000087_network.cockroach.up.sql │ │ ├── 20210410175418000087_network.mysql.down.sql │ │ ├── 20210410175418000087_network.mysql.up.sql │ │ ├── 20210410175418000087_network.postgres.down.sql │ │ ├── 20210410175418000087_network.postgres.up.sql │ │ ├── 20210410175418000087_network.sqlite3.down.sql │ │ ├── 20210410175418000087_network.sqlite3.up.sql │ │ ├── 20210410175418000088_network.cockroach.down.sql │ │ ├── 20210410175418000088_network.cockroach.up.sql │ │ ├── 20210410175418000088_network.mysql.down.sql │ │ ├── 20210410175418000088_network.mysql.up.sql │ │ ├── 20210410175418000088_network.postgres.down.sql │ │ ├── 20210410175418000088_network.postgres.up.sql │ │ ├── 20210410175418000088_network.sqlite3.down.sql │ │ ├── 20210410175418000088_network.sqlite3.up.sql │ │ ├── 20210410175418000089_network.cockroach.down.sql │ │ ├── 20210410175418000089_network.cockroach.up.sql │ │ ├── 20210410175418000089_network.mysql.down.sql │ │ ├── 20210410175418000089_network.mysql.up.sql │ │ ├── 20210410175418000089_network.postgres.down.sql │ │ ├── 20210410175418000089_network.postgres.up.sql │ │ ├── 20210410175418000089_network.sqlite3.down.sql │ │ ├── 20210410175418000089_network.sqlite3.up.sql │ │ ├── 20210410175418000090_network.cockroach.down.sql │ │ ├── 20210410175418000090_network.cockroach.up.sql │ │ ├── 20210410175418000090_network.sqlite3.down.sql │ │ ├── 20210410175418000090_network.sqlite3.up.sql │ │ ├── 20210410175418000091_network.cockroach.down.sql │ │ ├── 20210410175418000091_network.cockroach.up.sql │ │ ├── 20210410175418000091_network.sqlite3.down.sql │ │ ├── 20210410175418000091_network.sqlite3.up.sql │ │ ├── 20210410175418000092_network.cockroach.down.sql │ │ ├── 20210410175418000092_network.cockroach.up.sql │ │ ├── 20210410175418000092_network.sqlite3.down.sql │ │ ├── 20210410175418000092_network.sqlite3.up.sql │ │ ├── 20210410175418000093_network.cockroach.down.sql │ │ ├── 20210410175418000093_network.cockroach.up.sql │ │ ├── 20210410175418000093_network.sqlite3.down.sql │ │ ├── 20210410175418000093_network.sqlite3.up.sql │ │ ├── 20210410175418000094_network.cockroach.down.sql │ │ ├── 20210410175418000094_network.cockroach.up.sql │ │ ├── 20210410175418000094_network.sqlite3.down.sql │ │ ├── 20210410175418000094_network.sqlite3.up.sql │ │ ├── 20210410175418000095_network.cockroach.down.sql │ │ ├── 20210410175418000095_network.cockroach.up.sql │ │ ├── 20210410175418000095_network.sqlite3.down.sql │ │ ├── 20210410175418000095_network.sqlite3.up.sql │ │ ├── 20210410175418000096_network.cockroach.down.sql │ │ ├── 20210410175418000096_network.cockroach.up.sql │ │ ├── 20210410175418000096_network.sqlite3.down.sql │ │ ├── 20210410175418000096_network.sqlite3.up.sql │ │ ├── 20210410175418000097_network.cockroach.down.sql │ │ ├── 20210410175418000097_network.cockroach.up.sql │ │ ├── 20210410175418000097_network.sqlite3.down.sql │ │ ├── 20210410175418000097_network.sqlite3.up.sql │ │ ├── 20210410175418000098_network.cockroach.down.sql │ │ ├── 20210410175418000098_network.cockroach.up.sql │ │ ├── 20210410175418000098_network.sqlite3.down.sql │ │ ├── 20210410175418000098_network.sqlite3.up.sql │ │ ├── 20210410175418000099_network.cockroach.down.sql │ │ ├── 20210410175418000099_network.cockroach.up.sql │ │ ├── 20210410175418000099_network.sqlite3.down.sql │ │ ├── 20210410175418000099_network.sqlite3.up.sql │ │ ├── 20210410175418000100_network.cockroach.down.sql │ │ ├── 20210410175418000100_network.cockroach.up.sql │ │ ├── 20210410175418000100_network.sqlite3.down.sql │ │ ├── 20210410175418000100_network.sqlite3.up.sql │ │ ├── 20210410175418000101_network.cockroach.down.sql │ │ ├── 20210410175418000101_network.cockroach.up.sql │ │ ├── 20210410175418000101_network.sqlite3.down.sql │ │ ├── 20210410175418000101_network.sqlite3.up.sql │ │ ├── 20210410175418000102_network.cockroach.down.sql │ │ ├── 20210410175418000102_network.cockroach.up.sql │ │ ├── 20210410175418000102_network.sqlite3.down.sql │ │ ├── 20210410175418000102_network.sqlite3.up.sql │ │ ├── 20210410175418000103_network.cockroach.down.sql │ │ ├── 20210410175418000103_network.cockroach.up.sql │ │ ├── 20210410175418000103_network.sqlite3.down.sql │ │ ├── 20210410175418000103_network.sqlite3.up.sql │ │ ├── 20210410175418000104_network.cockroach.down.sql │ │ ├── 20210410175418000104_network.cockroach.up.sql │ │ ├── 20210410175418000104_network.sqlite3.down.sql │ │ ├── 20210410175418000104_network.sqlite3.up.sql │ │ ├── 20210410175418000105_network.cockroach.down.sql │ │ ├── 20210410175418000105_network.cockroach.up.sql │ │ ├── 20210410175418000105_network.sqlite3.down.sql │ │ ├── 20210410175418000105_network.sqlite3.up.sql │ │ ├── 20210410175418000106_network.cockroach.down.sql │ │ ├── 20210410175418000106_network.cockroach.up.sql │ │ ├── 20210410175418000106_network.sqlite3.down.sql │ │ ├── 20210410175418000106_network.sqlite3.up.sql │ │ ├── 20210410175418000107_network.cockroach.down.sql │ │ ├── 20210410175418000107_network.cockroach.up.sql │ │ ├── 20210410175418000107_network.sqlite3.down.sql │ │ ├── 20210410175418000107_network.sqlite3.up.sql │ │ ├── 20210410175418000108_network.cockroach.down.sql │ │ ├── 20210410175418000108_network.cockroach.up.sql │ │ ├── 20210410175418000108_network.sqlite3.down.sql │ │ ├── 20210410175418000108_network.sqlite3.up.sql │ │ ├── 20210410175418000109_network.cockroach.down.sql │ │ ├── 20210410175418000109_network.cockroach.up.sql │ │ ├── 20210410175418000109_network.sqlite3.down.sql │ │ ├── 20210410175418000109_network.sqlite3.up.sql │ │ ├── 20210410175418000110_network.cockroach.down.sql │ │ ├── 20210410175418000110_network.cockroach.up.sql │ │ ├── 20210410175418000110_network.sqlite3.down.sql │ │ ├── 20210410175418000110_network.sqlite3.up.sql │ │ ├── 20210410175418000111_network.cockroach.down.sql │ │ ├── 20210410175418000111_network.cockroach.up.sql │ │ ├── 20210410175418000111_network.sqlite3.down.sql │ │ ├── 20210410175418000111_network.sqlite3.up.sql │ │ ├── 20210410175418000112_network.cockroach.down.sql │ │ ├── 20210410175418000112_network.cockroach.up.sql │ │ ├── 20210410175418000112_network.sqlite3.down.sql │ │ ├── 20210410175418000112_network.sqlite3.up.sql │ │ ├── 20210410175418000113_network.cockroach.down.sql │ │ ├── 20210410175418000113_network.cockroach.up.sql │ │ ├── 20210410175418000113_network.sqlite3.down.sql │ │ ├── 20210410175418000113_network.sqlite3.up.sql │ │ ├── 20210410175418000114_network.cockroach.down.sql │ │ ├── 20210410175418000114_network.cockroach.up.sql │ │ ├── 20210410175418000114_network.sqlite3.down.sql │ │ ├── 20210410175418000114_network.sqlite3.up.sql │ │ ├── 20210410175418000115_network.cockroach.down.sql │ │ ├── 20210410175418000115_network.cockroach.up.sql │ │ ├── 20210410175418000115_network.sqlite3.down.sql │ │ ├── 20210410175418000115_network.sqlite3.up.sql │ │ ├── 20210410175418000116_network.cockroach.down.sql │ │ ├── 20210410175418000116_network.cockroach.up.sql │ │ ├── 20210410175418000116_network.sqlite3.down.sql │ │ ├── 20210410175418000116_network.sqlite3.up.sql │ │ ├── 20210410175418000117_network.cockroach.down.sql │ │ ├── 20210410175418000117_network.cockroach.up.sql │ │ ├── 20210410175418000117_network.sqlite3.down.sql │ │ ├── 20210410175418000117_network.sqlite3.up.sql │ │ ├── 20210410175418000118_network.cockroach.down.sql │ │ ├── 20210410175418000118_network.cockroach.up.sql │ │ ├── 20210410175418000118_network.sqlite3.down.sql │ │ ├── 20210410175418000118_network.sqlite3.up.sql │ │ ├── 20210410175418000119_network.cockroach.down.sql │ │ ├── 20210410175418000119_network.cockroach.up.sql │ │ ├── 20210410175418000119_network.sqlite3.down.sql │ │ ├── 20210410175418000119_network.sqlite3.up.sql │ │ ├── 20210410175418000120_network.cockroach.down.sql │ │ ├── 20210410175418000120_network.cockroach.up.sql │ │ ├── 20210410175418000120_network.sqlite3.down.sql │ │ ├── 20210410175418000120_network.sqlite3.up.sql │ │ ├── 20210410175418000121_network.cockroach.down.sql │ │ ├── 20210410175418000121_network.cockroach.up.sql │ │ ├── 20210410175418000121_network.sqlite3.down.sql │ │ ├── 20210410175418000121_network.sqlite3.up.sql │ │ ├── 20210410175418000122_network.cockroach.down.sql │ │ ├── 20210410175418000122_network.cockroach.up.sql │ │ ├── 20210410175418000122_network.sqlite3.down.sql │ │ ├── 20210410175418000122_network.sqlite3.up.sql │ │ ├── 20210410175418000123_network.cockroach.down.sql │ │ ├── 20210410175418000123_network.cockroach.up.sql │ │ ├── 20210410175418000123_network.sqlite3.down.sql │ │ ├── 20210410175418000123_network.sqlite3.up.sql │ │ ├── 20210410175418000124_network.cockroach.down.sql │ │ ├── 20210410175418000124_network.cockroach.up.sql │ │ ├── 20210410175418000124_network.sqlite3.down.sql │ │ ├── 20210410175418000124_network.sqlite3.up.sql │ │ ├── 20210410175418000125_network.cockroach.down.sql │ │ ├── 20210410175418000125_network.cockroach.up.sql │ │ ├── 20210410175418000125_network.sqlite3.down.sql │ │ ├── 20210410175418000125_network.sqlite3.up.sql │ │ ├── 20210410175418000126_network.cockroach.down.sql │ │ ├── 20210410175418000126_network.cockroach.up.sql │ │ ├── 20210410175418000126_network.sqlite3.down.sql │ │ ├── 20210410175418000126_network.sqlite3.up.sql │ │ ├── 20210410175418000127_network.cockroach.down.sql │ │ ├── 20210410175418000127_network.cockroach.up.sql │ │ ├── 20210410175418000127_network.sqlite3.down.sql │ │ ├── 20210410175418000127_network.sqlite3.up.sql │ │ ├── 20210410175418000128_network.cockroach.down.sql │ │ ├── 20210410175418000128_network.cockroach.up.sql │ │ ├── 20210410175418000128_network.sqlite3.down.sql │ │ ├── 20210410175418000128_network.sqlite3.up.sql │ │ ├── 20210410175418000129_network.cockroach.down.sql │ │ ├── 20210410175418000129_network.cockroach.up.sql │ │ ├── 20210410175418000129_network.sqlite3.down.sql │ │ ├── 20210410175418000129_network.sqlite3.up.sql │ │ ├── 20210410175418000130_network.cockroach.down.sql │ │ ├── 20210410175418000130_network.cockroach.up.sql │ │ ├── 20210410175418000130_network.sqlite3.down.sql │ │ ├── 20210410175418000130_network.sqlite3.up.sql │ │ ├── 20210410175418000131_network.cockroach.down.sql │ │ ├── 20210410175418000131_network.cockroach.up.sql │ │ ├── 20210410175418000131_network.sqlite3.down.sql │ │ ├── 20210410175418000131_network.sqlite3.up.sql │ │ ├── 20210410175418000132_network.cockroach.down.sql │ │ ├── 20210410175418000132_network.cockroach.up.sql │ │ ├── 20210410175418000132_network.sqlite3.down.sql │ │ ├── 20210410175418000132_network.sqlite3.up.sql │ │ ├── 20210410175418000133_network.cockroach.down.sql │ │ ├── 20210410175418000133_network.cockroach.up.sql │ │ ├── 20210410175418000133_network.sqlite3.down.sql │ │ ├── 20210410175418000133_network.sqlite3.up.sql │ │ ├── 20210410175418000134_network.cockroach.down.sql │ │ ├── 20210410175418000134_network.cockroach.up.sql │ │ ├── 20210410175418000134_network.sqlite3.down.sql │ │ ├── 20210410175418000134_network.sqlite3.up.sql │ │ ├── 20210410175418000135_network.cockroach.down.sql │ │ ├── 20210410175418000135_network.cockroach.up.sql │ │ ├── 20210410175418000135_network.sqlite3.down.sql │ │ ├── 20210410175418000135_network.sqlite3.up.sql │ │ ├── 20210410175418000136_network.cockroach.down.sql │ │ ├── 20210410175418000136_network.cockroach.up.sql │ │ ├── 20210410175418000136_network.sqlite3.down.sql │ │ ├── 20210410175418000136_network.sqlite3.up.sql │ │ ├── 20210410175418000137_network.cockroach.down.sql │ │ ├── 20210410175418000137_network.cockroach.up.sql │ │ ├── 20210410175418000137_network.sqlite3.down.sql │ │ ├── 20210410175418000137_network.sqlite3.up.sql │ │ ├── 20210410175418000138_network.cockroach.down.sql │ │ ├── 20210410175418000138_network.cockroach.up.sql │ │ ├── 20210410175418000138_network.sqlite3.down.sql │ │ ├── 20210410175418000138_network.sqlite3.up.sql │ │ ├── 20210410175418000139_network.cockroach.down.sql │ │ ├── 20210410175418000139_network.cockroach.up.sql │ │ ├── 20210410175418000139_network.sqlite3.down.sql │ │ ├── 20210410175418000139_network.sqlite3.up.sql │ │ ├── 20210410175418000140_network.cockroach.down.sql │ │ ├── 20210410175418000140_network.cockroach.up.sql │ │ ├── 20210410175418000140_network.sqlite3.down.sql │ │ ├── 20210410175418000140_network.sqlite3.up.sql │ │ ├── 20210410175418000141_network.cockroach.down.sql │ │ ├── 20210410175418000141_network.cockroach.up.sql │ │ ├── 20210410175418000141_network.sqlite3.down.sql │ │ ├── 20210410175418000141_network.sqlite3.up.sql │ │ ├── 20210410175418000142_network.cockroach.down.sql │ │ ├── 20210410175418000142_network.cockroach.up.sql │ │ ├── 20210410175418000142_network.sqlite3.down.sql │ │ ├── 20210410175418000142_network.sqlite3.up.sql │ │ ├── 20210410175418000143_network.cockroach.down.sql │ │ ├── 20210410175418000143_network.cockroach.up.sql │ │ ├── 20210410175418000143_network.sqlite3.down.sql │ │ ├── 20210410175418000143_network.sqlite3.up.sql │ │ ├── 20210410175418000144_network.cockroach.down.sql │ │ ├── 20210410175418000144_network.cockroach.up.sql │ │ ├── 20210410175418000144_network.sqlite3.down.sql │ │ ├── 20210410175418000144_network.sqlite3.up.sql │ │ ├── 20210410175418000145_network.cockroach.down.sql │ │ ├── 20210410175418000145_network.cockroach.up.sql │ │ ├── 20210410175418000145_network.sqlite3.down.sql │ │ ├── 20210410175418000145_network.sqlite3.up.sql │ │ ├── 20210410175418000146_network.cockroach.down.sql │ │ ├── 20210410175418000146_network.cockroach.up.sql │ │ ├── 20210410175418000146_network.sqlite3.down.sql │ │ ├── 20210410175418000146_network.sqlite3.up.sql │ │ ├── 20210410175418000147_network.cockroach.down.sql │ │ ├── 20210410175418000147_network.cockroach.up.sql │ │ ├── 20210410175418000147_network.sqlite3.down.sql │ │ ├── 20210410175418000147_network.sqlite3.up.sql │ │ ├── 20210410175418000148_network.cockroach.down.sql │ │ ├── 20210410175418000148_network.cockroach.up.sql │ │ ├── 20210410175418000148_network.sqlite3.down.sql │ │ ├── 20210410175418000148_network.sqlite3.up.sql │ │ ├── 20210410175418000149_network.cockroach.down.sql │ │ ├── 20210410175418000149_network.cockroach.up.sql │ │ ├── 20210410175418000149_network.sqlite3.down.sql │ │ ├── 20210410175418000149_network.sqlite3.up.sql │ │ ├── 20210410175418000150_network.cockroach.down.sql │ │ ├── 20210410175418000150_network.cockroach.up.sql │ │ ├── 20210410175418000150_network.sqlite3.down.sql │ │ ├── 20210410175418000150_network.sqlite3.up.sql │ │ ├── 20210410175418000151_network.cockroach.down.sql │ │ ├── 20210410175418000151_network.cockroach.up.sql │ │ ├── 20210410175418000151_network.sqlite3.down.sql │ │ ├── 20210410175418000151_network.sqlite3.up.sql │ │ ├── 20210410175418000152_network.cockroach.down.sql │ │ ├── 20210410175418000152_network.cockroach.up.sql │ │ ├── 20210410175418000152_network.sqlite3.down.sql │ │ ├── 20210410175418000152_network.sqlite3.up.sql │ │ ├── 20210410175418000153_network.cockroach.down.sql │ │ ├── 20210410175418000153_network.cockroach.up.sql │ │ ├── 20210410175418000153_network.sqlite3.down.sql │ │ ├── 20210410175418000153_network.sqlite3.up.sql │ │ ├── 20210410175418000154_network.cockroach.down.sql │ │ ├── 20210410175418000154_network.cockroach.up.sql │ │ ├── 20210410175418000154_network.sqlite3.down.sql │ │ ├── 20210410175418000154_network.sqlite3.up.sql │ │ ├── 20210410175418000155_network.cockroach.down.sql │ │ ├── 20210410175418000155_network.cockroach.up.sql │ │ ├── 20210410175418000155_network.sqlite3.down.sql │ │ ├── 20210410175418000155_network.sqlite3.up.sql │ │ ├── 20210410175418000156_network.cockroach.down.sql │ │ ├── 20210410175418000156_network.cockroach.up.sql │ │ ├── 20210410175418000156_network.sqlite3.down.sql │ │ ├── 20210410175418000156_network.sqlite3.up.sql │ │ ├── 20210410175418000157_network.cockroach.down.sql │ │ ├── 20210410175418000157_network.cockroach.up.sql │ │ ├── 20210410175418000157_network.sqlite3.down.sql │ │ ├── 20210410175418000157_network.sqlite3.up.sql │ │ ├── 20210410175418000158_network.cockroach.down.sql │ │ ├── 20210410175418000158_network.cockroach.up.sql │ │ ├── 20210410175418000158_network.sqlite3.down.sql │ │ ├── 20210410175418000158_network.sqlite3.up.sql │ │ ├── 20210410175418000159_network.cockroach.down.sql │ │ ├── 20210410175418000159_network.cockroach.up.sql │ │ ├── 20210410175418000159_network.sqlite3.down.sql │ │ ├── 20210410175418000159_network.sqlite3.up.sql │ │ ├── 20210410175418000160_network.cockroach.down.sql │ │ ├── 20210410175418000160_network.cockroach.up.sql │ │ ├── 20210410175418000160_network.sqlite3.down.sql │ │ ├── 20210410175418000160_network.sqlite3.up.sql │ │ ├── 20210410175418000161_network.cockroach.down.sql │ │ ├── 20210410175418000161_network.cockroach.up.sql │ │ ├── 20210410175418000161_network.sqlite3.down.sql │ │ ├── 20210410175418000161_network.sqlite3.up.sql │ │ ├── 20210410175418000162_network.cockroach.down.sql │ │ ├── 20210410175418000162_network.cockroach.up.sql │ │ ├── 20210410175418000162_network.sqlite3.down.sql │ │ ├── 20210410175418000162_network.sqlite3.up.sql │ │ ├── 20210410175418000163_network.cockroach.down.sql │ │ ├── 20210410175418000163_network.cockroach.up.sql │ │ ├── 20210410175418000163_network.sqlite3.down.sql │ │ ├── 20210410175418000163_network.sqlite3.up.sql │ │ ├── 20210410175418000164_network.cockroach.down.sql │ │ ├── 20210410175418000164_network.cockroach.up.sql │ │ ├── 20210410175418000164_network.sqlite3.down.sql │ │ ├── 20210410175418000164_network.sqlite3.up.sql │ │ ├── 20210410175418000165_network.cockroach.down.sql │ │ ├── 20210410175418000165_network.cockroach.up.sql │ │ ├── 20210410175418000165_network.sqlite3.down.sql │ │ ├── 20210410175418000165_network.sqlite3.up.sql │ │ ├── 20210410175418000166_network.cockroach.down.sql │ │ ├── 20210410175418000166_network.cockroach.up.sql │ │ ├── 20210410175418000166_network.sqlite3.down.sql │ │ ├── 20210410175418000166_network.sqlite3.up.sql │ │ ├── 20210410175418000167_network.cockroach.down.sql │ │ ├── 20210410175418000167_network.cockroach.up.sql │ │ ├── 20210410175418000167_network.sqlite3.down.sql │ │ ├── 20210410175418000167_network.sqlite3.up.sql │ │ ├── 20210410175418000168_network.cockroach.down.sql │ │ ├── 20210410175418000168_network.cockroach.up.sql │ │ ├── 20210410175418000168_network.sqlite3.down.sql │ │ ├── 20210410175418000168_network.sqlite3.up.sql │ │ ├── 20210410175418000169_network.cockroach.down.sql │ │ ├── 20210410175418000169_network.cockroach.up.sql │ │ ├── 20210410175418000169_network.sqlite3.down.sql │ │ ├── 20210410175418000169_network.sqlite3.up.sql │ │ ├── 20210410175418000170_network.sqlite3.down.sql │ │ ├── 20210410175418000170_network.sqlite3.up.sql │ │ ├── 20210410175418000171_network.sqlite3.down.sql │ │ ├── 20210410175418000171_network.sqlite3.up.sql │ │ ├── 20210410175418000172_network.sqlite3.down.sql │ │ ├── 20210410175418000172_network.sqlite3.up.sql │ │ ├── 20210410175418000173_network.sqlite3.down.sql │ │ ├── 20210410175418000173_network.sqlite3.up.sql │ │ ├── 20210410175418000174_network.sqlite3.down.sql │ │ ├── 20210410175418000174_network.sqlite3.up.sql │ │ ├── 20210410175418000175_network.sqlite3.down.sql │ │ ├── 20210410175418000175_network.sqlite3.up.sql │ │ ├── 20210410175418000176_network.sqlite3.down.sql │ │ ├── 20210410175418000176_network.sqlite3.up.sql │ │ ├── 20210410175418000177_network.sqlite3.down.sql │ │ ├── 20210410175418000177_network.sqlite3.up.sql │ │ ├── 20210410175418000178_network.sqlite3.down.sql │ │ ├── 20210410175418000178_network.sqlite3.up.sql │ │ ├── 20210410175418000179_network.sqlite3.down.sql │ │ ├── 20210410175418000179_network.sqlite3.up.sql │ │ ├── 20210410175418000180_network.sqlite3.down.sql │ │ ├── 20210410175418000180_network.sqlite3.up.sql │ │ ├── 20210410175418000181_network.sqlite3.down.sql │ │ ├── 20210410175418000181_network.sqlite3.up.sql │ │ ├── 20210504121624000000_add_identity_states.cockroach.down.sql │ │ ├── 20210504121624000000_add_identity_states.cockroach.up.sql │ │ ├── 20210504121624000000_add_identity_states.mysql.down.sql │ │ ├── 20210504121624000000_add_identity_states.mysql.up.sql │ │ ├── 20210504121624000000_add_identity_states.postgres.down.sql │ │ ├── 20210504121624000000_add_identity_states.postgres.up.sql │ │ ├── 20210504121624000000_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000000_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000001_add_identity_states.cockroach.down.sql │ │ ├── 20210504121624000001_add_identity_states.cockroach.up.sql │ │ ├── 20210504121624000001_add_identity_states.mysql.down.sql │ │ ├── 20210504121624000001_add_identity_states.mysql.up.sql │ │ ├── 20210504121624000001_add_identity_states.postgres.down.sql │ │ ├── 20210504121624000001_add_identity_states.postgres.up.sql │ │ ├── 20210504121624000001_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000001_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000002_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000002_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000003_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000003_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000004_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000004_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000005_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000005_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000006_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000006_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000007_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000007_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000008_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000008_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000009_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000009_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000010_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000010_add_identity_states.sqlite3.up.sql │ │ ├── 20210504121624000011_add_identity_states.sqlite3.down.sql │ │ ├── 20210504121624000011_add_identity_states.sqlite3.up.sql │ │ ├── 20210618103120000000_logout_token.cockroach.down.sql │ │ ├── 20210618103120000000_logout_token.cockroach.up.sql │ │ ├── 20210618103120000000_logout_token.mysql.down.sql │ │ ├── 20210618103120000000_logout_token.mysql.up.sql │ │ ├── 20210618103120000000_logout_token.postgres.down.sql │ │ ├── 20210618103120000000_logout_token.postgres.up.sql │ │ ├── 20210618103120000000_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000000_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000001_logout_token.cockroach.down.sql │ │ ├── 20210618103120000001_logout_token.cockroach.up.sql │ │ ├── 20210618103120000001_logout_token.mysql.down.sql │ │ ├── 20210618103120000001_logout_token.mysql.up.sql │ │ ├── 20210618103120000001_logout_token.postgres.down.sql │ │ ├── 20210618103120000001_logout_token.postgres.up.sql │ │ ├── 20210618103120000001_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000001_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000002_logout_token.cockroach.down.sql │ │ ├── 20210618103120000002_logout_token.cockroach.up.sql │ │ ├── 20210618103120000002_logout_token.mysql.down.sql │ │ ├── 20210618103120000002_logout_token.mysql.up.sql │ │ ├── 20210618103120000002_logout_token.postgres.down.sql │ │ ├── 20210618103120000002_logout_token.postgres.up.sql │ │ ├── 20210618103120000002_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000002_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000003_logout_token.cockroach.down.sql │ │ ├── 20210618103120000003_logout_token.cockroach.up.sql │ │ ├── 20210618103120000003_logout_token.mysql.down.sql │ │ ├── 20210618103120000003_logout_token.mysql.up.sql │ │ ├── 20210618103120000003_logout_token.postgres.down.sql │ │ ├── 20210618103120000003_logout_token.postgres.up.sql │ │ ├── 20210618103120000003_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000003_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000004_logout_token.cockroach.down.sql │ │ ├── 20210618103120000004_logout_token.cockroach.up.sql │ │ ├── 20210618103120000004_logout_token.mysql.down.sql │ │ ├── 20210618103120000004_logout_token.mysql.up.sql │ │ ├── 20210618103120000004_logout_token.postgres.down.sql │ │ ├── 20210618103120000004_logout_token.postgres.up.sql │ │ ├── 20210618103120000004_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000004_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000005_logout_token.cockroach.down.sql │ │ ├── 20210618103120000005_logout_token.cockroach.up.sql │ │ ├── 20210618103120000005_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000005_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000006_logout_token.cockroach.down.sql │ │ ├── 20210618103120000006_logout_token.cockroach.up.sql │ │ ├── 20210618103120000006_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000006_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000007_logout_token.cockroach.down.sql │ │ ├── 20210618103120000007_logout_token.cockroach.up.sql │ │ ├── 20210618103120000007_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000007_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000008_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000008_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000009_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000009_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000010_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000010_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000011_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000011_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000012_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000012_logout_token.sqlite3.up.sql │ │ ├── 20210618103120000013_logout_token.sqlite3.down.sql │ │ ├── 20210618103120000013_logout_token.sqlite3.up.sql │ │ ├── 20210805112414000000_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000000_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000000_settings_flow_context.mysql.down.sql │ │ ├── 20210805112414000000_settings_flow_context.mysql.up.sql │ │ ├── 20210805112414000000_settings_flow_context.postgres.down.sql │ │ ├── 20210805112414000000_settings_flow_context.postgres.up.sql │ │ ├── 20210805112414000000_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000000_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000001_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000001_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000001_settings_flow_context.mysql.down.sql │ │ ├── 20210805112414000001_settings_flow_context.mysql.up.sql │ │ ├── 20210805112414000001_settings_flow_context.postgres.down.sql │ │ ├── 20210805112414000001_settings_flow_context.postgres.up.sql │ │ ├── 20210805112414000001_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000001_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000002_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000002_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000002_settings_flow_context.mysql.down.sql │ │ ├── 20210805112414000002_settings_flow_context.mysql.up.sql │ │ ├── 20210805112414000002_settings_flow_context.postgres.down.sql │ │ ├── 20210805112414000002_settings_flow_context.postgres.up.sql │ │ ├── 20210805112414000002_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000002_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000003_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000003_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000003_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000003_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000004_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000004_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000004_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000004_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000005_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000005_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000005_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000005_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000006_settings_flow_context.cockroach.down.sql │ │ ├── 20210805112414000006_settings_flow_context.cockroach.up.sql │ │ ├── 20210805112414000006_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000006_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805112414000007_settings_flow_context.sqlite3.down.sql │ │ ├── 20210805112414000007_settings_flow_context.sqlite3.up.sql │ │ ├── 20210805122535000000_credential_types_totp.cockroach.down.sql │ │ ├── 20210805122535000000_credential_types_totp.cockroach.up.sql │ │ ├── 20210805122535000000_credential_types_totp.mysql.down.sql │ │ ├── 20210805122535000000_credential_types_totp.mysql.up.sql │ │ ├── 20210805122535000000_credential_types_totp.postgres.down.sql │ │ ├── 20210805122535000000_credential_types_totp.postgres.up.sql │ │ ├── 20210805122535000000_credential_types_totp.sqlite3.down.sql │ │ ├── 20210805122535000000_credential_types_totp.sqlite3.up.sql │ │ ├── 20210810153530000000_aal.cockroach.down.sql │ │ ├── 20210810153530000000_aal.cockroach.up.sql │ │ ├── 20210810153530000000_aal.mysql.down.sql │ │ ├── 20210810153530000000_aal.mysql.up.sql │ │ ├── 20210810153530000000_aal.postgres.down.sql │ │ ├── 20210810153530000000_aal.postgres.up.sql │ │ ├── 20210810153530000000_aal.sqlite3.down.sql │ │ ├── 20210810153530000000_aal.sqlite3.up.sql │ │ ├── 20210810153530000001_aal.cockroach.down.sql │ │ ├── 20210810153530000001_aal.cockroach.up.sql │ │ ├── 20210810153530000001_aal.mysql.down.sql │ │ ├── 20210810153530000001_aal.mysql.up.sql │ │ ├── 20210810153530000001_aal.postgres.down.sql │ │ ├── 20210810153530000001_aal.postgres.up.sql │ │ ├── 20210810153530000001_aal.sqlite3.down.sql │ │ ├── 20210810153530000001_aal.sqlite3.up.sql │ │ ├── 20210810153530000002_aal.cockroach.down.sql │ │ ├── 20210810153530000002_aal.cockroach.up.sql │ │ ├── 20210810153530000002_aal.mysql.down.sql │ │ ├── 20210810153530000002_aal.mysql.up.sql │ │ ├── 20210810153530000002_aal.postgres.down.sql │ │ ├── 20210810153530000002_aal.postgres.up.sql │ │ ├── 20210810153530000002_aal.sqlite3.down.sql │ │ ├── 20210810153530000002_aal.sqlite3.up.sql │ │ ├── 20210810153530000003_aal.cockroach.down.sql │ │ ├── 20210810153530000003_aal.cockroach.up.sql │ │ ├── 20210810153530000003_aal.mysql.down.sql │ │ ├── 20210810153530000003_aal.mysql.up.sql │ │ ├── 20210810153530000003_aal.postgres.down.sql │ │ ├── 20210810153530000003_aal.postgres.up.sql │ │ ├── 20210810153530000003_aal.sqlite3.down.sql │ │ ├── 20210810153530000003_aal.sqlite3.up.sql │ │ ├── 20210810153530000004_aal.cockroach.down.sql │ │ ├── 20210810153530000004_aal.cockroach.up.sql │ │ ├── 20210810153530000004_aal.mysql.down.sql │ │ ├── 20210810153530000004_aal.mysql.up.sql │ │ ├── 20210810153530000004_aal.postgres.down.sql │ │ ├── 20210810153530000004_aal.postgres.up.sql │ │ ├── 20210810153530000004_aal.sqlite3.down.sql │ │ ├── 20210810153530000004_aal.sqlite3.up.sql │ │ ├── 20210810153530000005_aal.cockroach.down.sql │ │ ├── 20210810153530000005_aal.cockroach.up.sql │ │ ├── 20210810153530000005_aal.sqlite3.down.sql │ │ ├── 20210810153530000005_aal.sqlite3.up.sql │ │ ├── 20210810153530000006_aal.cockroach.down.sql │ │ ├── 20210810153530000006_aal.cockroach.up.sql │ │ ├── 20210810153530000006_aal.sqlite3.down.sql │ │ ├── 20210810153530000006_aal.sqlite3.up.sql │ │ ├── 20210810153530000007_aal.cockroach.down.sql │ │ ├── 20210810153530000007_aal.cockroach.up.sql │ │ ├── 20210810153530000007_aal.sqlite3.down.sql │ │ ├── 20210810153530000007_aal.sqlite3.up.sql │ │ ├── 20210810153530000008_aal.cockroach.down.sql │ │ ├── 20210810153530000008_aal.cockroach.up.sql │ │ ├── 20210810153530000008_aal.sqlite3.down.sql │ │ ├── 20210810153530000008_aal.sqlite3.up.sql │ │ ├── 20210810153530000009_aal.sqlite3.down.sql │ │ ├── 20210810153530000009_aal.sqlite3.up.sql │ │ ├── 20210810153530000010_aal.sqlite3.down.sql │ │ ├── 20210810153530000010_aal.sqlite3.up.sql │ │ ├── 20210810153530000011_aal.sqlite3.down.sql │ │ ├── 20210810153530000011_aal.sqlite3.up.sql │ │ ├── 20210810153530000012_aal.sqlite3.down.sql │ │ ├── 20210810153530000012_aal.sqlite3.up.sql │ │ ├── 20210810153530000013_aal.sqlite3.down.sql │ │ ├── 20210810153530000013_aal.sqlite3.up.sql │ │ ├── 20210810153530000014_aal.sqlite3.down.sql │ │ ├── 20210810153530000014_aal.sqlite3.up.sql │ │ ├── 20210810153530000015_aal.sqlite3.down.sql │ │ ├── 20210810153530000015_aal.sqlite3.up.sql │ │ ├── 20210810153530000016_aal.sqlite3.down.sql │ │ ├── 20210810153530000016_aal.sqlite3.up.sql │ │ ├── 20210810153530000017_aal.sqlite3.down.sql │ │ ├── 20210810153530000017_aal.sqlite3.up.sql │ │ ├── 20210810153530000018_aal.sqlite3.down.sql │ │ ├── 20210810153530000018_aal.sqlite3.up.sql │ │ ├── 20210810153530000019_aal.sqlite3.down.sql │ │ ├── 20210810153530000019_aal.sqlite3.up.sql │ │ ├── 20210810153530000020_aal.sqlite3.down.sql │ │ ├── 20210810153530000020_aal.sqlite3.up.sql │ │ ├── 20210810153530000021_aal.sqlite3.down.sql │ │ ├── 20210810153530000021_aal.sqlite3.up.sql │ │ ├── 20210810153530000022_aal.sqlite3.down.sql │ │ ├── 20210810153530000022_aal.sqlite3.up.sql │ │ ├── 20210810153530000023_aal.sqlite3.down.sql │ │ ├── 20210810153530000023_aal.sqlite3.up.sql │ │ ├── 20210810153530000024_aal.sqlite3.down.sql │ │ ├── 20210810153530000024_aal.sqlite3.up.sql │ │ ├── 20210810153530000025_aal.sqlite3.down.sql │ │ ├── 20210810153530000025_aal.sqlite3.up.sql │ │ ├── 20210810153530000026_aal.sqlite3.down.sql │ │ ├── 20210810153530000026_aal.sqlite3.up.sql │ │ ├── 20210810153530000027_aal.sqlite3.down.sql │ │ ├── 20210810153530000027_aal.sqlite3.up.sql │ │ ├── 20210810153530000028_aal.sqlite3.down.sql │ │ ├── 20210810153530000028_aal.sqlite3.up.sql │ │ ├── 20210810153530000029_aal.sqlite3.down.sql │ │ ├── 20210810153530000029_aal.sqlite3.up.sql │ │ ├── 20210810153530000030_aal.sqlite3.down.sql │ │ ├── 20210810153530000030_aal.sqlite3.up.sql │ │ ├── 20210810153530000031_aal.sqlite3.down.sql │ │ ├── 20210810153530000031_aal.sqlite3.up.sql │ │ ├── 20210810153530000032_aal.sqlite3.down.sql │ │ ├── 20210810153530000032_aal.sqlite3.up.sql │ │ ├── 20210810153530000033_aal.sqlite3.down.sql │ │ ├── 20210810153530000033_aal.sqlite3.up.sql │ │ ├── 20210813150152000000_credential_types_lookup.cockroach.down.sql │ │ ├── 20210813150152000000_credential_types_lookup.cockroach.up.sql │ │ ├── 20210813150152000000_credential_types_lookup.mysql.down.sql │ │ ├── 20210813150152000000_credential_types_lookup.mysql.up.sql │ │ ├── 20210813150152000000_credential_types_lookup.postgres.down.sql │ │ ├── 20210813150152000000_credential_types_lookup.postgres.up.sql │ │ ├── 20210813150152000000_credential_types_lookup.sqlite3.down.sql │ │ ├── 20210813150152000000_credential_types_lookup.sqlite3.up.sql │ │ ├── 20210816113956000000_webauthn.cockroach.down.sql │ │ ├── 20210816113956000000_webauthn.cockroach.up.sql │ │ ├── 20210816113956000000_webauthn.mysql.down.sql │ │ ├── 20210816113956000000_webauthn.mysql.up.sql │ │ ├── 20210816113956000000_webauthn.postgres.down.sql │ │ ├── 20210816113956000000_webauthn.postgres.up.sql │ │ ├── 20210816113956000000_webauthn.sqlite3.down.sql │ │ ├── 20210816113956000000_webauthn.sqlite3.up.sql │ │ ├── 20210816142650000000_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000000_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000000_flow_internal_context.mysql.down.sql │ │ ├── 20210816142650000000_flow_internal_context.mysql.up.sql │ │ ├── 20210816142650000000_flow_internal_context.postgres.down.sql │ │ ├── 20210816142650000000_flow_internal_context.postgres.up.sql │ │ ├── 20210816142650000000_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000000_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000001_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000001_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000001_flow_internal_context.mysql.down.sql │ │ ├── 20210816142650000001_flow_internal_context.mysql.up.sql │ │ ├── 20210816142650000001_flow_internal_context.postgres.down.sql │ │ ├── 20210816142650000001_flow_internal_context.postgres.up.sql │ │ ├── 20210816142650000001_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000001_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000002_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000002_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000002_flow_internal_context.mysql.down.sql │ │ ├── 20210816142650000002_flow_internal_context.mysql.up.sql │ │ ├── 20210816142650000002_flow_internal_context.postgres.down.sql │ │ ├── 20210816142650000002_flow_internal_context.postgres.up.sql │ │ ├── 20210816142650000002_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000002_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000003_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000003_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000003_flow_internal_context.mysql.down.sql │ │ ├── 20210816142650000003_flow_internal_context.mysql.up.sql │ │ ├── 20210816142650000003_flow_internal_context.postgres.down.sql │ │ ├── 20210816142650000003_flow_internal_context.postgres.up.sql │ │ ├── 20210816142650000003_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000003_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000004_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000004_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000004_flow_internal_context.mysql.down.sql │ │ ├── 20210816142650000004_flow_internal_context.mysql.up.sql │ │ ├── 20210816142650000004_flow_internal_context.postgres.down.sql │ │ ├── 20210816142650000004_flow_internal_context.postgres.up.sql │ │ ├── 20210816142650000004_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000004_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000005_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000005_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000005_flow_internal_context.mysql.down.sql │ │ ├── 20210816142650000005_flow_internal_context.mysql.up.sql │ │ ├── 20210816142650000005_flow_internal_context.postgres.down.sql │ │ ├── 20210816142650000005_flow_internal_context.postgres.up.sql │ │ ├── 20210816142650000005_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000005_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000006_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000006_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000006_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000006_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000007_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000007_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000007_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000007_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000008_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000008_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000008_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000008_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000009_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000009_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000009_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000009_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000010_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000010_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000010_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000010_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000011_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000011_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000011_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000011_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000012_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000012_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000012_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000012_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000013_flow_internal_context.cockroach.down.sql │ │ ├── 20210816142650000013_flow_internal_context.cockroach.up.sql │ │ ├── 20210816142650000013_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000013_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000014_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000014_flow_internal_context.sqlite3.up.sql │ │ ├── 20210816142650000015_flow_internal_context.sqlite3.down.sql │ │ ├── 20210816142650000015_flow_internal_context.sqlite3.up.sql │ │ ├── 20210817181232000000_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000000_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000000_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000000_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000000_unique_credentials.postgres.down.sql │ │ ├── 20210817181232000000_unique_credentials.postgres.up.sql │ │ ├── 20210817181232000000_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000000_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000001_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000001_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000001_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000001_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000001_unique_credentials.postgres.down.sql │ │ ├── 20210817181232000001_unique_credentials.postgres.up.sql │ │ ├── 20210817181232000001_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000001_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000002_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000002_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000002_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000002_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000002_unique_credentials.postgres.down.sql │ │ ├── 20210817181232000002_unique_credentials.postgres.up.sql │ │ ├── 20210817181232000002_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000002_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000003_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000003_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000003_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000003_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000003_unique_credentials.postgres.down.sql │ │ ├── 20210817181232000003_unique_credentials.postgres.up.sql │ │ ├── 20210817181232000003_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000003_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000004_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000004_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000004_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000004_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000004_unique_credentials.postgres.down.sql │ │ ├── 20210817181232000004_unique_credentials.postgres.up.sql │ │ ├── 20210817181232000004_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000004_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000005_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000005_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000005_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000005_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000005_unique_credentials.postgres.down.sql │ │ ├── 20210817181232000005_unique_credentials.postgres.up.sql │ │ ├── 20210817181232000005_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000005_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000006_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000006_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000006_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000006_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000006_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000006_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000007_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000007_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000007_unique_credentials.mysql.down.sql │ │ ├── 20210817181232000007_unique_credentials.mysql.up.sql │ │ ├── 20210817181232000007_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000007_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000008_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000008_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000008_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000008_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000009_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000009_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000009_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000009_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000010_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000010_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000010_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000010_unique_credentials.sqlite3.up.sql │ │ ├── 20210817181232000011_unique_credentials.cockroach.down.sql │ │ ├── 20210817181232000011_unique_credentials.cockroach.up.sql │ │ ├── 20210817181232000011_unique_credentials.sqlite3.down.sql │ │ ├── 20210817181232000011_unique_credentials.sqlite3.up.sql │ │ ├── 20210829131458000000_session_aal_legacy.cockroach.down.sql │ │ ├── 20210829131458000000_session_aal_legacy.cockroach.up.sql │ │ ├── 20210829131458000000_session_aal_legacy.mysql.down.sql │ │ ├── 20210829131458000000_session_aal_legacy.mysql.up.sql │ │ ├── 20210829131458000000_session_aal_legacy.postgres.down.sql │ │ ├── 20210829131458000000_session_aal_legacy.postgres.up.sql │ │ ├── 20210829131458000000_session_aal_legacy.sqlite3.down.sql │ │ ├── 20210829131458000000_session_aal_legacy.sqlite3.up.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.mysql.down.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.mysql.up.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.postgres.down.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.postgres.up.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000000_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.mysql.down.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.mysql.up.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.postgres.down.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.postgres.up.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000001_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.mysql.down.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.mysql.up.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.postgres.down.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.postgres.up.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000002_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.mysql.down.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.mysql.up.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.postgres.down.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.postgres.up.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000003_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.mysql.down.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.mysql.up.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.postgres.down.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.postgres.up.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000004_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000005_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000005_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000005_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000005_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000006_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000006_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000006_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000006_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000007_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000007_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000007_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000007_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000008_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000008_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000008_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000008_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000009_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000009_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000009_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000009_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000010_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000010_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000010_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000010_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000011_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000011_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000011_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000011_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000012_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000012_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000012_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000012_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000013_identity_recovery_tokens.cockroach.down.sql │ │ ├── 20210913095309000013_identity_recovery_tokens.cockroach.up.sql │ │ ├── 20210913095309000013_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000013_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000014_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000014_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000015_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000015_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000016_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000016_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000017_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000017_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000018_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000018_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000019_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000019_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20210913095309000020_identity_recovery_tokens.sqlite3.down.sql │ │ ├── 20210913095309000020_identity_recovery_tokens.sqlite3.up.sql │ │ ├── 20220118104539000000_identity_fk_indexes.cockroach.down.sql │ │ ├── 20220118104539000000_identity_fk_indexes.cockroach.up.sql │ │ ├── 20220118104539000000_identity_fk_indexes.postgres.down.sql │ │ ├── 20220118104539000000_identity_fk_indexes.postgres.up.sql │ │ ├── 20220118104539000000_identity_fk_indexes.sqlite3.down.sql │ │ ├── 20220118104539000000_identity_fk_indexes.sqlite3.up.sql │ │ ├── 20220118104539000001_identity_fk_indexes.cockroach.down.sql │ │ ├── 20220118104539000001_identity_fk_indexes.cockroach.up.sql │ │ ├── 20220118104539000001_identity_fk_indexes.postgres.down.sql │ │ ├── 20220118104539000001_identity_fk_indexes.postgres.up.sql │ │ ├── 20220118104539000001_identity_fk_indexes.sqlite3.down.sql │ │ ├── 20220118104539000001_identity_fk_indexes.sqlite3.up.sql │ │ ├── 20220118104539000002_identity_fk_indexes.cockroach.down.sql │ │ ├── 20220118104539000002_identity_fk_indexes.cockroach.up.sql │ │ ├── 20220118104539000002_identity_fk_indexes.postgres.down.sql │ │ ├── 20220118104539000002_identity_fk_indexes.postgres.up.sql │ │ ├── 20220118104539000002_identity_fk_indexes.sqlite3.down.sql │ │ ├── 20220118104539000002_identity_fk_indexes.sqlite3.up.sql │ │ ├── 20220118104539000003_identity_fk_indexes.cockroach.down.sql │ │ ├── 20220118104539000003_identity_fk_indexes.cockroach.up.sql │ │ ├── 20220118104539000003_identity_fk_indexes.postgres.down.sql │ │ ├── 20220118104539000003_identity_fk_indexes.postgres.up.sql │ │ ├── 20220118104539000003_identity_fk_indexes.sqlite3.down.sql │ │ ├── 20220118104539000003_identity_fk_indexes.sqlite3.up.sql │ │ ├── 20220301102701000000_identity_credentials_version.cockroach.down.sql │ │ ├── 20220301102701000000_identity_credentials_version.cockroach.up.sql │ │ ├── 20220301102701000000_identity_credentials_version.mysql.down.sql │ │ ├── 20220301102701000000_identity_credentials_version.mysql.up.sql │ │ ├── 20220301102701000000_identity_credentials_version.postgres.down.sql │ │ ├── 20220301102701000000_identity_credentials_version.postgres.up.sql │ │ ├── 20220301102701000000_identity_credentials_version.sqlite3.down.sql │ │ ├── 20220301102701000000_identity_credentials_version.sqlite3.up.sql │ │ ├── 20220301102701000001_identity_credentials_version.cockroach.down.sql │ │ ├── 20220301102701000001_identity_credentials_version.cockroach.up.sql │ │ ├── 20220301102701000001_identity_credentials_version.mysql.down.sql │ │ ├── 20220301102701000001_identity_credentials_version.mysql.up.sql │ │ ├── 20220301102701000001_identity_credentials_version.postgres.down.sql │ │ ├── 20220301102701000001_identity_credentials_version.postgres.up.sql │ │ ├── 20220301102701000001_identity_credentials_version.sqlite3.down.sql │ │ ├── 20220301102701000001_identity_credentials_version.sqlite3.up.sql │ │ ├── 20220420102701000000_identity_metadata.down.sql │ │ ├── 20220420102701000000_identity_metadata.mysql.up.sql │ │ ├── 20220420102701000000_identity_metadata.up.sql │ │ ├── 20220512102703000000_missing_indices.down.sql │ │ ├── 20220512102703000000_missing_indices.mysql.down.sql │ │ ├── 20220512102703000000_missing_indices.mysql.up.sql │ │ ├── 20220512102703000000_missing_indices.up.sql │ │ ├── 20220607000001000000_hydra_login_challenge.cockroach.down.sql │ │ ├── 20220607000001000000_hydra_login_challenge.cockroach.up.sql │ │ ├── 20220607000001000000_hydra_login_challenge.mysql.down.sql │ │ ├── 20220607000001000000_hydra_login_challenge.mysql.up.sql │ │ ├── 20220607000001000000_hydra_login_challenge.postgres.down.sql │ │ ├── 20220607000001000000_hydra_login_challenge.postgres.up.sql │ │ ├── 20220607000001000000_hydra_login_challenge.sqlite3.down.sql │ │ ├── 20220607000001000000_hydra_login_challenge.sqlite3.up.sql │ │ ├── 20220610155809000000_identity_address_casing.cockroach.down.sql │ │ ├── 20220610155809000000_identity_address_casing.cockroach.up.sql │ │ ├── 20220610155809000000_identity_address_casing.mysql.down.sql │ │ ├── 20220610155809000000_identity_address_casing.mysql.up.sql │ │ ├── 20220610155809000000_identity_address_casing.postgres.down.sql │ │ ├── 20220610155809000000_identity_address_casing.postgres.up.sql │ │ ├── 20220610155809000000_identity_address_casing.sqlite3.down.sql │ │ ├── 20220610155809000000_identity_address_casing.sqlite3.up.sql │ │ ├── 20220802103909000000_courier_send_count.down.sql │ │ ├── 20220802103909000000_courier_send_count.up.sql │ │ ├── 20220824165300000000_add_flow_type_to_identity_recovery_tokens.down.sql │ │ ├── 20220824165300000000_add_flow_type_to_identity_recovery_tokens.up.sql │ │ ├── 20220824165300000001_populate_flow_type_in_recovery_tokens.down.sql │ │ ├── 20220824165300000001_populate_flow_type_in_recovery_tokens.up.sql │ │ ├── 20220824165300000002_add_flow_type_check_constraint.down.sql │ │ ├── 20220824165300000002_add_flow_type_check_constraint.sqlite3.down.sql │ │ ├── 20220824165300000002_add_flow_type_check_constraint.sqlite3.up.sql │ │ ├── 20220824165300000002_add_flow_type_check_constraint.up.sql │ │ ├── 20220825134336000000_delete_verification_token_without_flow_id.down.sql │ │ ├── 20220825134336000000_delete_verification_token_without_flow_id.up.sql │ │ ├── 20220825134336000001_not_null_constraint_verification_token_flow_id.down.sql │ │ ├── 20220825134336000001_not_null_constraint_verification_token_flow_id.mysql.down.sql │ │ ├── 20220825134336000001_not_null_constraint_verification_token_flow_id.mysql.up.sql │ │ ├── 20220825134336000001_not_null_constraint_verification_token_flow_id.sqlite3.down.sql │ │ ├── 20220825134336000001_not_null_constraint_verification_token_flow_id.sqlite3.up.sql │ │ ├── 20220825134336000001_not_null_constraint_verification_token_flow_id.up.sql │ │ ├── 20220901123209000000_recovery_code.down.sql │ │ ├── 20220901123209000000_recovery_code.mysql.down.sql │ │ ├── 20220901123209000000_recovery_code.mysql.up.sql │ │ ├── 20220901123209000000_recovery_code.up.sql │ │ ├── 20220907132836000000_add_session_devices_table.down.sql │ │ ├── 20220907132836000000_add_session_devices_table.mysql.down.sql │ │ ├── 20220907132836000000_add_session_devices_table.mysql.up.sql │ │ ├── 20220907132836000000_add_session_devices_table.up.sql │ │ ├── 20221024182336000000_verification_code.down.sql │ │ ├── 20221024182336000000_verification_code.mysql.up.sql │ │ ├── 20221024182336000000_verification_code.up.sql │ │ ├── 20221205092803000000_add_courier_send_attempts_table.down.sql │ │ ├── 20221205092803000000_add_courier_send_attempts_table.mysql.up.sql │ │ ├── 20221205092803000000_add_courier_send_attempts_table.up.sql │ │ ├── 20221214101328000000_identity_delete_indices.down.sql │ │ ├── 20221214101328000000_identity_delete_indices.mysql.down.sql │ │ ├── 20221214101328000000_identity_delete_indices.mysql.up.sql │ │ ├── 20221214101328000000_identity_delete_indices.up.sql │ │ ├── 20221220124639000000_errors_index.down.sql │ │ ├── 20221220124639000000_errors_index.mysql.down.sql │ │ ├── 20221220124639000000_errors_index.mysql.up.sql │ │ ├── 20221220124639000000_errors_index.up.sql │ │ ├── 20230104193739000000_courier_list_index.down.sql │ │ ├── 20230104193739000000_courier_list_index.mysql.down.sql │ │ ├── 20230104193739000000_courier_list_index.up.sql │ │ ├── 20230216142104000000_session_devices_index_drop.cockroach.down.sql │ │ ├── 20230216142104000000_session_devices_index_drop.cockroach.up.sql │ │ ├── 20230216142104000000_session_devices_index_drop.down.sql │ │ ├── 20230216142104000000_session_devices_index_drop.mysql.down.sql │ │ ├── 20230216142104000000_session_devices_index_drop.mysql.up.sql │ │ ├── 20230216142104000000_session_devices_index_drop.up.sql │ │ ├── 20230313141439000000_session_token_length.cockroach.down.sql │ │ ├── 20230313141439000000_session_token_length.down.sql │ │ ├── 20230313141439000000_session_token_length.mysql.down.sql │ │ ├── 20230313141439000000_session_token_length.mysql.up.sql │ │ ├── 20230313141439000000_session_token_length.sqlite3.down.sql │ │ ├── 20230313141439000000_session_token_length.sqlite3.up.sql │ │ ├── 20230313141439000000_session_token_length.up.sql │ │ ├── 20230313141439000001_session_token_length.down.sql │ │ ├── 20230313141439000001_session_token_length.sqlite3.down.sql │ │ ├── 20230313141439000001_session_token_length.up.sql │ │ ├── 20230322144139000001_missing_login_index.down.sql │ │ ├── 20230322144139000001_missing_login_index.mysql.down.sql │ │ ├── 20230322144139000001_missing_login_index.up.sql │ │ ├── 20230405000000000001_create_session_token_exchanges.down.sql │ │ ├── 20230405000000000001_create_session_token_exchanges.mysql.down.sql │ │ ├── 20230405000000000001_create_session_token_exchanges.mysql.up.sql │ │ ├── 20230405000000000001_create_session_token_exchanges.up.sql │ │ ├── 20230614000001000000_hydra_login_challenge_format.down.sql │ │ ├── 20230614000001000000_hydra_login_challenge_format.mysql.down.sql │ │ ├── 20230614000001000000_hydra_login_challenge_format.sqlite3.down.sql │ │ ├── 20230614000001000000_hydra_login_challenge_format.up.sql │ │ ├── 20230619000000000001_sessions_add_sorted_indices.down.sql │ │ ├── 20230619000000000001_sessions_add_sorted_indices.mysql.down.sql │ │ ├── 20230619000000000001_sessions_add_sorted_indices.mysql.up.sql │ │ ├── 20230619000000000001_sessions_add_sorted_indices.up.sql │ │ ├── 20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.down.sql │ │ ├── 20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.mysql.down.sql │ │ ├── 20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.mysql.up.sql │ │ ├── 20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.up.sql │ │ ├── 20230703143600000001_selfservice_registration_login_flows_state.down.sql │ │ ├── 20230703143600000001_selfservice_registration_login_flows_state.up.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.cockroach.down.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.down.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.mysql.down.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.mysql.up.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.sqlite.down.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.sqlite.up.sql │ │ ├── 20230705000000000001_cookie_flow_request_url.up.sql │ │ ├── 20230706000000000001_available_aal.down.sql │ │ ├── 20230706000000000001_available_aal.up.sql │ │ ├── 20230707133700000000_identity_login_code.down.sql │ │ ├── 20230707133700000000_identity_login_code.mysql.up.sql │ │ ├── 20230707133700000000_identity_login_code.up.sql │ │ ├── 20230707133700000001_identity_registration_code.down.sql │ │ ├── 20230707133700000001_identity_registration_code.mysql.up.sql │ │ ├── 20230707133700000001_identity_registration_code.up.sql │ │ ├── 20230712173852000000_credential_types_code.down.sql │ │ ├── 20230712173852000000_credential_types_code.up.sql │ │ ├── 20230811000000000001_verification_add_oauth2_login_challenge.down.sql │ │ ├── 20230811000000000001_verification_add_oauth2_login_challenge.up.sql │ │ ├── 20230818000000000001_verification_add_oauth2_login_challenge_identity_id.down.sql │ │ ├── 20230818000000000001_verification_add_oauth2_login_challenge_identity_id.mysql.up.sql │ │ ├── 20230818000000000001_verification_add_oauth2_login_challenge_identity_id.up.sql │ │ ├── 20230823000000000001_verification_add_oauth2_login_challenge_params.down.sql │ │ ├── 20230823000000000001_verification_add_oauth2_login_challenge_params.mysql.up.sql │ │ ├── 20230823000000000001_verification_add_oauth2_login_challenge_params.sqlite.up.sql │ │ ├── 20230823000000000001_verification_add_oauth2_login_challenge_params.up.sql │ │ ├── 20230907085000000000_add_organization_id.down.sql │ │ ├── 20230907085000000000_add_organization_id.mysql.up.sql │ │ ├── 20230907085000000000_add_organization_id.up.sql │ │ ├── 20230920171028000000_identity_search_index.cockroach.down.sql │ │ ├── 20230920171028000000_identity_search_index.cockroach.up.sql │ │ ├── 20230920171028000000_identity_search_index.down.sql │ │ ├── 20230920171028000000_identity_search_index.postgres.down.sql │ │ ├── 20230920171028000000_identity_search_index.postgres.up.sql │ │ ├── 20230920171028000000_identity_search_index.up.sql │ │ ├── 20231108111100000000_credential_types_passkey.down.sql │ │ ├── 20231108111100000000_credential_types_passkey.up.sql │ │ ├── 20231130094628000000_courier_message_channel.down.sql │ │ ├── 20231130094628000000_courier_message_channel.up.sql │ │ ├── 20240119094628000000_sessions_created_at_index.down.sql │ │ ├── 20240119094628000000_sessions_created_at_index.mysql.down.sql │ │ ├── 20240119094628000000_sessions_created_at_index.up.sql │ │ ├── 20240213095000000000_identity_credentials_user_handle_index.cockroach.down.sql │ │ ├── 20240213095000000000_identity_credentials_user_handle_index.cockroach.up.sql │ │ ├── 20240213095000000000_identity_credentials_user_handle_index.down.sql │ │ ├── 20240213095000000000_identity_credentials_user_handle_index.up.sql │ │ ├── 20240214113828000000_courier_dispatch_indices.down.sql │ │ ├── 20240214113828000000_courier_dispatch_indices.mysql.down.sql │ │ ├── 20240214113828000000_courier_dispatch_indices.mysql.up.sql │ │ ├── 20240214113828000000_courier_dispatch_indices.up.sql │ │ ├── 20240221000000000000_identity_recovery_codes_flow_id_idx.cockroach.down.sql │ │ ├── 20240221000000000000_identity_recovery_codes_flow_id_idx.down.sql │ │ ├── 20240221000000000000_identity_recovery_codes_flow_id_idx.mysql.down.sql │ │ ├── 20240221000000000000_identity_recovery_codes_flow_id_idx.mysql.up.sql │ │ ├── 20240221000000000000_identity_recovery_codes_flow_id_idx.sqlite.up.sql │ │ ├── 20240221000000000000_identity_recovery_codes_flow_id_idx.up.sql │ │ ├── 20240318143139000000_drop_identity_search_index.down.sql │ │ ├── 20240318143139000000_drop_identity_search_index.postgres.down.sql │ │ ├── 20240318143139000000_drop_identity_search_index.postgres.up.sql │ │ ├── 20240318143139000000_drop_identity_search_index.up.sql │ │ ├── 20240325153839000000_drop_identity_search_index.cockroach.down.sql │ │ ├── 20240325153839000000_drop_identity_search_index.cockroach.up.sql │ │ ├── 20240325153839000000_drop_identity_search_index.down.sql │ │ ├── 20240325153839000000_drop_identity_search_index.up.sql │ │ ├── 20240425095000000000_identity_credentials_fix_user_handle_index.cockroach.down.sql │ │ ├── 20240425095000000000_identity_credentials_fix_user_handle_index.cockroach.up.sql │ │ ├── 20240425095000000000_identity_credentials_fix_user_handle_index.down.sql │ │ ├── 20240425095000000000_identity_credentials_fix_user_handle_index.up.sql │ │ ├── 20240425095000000001_identity_credentials_fix_user_handle_index.cockroach.down.sql │ │ ├── 20240425095000000001_identity_credentials_fix_user_handle_index.cockroach.up.sql │ │ ├── 20240425095000000001_identity_credentials_fix_user_handle_index.down.sql │ │ ├── 20240425095000000001_identity_credentials_fix_user_handle_index.up.sql │ │ ├── 20240923095000000001_organization_id_index.down.sql │ │ ├── 20240923095000000001_organization_id_index.mysql.down.sql │ │ ├── 20240923095000000001_organization_id_index.up.sql │ │ ├── 20241023142500000001_drop_unused_indices_identity_credentials.down.sql │ │ ├── 20241023142500000001_drop_unused_indices_identity_credentials.mysql.down.sql │ │ ├── 20241023142500000001_drop_unused_indices_identity_credentials.mysql.up.sql │ │ ├── 20241023142500000001_drop_unused_indices_identity_credentials.up.sql │ │ ├── 20241023142500000002_drop_unused_indices_sessions.down.sql │ │ ├── 20241023142500000002_drop_unused_indices_sessions.mysql.down.sql │ │ ├── 20241023142500000002_drop_unused_indices_sessions.mysql.up.sql │ │ ├── 20241023142500000002_drop_unused_indices_sessions.up.sql │ │ ├── 20241023142500000003_drop_unused_indices_credential_identifiers.cockroach.down.sql │ │ ├── 20241023142500000003_drop_unused_indices_credential_identifiers.cockroach.up.sql │ │ ├── 20241023142500000003_drop_unused_indices_credential_identifiers.down.sql │ │ ├── 20241023142500000003_drop_unused_indices_credential_identifiers.mysql.down.sql │ │ ├── 20241023142500000003_drop_unused_indices_credential_identifiers.mysql.up.sql │ │ ├── 20241023142500000003_drop_unused_indices_credential_identifiers.up.sql │ │ ├── 20241029102200000001_self_service.down.sql │ │ ├── 20241029102200000001_self_service.mysql.down.sql │ │ ├── 20241029102200000001_self_service.mysql.up.sql │ │ ├── 20241029102200000001_self_service.up.sql │ │ ├── 20241029153900000001_identities.autocommit.down.sql │ │ ├── 20241029153900000001_identities.autocommit.up.sql │ │ ├── 20241029153900000001_identities.mysql.down.sql │ │ ├── 20241029153900000001_identities.mysql.up.sql │ │ ├── 20241031094100000001_remaining_unused_indices.autocommit.down.sql │ │ ├── 20241031094100000001_remaining_unused_indices.autocommit.up.sql │ │ ├── 20241031094100000001_remaining_unused_indices.mysql.down.sql │ │ ├── 20241031094100000001_remaining_unused_indices.mysql.up.sql │ │ ├── 20241031094100000002_foreign_key.autocommit.down.sql │ │ ├── 20241031094100000002_foreign_key.autocommit.up.sql │ │ ├── 20241031094100000002_foreign_key.sqlite.down.sql │ │ ├── 20241031094100000002_foreign_key.sqlite.up.sql │ │ ├── 20241106142200000001_identities.autocommit.down.sql │ │ ├── 20241106142200000001_identities.autocommit.up.sql │ │ ├── 20241106142200000001_identities.mysql.down.sql │ │ ├── 20241106142200000001_identities.mysql.up.sql │ │ ├── 20241106142200000002_identities.autocommit.down.sql │ │ ├── 20241106142200000002_identities.autocommit.up.sql │ │ ├── 20241106142200000002_identities.mysql.down.sql │ │ ├── 20241106142200000002_identities.mysql.up.sql │ │ ├── 20241108105000000001_index_cleanup.autocommit.down.sql │ │ ├── 20241108105000000001_index_cleanup.autocommit.up.sql │ │ ├── 20241108105000000001_index_cleanup.mysql.down.sql │ │ ├── 20241108105000000001_index_cleanup.mysql.up.sql │ │ ├── 20241203105600000000_saml_credential_type.down.sql │ │ ├── 20241203105600000000_saml_credential_type.up.sql │ │ ├── 20250505150900000000_code_address_type.autocommit.down.sql │ │ ├── 20250505150900000000_code_address_type.autocommit.up.sql │ │ ├── 20250505150900000000_code_address_type.mysql.down.sql │ │ ├── 20250505150900000000_code_address_type.mysql.up.sql │ │ ├── 20250505150900000000_code_address_type.sqlite.down.sql │ │ ├── 20250505150900000000_code_address_type.sqlite.up.sql │ │ ├── 20250708190000000000_identities_external_id.autocommit.down.sql │ │ ├── 20250708190000000000_identities_external_id.autocommit.up.sql │ │ ├── 20250708190000000000_identities_external_id.cockroach.autocommit.down.sql │ │ ├── 20250708190000000000_identities_external_id.cockroach.autocommit.up.sql │ │ ├── 20250708190000000001_identities_external_id_index.cockroach.autocommit.up.sql │ │ ├── 20250708190000000001_identities_external_id_index.down.sql │ │ ├── 20250708190000000001_identities_external_id_index.mysql.down.sql │ │ ├── 20250708190000000001_identities_external_id_index.mysql.up.sql │ │ ├── 20250708190000000001_identities_external_id_index.up.sql │ │ ├── 20250710085000000000_add_schema_id.cockroach.autocommit.down.sql │ │ ├── 20250710085000000000_add_schema_id.cockroach.autocommit.up.sql │ │ ├── 20250710085000000000_add_schema_id.down.sql │ │ ├── 20250710085000000000_add_schema_id.up.sql │ │ ├── 20251104000000000000_identifiers_devices_identity_id.down.sql │ │ ├── 20251104000000000000_identifiers_devices_identity_id.mysql.up.sql │ │ ├── 20251104000000000000_identifiers_devices_identity_id.sqlite.down.sql │ │ ├── 20251104000000000000_identifiers_devices_identity_id.sqlite.up.sql │ │ ├── 20251104000000000000_identifiers_devices_identity_id.up.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.cockroach.up.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.down.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.mysql.down.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.mysql.up.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.postgres.up.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.sqlite.down.sql │ │ ├── 20251105000000000003_identity_id_not_null_fks.sqlite.up.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.cockroach.down.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.cockroach.up.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.mysql.down.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.mysql.up.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.postgres.down.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.postgres.up.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.sqlite.down.sql │ │ ├── 20251105000000000004_identity_id_not_null_fks.sqlite.up.sql │ │ ├── 20251215000000000000_courier_messages_add_request_headers.down.sql │ │ ├── 20251215000000000000_courier_messages_add_request_headers.mysql.down.sql │ │ ├── 20251215000000000000_courier_messages_add_request_headers.mysql.up.sql │ │ ├── 20251215000000000000_courier_messages_add_request_headers.sqlite.down.sql │ │ ├── 20251215000000000000_courier_messages_add_request_headers.sqlite.up.sql │ │ ├── 20251215000000000000_courier_messages_add_request_headers.up.sql │ │ ├── 20260114175904000000_drop_identity_credentials_nid_idx.down.sql │ │ ├── 20260114175904000000_drop_identity_credentials_nid_idx.mysql.down.sql │ │ ├── 20260114175904000000_drop_identity_credentials_nid_idx.mysql.up.sql │ │ └── 20260114175904000000_drop_identity_credentials_nid_idx.up.sql │ ├── persister.go │ ├── persister_cleanup_test.go │ ├── persister_code.go │ ├── persister_continuity.go │ ├── persister_courier.go │ ├── persister_errorx.go │ ├── persister_hmac.go │ ├── persister_hmac_test.go │ ├── persister_login.go │ ├── persister_login_code.go │ ├── persister_recovery.go │ ├── persister_recovery_code.go │ ├── persister_registration.go │ ├── persister_registration_code.go │ ├── persister_session.go │ ├── persister_sessiontokenexchanger.go │ ├── persister_settings.go │ ├── persister_test.go │ ├── persister_transaction_helpers.go │ ├── persister_verification.go │ ├── persister_verification_code.go │ ├── stub/ │ │ ├── expand.schema.json │ │ ├── identity-2.schema.json │ │ └── identity.schema.json │ ├── testhelpers/ │ │ └── network.go │ └── update/ │ └── update.go ├── pkg/ │ ├── client-go/ │ │ ├── .gitignore │ │ ├── .openapi-generator/ │ │ │ ├── FILES │ │ │ └── VERSION │ │ ├── .openapi-generator-ignore │ │ ├── .travis.yml │ │ ├── README.md │ │ ├── api_courier.go │ │ ├── api_frontend.go │ │ ├── api_identity.go │ │ ├── api_metadata.go │ │ ├── client.go │ │ ├── configuration.go │ │ ├── git_push.sh │ │ ├── go.mod │ │ ├── go.sum │ │ ├── model_authenticator_assurance_level.go │ │ ├── model_batch_patch_identities_response.go │ │ ├── model_consistency_request_parameters.go │ │ ├── model_continue_with.go │ │ ├── model_continue_with_recovery_ui.go │ │ ├── model_continue_with_recovery_ui_flow.go │ │ ├── model_continue_with_redirect_browser_to.go │ │ ├── model_continue_with_set_ory_session_token.go │ │ ├── model_continue_with_settings_ui.go │ │ ├── model_continue_with_settings_ui_flow.go │ │ ├── model_continue_with_verification_ui.go │ │ ├── model_continue_with_verification_ui_flow.go │ │ ├── model_courier_message_status.go │ │ ├── model_courier_message_type.go │ │ ├── model_create_fedcm_flow_response.go │ │ ├── model_create_identity_body.go │ │ ├── model_create_recovery_code_for_identity_body.go │ │ ├── model_create_recovery_link_for_identity_body.go │ │ ├── model_delete_my_sessions_count.go │ │ ├── model_error_authenticator_assurance_level_not_satisfied.go │ │ ├── model_error_browser_location_change_required.go │ │ ├── model_error_flow_replaced.go │ │ ├── model_error_generic.go │ │ ├── model_flow_error.go │ │ ├── model_generic_error.go │ │ ├── model_get_version_200_response.go │ │ ├── model_health_not_ready_status.go │ │ ├── model_health_status.go │ │ ├── model_identity.go │ │ ├── model_identity_credentials.go │ │ ├── model_identity_credentials_code.go │ │ ├── model_identity_credentials_code_address.go │ │ ├── model_identity_credentials_oidc.go │ │ ├── model_identity_credentials_oidc_provider.go │ │ ├── model_identity_credentials_password.go │ │ ├── model_identity_patch.go │ │ ├── model_identity_patch_response.go │ │ ├── model_identity_schema_container.go │ │ ├── model_identity_with_credentials.go │ │ ├── model_identity_with_credentials_oidc.go │ │ ├── model_identity_with_credentials_oidc_config.go │ │ ├── model_identity_with_credentials_oidc_config_provider.go │ │ ├── model_identity_with_credentials_password.go │ │ ├── model_identity_with_credentials_password_config.go │ │ ├── model_identity_with_credentials_saml.go │ │ ├── model_identity_with_credentials_saml_config.go │ │ ├── model_identity_with_credentials_saml_config_provider.go │ │ ├── model_is_alive_200_response.go │ │ ├── model_is_ready_503_response.go │ │ ├── model_json_patch.go │ │ ├── model_login_flow.go │ │ ├── model_login_flow_state.go │ │ ├── model_logout_flow.go │ │ ├── model_message.go │ │ ├── model_message_dispatch.go │ │ ├── model_needs_privileged_session_error.go │ │ ├── model_o_auth2_client.go │ │ ├── model_o_auth2_consent_request_open_id_connect_context.go │ │ ├── model_o_auth2_login_request.go │ │ ├── model_patch_identities_body.go │ │ ├── model_perform_native_logout_body.go │ │ ├── model_provider.go │ │ ├── model_recovery_code_for_identity.go │ │ ├── model_recovery_flow.go │ │ ├── model_recovery_flow_state.go │ │ ├── model_recovery_identity_address.go │ │ ├── model_recovery_link_for_identity.go │ │ ├── model_registration_flow.go │ │ ├── model_registration_flow_state.go │ │ ├── model_self_service_flow_expired_error.go │ │ ├── model_session.go │ │ ├── model_session_authentication_method.go │ │ ├── model_session_device.go │ │ ├── model_settings_flow.go │ │ ├── model_settings_flow_state.go │ │ ├── model_successful_code_exchange_response.go │ │ ├── model_successful_native_login.go │ │ ├── model_successful_native_registration.go │ │ ├── model_token_pagination.go │ │ ├── model_token_pagination_headers.go │ │ ├── model_ui_container.go │ │ ├── model_ui_node.go │ │ ├── model_ui_node_anchor_attributes.go │ │ ├── model_ui_node_attributes.go │ │ ├── model_ui_node_division_attributes.go │ │ ├── model_ui_node_image_attributes.go │ │ ├── model_ui_node_input_attributes.go │ │ ├── model_ui_node_meta.go │ │ ├── model_ui_node_script_attributes.go │ │ ├── model_ui_node_text_attributes.go │ │ ├── model_ui_text.go │ │ ├── model_update_fedcm_flow_body.go │ │ ├── model_update_identity_body.go │ │ ├── model_update_login_flow_body.go │ │ ├── model_update_login_flow_with_code_method.go │ │ ├── model_update_login_flow_with_identifier_first_method.go │ │ ├── model_update_login_flow_with_lookup_secret_method.go │ │ ├── model_update_login_flow_with_oidc_method.go │ │ ├── model_update_login_flow_with_passkey_method.go │ │ ├── model_update_login_flow_with_password_method.go │ │ ├── model_update_login_flow_with_saml_method.go │ │ ├── model_update_login_flow_with_totp_method.go │ │ ├── model_update_login_flow_with_web_authn_method.go │ │ ├── model_update_recovery_flow_body.go │ │ ├── model_update_recovery_flow_with_code_method.go │ │ ├── model_update_recovery_flow_with_link_method.go │ │ ├── model_update_registration_flow_body.go │ │ ├── model_update_registration_flow_with_code_method.go │ │ ├── model_update_registration_flow_with_oidc_method.go │ │ ├── model_update_registration_flow_with_passkey_method.go │ │ ├── model_update_registration_flow_with_password_method.go │ │ ├── model_update_registration_flow_with_profile_method.go │ │ ├── model_update_registration_flow_with_saml_method.go │ │ ├── model_update_registration_flow_with_web_authn_method.go │ │ ├── model_update_settings_flow_body.go │ │ ├── model_update_settings_flow_with_lookup_method.go │ │ ├── model_update_settings_flow_with_oidc_method.go │ │ ├── model_update_settings_flow_with_passkey_method.go │ │ ├── model_update_settings_flow_with_password_method.go │ │ ├── model_update_settings_flow_with_profile_method.go │ │ ├── model_update_settings_flow_with_saml_method.go │ │ ├── model_update_settings_flow_with_totp_method.go │ │ ├── model_update_settings_flow_with_web_authn_method.go │ │ ├── model_update_verification_flow_body.go │ │ ├── model_update_verification_flow_with_code_method.go │ │ ├── model_update_verification_flow_with_link_method.go │ │ ├── model_verifiable_identity_address.go │ │ ├── model_verification_flow.go │ │ ├── model_verification_flow_state.go │ │ ├── model_version.go │ │ ├── response.go │ │ └── utils.go │ ├── clihelpers/ │ │ └── helpers.go │ ├── driver.go │ ├── httpclient/ │ │ ├── .gitignore │ │ ├── .openapi-generator/ │ │ │ ├── FILES │ │ │ └── VERSION │ │ ├── .openapi-generator-ignore │ │ ├── .travis.yml │ │ ├── README.md │ │ ├── api_courier.go │ │ ├── api_frontend.go │ │ ├── api_identity.go │ │ ├── api_metadata.go │ │ ├── client.go │ │ ├── configuration.go │ │ ├── git_push.sh │ │ ├── model_authenticator_assurance_level.go │ │ ├── model_batch_patch_identities_response.go │ │ ├── model_consistency_request_parameters.go │ │ ├── model_continue_with.go │ │ ├── model_continue_with_recovery_ui.go │ │ ├── model_continue_with_recovery_ui_flow.go │ │ ├── model_continue_with_redirect_browser_to.go │ │ ├── model_continue_with_set_ory_session_token.go │ │ ├── model_continue_with_settings_ui.go │ │ ├── model_continue_with_settings_ui_flow.go │ │ ├── model_continue_with_verification_ui.go │ │ ├── model_continue_with_verification_ui_flow.go │ │ ├── model_courier_message_status.go │ │ ├── model_courier_message_type.go │ │ ├── model_create_fedcm_flow_response.go │ │ ├── model_create_identity_body.go │ │ ├── model_create_recovery_code_for_identity_body.go │ │ ├── model_create_recovery_link_for_identity_body.go │ │ ├── model_delete_my_sessions_count.go │ │ ├── model_error_authenticator_assurance_level_not_satisfied.go │ │ ├── model_error_browser_location_change_required.go │ │ ├── model_error_flow_replaced.go │ │ ├── model_error_generic.go │ │ ├── model_flow_error.go │ │ ├── model_generic_error.go │ │ ├── model_get_version_200_response.go │ │ ├── model_health_not_ready_status.go │ │ ├── model_health_status.go │ │ ├── model_identity.go │ │ ├── model_identity_credentials.go │ │ ├── model_identity_credentials_code.go │ │ ├── model_identity_credentials_code_address.go │ │ ├── model_identity_credentials_oidc.go │ │ ├── model_identity_credentials_oidc_provider.go │ │ ├── model_identity_credentials_password.go │ │ ├── model_identity_patch.go │ │ ├── model_identity_patch_response.go │ │ ├── model_identity_schema_container.go │ │ ├── model_identity_with_credentials.go │ │ ├── model_identity_with_credentials_oidc.go │ │ ├── model_identity_with_credentials_oidc_config.go │ │ ├── model_identity_with_credentials_oidc_config_provider.go │ │ ├── model_identity_with_credentials_password.go │ │ ├── model_identity_with_credentials_password_config.go │ │ ├── model_identity_with_credentials_saml.go │ │ ├── model_identity_with_credentials_saml_config.go │ │ ├── model_identity_with_credentials_saml_config_provider.go │ │ ├── model_is_alive_200_response.go │ │ ├── model_is_ready_503_response.go │ │ ├── model_json_patch.go │ │ ├── model_login_flow.go │ │ ├── model_login_flow_state.go │ │ ├── model_logout_flow.go │ │ ├── model_message.go │ │ ├── model_message_dispatch.go │ │ ├── model_needs_privileged_session_error.go │ │ ├── model_o_auth2_client.go │ │ ├── model_o_auth2_consent_request_open_id_connect_context.go │ │ ├── model_o_auth2_login_request.go │ │ ├── model_patch_identities_body.go │ │ ├── model_perform_native_logout_body.go │ │ ├── model_provider.go │ │ ├── model_recovery_code_for_identity.go │ │ ├── model_recovery_flow.go │ │ ├── model_recovery_flow_state.go │ │ ├── model_recovery_identity_address.go │ │ ├── model_recovery_link_for_identity.go │ │ ├── model_registration_flow.go │ │ ├── model_registration_flow_state.go │ │ ├── model_self_service_flow_expired_error.go │ │ ├── model_session.go │ │ ├── model_session_authentication_method.go │ │ ├── model_session_device.go │ │ ├── model_settings_flow.go │ │ ├── model_settings_flow_state.go │ │ ├── model_successful_code_exchange_response.go │ │ ├── model_successful_native_login.go │ │ ├── model_successful_native_registration.go │ │ ├── model_token_pagination.go │ │ ├── model_token_pagination_headers.go │ │ ├── model_ui_container.go │ │ ├── model_ui_node.go │ │ ├── model_ui_node_anchor_attributes.go │ │ ├── model_ui_node_attributes.go │ │ ├── model_ui_node_division_attributes.go │ │ ├── model_ui_node_image_attributes.go │ │ ├── model_ui_node_input_attributes.go │ │ ├── model_ui_node_meta.go │ │ ├── model_ui_node_script_attributes.go │ │ ├── model_ui_node_text_attributes.go │ │ ├── model_ui_text.go │ │ ├── model_update_fedcm_flow_body.go │ │ ├── model_update_identity_body.go │ │ ├── model_update_login_flow_body.go │ │ ├── model_update_login_flow_with_code_method.go │ │ ├── model_update_login_flow_with_identifier_first_method.go │ │ ├── model_update_login_flow_with_lookup_secret_method.go │ │ ├── model_update_login_flow_with_oidc_method.go │ │ ├── model_update_login_flow_with_passkey_method.go │ │ ├── model_update_login_flow_with_password_method.go │ │ ├── model_update_login_flow_with_saml_method.go │ │ ├── model_update_login_flow_with_totp_method.go │ │ ├── model_update_login_flow_with_web_authn_method.go │ │ ├── model_update_recovery_flow_body.go │ │ ├── model_update_recovery_flow_with_code_method.go │ │ ├── model_update_recovery_flow_with_link_method.go │ │ ├── model_update_registration_flow_body.go │ │ ├── model_update_registration_flow_with_code_method.go │ │ ├── model_update_registration_flow_with_oidc_method.go │ │ ├── model_update_registration_flow_with_passkey_method.go │ │ ├── model_update_registration_flow_with_password_method.go │ │ ├── model_update_registration_flow_with_profile_method.go │ │ ├── model_update_registration_flow_with_saml_method.go │ │ ├── model_update_registration_flow_with_web_authn_method.go │ │ ├── model_update_settings_flow_body.go │ │ ├── model_update_settings_flow_with_lookup_method.go │ │ ├── model_update_settings_flow_with_oidc_method.go │ │ ├── model_update_settings_flow_with_passkey_method.go │ │ ├── model_update_settings_flow_with_password_method.go │ │ ├── model_update_settings_flow_with_profile_method.go │ │ ├── model_update_settings_flow_with_saml_method.go │ │ ├── model_update_settings_flow_with_totp_method.go │ │ ├── model_update_settings_flow_with_web_authn_method.go │ │ ├── model_update_verification_flow_body.go │ │ ├── model_update_verification_flow_with_code_method.go │ │ ├── model_update_verification_flow_with_link_method.go │ │ ├── model_verifiable_identity_address.go │ │ ├── model_verification_flow.go │ │ ├── model_verification_flow_state.go │ │ ├── model_version.go │ │ ├── response.go │ │ └── utils.go │ ├── registrationhelpers/ │ │ ├── helpers.go │ │ └── stub/ │ │ ├── basic.schema.json │ │ └── multifield.schema.json │ ├── settingshelpers/ │ │ └── helpers.go │ └── testhelpers/ │ ├── config.go │ ├── courier.go │ ├── e2e_server.go │ ├── errorx.go │ ├── fake.go │ ├── handler_mock.go │ ├── http.go │ ├── httptest.go │ ├── identity.go │ ├── identity_schema.go │ ├── json.go │ ├── network.go │ ├── sdk.go │ ├── selfservice.go │ ├── selfservice_login.go │ ├── selfservice_recovery.go │ ├── selfservice_registration.go │ ├── selfservice_settings.go │ ├── selfservice_verification.go │ ├── server.go │ ├── session.go │ ├── session_active.go │ ├── snapshot.go │ ├── strategies.go │ ├── strategy_code.go │ └── stub/ │ └── fake-session.schema.json ├── proto/ │ └── oidc/ │ └── v1/ │ └── state.proto ├── quickstart-crdb.yml ├── quickstart-debug.yml ├── quickstart-latest.yml ├── quickstart-mysql.yml ├── quickstart-oathkeeper.yml ├── quickstart-postgres.yml ├── quickstart-selinux.yml ├── quickstart-standalone.yml ├── quickstart-tracing.yml ├── quickstart-webauthn.yml ├── quickstart.yml ├── request/ │ ├── auth.go │ ├── auth_test.go │ ├── builder.go │ ├── builder_test.go │ ├── config.go │ └── stub/ │ ├── cancel_body.jsonnet │ └── test_body.jsonnet ├── schema/ │ ├── context.go │ ├── context_test.go │ ├── errors.go │ ├── errors_test.go │ ├── extension.go │ ├── extension_test.go │ ├── handler.go │ ├── handler_test.go │ ├── loader.go │ ├── schema.go │ ├── schema_test.go │ ├── stub/ │ │ ├── complex.schema.json │ │ ├── extension/ │ │ │ ├── invalid.schema.json │ │ │ ├── schema.json │ │ │ └── schema.nested.json │ │ ├── identity-2.schema.json │ │ ├── identity.schema.json │ │ └── validator/ │ │ ├── firstName.schema.json │ │ └── whatever.schema.json │ ├── test/ │ │ └── schema_provider.go │ ├── validator.go │ └── validator_test.go ├── script/ │ ├── add-down-migrations.sh │ ├── debug-entrypoint.sh │ ├── render-schemas.sh │ ├── test-envs.sh │ ├── testenv.sh │ └── unset-e2e-uis.sh ├── selfservice/ │ ├── errorx/ │ │ ├── error.go │ │ ├── handler.go │ │ ├── handler_test.go │ │ ├── manager.go │ │ ├── persistence.go │ │ └── test/ │ │ └── persistence.go │ ├── flow/ │ │ ├── .schema/ │ │ │ └── method.schema.json │ │ ├── config.go │ │ ├── continue_with.go │ │ ├── duplicate_credentials.go │ │ ├── error.go │ │ ├── error_test.go │ │ ├── flow.go │ │ ├── flow_identity_schema.go │ │ ├── flow_test.go │ │ ├── internal_context.go │ │ ├── login/ │ │ │ ├── aal.go │ │ │ ├── aal_test.go │ │ │ ├── error.go │ │ │ ├── error_test.go │ │ │ ├── export_test.go │ │ │ ├── extension_identifier_label.go │ │ │ ├── extension_identifier_label_test.go │ │ │ ├── flow.go │ │ │ ├── flow_test.go │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ ├── hook.go │ │ │ ├── hook_test.go │ │ │ ├── organizations.go │ │ │ ├── organizations_test.go │ │ │ ├── persistence.go │ │ │ ├── session.go │ │ │ ├── sort.go │ │ │ ├── state.go │ │ │ ├── strategy.go │ │ │ ├── strategy_form_hydrator.go │ │ │ ├── strategy_form_hydrator_test.go │ │ │ ├── stub/ │ │ │ │ ├── email.schema.json │ │ │ │ ├── fake-session.schema.json │ │ │ │ ├── login.schema.json │ │ │ │ ├── password.schema.json │ │ │ │ ├── phone.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ └── updated.schema.json │ │ │ ├── test/ │ │ │ │ └── persistence.go │ │ │ └── testsetup_test.go │ │ ├── logout/ │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ └── stub/ │ │ │ └── identity.schema.json │ │ ├── name.go │ │ ├── nosurf.go │ │ ├── nosurf_test.go │ │ ├── organizations.go │ │ ├── organizations_test.go │ │ ├── recovery/ │ │ │ ├── .snapshots/ │ │ │ │ ├── TestHandleError-flow=api-case=fails_if_active_strategy_is_disabled.json │ │ │ │ ├── TestHandleError-flow=browser-case=fails_to_retry_flow_if_recovery_strategy_id_is_not_valid.json │ │ │ │ ├── TestHandleError-flow=spa-case=fails_if_active_strategy_is_disabled.json │ │ │ │ ├── TestHandleError_WithContinueWith-flow=api-case=fails_if_active_strategy_is_disabled.json │ │ │ │ ├── TestHandleError_WithContinueWith-flow=browser-case=fails_to_retry_flow_if_recovery_strategy_id_is_not_valid.json │ │ │ │ └── TestHandleError_WithContinueWith-flow=spa-case=fails_if_active_strategy_is_disabled.json │ │ │ ├── error.go │ │ │ ├── error_test.go │ │ │ ├── flow.go │ │ │ ├── flow_test.go │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ ├── hook.go │ │ │ ├── hook_test.go │ │ │ ├── persistence.go │ │ │ ├── state.go │ │ │ ├── strategy.go │ │ │ ├── stub/ │ │ │ │ └── identity.schema.json │ │ │ └── test/ │ │ │ └── persistence.go │ │ ├── registration/ │ │ │ ├── .snapshots/ │ │ │ │ └── TestSortNodes-case=1.json.json │ │ │ ├── decoder.go │ │ │ ├── error.go │ │ │ ├── error_test.go │ │ │ ├── fixtures/ │ │ │ │ ├── sort/ │ │ │ │ │ └── 1.json │ │ │ │ └── sort.schema.json │ │ │ ├── flow.go │ │ │ ├── flow_test.go │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ ├── hook.go │ │ │ ├── hook_test.go │ │ │ ├── oragnizations.go │ │ │ ├── organizations_test.go │ │ │ ├── persistence.go │ │ │ ├── session.go │ │ │ ├── sort.go │ │ │ ├── sort_test.go │ │ │ ├── state.go │ │ │ ├── strategy.go │ │ │ ├── strategy_form_hydrator.go │ │ │ ├── stub/ │ │ │ │ ├── fake-session.schema.json │ │ │ │ ├── identity.schema.json │ │ │ │ ├── login.schema.json │ │ │ │ ├── registration-multi-email.schema.json │ │ │ │ ├── registration.phone.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ └── updated.schema.json │ │ │ ├── test/ │ │ │ │ └── persistence.go │ │ │ └── testsetup_test.go │ │ ├── request.go │ │ ├── request_test.go │ │ ├── settings/ │ │ │ ├── .snapshots/ │ │ │ │ ├── TestHandler-case=multi-schema_endpoint=init-description=init_a_flow_as_API-description=success.json │ │ │ │ ├── TestHandler-case=multi-schema_endpoint=init-description=init_a_flow_as_SPA-description=success.json │ │ │ │ ├── TestHandler-case=multi-schema_endpoint=init-description=init_a_flow_as_browser-description=success.json │ │ │ │ └── TestHandler-endpoint=init-description=init_a_flow_as_API-description=success.json │ │ │ ├── error.go │ │ │ ├── error_test.go │ │ │ ├── flow.go │ │ │ ├── flow_test.go │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ ├── hook.go │ │ │ ├── hook_test.go │ │ │ ├── persistence.go │ │ │ ├── sort.go │ │ │ ├── state.go │ │ │ ├── strategy.go │ │ │ ├── strategy_helper.go │ │ │ ├── strategy_helper_test.go │ │ │ ├── stub/ │ │ │ │ ├── identity.schema.json │ │ │ │ └── multi-email.schema.json │ │ │ ├── test/ │ │ │ │ └── persistence.go │ │ │ └── testsetup_test.go │ │ ├── state.go │ │ ├── state_recovery_v1.mermaid │ │ ├── state_recovery_v2.mermaid │ │ ├── state_test.go │ │ ├── type.go │ │ └── verification/ │ │ ├── .snapshots/ │ │ │ └── TestHandleError-flow=browser-case=fails_to_retry_flow_if_recovery_strategy_id_is_not_valid.json │ │ ├── error.go │ │ ├── error_test.go │ │ ├── fake_strategy.go │ │ ├── flow.go │ │ ├── flow_test.go │ │ ├── handler.go │ │ ├── handler_test.go │ │ ├── hook.go │ │ ├── hook_test.go │ │ ├── persistence.go │ │ ├── state.go │ │ ├── strategy.go │ │ ├── stub/ │ │ │ ├── extension/ │ │ │ │ └── schema.json │ │ │ └── identity.schema.json │ │ └── test/ │ │ └── persistence.go │ ├── flowhelpers/ │ │ ├── login.go │ │ ├── login_test.go │ │ └── stub/ │ │ └── login.schema.json │ ├── hook/ │ │ ├── .snapshots/ │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=body_is_empty.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_admin_metadata.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_external_id.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_public_metadata.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_recovery_addresses.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_state.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_traits.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_has_updated_verified_addresses.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_is_present_but_empty#01.json │ │ │ ├── TestWebHooks-update_identity_fields-case=update_identity_fields-case=identity_is_present_but_empty.json │ │ │ └── TestWebHooks-update_identity_fields-case=update_identity_fields-case=unset_external_id.json │ │ ├── error.go │ │ ├── hooks.go │ │ ├── hooktest/ │ │ │ └── web_hook_test_server.go │ │ ├── password_migration_hook.go │ │ ├── require_verified_address.go │ │ ├── require_verified_address_test.go │ │ ├── session_destroyer.go │ │ ├── session_destroyer_test.go │ │ ├── session_issuer.go │ │ ├── session_issuer_test.go │ │ ├── show_verification_ui.go │ │ ├── show_verification_ui_test.go │ │ ├── stub/ │ │ │ ├── bad_template.jsonnet │ │ │ ├── cancel_template.jsonnet │ │ │ ├── code.schema.json │ │ │ ├── require_verified.schema.json │ │ │ ├── stub.schema.json │ │ │ ├── test_body.jsonnet │ │ │ └── verify.schema.json │ │ ├── verification.go │ │ ├── verification_test.go │ │ ├── web_hook.go │ │ └── web_hook_integration_test.go │ ├── sessiontokenexchange/ │ │ ├── persistence.go │ │ └── test/ │ │ └── persistence.go │ ├── strategy/ │ │ ├── code/ │ │ │ ├── .schema/ │ │ │ │ ├── login.schema.json │ │ │ │ ├── recovery.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ └── verification.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json │ │ │ │ ├── TestAdminStrategy-description=should_fail_on_malformed_expiry_time.json │ │ │ │ ├── TestAdminStrategy-description=should_fail_on_negative_expiry_time.json │ │ │ │ ├── TestAdminStrategy-description=should_not_be_able_to_recover_an_account_that_does_not_exist.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_2fa_but_request_is_1fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor-case=code_is_used_for_passwordless_login_and_request_is_1fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_2fa_and_request_is_1fa_with_refresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_1fa_with_refresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_code_method-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_code_method-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_code_method-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_code_method-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=with_no_identity-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=with_no_identity-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-case=code_is_used_for_2fa_and_request_is_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-case=code_is_used_for_passwordless_login_and_request_is_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-using_via-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-using_via-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-without_via-case=code_is_used_for_2fa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-without_via-case=code_is_used_for_passwordless_login.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_2fa_and_request_is_2fa_with_refresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh-case=code_is_used_for_passwordless_login_and_request_is_2fa_with_refresh.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=verify_initial_payload_with_fast_login.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=verify_initial_payload_with_fast_login_and_fallback_enabled-case=no_code_credential.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=verify_initial_payload_with_fast_login_and_fallback_enabled-case=with_code_credential.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=verify_initial_payload_without_fast_login.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=address-email.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=address-phone.json │ │ │ │ ├── TestLoginCodeStrategy-test=Browser_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=identifier-email.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=verify_initial_payload_with_fast_login.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=verify_initial_payload_with_fast_login_and_fallback_enabled-case=no_code_credential.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=verify_initial_payload_with_fast_login_and_fallback_enabled-case=with_code_credential.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=verify_initial_payload_without_fast_login.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=address-email.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=address-phone.json │ │ │ │ ├── TestLoginCodeStrategy-test=Native_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=identifier-email.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=verify_initial_payload_with_fast_login.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=verify_initial_payload_with_fast_login_and_fallback_enabled-case=no_code_credential.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=verify_initial_payload_with_fast_login_and_fallback_enabled-case=with_code_credential.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=verify_initial_payload_without_fast_login.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=address-email.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=address-phone.json │ │ │ │ ├── TestLoginCodeStrategy-test=SPA_client-suite=mfa-case=without_via_parameter_all_options_are_shown-field=identifier-email.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodProfile.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=1.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=2.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=3.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=4.json │ │ │ │ ├── TestRecovery-description=should_set_all_the_correct_recovery_payloads.json │ │ │ │ ├── TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Email-description=should_set_all_the_correct_recovery_payloads-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Email-description=should_set_all_the_correct_recovery_payloads-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Email-description=should_set_all_the_correct_recovery_payloads-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Email-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Email-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Email-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Phone-description=should_set_all_the_correct_recovery_payloads-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Phone-description=should_set_all_the_correct_recovery_payloads-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Phone-description=should_set_all_the_correct_recovery_payloads-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Phone-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Phone-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_OneAddress_Phone-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_SeveralAddresses-description=should_set_all_the_correct_recovery_payloads-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_SeveralAddresses-description=should_set_all_the_correct_recovery_payloads-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_SeveralAddresses-description=should_set_all_the_correct_recovery_payloads-type=spa.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_SeveralAddresses-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_SeveralAddresses-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json │ │ │ │ ├── TestRecovery_V2_WithContinueWith_SeveralAddresses-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json │ │ │ │ ├── TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=api.json │ │ │ │ ├── TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=browser.json │ │ │ │ ├── TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads-type=spa.json │ │ │ │ ├── TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=api.json │ │ │ │ ├── TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=browser.json │ │ │ │ ├── TestRecovery_WithContinueWith-description=should_set_all_the_correct_recovery_payloads_after_submission-type=spa.json │ │ │ │ ├── TestVerification-description=should_set_all_the_correct_verification_payloads.json │ │ │ │ └── TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json │ │ │ ├── code_login.go │ │ │ ├── code_login_test.go │ │ │ ├── code_recovery.go │ │ │ ├── code_recovery_test.go │ │ │ ├── code_registration.go │ │ │ ├── code_registration_test.go │ │ │ ├── code_sender.go │ │ │ ├── code_sender_test.go │ │ │ ├── code_verification.go │ │ │ ├── code_verification_test.go │ │ │ ├── node.go │ │ │ ├── persistence.go │ │ │ ├── schema.go │ │ │ ├── strategy.go │ │ │ ├── strategy_login.go │ │ │ ├── strategy_login_test.go │ │ │ ├── strategy_mfa.go │ │ │ ├── strategy_mfa_test.go │ │ │ ├── strategy_recovery.go │ │ │ ├── strategy_recovery_admin.go │ │ │ ├── strategy_recovery_admin_test.go │ │ │ ├── strategy_recovery_test.go │ │ │ ├── strategy_registration.go │ │ │ ├── strategy_registration_test.go │ │ │ ├── strategy_test.go │ │ │ ├── strategy_verification.go │ │ │ ├── strategy_verification_test.go │ │ │ ├── stub/ │ │ │ │ ├── code-mfa.identity.schema.json │ │ │ │ ├── code.identity.schema.json │ │ │ │ ├── default.schema.json │ │ │ │ ├── no-code-id.schema.json │ │ │ │ └── no-code.schema.json │ │ │ └── test/ │ │ │ └── persistence.go │ │ ├── handler.go │ │ ├── idfirst/ │ │ │ ├── .schema/ │ │ │ │ └── login.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestFormHydration-case=Multi-Schema-method=PopulateLoginMethodIdentifierFirstIdentification.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_password.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_password.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodRefresh.json │ │ │ │ └── TestFormHydration-method=PopulateLoginMethodSecondFactor.json │ │ │ ├── schema.go │ │ │ ├── strategy.go │ │ │ ├── strategy_login.go │ │ │ ├── strategy_login_test.go │ │ │ ├── strategy_test.go │ │ │ ├── stub/ │ │ │ │ └── default.schema.json │ │ │ └── types.go │ │ ├── link/ │ │ │ ├── .schema/ │ │ │ │ ├── email.schema.json │ │ │ │ ├── recovery.schema.json │ │ │ │ └── verification.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestRecovery-description=should_set_all_the_correct_recovery_payloads.json │ │ │ │ ├── TestRecovery-description=should_set_all_the_correct_recovery_payloads_after_submission.json │ │ │ │ ├── TestVerification-description=should_set_all_the_correct_verification_payloads.json │ │ │ │ └── TestVerification-description=should_set_all_the_correct_verification_payloads_after_submission.json │ │ │ ├── persistence.go │ │ │ ├── schema.go │ │ │ ├── sender.go │ │ │ ├── sender_test.go │ │ │ ├── strategy.go │ │ │ ├── strategy_recovery.go │ │ │ ├── strategy_recovery_test.go │ │ │ ├── strategy_test.go │ │ │ ├── strategy_verification.go │ │ │ ├── strategy_verification_test.go │ │ │ ├── stub/ │ │ │ │ └── default.schema.json │ │ │ ├── test/ │ │ │ │ └── persistence.go │ │ │ ├── token_recovery.go │ │ │ ├── token_recovery_test.go │ │ │ ├── token_verification.go │ │ │ └── token_verification_test.go │ │ ├── lookup/ │ │ │ ├── .schema/ │ │ │ │ ├── login.schema.json │ │ │ │ └── settings.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestCompleteLogin-case=lookup_payload_is_set_when_identity_has_lookup.json │ │ │ │ ├── TestCompleteSettings-case=button_for_regeneration_is_displayed_when_identity_has_no_recovery_codes_yet-case=api.json │ │ │ │ ├── TestCompleteSettings-case=button_for_regeneration_is_displayed_when_identity_has_no_recovery_codes_yet-case=browser.json │ │ │ │ ├── TestCompleteSettings-case=button_for_regeneration_is_displayed_when_identity_has_no_recovery_codes_yet-case=spa.json │ │ │ │ ├── TestCompleteSettings-case=hide_recovery_codes_behind_reveal_button-case=api.json │ │ │ │ ├── TestCompleteSettings-case=hide_recovery_codes_behind_reveal_button-case=browser.json │ │ │ │ ├── TestCompleteSettings-case=hide_recovery_codes_behind_reveal_button-case=spa.json │ │ │ │ ├── TestCompleteSettings-case=hide_recovery_codes_behind_reveal_button_and_show_disable_button-case=api.json │ │ │ │ ├── TestCompleteSettings-case=hide_recovery_codes_behind_reveal_button_and_show_disable_button-case=browser.json │ │ │ │ ├── TestCompleteSettings-case=hide_recovery_codes_behind_reveal_button_and_show_disable_button-case=spa.json │ │ │ │ ├── TestCompleteSettings-case=should_pass_without_csrf_if_API_flow.json │ │ │ │ ├── TestCompleteSettings-type=regenerate_but_no_confirmation-type=api.json │ │ │ │ ├── TestCompleteSettings-type=regenerate_but_no_confirmation-type=browser.json │ │ │ │ ├── TestCompleteSettings-type=regenerate_but_no_confirmation-type=spa.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor.json │ │ │ │ └── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json │ │ │ ├── login.go │ │ │ ├── login_test.go │ │ │ ├── nodes.go │ │ │ ├── schema.go │ │ │ ├── settings.go │ │ │ ├── settings_test.go │ │ │ ├── strategy.go │ │ │ ├── strategy_test.go │ │ │ └── stub/ │ │ │ └── login.schema.json │ │ ├── oidc/ │ │ │ ├── .schema/ │ │ │ │ ├── link.schema.json │ │ │ │ └── settings.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_oidc.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_oidc.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodRefresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodProfile.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=1.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=2.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=3.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=4.json │ │ │ │ ├── TestSettingsStrategy-case=should_adjust_linkable_providers_based_on_linked_credentials-agent=githuber.json │ │ │ │ ├── TestSettingsStrategy-case=should_adjust_linkable_providers_based_on_linked_credentials-agent=multiuser.json │ │ │ │ ├── TestSettingsStrategy-case=should_adjust_linkable_providers_based_on_linked_credentials-agent=oryer.json │ │ │ │ ├── TestSettingsStrategy-case=should_adjust_linkable_providers_based_on_linked_credentials-agent=password.json │ │ │ │ ├── TestSettingsStrategy-suite=api-case=API_flow_populates_link_and_unlink_nodes-identity=multi_credentials.json │ │ │ │ ├── TestSettingsStrategy-suite=api-case=API_flow_populates_link_and_unlink_nodes-identity=multi_oidc.json │ │ │ │ ├── TestSettingsStrategy-suite=api-case=API_flow_populates_link_and_unlink_nodes-identity=password_only.json │ │ │ │ ├── TestSettingsStrategy-suite=api-case=API_flow_populates_link_and_unlink_nodes-identity=single_oidc.json │ │ │ │ ├── TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=fetch.json │ │ │ │ ├── TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=original.json │ │ │ │ ├── TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=response.json │ │ │ │ ├── TestSettingsStrategy-suite=link-case=should_link_a_connection_even_if_user_does_not_have_oidc_credentials_yet.json │ │ │ │ ├── TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_a_connection_which_already_exists.json │ │ │ │ ├── TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_an_non-existing_connection.json │ │ │ │ ├── TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_a_connection_not_yet_linked-flow=fetch.json │ │ │ │ ├── TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_a_connection_not_yet_linked-flow=json.json │ │ │ │ ├── TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_an_non-existing_connection-flow=fetch.json │ │ │ │ ├── TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_an_non-existing_connection-flow=json.json │ │ │ │ ├── TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_the_last_remaining_connection-flow=fetch.json │ │ │ │ ├── TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_the_last_remaining_connection-flow=json.json │ │ │ │ ├── TestStrategy-login-hints-enabled=false-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_oidc_credentials-case=should_fail_login.json │ │ │ │ ├── TestStrategy-login-hints-enabled=false-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_oidc_credentials-case=should_fail_registration.json │ │ │ │ ├── TestStrategy-login-hints-enabled=false-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_oidc_credentials-case=should_fail_registration_id_first_strategy_enabled.json │ │ │ │ ├── TestStrategy-login-hints-enabled=false-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_login.json │ │ │ │ ├── TestStrategy-login-hints-enabled=false-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration.json │ │ │ │ ├── TestStrategy-login-hints-enabled=false-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration_id_first_strategy_enabled.json │ │ │ │ ├── TestStrategy-login-hints-enabled=true-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_oidc_credentials-case=should_fail_login.json │ │ │ │ ├── TestStrategy-login-hints-enabled=true-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_oidc_credentials-case=should_fail_registration.json │ │ │ │ ├── TestStrategy-login-hints-enabled=true-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_oidc_credentials-case=should_fail_registration_id_first_strategy_enabled.json │ │ │ │ ├── TestStrategy-login-hints-enabled=true-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_login.json │ │ │ │ ├── TestStrategy-login-hints-enabled=true-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration.json │ │ │ │ ├── TestStrategy-login-hints-enabled=true-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration_id_first_strategy_enabled.json │ │ │ │ └── TestStrategy-method=TestPopulateSignUpMethod.json │ │ │ ├── const.go │ │ │ ├── error.go │ │ │ ├── fedcm/ │ │ │ │ └── definitions.go │ │ │ ├── form.go │ │ │ ├── form_test.go │ │ │ ├── nodes.go │ │ │ ├── pkce.go │ │ │ ├── pkce_test.go │ │ │ ├── provider.go │ │ │ ├── provider_amazon.go │ │ │ ├── provider_amazon_test.go │ │ │ ├── provider_apple.go │ │ │ ├── provider_apple_test.go │ │ │ ├── provider_auth0.go │ │ │ ├── provider_auth0_unit_test.go │ │ │ ├── provider_config.go │ │ │ ├── provider_config_test.go │ │ │ ├── provider_dingtalk.go │ │ │ ├── provider_discord.go │ │ │ ├── provider_facebook.go │ │ │ ├── provider_generic_oidc.go │ │ │ ├── provider_generic_test.go │ │ │ ├── provider_github.go │ │ │ ├── provider_github_app.go │ │ │ ├── provider_gitlab.go │ │ │ ├── provider_google.go │ │ │ ├── provider_google_test.go │ │ │ ├── provider_jackson.go │ │ │ ├── provider_jackson_test.go │ │ │ ├── provider_lark.go │ │ │ ├── provider_line_2_1.go │ │ │ ├── provider_linkedin.go │ │ │ ├── provider_linkedin_test.go │ │ │ ├── provider_linkedin_v2.go │ │ │ ├── provider_linkedin_v2_test.go │ │ │ ├── provider_microsoft.go │ │ │ ├── provider_netid.go │ │ │ ├── provider_netid_test.go │ │ │ ├── provider_patreon.go │ │ │ ├── provider_private_net_test.go │ │ │ ├── provider_salesforce.go │ │ │ ├── provider_salesforce_unit_test.go │ │ │ ├── provider_slack.go │ │ │ ├── provider_spotify.go │ │ │ ├── provider_test.go │ │ │ ├── provider_test_fedcm.go │ │ │ ├── provider_test_fedcm_test.go │ │ │ ├── provider_uaepass.go │ │ │ ├── provider_uaepass_test.go │ │ │ ├── provider_userinfo_test.go │ │ │ ├── provider_vk.go │ │ │ ├── provider_x.go │ │ │ ├── provider_yandex.go │ │ │ ├── schema.go │ │ │ ├── state.go │ │ │ ├── state_test.go │ │ │ ├── strategy.go │ │ │ ├── strategy_helper_test.go │ │ │ ├── strategy_login.go │ │ │ ├── strategy_login_test.go │ │ │ ├── strategy_registration.go │ │ │ ├── strategy_registration_test.go │ │ │ ├── strategy_settings.go │ │ │ ├── strategy_settings_test.go │ │ │ ├── strategy_test.go │ │ │ ├── stub/ │ │ │ │ ├── extension/ │ │ │ │ │ ├── payload.json │ │ │ │ │ └── schema.json │ │ │ │ ├── jwk.json │ │ │ │ ├── jwks_public.json │ │ │ │ ├── jwks_public2.json │ │ │ │ ├── merge/ │ │ │ │ │ ├── 0.schema.json │ │ │ │ │ ├── 1.schema.json │ │ │ │ │ ├── 2.schema.json │ │ │ │ │ └── 3.schema.json │ │ │ │ ├── oidc.facebook.jsonnet │ │ │ │ ├── oidc.hydra.jsonnet │ │ │ │ ├── oidc.linkedin.jsonnet │ │ │ │ ├── oidc.uaepass.jsonnet │ │ │ │ ├── registration-aal.schema.json │ │ │ │ ├── registration-multi-schema-extra-fields.schema.json │ │ │ │ ├── registration-phone.schema.json │ │ │ │ ├── registration-verifiable-email.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ ├── settings.schema.json │ │ │ │ └── stub.schema.json │ │ │ ├── token_verifier.go │ │ │ └── types.go │ │ ├── passkey/ │ │ │ ├── .schema/ │ │ │ │ ├── login.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ └── settings.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=passkey_button_exists.json │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=succeeds_with_passwordless_login-passkey_display_name-type=api.json │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=succeeds_with_passwordless_login-passkey_display_name-type=browser.json │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=succeeds_with_passwordless_login-passkey_display_name-type=spa.json │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=succeeds_with_passwordless_login-webauthn_identifier-type=api.json │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=succeeds_with_passwordless_login-webauthn_identifier-type=browser.json │ │ │ │ ├── TestCompleteLogin-flow=passwordless-case=succeeds_with_passwordless_login-webauthn_identifier-type=spa.json │ │ │ │ ├── TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-browser.json │ │ │ │ ├── TestCompleteLogin-flow=refresh-case=refresh_passwordless_credentials-spa.json │ │ │ │ ├── TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json │ │ │ │ ├── TestCompleteSettings-case=fails_to_remove_passkey_if_it_is_the_last_credential_available-type=api-response.json │ │ │ │ ├── TestCompleteSettings-case=fails_to_remove_passkey_if_it_is_the_last_credential_available-type=api.json │ │ │ │ ├── TestCompleteSettings-case=fails_to_remove_passkey_if_it_is_the_last_credential_available-type=browser-response.json │ │ │ │ ├── TestCompleteSettings-case=fails_to_remove_passkey_if_it_is_the_last_credential_available-type=browser.json │ │ │ │ ├── TestCompleteSettings-case=fails_to_remove_passkey_if_it_is_the_last_credential_available-type=spa-response.json │ │ │ │ ├── TestCompleteSettings-case=fails_to_remove_passkey_if_it_is_the_last_credential_available-type=spa.json │ │ │ │ ├── TestCompleteSettings-case=one_activation_element_is_shown.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=account_enumeration_mitigation_disabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_passkey.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_passkey.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=account_enumeration_mitigation_disabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod-type=api.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod-type=browser.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials-type=api.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials-type=browser.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodProfile.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=1.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=2.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=3.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=4.json │ │ │ │ ├── TestPopulateSettingsMethod-method=PopulateSettingsMethod-type=api.json │ │ │ │ ├── TestPopulateSettingsMethod-method=PopulateSettingsMethod-type=browser.json │ │ │ │ ├── TestRegistration-case=passkey_button_does_not_exist_when_passwordless_is_disabled-browser.json │ │ │ │ ├── TestRegistration-case=passkey_button_does_not_exist_when_passwordless_is_disabled-spa.json │ │ │ │ ├── TestRegistration-case=passkey_button_exists-browser.json │ │ │ │ └── TestRegistration-case=passkey_button_exists-spa.json │ │ │ ├── fixtures/ │ │ │ │ ├── login/ │ │ │ │ │ └── success/ │ │ │ │ │ ├── credentials.json │ │ │ │ │ ├── identity.json │ │ │ │ │ ├── internal_context.json │ │ │ │ │ └── response.json │ │ │ │ ├── registration/ │ │ │ │ │ ├── failure/ │ │ │ │ │ │ ├── internal_context_missing_user_id.json │ │ │ │ │ │ └── internal_context_wrong_user_id.json │ │ │ │ │ └── success/ │ │ │ │ │ ├── android/ │ │ │ │ │ │ ├── internal_context.json │ │ │ │ │ │ └── response.json │ │ │ │ │ └── browser/ │ │ │ │ │ ├── identity.json │ │ │ │ │ ├── internal_context.json │ │ │ │ │ └── response.json │ │ │ │ └── settings/ │ │ │ │ └── success/ │ │ │ │ ├── identity.json │ │ │ │ ├── internal_context.json │ │ │ │ └── response.json │ │ │ ├── nodes.go │ │ │ ├── passkey_login.go │ │ │ ├── passkey_login_test.go │ │ │ ├── passkey_registration.go │ │ │ ├── passkey_registration_test.go │ │ │ ├── passkey_schema_extension.go │ │ │ ├── passkey_settings.go │ │ │ ├── passkey_settings_test.go │ │ │ ├── passkey_strategy.go │ │ │ ├── schema.go │ │ │ ├── stub/ │ │ │ │ ├── login.schema.json │ │ │ │ ├── login_webauthn.schema.json │ │ │ │ ├── missing-identifier.schema.json │ │ │ │ ├── noid.schema.json │ │ │ │ ├── profile.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ └── settings.schema.json │ │ │ └── testfixture_test.go │ │ ├── password/ │ │ │ ├── .schema/ │ │ │ │ ├── login.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ └── settings.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactorRefresh.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=account_enumeration_mitigation_disabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_password.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_password.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled_and_identity_has_no_password.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=account_enumeration_mitigation_disabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=account_enumeration_mitigation_enabled.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodProfile.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=1.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=2.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=3.json │ │ │ │ └── TestPopulateRegistrationMethod-method=idempotency-case=4.json │ │ │ ├── helpers.go │ │ │ ├── helpers_test.go │ │ │ ├── login.go │ │ │ ├── login_test.go │ │ │ ├── nodes.go │ │ │ ├── op_helpers_test.go │ │ │ ├── op_login_test.go │ │ │ ├── op_registration_test.go │ │ │ ├── registration.go │ │ │ ├── registration_test.go │ │ │ ├── schema.go │ │ │ ├── settings.go │ │ │ ├── settings_test.go │ │ │ ├── strategy.go │ │ │ ├── strategy_disabled_test.go │ │ │ ├── strategy_test.go │ │ │ ├── stub/ │ │ │ │ ├── email.schema.json │ │ │ │ ├── login.schema.json │ │ │ │ ├── migration.schema.json │ │ │ │ ├── missing-identifier.schema.json │ │ │ │ ├── phone.schema.json │ │ │ │ ├── profile.schema.json │ │ │ │ ├── registration.schema.json │ │ │ │ ├── registration.secondary.schema.json │ │ │ │ └── sort.schema.json │ │ │ ├── types.go │ │ │ ├── validator.go │ │ │ ├── validator_lcs_test.go │ │ │ └── validator_test.go │ │ ├── profile/ │ │ │ ├── .schema/ │ │ │ │ ├── registration.schema.json │ │ │ │ └── settings.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestOneStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-case=multi-schema-empty_flow.json │ │ │ │ ├── TestOneStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-empty_flow.json │ │ │ │ ├── TestPopulateRegistrationMethod-case=multi-schema-method=PopulateRegistrationMethodProfile.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodProfile.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=1.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=2.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=3.json │ │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=4.json │ │ │ │ ├── TestStrategyTraits-description=hydrate_the_proper_fields-type=api.json │ │ │ │ ├── TestStrategyTraits-description=hydrate_the_proper_fields-type=browser.json │ │ │ │ ├── TestStrategyTraits-description=hydrate_the_proper_fields-type=spa.json │ │ │ │ ├── TestTwoStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-case=multi-schema-empty_flow.json │ │ │ │ ├── TestTwoStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-empty_flow.json │ │ │ │ ├── TestTwoStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-return_to_profile.json │ │ │ │ ├── TestTwoStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-select_credentials.json │ │ │ │ └── TestTwoStepRegistration-initial_form_is_populated_with_identity_traits-type=browser-select_credentials_again.json │ │ │ ├── nodes.go │ │ │ ├── registration.go │ │ │ ├── registration_test.go │ │ │ ├── schema.go │ │ │ ├── strategy.go │ │ │ ├── strategy_test.go │ │ │ └── stub/ │ │ │ └── identity.schema.json │ │ ├── saml/ │ │ │ ├── login.go │ │ │ ├── registration.go │ │ │ └── settings.go │ │ ├── totp/ │ │ │ ├── .schema/ │ │ │ │ ├── login.schema.json │ │ │ │ └── settings.schema.json │ │ │ ├── .snapshots/ │ │ │ │ ├── TestCompleteLogin-case=totp_payload_is_set_when_identity_has_totp.json │ │ │ │ ├── TestCompleteSettings-case=device_setup_is_available_when_identity_has_no_totp_yet.json │ │ │ │ ├── TestCompleteSettings-case=device_unlinking_is_available_when_identity_has_totp.json │ │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor.json │ │ │ │ └── TestFormHydration-method=PopulateLoginMethodSecondFactorRefresh.json │ │ │ ├── generator.go │ │ │ ├── generator_test.go │ │ │ ├── login.go │ │ │ ├── login_test.go │ │ │ ├── nodes.go │ │ │ ├── schema.go │ │ │ ├── schema_extension.go │ │ │ ├── settings.go │ │ │ ├── settings_test.go │ │ │ ├── strategy.go │ │ │ ├── strategy_test.go │ │ │ └── stub/ │ │ │ ├── login.schema.json │ │ │ └── settings.schema.json │ │ └── webauthn/ │ │ ├── .schema/ │ │ │ ├── login.schema.json │ │ │ ├── registration.schema.json │ │ │ └── settings.schema.json │ │ ├── .snapshots/ │ │ │ ├── TestCompleteLogin-flow=mfa-case=can_not_use_security_key_for_passwordless_in_mfa_flow.json │ │ │ ├── TestCompleteLogin-flow=mfa-case=webauthn_payload_is_not_set_when_identity_has_no_webauthn.json │ │ │ ├── TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json │ │ │ ├── TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json │ │ │ ├── TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json │ │ │ ├── TestCompleteLogin-flow=passwordless-case=webauthn_button_exists.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=no_webauth_credentials-passwordless=false-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=no_webauth_credentials-passwordless=false-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=no_webauth_credentials-passwordless=true-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=no_webauth_credentials-passwordless=true-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=false-case=mfa_v0_credentials-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=false-case=mfa_v0_credentials-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=false-case=mfa_v1_credentials-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=false-case=mfa_v1_credentials-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=false-case=passwordless_credentials-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=false-case=passwordless_credentials-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=true-case=mfa_v0_credentials-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=true-case=mfa_v0_credentials-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=true-case=mfa_v1_credentials-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=true-case=mfa_v1_credentials-spa.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=true-case=passwordless_credentials-browser.json │ │ │ ├── TestCompleteLogin-flow=refresh-case=passwordless-passwordless_enabled=true-case=passwordless_credentials-spa.json │ │ │ ├── TestCompleteSettings-case=a_device_is_shown_which_can_be_unlinked.json │ │ │ ├── TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=browser-response.json │ │ │ ├── TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=browser.json │ │ │ ├── TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=spa-response.json │ │ │ ├── TestCompleteSettings-case=fails_to_remove_security_key_if_it_is_passwordless_and_the_last_credential_available-type=spa.json │ │ │ ├── TestCompleteSettings-case=one_activation_element_is_shown.json │ │ │ ├── TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=browser.json │ │ │ ├── TestCompleteSettings-case=possible_to_remove_webauthn_credential_if_it_is_MFA_at_all_times-type=spa.json │ │ │ ├── TestFormHydration-case=Multi-Schema-method=PopulateLoginMethodFirstFactor-case=mfa_enabled.json │ │ │ ├── TestFormHydration-case=Multi-Schema-method=PopulateLoginMethodFirstFactor-case=passwordless_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodFirstFactor-case=passwordless_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=mfa_enabled-case=account_enumeration_mitigation_disabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=mfa_enabled-case=account_enumeration_mitigation_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=passwordless_enabled-case=account_enumeration_mitigation_disabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentifier-case=passwordless_enabled-case=account_enumeration_mitigation_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_webauthn-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_does_not_have_a_webauthn-case=passwordless_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_webauthn-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_disabled-case=identity_has_webauthn-case=passwordless_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=WithIdentityHint-case=account_enumeration_mitigation_enabled-case=passwordless_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=mfa_enabled-case=account_enumeration_mitigation_disabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=mfa_enabled-case=account_enumeration_mitigation_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=passwordless_enabled-case=account_enumeration_mitigation_disabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstCredentials-case=no_options-case=passwordless_enabled-case=account_enumeration_mitigation_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodIdentifierFirstIdentification-case=passwordless_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodRefresh-case=mfa_enabled_and_user_has_mfa_credentials.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodRefresh-case=mfa_enabled_but_user_has_passwordless_credentials.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodRefresh-case=passwordless_enabled_and_user_has_passwordless_credentials.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodRefresh-case=passwordless_enabled_but_user_has_no_passwordless_credentials.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-case=mfa_enabled.json │ │ │ ├── TestFormHydration-method=PopulateLoginMethodSecondFactor-case=passwordless_enabled.json │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethod.json │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodCredentials.json │ │ │ ├── TestPopulateRegistrationMethod-method=PopulateRegistrationMethodProfile.json │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=1.json │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=2.json │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=3.json │ │ │ ├── TestPopulateRegistrationMethod-method=idempotency-case=4.json │ │ │ ├── TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-browser.json │ │ │ ├── TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-spa.json │ │ │ ├── TestRegistration-case=webauthn_button_exists-browser.json │ │ │ └── TestRegistration-case=webauthn_button_exists-spa.json │ │ ├── fixtures/ │ │ │ ├── login/ │ │ │ │ └── success/ │ │ │ │ └── mfa/ │ │ │ │ ├── identity.json │ │ │ │ ├── response.invalid.json │ │ │ │ ├── v0/ │ │ │ │ │ ├── credentials.json │ │ │ │ │ ├── internal_context.json │ │ │ │ │ └── response.json │ │ │ │ ├── v1/ │ │ │ │ │ ├── credentials.json │ │ │ │ │ ├── internal_context.json │ │ │ │ │ └── response.json │ │ │ │ ├── v1_handle/ │ │ │ │ │ ├── credentials.json │ │ │ │ │ ├── internal_context.json │ │ │ │ │ └── response.json │ │ │ │ └── v1_passwordless/ │ │ │ │ ├── credentials.json │ │ │ │ ├── internal_context.json │ │ │ │ └── response.json │ │ │ ├── registration/ │ │ │ │ ├── failure/ │ │ │ │ │ ├── internal_context_missing_user_id.json │ │ │ │ │ └── internal_context_wrong_user_id.json │ │ │ │ └── success/ │ │ │ │ ├── identity.json │ │ │ │ ├── internal_context.json │ │ │ │ └── response.json │ │ │ └── settings/ │ │ │ └── success/ │ │ │ ├── identity.json │ │ │ ├── internal_context.json │ │ │ └── response.json │ │ ├── login.go │ │ ├── login_test.go │ │ ├── nodes.go │ │ ├── registration.go │ │ ├── registration_test.go │ │ ├── schema.go │ │ ├── settings.go │ │ ├── settings_test.go │ │ ├── strategy.go │ │ ├── strategy_test.go │ │ ├── stub/ │ │ │ ├── login.schema.json │ │ │ ├── missing-identifier.schema.json │ │ │ ├── noid.schema.json │ │ │ ├── profile.schema.json │ │ │ ├── registration.schema.json │ │ │ └── settings.schema.json │ │ └── validate.go │ └── stub/ │ ├── fake-session.schema.json │ ├── identity.schema.json │ ├── new-form.json │ ├── registration.schema.json │ └── updated.schema.json ├── session/ │ ├── .snapshots/ │ │ ├── TestTokenizer-case=es256-without-jsonnet.json │ │ ├── TestTokenizer-case=es512-without-jsonnet.json │ │ ├── TestTokenizer-case=rs512-with-external_id-in-sub.json │ │ └── TestTokenizer-case=rs512-with-jsonnet.json │ ├── error.go │ ├── expand.go │ ├── expand_test.go │ ├── handler.go │ ├── handler_test.go │ ├── helper.go │ ├── helper_test.go │ ├── manager.go │ ├── manager_http.go │ ├── manager_http_test.go │ ├── manager_test.go │ ├── persistence.go │ ├── session.go │ ├── session_test.go │ ├── stub/ │ │ ├── fake-session.schema.json │ │ ├── identity.schema.json │ │ ├── jwk.es256.json │ │ ├── jwk.es512.broken.json │ │ ├── jwk.es512.json │ │ ├── jwk.rs512.json │ │ └── rs512-template.jsonnet │ ├── test/ │ │ └── persistence.go │ ├── tokenizer.go │ └── tokenizer_test.go ├── spec/ │ ├── api.go │ ├── api.json │ └── swagger.json ├── test/ │ ├── e2e/ │ │ ├── .gitignore │ │ ├── .go-version │ │ ├── .npmignore │ │ ├── cypress/ │ │ │ ├── fixtures/ │ │ │ │ └── example.json │ │ │ ├── helpers/ │ │ │ │ ├── express.ts │ │ │ │ ├── httpbin.ts │ │ │ │ ├── index.ts │ │ │ │ ├── oauth2.ts │ │ │ │ ├── react.ts │ │ │ │ └── webhook.ts │ │ │ ├── integration/ │ │ │ │ └── profiles/ │ │ │ │ ├── code/ │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ └── registration/ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ └── success.spec.ts │ │ │ │ ├── email/ │ │ │ │ │ ├── error/ │ │ │ │ │ │ └── ui.spec.ts │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ │ ├── success.spec.ts │ │ │ │ │ │ └── ui.spec.ts │ │ │ │ │ ├── logout/ │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── registration/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ ├── success.spec.ts │ │ │ │ │ │ └── ui.spec.ts │ │ │ │ │ └── settings/ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ ├── success.spec.ts │ │ │ │ │ └── ui.spec.ts │ │ │ │ ├── import/ │ │ │ │ │ └── import.spec.ts │ │ │ │ ├── mfa/ │ │ │ │ │ ├── code.spec.ts │ │ │ │ │ ├── code_optional.spec.ts │ │ │ │ │ ├── lookup.spec.ts │ │ │ │ │ ├── mix.spec.ts │ │ │ │ │ ├── settings.spec.ts │ │ │ │ │ ├── totp.spec.ts │ │ │ │ │ └── webauthn.spec.ts │ │ │ │ ├── mobile/ │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── mfa/ │ │ │ │ │ │ ├── backup.spec.ts │ │ │ │ │ │ ├── mix.spec.ts │ │ │ │ │ │ └── totp.spec.ts │ │ │ │ │ ├── passkey/ │ │ │ │ │ │ └── flows.spec.ts │ │ │ │ │ ├── registration/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ └── settings/ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ ├── oidc.spec.ts │ │ │ │ │ └── success.spec.ts │ │ │ │ ├── network/ │ │ │ │ │ └── errors.spec.ts │ │ │ │ ├── oidc/ │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── logout/ │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── registration/ │ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ └── settings/ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ └── success.spec.ts │ │ │ │ ├── oidc-provider/ │ │ │ │ │ ├── login.spec.ts │ │ │ │ │ ├── mfa.spec.ts │ │ │ │ │ └── registration.spec.ts │ │ │ │ ├── passkey/ │ │ │ │ │ └── flows.spec.ts │ │ │ │ ├── passwordless/ │ │ │ │ │ └── flows.spec.ts │ │ │ │ ├── recovery/ │ │ │ │ │ ├── code/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── link/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── return-to/ │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ └── settings/ │ │ │ │ │ └── success.spec.ts │ │ │ │ ├── two-steps/ │ │ │ │ │ └── registration/ │ │ │ │ │ ├── code.spec.ts │ │ │ │ │ ├── oidc.spec.ts │ │ │ │ │ └── password.spec.ts │ │ │ │ ├── verification/ │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── registration/ │ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ ├── settings/ │ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ │ └── success.spec.ts │ │ │ │ │ └── verify/ │ │ │ │ │ ├── errors.spec.ts │ │ │ │ │ └── success.spec.ts │ │ │ │ └── webhooks/ │ │ │ │ ├── login/ │ │ │ │ │ ├── error.spec.ts │ │ │ │ │ └── success.spec.ts │ │ │ │ └── registration/ │ │ │ │ ├── errors.spec.ts │ │ │ │ └── success.spec.ts │ │ │ ├── plugins/ │ │ │ │ └── index.js │ │ │ ├── support/ │ │ │ │ ├── commands.ts │ │ │ │ ├── configHelpers.ts │ │ │ │ ├── index.d.ts │ │ │ │ └── index.js │ │ │ └── tsconfig.json │ │ ├── cypress.config.ts │ │ ├── hydra-kratos-login-consent/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ ├── hydra-login-consent/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ ├── hydra.yml │ │ ├── mock/ │ │ │ ├── httptarget/ │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── main.go │ │ │ └── webhook/ │ │ │ ├── Dockerfile │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ ├── modd.conf │ │ ├── package.json │ │ ├── playwright/ │ │ │ ├── actions/ │ │ │ │ ├── identity.ts │ │ │ │ ├── login.ts │ │ │ │ ├── mail.ts │ │ │ │ ├── session.ts │ │ │ │ └── webhook.ts │ │ │ ├── fixtures/ │ │ │ │ ├── index.ts │ │ │ │ └── schemas/ │ │ │ │ └── sms.ts │ │ │ ├── kratos.base-config.json │ │ │ ├── lib/ │ │ │ │ ├── config.ts │ │ │ │ ├── helper.ts │ │ │ │ └── request.ts │ │ │ ├── models/ │ │ │ │ └── elements/ │ │ │ │ ├── login.ts │ │ │ │ ├── registration.ts │ │ │ │ └── settings.ts │ │ │ ├── selectors/ │ │ │ │ └── input.ts │ │ │ ├── setup/ │ │ │ │ └── default_config.ts │ │ │ ├── tests/ │ │ │ │ ├── desktop/ │ │ │ │ │ ├── code/ │ │ │ │ │ │ └── sms.spec.ts │ │ │ │ │ ├── identifier_first/ │ │ │ │ │ │ ├── code.login.spec.ts │ │ │ │ │ │ ├── oidc.login.spec.ts │ │ │ │ │ │ ├── passkeys.login.spec.ts │ │ │ │ │ │ └── password.login.spec.ts │ │ │ │ │ └── profile_first/ │ │ │ │ │ └── everything.registration.spec.ts │ │ │ │ └── mobile/ │ │ │ │ ├── app_login.spec.ts │ │ │ │ └── app_recovery.spec.ts │ │ │ └── types/ │ │ │ └── index.ts │ │ ├── playwright.config.ts │ │ ├── profiles/ │ │ │ ├── code/ │ │ │ │ ├── .kratos.yml │ │ │ │ ├── identity.code.only.traits.schema.json │ │ │ │ ├── identity.complex.traits.schema.json │ │ │ │ └── identity.traits.schema.json │ │ │ ├── email/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── kratos.base.yml │ │ │ ├── mfa/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── mfa-optional/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── mobile/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── network/ │ │ │ │ ├── .kratos.yml │ │ │ │ ├── identity.traits.schema.json │ │ │ │ └── webhook.jsonnet │ │ │ ├── oidc/ │ │ │ │ ├── .kratos.yml │ │ │ │ ├── hydra.jsonnet │ │ │ │ ├── identity-required.traits.schema.json │ │ │ │ └── identity.traits.schema.json │ │ │ ├── oidc-provider/ │ │ │ │ ├── .kratos.yml │ │ │ │ ├── hydra.jsonnet │ │ │ │ └── identity.traits.schema.json │ │ │ ├── oidc-provider-mfa/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── passkey/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── passwordless/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── recovery/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── recovery-mfa/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── spa/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── two-steps/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ ├── verification/ │ │ │ │ ├── .kratos.yml │ │ │ │ └── identity.traits.schema.json │ │ │ └── webhooks/ │ │ │ ├── .kratos.yml │ │ │ ├── identity.traits.schema.json │ │ │ └── webhook_body.jsonnet │ │ ├── proxy/ │ │ │ ├── package.json │ │ │ └── proxy.js │ │ ├── render-kratos-config.sh │ │ ├── run.sh │ │ ├── shared/ │ │ │ └── config.d.ts │ │ └── tsconfig.json │ ├── schema/ │ │ ├── README.md │ │ ├── fixtures/ │ │ │ ├── config.schema.test.failure/ │ │ │ │ ├── OIDCClaims.malformed.yaml │ │ │ │ ├── courierTemplates.bodyHtmlMissing.yaml │ │ │ │ ├── courierTemplates.emailMissing.yaml │ │ │ │ ├── emailCourierTemplate.malformed.yaml │ │ │ │ ├── root.SMSConfigmalformedURL.yaml │ │ │ │ ├── root.invalidTypes.yaml │ │ │ │ ├── root.invalidVersion.yaml │ │ │ │ ├── root.missingCourierTemplatesVerificationInvalid.yaml │ │ │ │ ├── root.requiresCourierRecovery.yml │ │ │ │ ├── root.requiresCourierVerification.yml │ │ │ │ ├── selfServiceOIDCProvider.invalidTypes.yaml │ │ │ │ ├── selfServiceOIDCProvider.privateKeyIdNotWithGithub.yaml │ │ │ │ ├── selfServiceOIDCProvider.privateKeyIdRequiredWithApple.yaml │ │ │ │ ├── selfServiceOIDCProvider.privateKeyNotWithGithub.yaml │ │ │ │ ├── selfServiceOIDCProvider.privateKeyRequiredWithApple.yaml │ │ │ │ ├── selfServiceOIDCProvider.teamIdNotWithGithub.yaml │ │ │ │ ├── selfServiceOIDCProvider.teamIdRequiredWithApple.yaml │ │ │ │ ├── selfServiceOIDCProvider.tenantNotWithGithub.yaml │ │ │ │ └── selfServiceOIDCProvider.tenantRequiredWithMicrosoft.yaml │ │ │ └── config.schema.test.success/ │ │ │ ├── OIDCClaims.full.yaml │ │ │ ├── courierTemplates.full.yaml │ │ │ ├── defaultReturnTo.URI-Reference.yaml │ │ │ ├── defaultReturnTo.URL.yaml │ │ │ ├── emailCourierTemplate.full.yaml │ │ │ ├── emailCourierTemplate.withMissingBody.yaml │ │ │ ├── emailCourierTemplate.withMissingSubject.yaml │ │ │ ├── root.courierSMS.yaml │ │ │ ├── root.courierTemplates.yaml │ │ │ ├── root.full.yml │ │ │ ├── root.no_aliases.yaml │ │ │ ├── root.required.yml │ │ │ ├── selfServiceAfterDefaultLoginMethod.full.yaml │ │ │ ├── selfServiceAfterDefaultLoginMethod.required.yaml │ │ │ ├── selfServiceAfterDefaultLoginMethodHooks.full.yaml │ │ │ ├── selfServiceAfterDefaultLoginMethodHooks.required.yaml │ │ │ ├── selfServiceAfterLogin.full.yaml │ │ │ ├── selfServiceAfterLogin.required.yaml │ │ │ ├── selfServiceAfterOIDCLoginMethod.full.yaml │ │ │ ├── selfServiceAfterOIDCLoginMethod.required.yaml │ │ │ ├── selfServiceAfterRegistration.full.yaml │ │ │ ├── selfServiceAfterRegistration.required.yaml │ │ │ ├── selfServiceAfterRegistrationMethod.full.yaml │ │ │ ├── selfServiceAfterRegistrationMethod.required.yaml │ │ │ ├── selfServiceAfterSettings.full.yaml │ │ │ ├── selfServiceAfterSettings.required.yaml │ │ │ ├── selfServiceAfterSettingsAuthMethod.full.yaml │ │ │ ├── selfServiceAfterSettingsProfileMethod.full.yaml │ │ │ ├── selfServiceAfterSettingsProfileMethod.required.yaml │ │ │ ├── selfServiceOIDCProvider.fullApple.yaml │ │ │ ├── selfServiceOIDCProvider.fullGithub.yaml │ │ │ ├── selfServiceOIDCProvider.fullMicrosoft.yaml │ │ │ ├── selfServiceOIDCProvider.required.yaml │ │ │ ├── selfServiceSessionIssuerHook.full.yaml │ │ │ ├── selfServiceSessionRevokerHook.full.yaml │ │ │ ├── selfServiceWebHook.noauth.yaml │ │ │ ├── webHookAuthApiKeyProperties.full.yaml │ │ │ └── webHookAuthBasicAuthProperties.full.yaml │ │ └── schema_test.go │ └── stub/ │ └── identity/ │ └── empty.schema.json ├── text/ │ ├── context.go │ ├── id.go │ ├── id_test.go │ ├── message.go │ ├── message_error.go │ ├── message_login.go │ ├── message_node.go │ ├── message_recovery.go │ ├── message_registration.go │ ├── message_settings.go │ ├── message_system.go │ ├── message_test.go │ ├── message_validation.go │ ├── message_verification.go │ └── type.go ├── ui/ │ ├── container/ │ │ ├── container.go │ │ ├── container_test.go │ │ ├── error.go │ │ ├── stub/ │ │ │ ├── all_formats.schema.json │ │ │ ├── complex.schema.json │ │ │ ├── identity.schema.json │ │ │ └── simple.schema.json │ │ └── types.go │ └── node/ │ ├── .snapshots/ │ │ ├── TestNodeMarshalJSON-anchor_node.json │ │ ├── TestNodeMarshalJSON-division_node.json │ │ ├── TestNodeMarshalJSON-empty_type_inferred_from_attributes.json │ │ ├── TestNodeMarshalJSON-image_node.json │ │ ├── TestNodeMarshalJSON-input_node.json │ │ ├── TestNodeMarshalJSON-script_node.json │ │ └── TestNodeMarshalJSON-text_node.json │ ├── attributes.go │ ├── attributes_input.go │ ├── attributes_input_csrf.go │ ├── attributes_input_test.go │ ├── attributes_test.go │ ├── fixtures/ │ │ ├── all_formats.schema.json │ │ ├── complex.schema.json │ │ ├── identity.schema.json │ │ ├── simple.schema.json │ │ └── sort/ │ │ ├── expected/ │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ ├── 3.json │ │ │ └── 4.json │ │ ├── input/ │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ ├── 3.json │ │ │ └── 4.json │ │ └── schema/ │ │ ├── 2.json │ │ ├── 3.json │ │ └── 4.json │ ├── helper.go │ ├── identifiers.go │ ├── node.go │ └── node_test.go └── x/ ├── clean_url_test.go ├── cookie.go ├── cookie_test.go ├── doc.go ├── err.go ├── err_test.go ├── events/ │ ├── events.go │ └── events_test.go ├── fetcher.go ├── http.go ├── http_test.go ├── httploadermiddleware.go ├── ider.go ├── isjsonrequest.go ├── isjsonrequest_test.go ├── json_bool.go ├── json_bool_test.go ├── json_marshal.go ├── json_marshal_test.go ├── json_number.go ├── keys.go ├── mailhog.go ├── map_json.go ├── map_json_test.go ├── maxitems.go ├── nocache.go ├── normalize.go ├── normalize_test.go ├── nosurfx/ │ ├── nosurf.go │ └── nosurf_test.go ├── pagination.go ├── pointer.go ├── provider.go ├── readall.go ├── redir/ │ ├── port_redirect.go │ ├── port_redirect_test.go │ ├── secure_redirect.go │ └── secure_redirect_test.go ├── require.go ├── router.go ├── sdkx.go ├── sql.go ├── sql_test.go ├── stub_fs.go ├── swagger/ │ ├── swagger_meta.go │ ├── swagger_types_global.go │ └── swagger_types_overrides.go ├── tests.go ├── time.go ├── time_test.go ├── token_prefixes.go ├── transaction.go ├── uuid.go └── webauthnx/ ├── aaguid/ │ ├── aaguid.go │ ├── aaguid_test.go │ ├── aaguids.json │ └── passkey-aaguids.json ├── errors.go ├── handler.go ├── js/ │ ├── trigger.go │ ├── trigger_test.go │ └── webauthn.js ├── nodes.go └── user.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .docker/Dockerfile-alpine ================================================ FROM alpine:3.20.0 # Because this image supports SQLite, we create /home/ory and /home/ory/sqlite which is owned by the ory user # and declare /home/ory/sqlite a volume. # # To get SQLite and Docker Volumes working with this image, mount the volume where SQLite should be written to at: # # /home/ory/sqlite/some-file. RUN addgroup -S ory; \ adduser -S ory -G ory -D -u 10000 -h /home/ory -s /bin/nologin; \ chown -R ory:ory /home/ory RUN apk --update upgrade && apk --no-cache --update-cache --upgrade --latest add ca-certificates WORKDIR /home/ory COPY kratos /usr/bin/kratos # By creating the sqlite folder as the ory user, the mounted volume will be owned by ory:ory, which # is required for read/write of SQLite. RUN mkdir -p /var/lib/sqlite RUN chown ory:ory /var/lib/sqlite VOLUME /var/lib/sqlite # Exposing the ory home directory to simplify passing in Kratos configuration (e.g. if the file $HOME/.kratos.yaml # exists, it will be automatically used as the configuration file). VOLUME /home/ory # Declare the standard ports used by Kratos (4433 for public service endpoint, 4434 for admin service endpoint) EXPOSE 4433 4434 USER 10000 ENTRYPOINT ["kratos"] CMD ["serve"] ================================================ FILE: .docker/Dockerfile-build ================================================ FROM golang:1.26-trixie AS builder RUN apt-get update && apt-get upgrade -y &&\ mkdir -p /var/lib/sqlite WORKDIR /go/src/github.com/ory/kratos COPY oryx/go.mod oryx/go.mod COPY oryx/go.sum oryx/go.sum COPY go.mod go.mod COPY go.sum go.sum COPY pkg/client-go/go.* pkg/client-go/ ENV CGO_ENABLED 1 ENV CGO_CPPFLAGS -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 RUN go mod download COPY . . ARG VERSION ARG COMMIT ARG BUILD_DATE RUN --mount=type=cache,target=/root/.cache/go-build go build -tags sqlite \ -ldflags="-X 'github.com/ory/kratos/driver/config.Version=${VERSION}' -X 'github.com/ory/kratos/driver/config.Date=${BUILD_DATE}' -X 'github.com/ory/kratos/driver/config.Commit=${COMMIT}'" \ -o /usr/bin/kratos ######################### FROM gcr.io/distroless/base-nossl-debian12:nonroot AS runner COPY --from=builder --chown=nonroot:nonroot /var/lib/sqlite /var/lib/sqlite COPY --from=builder --chown=nonroot:nonroot /usr/bin/kratos /usr/bin/kratos VOLUME /var/lib/sqlite # Declare the standard ports used by Kratos (4433 for public service endpoint, 4434 for admin service endpoint) EXPOSE 4433 4434 ENTRYPOINT ["kratos"] CMD ["serve"] ================================================ FILE: .docker/Dockerfile-debug ================================================ FROM golang:1.26-trixie ENV CGO_ENABLED 1 RUN apt-get update && apt-get install -y --no-install-recommends inotify-tools psmisc RUN go install github.com/go-delve/delve/cmd/dlv@latest COPY script/debug-entrypoint.sh /entrypoint.sh VOLUME /dockerdev WORKDIR /dockerdev ENV DELVE_PORT 40000 ENV SERVICE_NAME service EXPOSE 8000 $DELVE_PORT ENTRYPOINT ["/entrypoint.sh"] ================================================ FILE: .docker/Dockerfile-distroless-static ================================================ FROM gcr.io/distroless/static-debian12:nonroot COPY kratos /usr/bin/kratos EXPOSE 4433 4434 ENTRYPOINT ["kratos"] CMD ["serve"] ================================================ FILE: .docker/docker-compose.template.dbg ================================================ version: '3.7' services: ${SERVICE_NAME}: build: dockerfile: ./.docker/Dockerfile-debug ports: - ${REMOTE_DEBUGGING_PORT}:40000 security_opt: - apparmor=unconfined cap_add: - SYS_PTRACE volumes: - type: bind source: ${SERVICE_ROOT} target: /dockerdev read_only: false ================================================ FILE: .dockerignore ================================================ docs .releaser .github .circleci tmp scripts .idea .git/ database.yaml contrib/quickstart node_modules/ ./quickstart.yml ./quickstart-*.yml .bin/ test/ pgked.go ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.go] indent_size = 4 indent_style = tab ================================================ FILE: .github/CODEOWNERS ================================================ * @aeneasr @ory/product-development ================================================ FILE: .github/FUNDING.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/FUNDING.yml # These are supported funding model platforms # github: patreon: _ory open_collective: ory ================================================ FILE: .github/ISSUE_TEMPLATE/BUG-REPORT.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/BUG-REPORT.yml description: "Create a bug report" labels: - bug name: "Bug Report" body: - attributes: value: "Thank you for taking the time to fill out this bug report!\n" type: markdown - attributes: label: "Preflight checklist" options: - label: "I could not find a solution in the existing issues, docs, nor discussions." required: true - label: "I agree to follow this project's [Code of Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)." required: true - label: "I have read and am following this repository's [Contribution Guidelines](https://github.com/ory/kratos/blob/master/CONTRIBUTING.md)." required: true - label: "I have joined the [Ory Community Slack](https://slack.ory.com)." - label: "I am signed up to the [Ory Security Patch Newsletter](https://www.ory.com/l/sign-up-newsletter)." id: checklist type: checkboxes - attributes: description: "Enter the slug or API URL of the affected Ory Network project. Leave empty when you are self-hosting." label: "Ory Network Project" placeholder: "https://.projects.oryapis.com" id: ory-network-project type: input - attributes: description: "A clear and concise description of what the bug is." label: "Describe the bug" placeholder: "Tell us what you see!" id: describe-bug type: textarea validations: required: true - attributes: description: | Clear, formatted, and easy to follow steps to reproduce the behavior: placeholder: | Steps to reproduce the behavior: 1. Run `docker run ....` 2. Make API Request to with `curl ...` 3. Request fails with response: `{"some": "error"}` label: "Reproducing the bug" id: reproduce-bug type: textarea validations: required: true - attributes: description: "Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. Please redact any sensitive information" label: "Relevant log output" render: shell placeholder: | log=error .... id: logs type: textarea - attributes: description: "Please copy and paste any relevant configuration. This will be automatically formatted into code, so no need for backticks. Please redact any sensitive information!" label: "Relevant configuration" render: yml placeholder: | server: admin: port: 1234 id: config type: textarea - attributes: description: "What version of our software are you running?" label: Version id: version type: input validations: required: true - attributes: label: "On which operating system are you observing this issue?" options: - Ory Network - macOS - Linux - Windows - FreeBSD - Other id: operating-system type: dropdown - attributes: label: "In which environment are you deploying?" options: - Ory Network - Docker - "Docker Compose" - "Kubernetes with Helm" - Kubernetes - Binary - Other id: deployment type: dropdown - attributes: description: "Add any other context about the problem here." label: Additional Context id: additional type: textarea ================================================ FILE: .github/ISSUE_TEMPLATE/DESIGN-DOC.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/DESIGN-DOC.yml description: "A design document is needed for non-trivial changes to the code base." labels: - rfc name: "Design Document" body: - attributes: value: | Thank you for writing this design document. One of the key elements of Ory's software engineering culture is the use of defining software designs through design docs. These are relatively informal documents that the primary author or authors of a software system or application create before they embark on the coding project. The design doc documents the high level implementation strategy and key design decisions with emphasis on the trade-offs that were considered during those decisions. Ory is leaning heavily on [Google's design docs process](https://www.industrialempathy.com/posts/design-docs-at-google/) and [Golang Proposals](https://github.com/golang/proposal). Writing a design doc before contributing your change ensures that your ideas are checked with the community and maintainers. It will save you a lot of time developing things that might need to be changed after code reviews, and your pull requests will be merged faster. type: markdown - attributes: label: "Preflight checklist" options: - label: "I could not find a solution in the existing issues, docs, nor discussions." required: true - label: "I agree to follow this project's [Code of Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)." required: true - label: "I have read and am following this repository's [Contribution Guidelines](https://github.com/ory/kratos/blob/master/CONTRIBUTING.md)." required: true - label: "I have joined the [Ory Community Slack](https://slack.ory.com)." - label: "I am signed up to the [Ory Security Patch Newsletter](https://www.ory.com/l/sign-up-newsletter)." id: checklist type: checkboxes - attributes: description: "Enter the slug or API URL of the affected Ory Network project. Leave empty when you are self-hosting." label: "Ory Network Project" placeholder: "https://.projects.oryapis.com" id: ory-network-project type: input - attributes: description: | This section gives the reader a very rough overview of the landscape in which the new system is being built and what is actually being built. This isn’t a requirements doc. Keep it succinct! The goal is that readers are brought up to speed but some previous knowledge can be assumed and detailed info can be linked to. This section should be entirely focused on objective background facts. label: "Context and scope" id: scope type: textarea validations: required: true - attributes: description: | A short list of bullet points of what the goals of the system are, and, sometimes more importantly, what non-goals are. Note, that non-goals aren’t negated goals like “The system shouldn’t crash”, but rather things that could reasonably be goals, but are explicitly chosen not to be goals. A good example would be “ACID compliance”; when designing a database, you’d certainly want to know whether that is a goal or non-goal. And if it is a non-goal you might still select a solution that provides it, if it doesn’t introduce trade-offs that prevent achieving the goals. label: "Goals and non-goals" id: goals type: textarea validations: required: true - attributes: description: | This section should start with an overview and then go into details. The design doc is the place to write down the trade-offs you made in designing your software. Focus on those trade-offs to produce a useful document with long-term value. That is, given the context (facts), goals and non-goals (requirements), the design doc is the place to suggest solutions and show why a particular solution best satisfies those goals. The point of writing a document over a more formal medium is to provide the flexibility to express the problem at hand in an appropriate manner. Because of this, there is no explicit guidance on how to actually describe the design. label: "The design" id: design type: textarea validations: required: true - attributes: description: | If the system under design exposes an API, then sketching out that API is usually a good idea. In most cases, however, one should withstand the temptation to copy-paste formal interface or data definitions into the doc as these are often verbose, contain unnecessary detail and quickly get out of date. Instead, focus on the parts that are relevant to the design and its trade-offs. label: "APIs" id: apis type: textarea - attributes: description: | Systems that store data should likely discuss how and in what rough form this happens. Similar to the advice on APIs, and for the same reasons, copy-pasting complete schema definitions should be avoided. Instead, focus on the parts that are relevant to the design and its trade-offs. label: "Data storage" id: persistence type: textarea - attributes: description: | Design docs should rarely contain code, or pseudo-code except in situations where novel algorithms are described. As appropriate, link to prototypes that show the feasibility of the design. label: "Code and pseudo-code" id: pseudocode type: textarea - attributes: description: | One of the primary factors that would influence the shape of a software design and hence the design doc, is the degree of constraint of the solution space. On one end of the extreme is the “greenfield software project”, where all we know are the goals, and the solution can be whatever makes the most sense. Such a document may be wide-ranging, but it also needs to quickly define a set of rules that allow zooming in on a manageable set of solutions. On the other end are systems where the possible solutions are very well defined, but it isn't at all obvious how they could even be combined to achieve the goals. This may be a legacy system that is difficult to change and wasn't designed to do what you want it to do or a library design that needs to operate within the constraints of the host programming language. In this situation, you may be able to enumerate all the things you can do relatively easily, but you need to creatively put those things together to achieve the goals. There may be multiple solutions, and none of them are great, and hence such a document should focus on selecting the best way given all identified trade-offs. label: "Degree of constraint" id: constrait type: textarea - attributes: description: | This section lists alternative designs that would have reasonably achieved similar outcomes. The focus should be on the trade-offs that each respective design makes and how those trade-offs led to the decision to select the design that is the primary topic of the document. While it is fine to be succinct about a solution that ended up not being selected, this section is one of the most important ones as it shows very explicitly why the selected solution is the best given the project goals and how other solutions, that the reader may be wondering about, introduce trade-offs that are less desirable given the goals. label: Alternatives considered id: alternatives type: textarea ================================================ FILE: .github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml description: "Suggest an idea for this project without a plan for implementation" labels: - feat name: "Feature Request" body: - attributes: value: | Thank you for suggesting an idea for this project! If you already have a plan to implement a feature or a change, please create a [design document](https://github.com/aeneasr/gh-template-test/issues/new?assignees=&labels=rfc&template=DESIGN-DOC.yml) instead if the change is non-trivial! type: markdown - attributes: label: "Preflight checklist" options: - label: "I could not find a solution in the existing issues, docs, nor discussions." required: true - label: "I agree to follow this project's [Code of Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)." required: true - label: "I have read and am following this repository's [Contribution Guidelines](https://github.com/ory/kratos/blob/master/CONTRIBUTING.md)." required: true - label: "I have joined the [Ory Community Slack](https://slack.ory.com)." - label: "I am signed up to the [Ory Security Patch Newsletter](https://www.ory.com/l/sign-up-newsletter)." id: checklist type: checkboxes - attributes: description: "Enter the slug or API URL of the affected Ory Network project. Leave empty when you are self-hosting." label: "Ory Network Project" placeholder: "https://.projects.oryapis.com" id: ory-network-project type: input - attributes: description: "Is your feature request related to a problem? Please describe." label: "Describe your problem" placeholder: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" id: problem type: textarea validations: required: true - attributes: description: | Describe the solution you'd like placeholder: | A clear and concise description of what you want to happen. label: "Describe your ideal solution" id: solution type: textarea validations: required: true - attributes: description: "Describe alternatives you've considered" label: "Workarounds or alternatives" id: alternatives type: textarea validations: required: true - attributes: description: "What version of our software are you running?" label: Version id: version type: input validations: required: true - attributes: description: "Add any other context or screenshots about the feature request here." label: Additional Context id: additional type: textarea ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/config.yml blank_issues_enabled: false contact_links: - name: Ory Kratos Forum url: https://github.com/ory/kratos/discussions about: Please ask and answer questions here, show your implementations and discuss ideas. - name: Ory Chat url: https://www.ory.com/chat about: Hang out with other Ory community members to ask and answer questions. ================================================ FILE: .github/auto_assign.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/auto_assign.yml # Set to true to add reviewers to pull requests addReviewers: true # Set to true to add assignees to pull requests addAssignees: true # A list of reviewers to be added to pull requests (GitHub user name) assignees: - ory/maintainers # A number of reviewers added to the pull request # Set 0 to add all the reviewers (default: 0) numberOfReviewers: 0 ================================================ FILE: .github/codeql/codeql-config.yml ================================================ name: "CodeQL config" queries: - uses: security-and-quality paths-ignore: - "/test/" - "/pkg/testhelpers" ================================================ FILE: .github/config.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/config.yml todo: keyword: "@todo" label: todo ================================================ FILE: .github/conventional_commits.json ================================================ { "$schema": "https://raw.githubusercontent.com/ory/ci/master/conventional_commit_config/dist/config.schema.json", "addTypes": ["improvement", "perf"], "addScopes": ["cli", "sql"] } ================================================ FILE: .github/labels.json ================================================ [ { "name": "package/2fa", "color": "0A28FD", "aliases": ["module:2fa"] }, { "name": "package/cli", "color": "0A28FD", "aliases": ["module:cli"] }, { "name": "package/courier", "color": "0A28FD", "aliases": ["module:courier"] }, { "name": "package/courier", "color": "0A28FD", "aliases": ["module:docs"] }, { "name": "package/selfservice/errorx", "color": "0A28FD", "aliases": ["module:errorx"] }, { "name": "package/identity", "color": "0A28FD", "aliases": ["module:identity"] }, { "name": "package/persistence/sql", "color": "0A28FD", "aliases": ["module:migrations"] }, { "name": "package/selfservice", "color": "0A28FD", "aliases": ["module:selfservice"] }, { "name": "package/selfservice/oidc", "color": "0A28FD", "aliases": ["module:ss/oidc"] }, { "name": "package/selfservice/password", "color": "0A28FD", "aliases": ["module:ss/password"] }, { "name": "package/selfservice/verification", "color": "0A28FD", "aliases": ["module:verification"] }, { "name": "package/selfservice/recovery", "color": "0A28FD", "aliases": [] }, { "name": "package/session", "color": "0A28FD", "aliases": ["module:session"] } ] ================================================ FILE: .github/pull_request_template.md ================================================ ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code guidelines](../blob/master/CONTRIBUTING.md#contributing-code). - [ ] I have read the [security policy](../security/policy). - [ ] I confirm that this pull request does not address a security vulnerability. If this pull request addresses a security vulnerability, I confirm that I got the approval (please contact [security@ory.com](mailto:security@ory.com)) from the maintainers to push the changes. - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added or changed [the documentation](https://github.com/ory/docs). ## Further Comments ================================================ FILE: .github/workflows/ci.yaml ================================================ name: CI on: push: branches: - master tags: - "*" pull_request: # Cancel in-progress runs in current workflow. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Run tests and lints runs-on: ubuntu-latest services: postgres: image: postgres:18 env: POSTGRES_DB: postgres POSTGRES_PASSWORD: test POSTGRES_USER: test ports: - 5432:5432 mysql: image: mysql:9.6 env: MYSQL_ROOT_PASSWORD: test ports: - 3306:3306 env: TEST_DATABASE_POSTGRESQL: "postgres://test:test@localhost:5432/postgres?sslmode=disable" TEST_DATABASE_MYSQL: "mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true" TEST_DATABASE_COCKROACHDB: "cockroach://root@localhost:26257/defaultdb?sslmode=disable" steps: - run: | docker create --name cockroach -p 26257:26257 \ cockroachdb/cockroach:latest-v25.4 start-single-node --insecure \ || true docker start cockroach name: Start CockroachDB - run: docker pull oryd/hydra:v2.2.0 name: Pull Hydra - uses: ory/ci/checkout@master with: fetch-depth: 2 - uses: actions/setup-go@v6 with: check-latest: true go-version-file: go.mod - run: go list -json > go.list - name: Run nancy uses: sonatype-nexus-community/nancy-github-action@v1.0.3 with: nancyVersion: v1.0.42 - run: | sudo apt-get update name: apt-get update - run: npm install name: Install node deps - name: Run golangci-lint if: ${{ github.ref_type != 'tag' }} uses: golangci/golangci-lint-action@v9 env: GOGC: 100 with: args: --timeout 10m0s version: latest only-new-issues: "true" - name: Build Kratos run: make install - name: Run go tests run: make test-coverage - name: Submit to Codecov run: | bash <(curl -s https://codecov.io/bash) env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test-e2e: name: Run end-to-end tests runs-on: ubuntu-latest services: postgres: image: postgres:18 env: POSTGRES_DB: postgres POSTGRES_PASSWORD: test POSTGRES_USER: test ports: - 5432:5432 mysql: image: mysql:9.6 env: MYSQL_ROOT_PASSWORD: test ports: - 3306:3306 mailslurper: image: oryd/mailslurper:latest-smtps ports: - 4436:4436 - 4437:4437 - 1025:1025 env: TEST_DATABASE_POSTGRESQL: "postgres://test:test@localhost:5432/postgres?sslmode=disable" TEST_DATABASE_MYSQL: "mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true" TEST_DATABASE_COCKROACHDB: "cockroach://root@localhost:26257/defaultdb?sslmode=disable" strategy: fail-fast: false matrix: database: ["postgres", "sqlite"] # "cockroach", "mysql" TODO: fix tests and uncomment steps: - uses: actions/setup-node@v6 with: node-version: "24" - run: | docker create --name cockroach -p 26257:26257 \ cockroachdb/cockroach:latest-v25.4 start-single-node --insecure docker start cockroach name: Start CockroachDB - uses: browser-actions/setup-chrome@latest name: Install Chrome # - uses: browser-actions/setup-firefox@latest # name: Install Firefox # - uses: browser-actions/setup-geckodriver@latest # name: Install Geckodriver # with: # geckodriver-version: 0.32.0 - uses: ory/ci/checkout@master with: fetch-depth: 2 - run: | sudo apt-get update name: apt-get update - run: | npm ci cd test/e2e; npm ci npm i -g expo-cli name: Install node deps - run: | sudo apt-get install -y moreutils gettext name: Install tools - name: Setup Go uses: actions/setup-go@v6 with: check-latest: true go-version-file: go.mod - name: Install selfservice-ui-react-native uses: actions/checkout@v6 with: repository: ory/kratos-selfservice-ui-react-native path: react-native-ui - run: | cd react-native-ui npm install - name: Install selfservice-ui-node uses: actions/checkout@v6 with: repository: ory/kratos-selfservice-ui-node path: node-ui - run: | cd node-ui npm install --legacy-peer-deps - name: Install selfservice-ui-react-nextjs uses: actions/checkout@v6 with: repository: ory/kratos-selfservice-ui-react-nextjs path: react-ui - run: | cd react-ui npm ci - run: | echo 'RN_UI_PATH='"$(realpath react-native-ui)" >> "$GITHUB_ENV" echo 'NODE_UI_PATH='"$(realpath node-ui)" >> "$GITHUB_ENV" echo 'REACT_UI_PATH='"$(realpath react-ui)" >> "$GITHUB_ENV" - name: "Run Cypress tests" run: ./test/e2e/run.sh ${{ matrix.database }} env: RN_UI_PATH: react-native-ui NODE_UI_PATH: node-ui REACT_UI_PATH: react-ui CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - if: failure() uses: actions/upload-artifact@v7 with: name: cypress-${{ matrix.database }}-logs path: test/e2e/*.e2e.log test-e2e-playwright: name: Run Playwright end-to-end tests runs-on: ubuntu-latest services: postgres: image: postgres:18 env: POSTGRES_DB: postgres POSTGRES_PASSWORD: test POSTGRES_USER: test ports: - 5432:5432 mysql: image: mysql:9.6 env: MYSQL_ROOT_PASSWORD: test ports: - 3306:3306 mailslurper: image: oryd/mailslurper:latest-smtps ports: - 4436:4436 - 4437:4437 - 1025:1025 env: TEST_DATABASE_POSTGRESQL: "postgres://test:test@localhost:5432/postgres?sslmode=disable" TEST_DATABASE_MYSQL: "mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true" TEST_DATABASE_COCKROACHDB: "cockroach://root@localhost:26257/defaultdb?sslmode=disable" strategy: fail-fast: false matrix: database: ["postgres", "cockroach", "sqlite", "mysql"] steps: - uses: actions/setup-node@v6 with: node-version: "24" - run: | docker create --name cockroach -p 26257:26257 \ cockroachdb/cockroach:latest-v25.4 start-single-node --insecure docker start cockroach name: Start CockroachDB - uses: ory/ci/checkout@master with: fetch-depth: 2 - run: | sudo apt-get update name: apt-get update - run: | npm ci cd test/e2e; npm ci npx playwright install --with-deps npm i -g expo-cli name: Install node deps - run: | sudo apt-get install -y moreutils gettext name: Install tools - name: Setup Go uses: actions/setup-go@v6 with: check-latest: true go-version-file: go.mod - run: go build -tags sqlite,json1 . - name: Install selfservice-ui-react-native uses: actions/checkout@v6 with: repository: ory/kratos-selfservice-ui-react-native path: react-native-ui - run: | cd react-native-ui npm install - name: Install selfservice-ui-node uses: actions/checkout@v6 with: repository: ory/kratos-selfservice-ui-node path: node-ui - run: | cd node-ui npm install --legacy-peer-deps - name: Install selfservice-ui-react-nextjs uses: actions/checkout@v6 with: repository: ory/kratos-selfservice-ui-react-nextjs path: react-ui - run: | cd react-ui npm ci - run: | echo 'RN_UI_PATH='"$(realpath react-native-ui)" >> "$GITHUB_ENV" echo 'NODE_UI_PATH='"$(realpath node-ui)" >> "$GITHUB_ENV" echo 'REACT_UI_PATH='"$(realpath react-ui)" >> "$GITHUB_ENV" - name: "Set up environment" run: test/e2e/run.sh --only-setup - name: "Run Playwright tests" run: | cd test/e2e npm run playwright env: DB: ${{ matrix.database }} RN_UI_PATH: react-native-ui NODE_UI_PATH: node-ui REACT_UI_PATH: react-ui - if: failure() uses: actions/upload-artifact@v7 with: name: playwright-${{ matrix.database }}-logs path: test/e2e/*.e2e.log - if: failure() uses: actions/upload-artifact@v7 with: name: playwright-test-results-${{ matrix.database }}-${{ github.sha }} path: | test/e2e/test-results/ test/e2e/playwright-report/ docs-cli: runs-on: ubuntu-latest name: Build CLI docs needs: - test steps: - uses: ory/ci/docs/cli-next@master with: token: ${{ secrets.ORY_BOT_PAT }} arg: "." output-dir: docs/kratos release: name: Generate release runs-on: ubuntu-latest if: ${{ github.ref_type == 'tag' }} needs: - test - test-e2e steps: - uses: ory/ci/releaser@master with: token: ${{ secrets.ORY_BOT_PAT }} goreleaser_key: ${{ secrets.GORELEASER_KEY }} cosign_pwd: ${{ secrets.COSIGN_PWD }} docker_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_password: ${{ secrets.DOCKERHUB_PASSWORD }} newsletter-draft: name: Draft newsletter runs-on: ubuntu-latest if: ${{ github.ref_type == 'tag' }} needs: - release steps: - uses: ory/ci/newsletter@master with: mailchimp_list_id: f605a41b53 mailchmip_segment_id: 6479477 mailchimp_api_key: ${{ secrets.MAILCHIMP_API_KEY }} draft: "true" ssh_key: ${{ secrets.ORY_BOT_SSH_KEY }} slack-approval-notification: name: Pending approval Slack notification runs-on: ubuntu-latest if: ${{ github.ref_type == 'tag' }} needs: - newsletter-draft steps: - uses: ory/ci/newsletter/slack-notify@master with: slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} newsletter-send: name: Send newsletter runs-on: ubuntu-latest needs: - newsletter-draft if: ${{ github.ref_type == 'tag' }} environment: production steps: - uses: ory/ci/newsletter@master with: mailchimp_list_id: f605a41b53 mailchmip_segment_id: 6479477 mailchimp_api_key: ${{ secrets.MAILCHIMP_API_KEY }} draft: "false" ssh_key: ${{ secrets.ORY_BOT_SSH_KEY }} ================================================ FILE: .github/workflows/closed_references.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/closed_references.yml name: Closed Reference Notifier on: schedule: - cron: "0 0 * * *" workflow_dispatch: inputs: issueLimit: description: Max. number of issues to create required: true default: "5" jobs: find_closed_references: if: github.repository_owner == 'ory' runs-on: ubuntu-latest name: Find closed references steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: "24" - uses: ory/closed-reference-notifier@v1 with: token: ${{ secrets.GITHUB_TOKEN }} issueLabels: upstream,good first issue,help wanted issueLimit: ${{ github.event.inputs.issueLimit || '5' }} ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: "26 21 * * 3" jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: ["go", "javascript"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/conventional_commits.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/conventional_commits.yml name: Conventional commits # This GitHub CI Action enforces that pull request titles follow conventional commits. # More info at https://www.conventionalcommits.org. # # The Ory-wide defaults for commit titles and scopes are below. # Your repository can add/replace elements via a configuration file at the path below. # More info at https://github.com/ory/ci/blob/master/conventional_commit_config/README.md on: pull_request_target: types: - edited - opened - ready_for_review - reopened # pull_request: # for debugging, uses config in local branch but supports only Pull Requests from this repo jobs: main: name: Validate PR title runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - id: config uses: ory/ci/conventional_commit_config@master with: config_path: .github/conventional_commits.json default_types: | feat fix revert docs style refactor test build autogen security ci chore default_scopes: | deps docs default_require_scope: false - uses: amannn/action-semantic-pull-request@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: types: ${{ steps.config.outputs.types }} scopes: ${{ steps.config.outputs.scopes }} requireScope: ${{ steps.config.outputs.requireScope }} subjectPattern: ^(?![A-Z]).+$ subjectPatternError: | The subject should start with a lowercase letter, yours is uppercase: "{subject}" ================================================ FILE: .github/workflows/cve-scan.yaml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/server/.github/workflows/cve-scan.yaml name: Docker Image Scanners on: workflow_dispatch: push: branches: - "master" tags: - "v*.*.*" pull_request: branches: - "master" permissions: contents: read security-events: write jobs: scanners: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Env id: vars shell: bash run: | # Store values in local variables SHA_SHORT=$(git rev-parse --short HEAD) REPO_NAME=${{ github.event.repository.name }} IMAGE_NAME="oryd/${REPO_NAME}:${SHA_SHORT}" # Output values for debugging echo "Values to be set:" echo "SHA_SHORT: ${SHA_SHORT}" echo "REPO_NAME: ${REPO_NAME}" echo "IMAGE_NAME: ${IMAGE_NAME}" # Set GitHub Environment variables echo "SHA_SHORT=${SHA_SHORT}" >> "${GITHUB_ENV}" echo "IMAGE_NAME=${IMAGE_NAME}" >> "${GITHUB_ENV}" - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 with: driver: docker - name: Build images shell: bash run: | IMAGE_TAG="${{ env.SHA_SHORT }}" make docker - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Configure Trivy run: | mkdir -p "$HOME/.cache/trivy" echo "TRIVY_USERNAME=${{ github.actor }}" >> "$GITHUB_ENV" echo "TRIVY_PASSWORD=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_ENV" - name: Anchore Scanner uses: anchore/scan-action@v7 id: grype-scan with: image: ${{ env.IMAGE_NAME }} fail-build: true severity-cutoff: high add-cpes-if-none: true - name: Inspect action SARIF report shell: bash if: ${{ always() }} run: | echo "::group::Anchore Scan Details" jq '.runs[0].results' ${{ steps.grype-scan.outputs.sarif }} echo "::endgroup::" - name: Anchore upload scan SARIF report if: always() uses: github/codeql-action/upload-sarif@v4 with: sarif_file: ${{ steps.grype-scan.outputs.sarif }} - name: Kubescape scanner uses: kubescape/github-action@main id: kubescape with: image: ${{ env.IMAGE_NAME }} verbose: true format: pretty-printer # can't whitelist CVE yet: https://github.com/kubescape/kubescape/pull/1568 severityThreshold: critical - name: Trivy Scanner uses: aquasecurity/trivy-action@master if: ${{ always() }} with: image-ref: ${{ env.IMAGE_NAME }} format: "table" exit-code: "42" ignore-unfixed: true vuln-type: "os,library" severity: "CRITICAL,HIGH" scanners: "vuln,secret,misconfig" env: TRIVY_SKIP_JAVA_DB_UPDATE: "true" TRIVY_DISABLE_VEX_NOTICE: "true" TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db - name: Dockle Linter uses: erzz/dockle-action@v1 if: ${{ always() }} with: image: ${{ env.IMAGE_NAME }} exit-code: 42 failure-threshold: high - name: Hadolint uses: hadolint/hadolint-action@v3.3.0 id: hadolint if: ${{ always() }} with: dockerfile: .docker/Dockerfile-build verbose: true format: "json" failure-threshold: "error" - name: View Hadolint results if: ${{ always() }} shell: bash run: | echo "::group::Hadolint Scan Details" echo "${HADOLINT_RESULTS}" | jq '.' echo "::endgroup::" ================================================ FILE: .github/workflows/format.yml ================================================ name: Format on: pull_request: merge_group: jobs: format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: check-latest: true go-version-file: go.mod - run: make format - name: Indicate formatting issues run: git diff HEAD --exit-code --color ================================================ FILE: .github/workflows/labels.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/labels.yml name: Synchronize Issue Labels on: workflow_dispatch: push: branches: - master jobs: milestone: if: github.repository_owner == 'ory' name: Synchronize Issue Labels runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Synchronize Issue Labels uses: ory/label-sync-action@v0 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} dry: false forced: true ================================================ FILE: .github/workflows/milestone.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/server/.github/workflows/milestone.yml name: Generate and Publish Milestone Document on: workflow_dispatch: schedule: - cron: "0 0 * * *" jobs: milestone: if: github.repository_owner == 'ory' name: Generate and Publish Milestone Document runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 with: token: ${{ secrets.TOKEN_PRIVILEGED }} - name: Milestone Documentation Generator uses: ory/milestone-action@v0 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} outputFile: docs/docs/milestones.md - name: Commit Milestone Documentation uses: EndBug/add-and-commit@v9.1.4 with: message: "autogen(docs): update milestone document" author_name: aeneasr author_email: "3372410+aeneasr@users.noreply.github.com" env: GITHUB_TOKEN: ${{ secrets.TOKEN_PRIVILEGED }} ================================================ FILE: .github/workflows/pm.yml ================================================ name: Synchronize with product board on: issues: types: - opened pull_request: types: - opened - ready_for_review jobs: automate: if: github.event.pull_request.head.repo.fork == false name: Add issue to project runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: ory-corp/planning-automation-action@v0.2 with: project: 5 organization: ory-corp token: ${{ secrets.ORY_BOT_PAT }} todoLabel: "Needs Triage" statusName: Status prStatusValue: "Needs Triage" issueStatusValue: "Needs Triage" includeEffort: "false" monthlyMilestoneName: Roadmap Monthly quarterlyMilestoneName: Roadmap ================================================ FILE: .github/workflows/stale.yml ================================================ # AUTO-GENERATED, DO NOT EDIT! # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/stale.yml name: "Close Stale Issues" on: workflow_dispatch: schedule: - cron: "0 0 * * *" jobs: stale: if: github.repository_owner == 'ory' runs-on: ubuntu-latest steps: - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | Hello contributors! I am marking this issue as stale as it has not received any engagement from the community or maintainers for a year. That does not imply that the issue has no merit! If you feel strongly about this issue - open a PR referencing and resolving the issue; - leave a comment on it and discuss ideas on how you could contribute towards resolving it; - leave a comment and describe in detail why this issue is critical for your use case; - open a new issue with updated details and a plan for resolving the issue. Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic. Unfortunately, [burnout](https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes) has become a [topic](https://opensource.guide/best-practices/#its-okay-to-hit-pause) of [concern](https://docs.brew.sh/Maintainers-Avoiding-Burnout) amongst open-source projects. It can lead to severe personal and health issues as well as [opening](https://haacked.com/archive/2019/05/28/maintainer-burnout/) catastrophic [attack vectors](https://www.gradiant.org/en/blog/open-source-maintainer-burnout-as-an-attack-surface/). The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone. If this issue was marked as stale erroneously you can exempt it by adding the `backlog` label, assigning someone, or setting a milestone for it. Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you! Thank you 🙏✌️ stale-issue-label: "stale" exempt-issue-labels: "bug,blocking,docs,backlog" days-before-stale: 365 days-before-close: 30 exempt-milestones: true exempt-assignees: true only-pr-labels: "stale" ================================================ FILE: .gitignore ================================================ cover.out .idea/ tmp/ ddl/ .DS_Store /kratos packrd/ *-packr.go dist/ node_modules .bin/ test/e2e/cypress/videos test/e2e/cypress/screenshots test/e2e/.bin pkged.go coverage.* schema.sql *.sqlite heap_profiler/ goroutine_dump/ inflight_trace_dump/ contrib/quickstart/kratos/oidc e2e/*.log e2e/kratos.*.yml e2e/proxy.json e2e/cypress/downloads # Compiled Object files, Static and Dynamic libs (Shared Objects) internal/httpclient/*.o internal/httpclient/*.a internal/httpclient/*.so # Folders internal/httpclient/_obj internal/httpclient/_test # Architecture specific extensions/prefixes internal/httpclient/*.[568vq] internal/httpclient/[568vq].out internal/httpclient/*.cgo1.go internal/httpclient/*.cgo2.c internal/httpclient/_cgo_defun.c internal/httpclient/_cgo_gotypes.go internal/httpclient/_cgo_export.* internal/httpclient/_testmain.go internal/httpclient/*.exe internal/httpclient/*.test internal/httpclient/*.prof test/e2e/hydra-login-consent/hydra-login-consent persistence/sql/migrations/sql/schema.sql test/e2e/hydra-kratos-login-consent/hydra-kratos-login-consent *.log test/e2e/proxy.json test/e2e/kratos.*.yml # VSCode debug artifact __debug_bin .debug.sqlite.db .last-run.json ================================================ FILE: .golangci.yml ================================================ version: "2" linters: enable: - gosec - govet - errcheck - ineffassign - staticcheck - unused disable: - bodyclose # too many false negatives settings: gosec: excludes: - G101 - G117 - G306 - G704 - G705 exclusions: rules: - linters: - staticcheck text: "SA1019" # we do use deprecated APIs on purpose sometimes ================================================ FILE: .goreleaser.yml ================================================ version: 2 includes: - from_url: url: https://raw.githubusercontent.com/ory/xgoreleaser/master/build.tmpl.yml variables: brew_name: kratos brew_description: "The Ory Identity Platform (Ory Kratos)" buildinfo_hash: "github.com/ory/kratos/driver/config.Commit" buildinfo_tag: "github.com/ory/kratos/driver/config.Version" buildinfo_date: "github.com/ory/kratos/driver/config.Date" dockerfile_alpine: ".docker/Dockerfile-alpine" dockerfile_static: ".docker/Dockerfile-distroless-static" project_name: kratos after: hooks: - cmd: "bash <(curl -s https://raw.githubusercontent.com/ory/xgoreleaser/master/docs.sh)" env: - "TAG_VERSION={{ .Tag }}" - "DOCS_VERSION={{ .Major }}.{{ .Minor }}" ================================================ FILE: .grype.yaml ================================================ #only-fixed: true ignore: - vulnerability: GHSA-c5pj-mqfh-rvc3 # https://github.com/advisories/GHSA-c5pj-mqfh-rvc3 - vulnerability: CVE-2015-5237 - vulnerability: CVE-2022-30065 - vulnerability: CVE-2023-2650 - vulnerability: CVE-2023-4813 - vulnerability: CVE-2023-4806 - vulnerability: CVE-2025-0395 # no fix available ================================================ FILE: .mailmap ================================================ Aeneas Rekkas Aeneas Rekkas <3372410+aeneasr@users.noreply.github.com> Aeneas Rekkas Aeneas Rekkas Ajay Kelkar Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Alano Terblanche Jonas Hungershausen Matt Bonnell <64976795+mbonnell-wish@users.noreply.github.com> Nick Ufer Nick Ufer Patrik Neu Patrik Neu ================================================ FILE: .nancy-ignore ================================================ # HashiCorp Consul related CVEs # Consul is not used but instead an indirect dependency to the pf13/viper config backend which is not actively used. CVE-2020-7219 CVE-2019-12291 CVE-2020-7955 CVE-2020-12797 CVE-2020-13250 CVE-2020-13170 # End HashiCorp Consul # etcd issues - can be ignored because etcd is not used. CVE-2020-15114 CVE-2020-15136 CVE-2020-15115 # end ================================================ FILE: .nvmrc ================================================ v16.19.0 ================================================ FILE: .orycli.yml ================================================ project: kratos pre_release_hooks: - make sdk - ./script/render-schemas.sh - git config --unset user.email - git config --unset user.name ================================================ FILE: .prettierignore ================================================ .schema/ .github/ISSUE_TEMPLATE oryx ================================================ FILE: .reference-ignore ================================================ **/node_modules docs CHANGELOG.md ================================================ FILE: .reports/dep-licenses.csv ================================================ "module name","licenses" "github.com/arbovm/levenshtein","BSD-3-Clause" "github.com/ory/x","Apache-2.0" "github.com/stretchr/testify","MIT" "go.opentelemetry.io/otel/sdk","Apache-2.0" "golang.org/x/text","BSD-3-Clause" ================================================ FILE: .schema/api.openapi.json ================================================ { "components": { "responses": { "emptyResponse": { "description": "Empty responses are sent when, for example, resources are deleted. The HTTP status code for empty responses is\ntypically 201." }, "errorContainer": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/errorContainer" } } }, "description": "User-facing error response" }, "identityList": { "content": { "application/json": { "schema": { "items": { "$ref": "#/components/schemas/Identity" }, "type": "array" } } }, "description": "A list of identities." }, "identityResponse": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Identity" } } }, "description": "A single identity." } }, "schemas": { "CompleteSelfServiceBrowserSettingsProfileStrategyFlow": { "description": "nolint:deadcode,unused", "properties": { "csrf_token": { "description": "The Anti-CSRF Token\n\nThis token is only required when performing browser flows.", "type": "string" }, "traits": { "description": "Traits contains all of the identity's traits.", "type": "object" } }, "type": "object" }, "CompleteSelfServiceLoginFlowWithPasswordMethod": { "properties": { "csrf_token": { "description": "Sending the anti-csrf token is only required for browser login flows.", "type": "string" }, "identifier": { "description": "Identifier is the email or username of the user trying to log in.", "type": "string" }, "password": { "description": "The user's password.", "type": "string" } }, "type": "object" }, "CompleteSelfServiceSettingsFlowWithPasswordMethod": { "properties": { "csrf_token": { "description": "CSRFToken is the anti-CSRF token\n\ntype: string", "type": "string" }, "password": { "description": "Password is the updated password\n\ntype: string", "type": "string" } }, "required": [ "password" ], "type": "object" }, "CreateIdentity": { "properties": { "schema_id": { "description": "SchemaID is the ID of the JSON Schema to be used for validating the identity's traits.", "type": "string" }, "traits": { "description": "Traits represent an identity's traits. The identity is able to create, modify, and delete traits\nin a self-service manner. The input will always be validated against the JSON Schema defined\nin `schema_url`.", "type": "object" } }, "required": [ "schema_id", "traits" ], "type": "object" }, "CreateRecoveryLink": { "properties": { "expires_in": { "description": "Link Expires In\n\nThe recovery link will expire at that point in time. Defaults to the configuration value of\n`selfservice.flows.recovery.request_lifespan`.", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "type": "string" }, "identity_id": { "$ref": "#/components/schemas/UUID" } }, "required": [ "identity_id" ], "type": "object" }, "CredentialsType": { "description": "and so on.", "title": "CredentialsType represents several different credential types, like password credentials, passwordless credentials,", "type": "string" }, "ID": { "format": "int64", "type": "integer" }, "Identity": { "properties": { "id": { "$ref": "#/components/schemas/UUID" }, "recovery_addresses": { "description": "RecoveryAddresses contains all the addresses that can be used to recover an identity.", "items": { "$ref": "#/components/schemas/RecoveryAddress" }, "type": "array", "x-omitempty": true }, "schema_id": { "description": "SchemaID is the ID of the JSON Schema to be used for validating the identity's traits.", "type": "string" }, "schema_url": { "description": "SchemaURL is the URL of the endpoint where the identity's traits schema can be fetched from.\n\nformat: url", "type": "string" }, "traits": { "$ref": "#/components/schemas/Traits" }, "verifiable_addresses": { "description": "VerifiableAddresses contains all the addresses that can be verified by the user.", "items": { "$ref": "#/components/schemas/VerifiableAddress" }, "type": "array", "x-omitempty": true } }, "required": [ "id", "schema_id", "schema_url", "traits" ], "type": "object" }, "NullTime": { "format": "date-time", "title": "NullTime implements sql.NullTime functionality.", "type": "string" }, "RecoveryAddress": { "properties": { "id": { "$ref": "#/components/schemas/UUID" }, "value": { "type": "string" }, "via": { "$ref": "#/components/schemas/RecoveryAddressType" } }, "required": [ "id", "value", "via" ], "type": "object" }, "RecoveryAddressType": { "type": "string" }, "State": { "type": "string" }, "Traits": { "type": "object" }, "Type": { "description": "The flow type can either be `api` or `browser`.", "title": "Type is the flow type.", "type": "string" }, "UUID": { "format": "uuid4", "type": "string" }, "UpdateIdentity": { "properties": { "schema_id": { "description": "SchemaID is the ID of the JSON Schema to be used for validating the identity's traits. If set\nwill update the Identity's SchemaID.", "type": "string" }, "traits": { "description": "Traits represent an identity's traits. The identity is able to create, modify, and delete traits\nin a self-service manner. The input will always be validated against the JSON Schema defined\nin `schema_id`.", "type": "object" } }, "required": [ "traits" ], "type": "object" }, "VerifiableAddress": { "properties": { "id": { "$ref": "#/components/schemas/UUID" }, "status": { "$ref": "#/components/schemas/VerifiableAddressStatus" }, "value": { "type": "string" }, "verified": { "type": "boolean" }, "verified_at": { "$ref": "#/components/schemas/NullTime" }, "via": { "$ref": "#/components/schemas/VerifiableAddressType" } }, "required": [ "id", "value", "verified", "via", "status" ], "type": "object" }, "VerifiableAddressStatus": { "type": "string" }, "VerifiableAddressType": { "type": "string" }, "completeSelfServiceBrowserSettingsOIDCFlowPayload": { "properties": { "flow": { "description": "Flow ID is the flow's ID.\n\nin: query", "type": "string" }, "link": { "description": "Link this provider\n\nEither this or `unlink` must be set.\n\ntype: string\nin: body", "type": "string" }, "unlink": { "description": "Unlink this provider\n\nEither this or `link` must be set.\n\ntype: string\nin: body", "type": "string" } }, "type": "object" }, "completeSelfServiceRecoveryFlowWithLinkMethod": { "properties": { "csrf_token": { "description": "Sending the anti-csrf token is only required for browser login flows.", "type": "string" }, "email": { "description": "Email to Recover\n\nNeeds to be set when initiating the flow. If the email is a registered\nrecovery email, a recovery link will be sent. If the email is not known,\na email with details on what happened will be sent instead.\n\nformat: email\nin: body", "type": "string" } }, "type": "object" }, "completeSelfServiceVerificationFlowWithLinkMethod": { "properties": { "csrf_token": { "description": "Sending the anti-csrf token is only required for browser login flows.", "type": "string" }, "email": { "description": "Email to Verify\n\nNeeds to be set when initiating the flow. If the email is a registered\nverification email, a verification link will be sent. If the email is not known,\na email with details on what happened will be sent instead.\n\nformat: email\nin: body", "type": "string" } }, "type": "object" }, "errorContainer": { "properties": { "errors": { "description": "Errors in the container", "items": { "type": "object" }, "type": "array" }, "id": { "$ref": "#/components/schemas/UUID" } }, "required": [ "id", "errors" ], "type": "object" }, "genericError": { "description": "Error responses are sent when an error (e.g. unauthorized, bad request, ...) occurred.", "properties": { "error": { "$ref": "#/components/schemas/genericErrorPayload" } }, "title": "Error response", "type": "object" }, "genericErrorPayload": { "properties": { "code": { "description": "Code represents the error status code (404, 403, 401, ...).", "example": 404, "format": "int64", "type": "integer" }, "debug": { "description": "Debug contains debug information. This is usually not available and has to be enabled.", "example": "The database adapter was unable to find the element", "type": "string" }, "details": { "additionalProperties": true, "type": "object" }, "message": { "type": "string" }, "reason": { "type": "string" }, "request": { "type": "string" }, "status": { "type": "string" } }, "type": "object" }, "healthNotReadyStatus": { "properties": { "errors": { "additionalProperties": { "type": "string" }, "description": "Errors contains a list of errors that caused the not ready status.", "type": "object" } }, "type": "object" }, "healthStatus": { "properties": { "status": { "description": "Status always contains \"ok\".", "type": "string" } }, "type": "object" }, "jsonSchema": { "description": "Raw JSON Schema", "type": "object" }, "loginFlow": { "description": "This object represents a login flow. A login flow is initiated at the \"Initiate Login API / Browser Flow\"\nendpoint by a client.\n\nOnce a login flow is completed successfully, a session cookie or session token will be issued.", "properties": { "active": { "$ref": "#/components/schemas/CredentialsType" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to log in,\na new flow has to be initiated.", "format": "date-time", "type": "string" }, "forced": { "description": "Forced stores whether this login flow should enforce re-authentication.", "type": "boolean" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the flow started.", "format": "date-time", "type": "string" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "type": { "$ref": "#/components/schemas/Type" }, "ui": { "$ref": "#/components/schemas/uiContainer" } }, "required": [ "id", "type", "expires_at", "issued_at", "request_url", "ui" ], "title": "Login Flow", "type": "object" }, "loginViaApiResponse": { "description": "The Response for Login Flows via API", "properties": { "session": { "$ref": "#/components/schemas/session" }, "session_token": { "description": "The Session Token\n\nA session token is equivalent to a session cookie, but it can be sent in the HTTP Authorization\nHeader:\n\nAuthorization: bearer ${session-token}\n\nThe session token is only issued for API flows, not for Browser flows!", "type": "string" } }, "required": [ "session_token", "session" ], "type": "object" }, "recoveryFlow": { "description": "This request is used when an identity wants to recover their account.\n\nWe recommend reading the [Account Recovery Documentation](../self-service/flows/password-reset-account-recovery)", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the request expires. If the user still wishes to update the setting,\na new request has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the request occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/recoveryFlowMethod" }, "description": "Methods contains context for all account recovery methods. If a registration request has been\nprocessed, but for example the password is incorrect, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "state": { "$ref": "#/components/schemas/State" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods", "state" ], "title": "A Recovery Flow", "type": "object" }, "recoveryFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/recoveryFlowMethodConfig" }, "method": { "description": "Method contains the request credentials type.", "type": "string" } }, "required": [ "method", "config" ], "type": "object" }, "recoveryFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "recoveryFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "recoveryLink": { "properties": { "expires_at": { "description": "Recovery Link Expires At\n\nThe timestamp when the recovery link expires.", "format": "date-time", "type": "string" }, "recovery_link": { "description": "Recovery Link\n\nThis link can be used to recover the account.", "type": "string" } }, "required": [ "recovery_link" ], "type": "object" }, "registrationFlow": { "properties": { "active": { "$ref": "#/components/schemas/CredentialsType" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to log in,\na new flow has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the flow occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/registrationFlowMethod" }, "description": "Methods contains context for all enabled registration methods. If a registration flow has been\nprocessed, but for example the password is incorrect, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods" ], "type": "object" }, "registrationFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/registrationFlowMethodConfig" }, "method": { "$ref": "#/components/schemas/CredentialsType" } }, "required": [ "method", "config" ], "type": "object" }, "registrationFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" }, "providers": { "description": "Providers is set for the \"oidc\" registration method.", "items": { "$ref": "#/components/schemas/uiNodes" }, "type": "array" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "registrationFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" }, "providers": { "description": "Providers is set for the \"oidc\" registration method.", "items": { "$ref": "#/components/schemas/uiNodes" }, "type": "array" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "registrationViaApiResponse": { "description": "The Response for Registration Flows via API", "properties": { "identity": { "$ref": "#/components/schemas/Identity" }, "session": { "$ref": "#/components/schemas/session" }, "session_token": { "description": "The Session Token\n\nThis field is only set when the session hook is configured as a post-registration hook.\n\nA session token is equivalent to a session cookie, but it can be sent in the HTTP Authorization\nHeader:\n\nAuthorization: bearer ${session-token}\n\nThe session token is only issued for API flows, not for Browser flows!", "type": "string" } }, "required": [ "session_token", "identity" ], "type": "object" }, "revokeSession": { "properties": { "session_token": { "description": "The Session Token\n\nInvalidate this session token.", "type": "string" } }, "required": [ "session_token" ], "type": "object" }, "session": { "properties": { "active": { "type": "boolean" }, "authenticated_at": { "format": "date-time", "type": "string" }, "expires_at": { "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "identity": { "$ref": "#/components/schemas/Identity" }, "issued_at": { "format": "date-time", "type": "string" } }, "required": [ "id", "expires_at", "authenticated_at", "issued_at", "identity" ], "type": "object" }, "settingsFlow": { "description": "This flow is used when an identity wants to update settings\n(e.g. profile data, passwords, ...) in a selfservice manner.\n\nWe recommend reading the [User Settings Documentation](../self-service/flows/user-settings)", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to update the setting,\na new flow has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "identity": { "$ref": "#/components/schemas/Identity" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the flow occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/settingsFlowMethod" }, "description": "Methods contains context for all enabled registration methods. If a settings flow has been\nprocessed, but for example the first name is empty, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "state": { "$ref": "#/components/schemas/State" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods", "identity", "state" ], "title": "Flow represents a Settings Flow", "type": "object" }, "settingsFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/settingsFlowMethodConfig" }, "method": { "description": "Method is the name of this flow method.", "type": "string" } }, "required": [ "method", "config" ], "type": "object" }, "settingsFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "settingsFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "settingsProfileFormConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "settingsViaApiResponse": { "description": "The Response for Settings Flows via API", "properties": { "flow": { "$ref": "#/components/schemas/settingsFlow" }, "identity": { "$ref": "#/components/schemas/Identity" } }, "required": [ "flow", "identity" ], "type": "object" }, "uiContainer": { "description": "Container represents a HTML Form. The container can work with both HTTP Form and JSON requests", "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "uiNode": { "description": "Nodes are represented as HTML elements or their native UI equivalents. For example,\na node can be an `\u003cimg\u003e` tag, or an `\u003cinput element\u003e` but also `some plain text`.", "properties": { "attributes": { "$ref": "#/components/schemas/uiNodeAttributes" }, "group": { "$ref": "#/components/schemas/uiNodeGroup" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "type": { "$ref": "#/components/schemas/uiNodeType" } }, "required": [ "type", "group", "attributes", "messages" ], "title": "Node represents a flow's nodes", "type": "object" }, "uiNodeAnchorAttributes": { "properties": { "href": { "description": "The link's href (destination) URL.\n\nformat: uri", "type": "string" }, "title": { "$ref": "#/components/schemas/uiText" } }, "required": [ "href", "title" ], "title": "AnchorAttributes represents the attributes of an anchor node.", "type": "object" }, "uiNodeAttributes": { "oneOf": [ { "$ref": "#/components/schemas/uiNodeInputAttributes" }, { "$ref": "#/components/schemas/uiNodeTextAttributes" }, { "$ref": "#/components/schemas/uiNodeImageAttributes" }, { "$ref": "#/components/schemas/uiNodeAnchorAttributes" } ], "title": "Attributes represents a list of attributes (e.g. `href=\"foo\"` for links)." }, "uiNodeGroup": { "type": "string" }, "uiNodeImageAttributes": { "properties": { "src": { "description": "The image's source URL.\n\nformat: uri", "type": "string" } }, "required": [ "src" ], "title": "ImageAttributes represents the attributes of an image node.", "type": "object" }, "uiNodeInputAttributeType": { "type": "string" }, "uiNodeInputAttributes": { "description": "InputAttributes represents the attributes of an input node", "properties": { "disabled": { "description": "Sets the input's disabled field to true or false.", "type": "boolean" }, "label": { "$ref": "#/components/schemas/uiText" }, "name": { "description": "The input's element name.", "type": "string" }, "pattern": { "description": "The input's pattern.", "type": "string" }, "required": { "description": "Mark this input field as required.", "type": "boolean" }, "type": { "$ref": "#/components/schemas/uiNodeInputAttributeType" }, "value": { "$ref": "#/components/schemas/uiNodeInputAttributesValue", "description": "The input's value.", "nullable": true } }, "required": [ "name", "type", "disabled" ], "type": "object" }, "uiNodeInputAttributesValue": { "oneOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" } ] }, "uiNodeTextAttributes": { "properties": { "text": { "$ref": "#/components/schemas/uiText" } }, "required": [ "text" ], "title": "TextAttributes represents the attributes of a text node.", "type": "object" }, "uiNodeType": { "type": "string" }, "uiNodes": { "items": { "$ref": "#/components/schemas/uiNode" }, "type": "array" }, "uiText": { "properties": { "context": { "description": "The message's context. Useful when customizing messages.", "type": "object" }, "id": { "$ref": "#/components/schemas/ID" }, "text": { "description": "The message text. Written in american english.", "type": "string" }, "type": { "$ref": "#/components/schemas/uiTextType" } }, "required": [ "id", "text", "type" ], "type": "object" }, "uiTextType": { "type": "string" }, "uiTexts": { "items": { "$ref": "#/components/schemas/uiText" }, "type": "array" }, "verificationFlow": { "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the request expires. If the user still wishes to verify the address,\na new request has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the request occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/verificationFlowMethod" }, "description": "Methods contains context for all account verification methods. If a registration request has been\nprocessed, but for example the password is incorrect, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "state": { "$ref": "#/components/schemas/State" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "type", "methods", "state" ], "title": "A Verification Flow", "type": "object" }, "verificationFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/verificationFlowMethodConfig" }, "method": { "description": "Method contains the request credentials type.", "type": "string" } }, "required": [ "method", "config" ], "type": "object" }, "verificationFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "verificationFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "version": { "properties": { "version": { "description": "Version is the service's version.", "type": "string" } }, "type": "object" } }, "securitySchemes": { "sessionCookie": { "in": "cookie", "name": "ory_kratos_session", "type": "apiKey" }, "sessionToken": { "in": "header", "name": "X-Session-Token", "type": "apiKey" } } }, "info": { "contact": { "email": "hi@ory.sh" }, "description": "Documentation for all public and administrative Ory Kratos APIs. Public and administrative APIs\nare exposed on different ports. Public APIs can face the public internet without any protection\nwhile administrative APIs should never be exposed without prior authorization. To protect\nthe administative API port you should use something like Nginx, Ory Oathkeeper, or any other\ntechnology capable of authorizing incoming requests.\n", "license": { "name": "Apache 2.0" }, "title": "Ory Kratos API", "version": "" }, "openapi": "3.0.3", "paths": { "/health/alive": { "get": { "description": "This endpoint returns a HTTP 200 status code when Ory Kratos is accepting incoming\nHTTP requests. This status does currently not include checks whether the database connection is working.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the health status will never\nrefer to the cluster state, only to a single instance.", "operationId": "isAlive", "responses": { "200": { "content": { "application/json": { "schema": { "properties": { "status": { "description": "Always \"ok\".", "type": "string" } }, "required": [ "status" ], "type": "object" } } }, "description": "Ory Kratos is ready to accept connections." }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Check HTTP Server Status", "tags": [ "admin" ] } }, "/health/ready": { "get": { "description": "This endpoint returns a HTTP 200 status code when Ory Kratos is up running and the environment dependencies (e.g.\nthe database) are responsive as well.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of Ory Kratos, the health status will never\nrefer to the cluster state, only to a single instance.", "operationId": "isReady", "responses": { "200": { "content": { "application/json": { "schema": { "properties": { "status": { "description": "Always \"ok\".", "type": "string" } }, "required": [ "status" ], "type": "object" } } }, "description": "Ory Kratos is ready to accept requests." }, "503": { "content": { "application/json": { "schema": { "properties": { "errors": { "additionalProperties": { "type": "string" }, "description": "Errors contains a list of errors that caused the not ready status.", "type": "object" } }, "required": [ "errors" ], "type": "object" } } }, "description": "Ory Kratos is not yet ready to accept requests." } }, "summary": "Check HTTP Server and Database Status", "tags": [ "admin" ] } }, "/identities": { "get": { "description": "Lists all identities. Does not support search at the moment.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "listIdentities", "parameters": [ { "description": "Items per Page\n\nThis is the number of items per page.", "in": "query", "name": "per_page", "schema": { "default": 100, "format": "int64", "maximum": 500, "minimum": 1, "type": "integer" } }, { "description": "Pagination Page", "in": "query", "name": "page", "schema": { "default": 0, "format": "int64", "minimum": 0, "type": "integer" } } ], "responses": { "200": { "$ref": "#/components/responses/identityList" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "List Identities", "tags": [ "admin" ] }, "post": { "description": "This endpoint creates an identity. It is NOT possible to set an identity's credentials (password, ...)\nusing this method! A way to achieve that will be introduced in the future.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "createIdentity", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateIdentity" } } }, "x-originalParamName": "Body" }, "responses": { "201": { "$ref": "#/components/responses/identityResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "409": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Create an Identity", "tags": [ "admin" ] } }, "/identities/{id}": { "delete": { "description": "Calling this endpoint irrecoverably and permanently deletes the identity given its ID. This action can not be undone.\nThis endpoint returns 204 when the identity was deleted or when the identity was not found, in which case it is\nassumed that is has been deleted already.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "deleteIdentity", "parameters": [ { "description": "ID is the identity's ID.", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "$ref": "#/components/responses/emptyResponse" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Delete an Identity", "tags": [ "admin" ] }, "get": { "description": "Learn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "getIdentity", "parameters": [ { "description": "ID must be set to the ID of identity you want to get", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "$ref": "#/components/responses/identityResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get an Identity", "tags": [ "admin" ] }, "put": { "description": "This endpoint updates an identity. It is NOT possible to set an identity's credentials (password, ...)\nusing this method! A way to achieve that will be introduced in the future.\n\nThe full identity payload (except credentials) is expected. This endpoint does not support patching.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "updateIdentity", "parameters": [ { "description": "ID must be set to the ID of identity you want to update", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateIdentity" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "$ref": "#/components/responses/identityResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Update an Identity", "tags": [ "admin" ] } }, "/metrics/prometheus": { "get": { "description": "```\nmetadata:\nannotations:\nprometheus.io/port: \"4434\"\nprometheus.io/path: \"/metrics/prometheus\"\n```", "operationId": "prometheus", "responses": { "200": { "$ref": "#/components/responses/emptyResponse" } }, "summary": "Get snapshot metrics from the Kratos service. If you're using k8s, you can then add annotations to\nyour deployment like so:", "tags": [ "admin" ] } }, "/recovery/link": { "post": { "description": "This endpoint creates a recovery link which should be given to the user in order for them to recover\n(or activate) their account.", "operationId": "createRecoveryLink", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateRecoveryLink" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryLink" } } }, "description": "recoveryLink" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Create a Recovery Link", "tags": [ "admin" ] } }, "/schemas/{id}": { "get": { "description": "Get a Traits Schema Definition", "operationId": "getSchema", "parameters": [ { "description": "ID must be set to the ID of schema you want to get", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/jsonSchema" } } }, "description": "jsonSchema" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "tags": [ "public", "admin" ] } }, "/self-service/browser/flows/logout": { "get": { "description": "This endpoint initializes a logout flow.\n\n\u003e This endpoint is NOT INTENDED for API clients and only works\nwith browsers (Chrome, Firefox, ...).\n\nOn successful logout, the browser will be redirected (HTTP 302 Found) to the `return_to` parameter of the initial request\nor fall back to `urls.default_return_to`.\n\nMore information can be found at [Ory Kratos User Logout Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-logout).", "operationId": "initializeSelfServiceBrowserLogoutFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Browser-Based Logout User Flow", "tags": [ "public" ] } }, "/self-service/browser/flows/registration/strategies/oidc/settings/connections": { "post": { "description": "This endpoint completes a browser-based settings flow. This is usually achieved by POSTing data to this\nendpoint.\n\n\u003e This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...) and HTML Forms.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "completeSelfServiceBrowserSettingsOIDCSettingsFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete the Browser-Based Settings Flow for the OpenID Connect Strategy", "tags": [ "public" ] } }, "/self-service/errors": { "get": { "description": "This endpoint returns the error associated with a user-facing self service errors.\n\nThis endpoint supports stub values to help you implement the error UI:\n\n`?error=stub:500` - returns a stub 500 (Internal Server Error) error.\n\nMore information can be found at [Ory Kratos User User Facing Error Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-facing-errors).", "operationId": "getSelfServiceError", "parameters": [ { "description": "Error is the container's ID", "in": "query", "name": "error", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "$ref": "#/components/responses/errorContainer" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get User-Facing Self-Service Errors", "tags": [ "public", "admin" ] } }, "/self-service/login/api": { "get": { "description": "This endpoint initiates a login flow for API clients such as mobile devices, smart TVs, and so on.\n\nIf a valid provided session cookie or session token is provided, a 400 Bad Request error\nwill be returned unless the URL query parameter `?refresh=true` is set.\n\nTo fetch an existing login flow call `/self-service/login/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks, including CSRF login attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceLoginViaAPIFlow", "parameters": [ { "description": "Refresh a login session\n\nIf set to true, this will refresh an existing login session by\nasking the user to sign in again. This will reset the\nauthenticated_at time of the session.", "in": "query", "name": "refresh", "schema": { "type": "boolean" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginFlow" } } }, "description": "loginFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Login Flow for API clients", "tags": [ "public" ] } }, "/self-service/login/browser": { "get": { "description": "This endpoint initializes a browser-based user login flow. Once initialized, the browser will be redirected to\n`selfservice.flows.login.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session\nexists already, the browser will be redirected to `urls.default_redirect_url` unless the query parameter\n`?refresh=true` was set.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceLoginViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Login Flow for browsers", "tags": [ "public" ] } }, "/self-service/login/flows": { "get": { "description": "This endpoint returns a login flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "getSelfServiceLoginFlow", "parameters": [ { "description": "The Login Flow ID\n\nThe value for this parameter comes from `flow` URL Query parameter sent to your\napplication (e.g. `/login?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginFlow" } } }, "description": "loginFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get Login Flow", "tags": [ "public", "admin" ] } }, "/self-service/login/methods/password": { "post": { "description": "Use this endpoint to complete a login flow by sending an identity's identifier and password. This endpoint\nbehaves differently for API and browser flows.\n\nAPI flows expect `application/json` to be sent in the body and responds with\nHTTP 200 and a application/json body with the session token on success;\nHTTP 302 redirect to a fresh login flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after login URL or the `return_to` value if it was set and if the login succeeded;\na HTTP 302 redirect to the login UI URL with the flow ID containing the validation errors otherwise.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "completeSelfServiceLoginFlowWithPasswordMethod", "parameters": [ { "description": "The Flow ID", "in": "query", "name": "flow", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceLoginFlowWithPasswordMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceLoginFlowWithPasswordMethod" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginViaApiResponse" } } }, "description": "loginViaApiResponse" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginFlow" } } }, "description": "loginFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Login Flow with Username/Email Password Method", "tags": [ "public" ] } }, "/self-service/recovery/api": { "get": { "description": "This endpoint initiates a recovery flow for API clients such as mobile devices, smart TVs, and so on.\n\nIf a valid provided session cookie or session token is provided, a 400 Bad Request error.\n\nTo fetch an existing recovery flow call `/self-service/recovery/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "initializeSelfServiceRecoveryViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryFlow" } } }, "description": "recoveryFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Recovery Flow for API Clients", "tags": [ "public" ] } }, "/self-service/recovery/browser": { "get": { "description": "This endpoint initializes a browser-based account recovery flow. Once initialized, the browser will be redirected to\n`selfservice.flows.recovery.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session\nexists, the browser is returned to the configured return URL.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "initializeSelfServiceRecoveryViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Recovery Flow for Browser Clients", "tags": [ "public" ] } }, "/self-service/recovery/flows": { "get": { "description": "This endpoint returns a recovery flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "getSelfServiceRecoveryFlow", "parameters": [ { "description": "The Flow ID\n\nThe value for this parameter comes from `request` URL Query parameter sent to your\napplication (e.g. `/recovery?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryFlow" } } }, "description": "recoveryFlow" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get information about a recovery flow", "tags": [ "public", "admin" ] } }, "/self-service/recovery/methods/link": { "post": { "description": "Use this endpoint to complete a recovery flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh recovery flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Recovery UI URL with the Recovery Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another recovery email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a recovery link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Recover UI URL with\na new Recovery Flow ID which contains an error message that the recovery link was invalid.\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "completeSelfServiceRecoveryFlowWithLinkMethod", "parameters": [ { "description": "Recovery Token\n\nThe recovery token which completes the recovery request. If the token\nis invalid (e.g. expired) an error will be shown to the end-user.", "in": "query", "name": "token", "schema": { "type": "string" } }, { "description": "The Flow ID\n\nformat: uuid", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/completeSelfServiceRecoveryFlowWithLinkMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/completeSelfServiceRecoveryFlowWithLinkMethod" } } }, "x-originalParamName": "Body" }, "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryFlow" } } }, "description": "recoveryFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Recovery Flow with Link Method", "tags": [ "public" ] } }, "/self-service/registration/api": { "get": { "description": "This endpoint initiates a registration flow for API clients such as mobile devices, smart TVs, and so on.\n\nIf a valid provided session cookie or session token is provided, a 400 Bad Request error\nwill be returned unless the URL query parameter `?refresh=true` is set.\n\nTo fetch an existing registration flow call `/self-service/registration/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceRegistrationViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationFlow" } } }, "description": "registrationFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Registration Flow for API clients", "tags": [ "public" ] } }, "/self-service/registration/browser": { "get": { "description": "This endpoint initializes a browser-based user registration flow. Once initialized, the browser will be redirected to\n`selfservice.flows.registration.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session\nexists already, the browser will be redirected to `urls.default_redirect_url` unless the query parameter\n`?refresh=true` was set.\n\n:::note\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceRegistrationViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Registration Flow for browsers", "tags": [ "public" ] } }, "/self-service/registration/flows": { "get": { "description": "This endpoint returns a registration flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "getSelfServiceRegistrationFlow", "parameters": [ { "description": "The Registration Flow ID\n\nThe value for this parameter comes from `flow` URL Query parameter sent to your\napplication (e.g. `/registration?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationFlow" } } }, "description": "registrationFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get Registration Flow", "tags": [ "public", "admin" ] } }, "/self-service/registration/methods/password": { "post": { "description": "Use this endpoint to complete a registration flow by sending an identity's traits and password. This endpoint\nbehaves differently for API and browser flows.\n\nAPI flows expect `application/json` to be sent in the body and respond with\nHTTP 200 and a application/json body with the created identity success - if the session hook is configured the\n`session` and `session_token` will also be included;\nHTTP 302 redirect to a fresh registration flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after registration URL or the `return_to` value if it was set and if the registration succeeded;\na HTTP 302 redirect to the registration UI URL with the flow ID containing the validation errors otherwise.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "completeSelfServiceRegistrationFlowWithPasswordMethod", "parameters": [ { "description": "Flow is flow ID.", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } }, "application/x-www-form-urlencoded": { "schema": { "type": "object" } } }, "x-originalParamName": "Payload" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationViaApiResponse" } } }, "description": "registrationViaApiResponse" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationFlow" } } }, "description": "registrationFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Registration Flow with Username/Email Password Method", "tags": [ "public" ] } }, "/self-service/settings/api": { "get": { "description": "This endpoint initiates a settings flow for API clients such as mobile devices, smart TVs, and so on.\nYou must provide a valid Ory Kratos Session Token for this endpoint to respond with HTTP 200 OK.\n\nTo fetch an existing settings flow call `/self-service/settings/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "initializeSelfServiceSettingsViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Initialize Settings Flow for API Clients", "tags": [ "public" ] } }, "/self-service/settings/browser": { "get": { "description": "This endpoint initializes a browser-based user settings flow. Once initialized, the browser will be redirected to\n`selfservice.flows.settings.ui_url` with the flow ID set as the query parameter `?flow=`. If no valid\nOry Kratos Session Cookie is included in the request, a login flow will be initialized.\n\n:::note\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "initializeSelfServiceSettingsViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Initialize Settings Flow for Browsers", "tags": [ "public" ] } }, "/self-service/settings/flows": { "get": { "description": "When accessing this endpoint through Ory Kratos' Public API you must ensure that either the Ory Kratos Session Cookie\nor the Ory Kratos Session Token are set. The public endpoint does not return 404 status codes\nbut instead 403 or 500 to improve data privacy.\n\nYou can access this endpoint without credentials when using Ory Kratos' Admin API.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "getSelfServiceSettingsFlow", "parameters": [ { "description": "ID is the Settings Flow ID\n\nThe value for this parameter comes from `flow` URL Query parameter sent to your\napplication (e.g. `/settings?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Get Settings Flow", "tags": [ "public", "admin" ] } }, "/self-service/settings/methods/password": { "post": { "description": "Use this endpoint to complete a settings flow by sending an identity's updated password. This endpoint\nbehaves differently for API and browser flows.\n\nAPI-initiated flows expect `application/json` to be sent in the body and respond with\nHTTP 200 and an application/json body with the session token on success;\nHTTP 302 redirect to a fresh settings flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\nHTTP 401 when the endpoint is called without a valid session token.\nHTTP 403 when `selfservice.flows.settings.privileged_session_max_age` was reached.\nImplies that the user needs to re-authenticate.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after settings URL or the `return_to` value if it was set and if the flow succeeded;\na HTTP 302 redirect to the Settings UI URL with the flow ID containing the validation errors otherwise.\na HTTP 302 redirect to the login endpoint when `selfservice.flows.settings.privileged_session_max_age` was reached.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "completeSelfServiceSettingsFlowWithPasswordMethod", "parameters": [ { "description": "Flow is flow ID.", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceSettingsFlowWithPasswordMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceSettingsFlowWithPasswordMethod" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsViaApiResponse" } } }, "description": "settingsViaApiResponse" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "401": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Complete Settings Flow with Username/Email Password Method", "tags": [ "public" ] } }, "/self-service/settings/methods/profile": { "post": { "description": "Use this endpoint to complete a settings flow by sending an identity's updated traits. This endpoint\nbehaves differently for API and browser flows.\n\nAPI-initiated flows expect `application/json` to be sent in the body and respond with\nHTTP 200 and an application/json body with the session token on success;\nHTTP 302 redirect to a fresh settings flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\nHTTP 401 when the endpoint is called without a valid session token.\nHTTP 403 when `selfservice.flows.settings.privileged_session_max_age` was reached and a sensitive field was\nupdated (e.g. recovery email). Implies that the user needs to re-authenticate.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after settings URL or the `return_to` value if it was set and if the flow succeeded;\na HTTP 302 redirect to the settings UI URL with the flow ID containing the validation errors otherwise.\na HTTP 302 redirect to the login endpoint when `selfservice.flows.settings.privileged_session_max_age` was reached.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "completeSelfServiceSettingsFlowWithProfileMethod", "parameters": [ { "description": "Flow is flow ID.", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } }, "application/x-www-form-urlencoded": { "schema": { "type": "object" } } }, "x-originalParamName": "Payload" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "401": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Complete Settings Flow with Profile Method", "tags": [ "public" ] } }, "/self-service/verification/api": { "get": { "description": "This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on.\n\nTo fetch an existing verification flow call `/self-service/verification/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/verificationFlow" } } }, "description": "verificationFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Verification Flow for API Clients", "tags": [ "public" ] } }, "/self-service/verification/browser": { "get": { "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Verification Flow for Browser Clients", "tags": [ "public" ] } }, "/self-service/verification/flows": { "get": { "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "getSelfServiceVerificationFlow", "parameters": [ { "description": "The Flow ID\n\nThe value for this parameter comes from `request` URL Query parameter sent to your\napplication (e.g. `/verification?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/verificationFlow" } } }, "description": "verificationFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get Verification Flow", "tags": [ "public", "admin" ] } }, "/self-service/verification/methods/link": { "post": { "description": "Use this endpoint to complete a verification flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "completeSelfServiceVerificationFlowWithLinkMethod", "parameters": [ { "description": "Verification Token\n\nThe verification token which completes the verification request. If the token\nis invalid (e.g. expired) an error will be shown to the end-user.", "in": "query", "name": "token", "schema": { "type": "string" } }, { "description": "The Flow ID\n\nformat: uuid", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/completeSelfServiceVerificationFlowWithLinkMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/completeSelfServiceVerificationFlowWithLinkMethod" } } }, "x-originalParamName": "Body" }, "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/verificationFlow" } } }, "description": "verificationFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Verification Flow with Link Method", "tags": [ "public" ] } }, "/sessions": { "delete": { "description": "Use this endpoint to revoke a session using its token. This endpoint is particularly useful for API clients\nsuch as mobile apps to log the user out of the system and invalidate the session.\n\nThis endpoint does not remove any HTTP Cookies - use the Self-Service Logout Flow instead.", "operationId": "revokeSession", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/revokeSession" } } }, "required": true, "x-originalParamName": "Body" }, "responses": { "204": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Revoke and Invalidate a Session", "tags": [ "public" ] } }, "/sessions/whoami": { "get": { "description": "Uses the HTTP Headers in the GET request to determine (e.g. by using checking the cookies) who is authenticated.\nReturns a session object in the body or 401 if the credentials are invalid or no credentials were sent.\nAdditionally when the request it successful it adds the user ID to the 'X-Kratos-Authenticated-Identity-Id' header in the response.\n\nThis endpoint is useful for reverse proxies and API Gateways.", "operationId": "whoami", "parameters": [ { "in": "header", "name": "Cookie", "schema": { "type": "string" } }, { "description": "in: authorization", "in": "query", "name": "Authorization", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/session" } } }, "description": "session" }, "401": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Check Who the Current HTTP Session Belongs To", "tags": [ "public" ] } }, "/version": { "get": { "description": "This endpoint returns the version of Ory Kratos.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the version will never\nrefer to the cluster state, only to a single instance.", "operationId": "getVersion", "responses": { "200": { "content": { "application/json": { "schema": { "properties": { "version": { "description": "The version of Ory Kratos.", "type": "string" } }, "required": [ "version" ], "type": "object" } } }, "description": "Returns the Ory Kratos version." } }, "summary": "Return Running Software Version.", "tags": [ "admin" ] } } }, "servers": [ { "url": "https://{tenant}.tenants.oryapis.com/api/kratos/{api}", "variables": { "api": { "default": "public", "description": "Target the public or administrative API.", "enum": [ "public", "admin" ] }, "tenant": { "default": "demo", "description": "Tenant ID as provided by Ory Cloud." } } } ], "tags": [ { "description": "All administrative API endpoints exposed at the admin API port.", "externalDocs": { "url": "https://www.ory.sh/kratos/docs/reference/api" }, "name": "admin" }, { "description": "All public API endpoints exposed at the public API port.", "externalDocs": { "url": "https://www.ory.sh/kratos/docs/reference/api" }, "name": "public" } ], "x-forwarded-proto": "string", "x-request-id": "string" } ================================================ FILE: .schema/openapi/gen.go.yml ================================================ disallowAdditionalPropertiesIfNotPresent: false packageName: client generateInterfaces: true structPrefix: true enumClassPrefix: true useOneOfDiscriminatorLookup: true isGoSubmodule: false ================================================ FILE: .schema/openapi/gen.typescript.yml ================================================ npmName: "@ory/kratos-client" npmVersion: 0.0.0 # typescriptThreePlus: true #npmRepository: https://github.com/ory/sdk.git supportsES6: true ensureUniqueParams: true modelPropertyNaming: original disallowAdditionalPropertiesIfNotPresent: false withInterfaces: false useSingleRequestParameter: true enumUnknownDefaultCase: true ================================================ FILE: .schema/openapi/patches/common.yaml ================================================ [] ================================================ FILE: .schema/openapi/patches/courier.yaml ================================================ # Makes courierMessageStatus a string enum - op: remove path: /components/schemas/courierMessageStatus/format - op: replace path: /components/schemas/courierMessageStatus/type value: string - op: add path: /components/schemas/courierMessageStatus/enum value: - queued - sent - processing - abandoned # Makes courierMessageType a string enum - op: remove path: /components/schemas/courierMessageType/format - op: replace path: /components/schemas/courierMessageType/type value: string - op: add path: /components/schemas/courierMessageType/enum value: - email - phone # Fix courierMessageStatus query parameter in listMessages endpoint - op: replace path: /paths/~1admin~1courier~1messages/get/parameters/2/schema value: $ref: "#/components/schemas/courierMessageStatus" ================================================ FILE: .schema/openapi/patches/generic_error.yaml ================================================ - op: add path: /paths/~1sessions~1whoami/get/parameters/0/example value: MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj - op: add path: /paths/~1sessions~1whoami/get/parameters/1/example value: ory_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f== ================================================ FILE: .schema/openapi/patches/identity.yaml ================================================ - op: remove path: /components/schemas/updateIdentityBody/properties/metadata_admin/type - op: remove path: /components/schemas/updateIdentityBody/properties/metadata_public/type - op: remove path: /components/schemas/createIdentityBody/properties/metadata_admin/type - op: remove path: /components/schemas/createIdentityBody/properties/metadata_public/type - op: remove path: /components/schemas/nullJsonRawMessage/type - op: add path: /components/schemas/nullJsonRawMessage/nullable value: true ================================================ FILE: .schema/openapi/patches/meta.yaml ================================================ - op: replace path: /info value: title: Ory Identities API description: | This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. version: >- {{ getenv "CIRCLE_TAG" }} license: name: Apache 2.0 contact: email: "office@ory.sh" - op: replace path: /tags value: - name: identity description: APIs for managing identities. - name: frontend description: Endpoints used by frontend applications (e.g. Single-Page-App, Native Apps, Server Apps, ...) to manage a user's own profile. - name: courier description: APIs for managing email and SMS message delivery. - name: metadata description: Server Metadata provides relevant information about the running server. Only available when self-hosting this service. ================================================ FILE: .schema/openapi/patches/nulls.yaml ================================================ - op: replace path: "#/components/schemas/NullUUID" value: type: string format: uuid4 nullable: true - op: replace path: "#/components/schemas/NullTime" value: format: date-time type: string nullable: true - op: replace path: "#/components/schemas/Time" value: format: date-time type: string - op: replace path: "#/components/schemas/NullString" value: type: string nullable: true - op: replace path: "#/components/schemas/NullBool" value: type: boolean nullable: true - op: replace path: "#/components/schemas/NullInt" value: type: integer nullable: true - op: replace path: "#/components/schemas/nullInt64" value: type: integer nullable: true - op: replace path: "#/components/schemas/nullDuration" value: type: string nullable: true pattern: ^[0-9]+(ns|us|ms|s|m|h)$ ================================================ FILE: .schema/openapi/patches/schema.yaml ================================================ # Makes uiNodeAttributes polymorph - op: remove path: /components/schemas/uiNodeAttributes/type - op: add path: /components/schemas/uiNodeAttributes/discriminator value: propertyName: node_type mapping: input: "#/components/schemas/uiNodeInputAttributes" text: "#/components/schemas/uiNodeTextAttributes" img: "#/components/schemas/uiNodeImageAttributes" a: "#/components/schemas/uiNodeAnchorAttributes" script: "#/components/schemas/uiNodeScriptAttributes" div: "#/components/schemas/uiNodeDivisionAttributes" - op: add path: /components/schemas/uiNodeAttributes/oneOf value: - "$ref": "#/components/schemas/uiNodeInputAttributes" - "$ref": "#/components/schemas/uiNodeTextAttributes" - "$ref": "#/components/schemas/uiNodeImageAttributes" - "$ref": "#/components/schemas/uiNodeAnchorAttributes" - "$ref": "#/components/schemas/uiNodeScriptAttributes" - "$ref": "#/components/schemas/uiNodeDivisionAttributes" - op: replace path: /components/schemas/uiNodeDivisionAttributes/properties/node_type/enum value: - div - op: replace path: /components/schemas/uiNodeInputAttributes/properties/node_type/enum value: - input - op: replace path: /components/schemas/uiNodeTextAttributes/properties/node_type/enum value: - text - op: replace path: /components/schemas/uiNodeImageAttributes/properties/node_type/enum value: - img - op: replace path: /components/schemas/uiNodeAnchorAttributes/properties/node_type/enum value: - a - op: replace path: /components/schemas/uiNodeScriptAttributes/properties/node_type/enum value: - script # Makes the uiNodeInputAttributes value attribute polymorph - op: add path: /components/schemas/uiNodeInputAttributes/properties/value/nullable value: true - op: replace path: /components/schemas/flowError/properties/error value: type: object - op: remove path: "#/components/schemas/identityTraits/type" - op: add path: /components/schemas/continueWith/discriminator value: propertyName: action mapping: show_verification_ui: "#/components/schemas/continueWithVerificationUi" set_ory_session_token: "#/components/schemas/continueWithSetOrySessionToken" show_settings_ui: "#/components/schemas/continueWithSettingsUi" show_recovery_ui: "#/components/schemas/continueWithRecoveryUi" redirect_browser_to: "#/components/schemas/continueWithRedirectBrowserTo" - op: add path: /components/schemas/continueWith/oneOf value: - "$ref": "#/components/schemas/continueWithVerificationUi" - "$ref": "#/components/schemas/continueWithSetOrySessionToken" - "$ref": "#/components/schemas/continueWithSettingsUi" - "$ref": "#/components/schemas/continueWithRecoveryUi" - "$ref": "#/components/schemas/continueWithRedirectBrowserTo" ================================================ FILE: .schema/openapi/patches/security.yaml ================================================ - op: replace path: /components/schemas/genericError/properties/details/additionalProperties value: false ================================================ FILE: .schema/openapi/patches/selfservice.yaml ================================================ # Makes updateLoginFlowPayload polymorph #- op: remove # path: /components/schemas/updateLoginFlowBody/type #- op: add # path: /components/schemas/updateLoginFlowBody/oneOf # value: # - "$ref": "#/components/schemas/updateLoginFlowWithPasswordMethod" # Makes updateRegistrationFlowPayload polymorph # All modifications for the registration flow - op: remove path: /components/schemas/updateRegistrationFlowBody/type - op: add path: /components/schemas/updateRegistrationFlowBody/oneOf value: - "$ref": "#/components/schemas/updateRegistrationFlowWithPasswordMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithOidcMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithSamlMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithCodeMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" - "$ref": "#/components/schemas/updateRegistrationFlowWithProfileMethod" - op: add path: /components/schemas/updateRegistrationFlowBody/discriminator value: propertyName: method mapping: password: "#/components/schemas/updateRegistrationFlowWithPasswordMethod" oidc: "#/components/schemas/updateRegistrationFlowWithOidcMethod" saml: "#/components/schemas/updateRegistrationFlowWithSamlMethod" webauthn: "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod" code: "#/components/schemas/updateRegistrationFlowWithCodeMethod" passkey: "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" profile: "#/components/schemas/updateRegistrationFlowWithProfileMethod" - op: add path: /components/schemas/registrationFlowState value: title: Registration flow state (experimental) description: The experimental state represents the state of a registration flow. This field is EXPERIMENTAL and subject to change! type: string enum: - choose_method - sent_email - passed_challenge # end # All modifications for the login flow - op: remove path: /components/schemas/updateLoginFlowBody/type - op: add path: /components/schemas/updateLoginFlowBody/oneOf value: - "$ref": "#/components/schemas/updateLoginFlowWithPasswordMethod" - "$ref": "#/components/schemas/updateLoginFlowWithOidcMethod" - "$ref": "#/components/schemas/updateLoginFlowWithSamlMethod" - "$ref": "#/components/schemas/updateLoginFlowWithTotpMethod" - "$ref": "#/components/schemas/updateLoginFlowWithWebAuthnMethod" - "$ref": "#/components/schemas/updateLoginFlowWithLookupSecretMethod" - "$ref": "#/components/schemas/updateLoginFlowWithCodeMethod" - "$ref": "#/components/schemas/updateLoginFlowWithPasskeyMethod" - "$ref": "#/components/schemas/updateLoginFlowWithIdentifierFirstMethod" - op: add path: /components/schemas/updateLoginFlowBody/discriminator value: propertyName: method mapping: password: "#/components/schemas/updateLoginFlowWithPasswordMethod" oidc: "#/components/schemas/updateLoginFlowWithOidcMethod" saml: "#/components/schemas/updateLoginFlowWithSamlMethod" totp: "#/components/schemas/updateLoginFlowWithTotpMethod" webauthn: "#/components/schemas/updateLoginFlowWithWebAuthnMethod" lookup_secret: "#/components/schemas/updateLoginFlowWithLookupSecretMethod" code: "#/components/schemas/updateLoginFlowWithCodeMethod" passkey: "#/components/schemas/updateLoginFlowWithPasskeyMethod" identifier_first: "#/components/schemas/updateLoginFlowWithIdentifierFirstMethod" - op: add path: /components/schemas/loginFlowState value: title: Login flow state (experimental) description: The experimental state represents the state of a login flow. This field is EXPERIMENTAL and subject to change! type: string enum: - choose_method - sent_email - passed_challenge # end # All modifications for the recovery flow - op: remove path: /components/schemas/updateRecoveryFlowBody/type - op: add path: /components/schemas/updateRecoveryFlowBody/oneOf value: - "$ref": "#/components/schemas/updateRecoveryFlowWithLinkMethod" - "$ref": "#/components/schemas/updateRecoveryFlowWithCodeMethod" - op: add path: /components/schemas/updateRecoveryFlowBody/discriminator value: propertyName: method mapping: link: "#/components/schemas/updateRecoveryFlowWithLinkMethod" code: "#/components/schemas/updateRecoveryFlowWithCodeMethod" - op: add path: /components/schemas/recoveryFlowState type: string value: title: Recovery flow state (experimental) description: The experimental state represents the state of a recovery flow. This field is EXPERIMENTAL and subject to change! enum: - choose_method - sent_email - passed_challenge # End # All modifications for the verification flow - op: remove path: /components/schemas/updateVerificationFlowBody/type - op: add path: /components/schemas/updateVerificationFlowBody/oneOf value: - "$ref": "#/components/schemas/updateVerificationFlowWithLinkMethod" - "$ref": "#/components/schemas/updateVerificationFlowWithCodeMethod" - op: add path: /components/schemas/updateVerificationFlowBody/discriminator value: propertyName: method mapping: link: "#/components/schemas/updateVerificationFlowWithLinkMethod" code: "#/components/schemas/updateVerificationFlowWithCodeMethod" - op: add path: /components/schemas/verificationFlowState type: string value: title: Verification flow state (experimental) description: The experimental state represents the state of a verification flow. This field is EXPERIMENTAL and subject to change! enum: - choose_method - sent_email - passed_challenge # End # All modifications for the settings flow - op: remove path: /components/schemas/updateSettingsFlowBody/type - op: add path: /components/schemas/updateSettingsFlowBody/oneOf value: - "$ref": "#/components/schemas/updateSettingsFlowWithPasswordMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithProfileMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithOidcMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithSamlMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithTotpMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithWebAuthnMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithLookupMethod" - "$ref": "#/components/schemas/updateSettingsFlowWithPasskeyMethod" - op: add path: /components/schemas/updateSettingsFlowBody/discriminator value: propertyName: method mapping: password: "#/components/schemas/updateSettingsFlowWithPasswordMethod" profile: "#/components/schemas/updateSettingsFlowWithProfileMethod" oidc: "#/components/schemas/updateSettingsFlowWithOidcMethod" saml: "#/components/schemas/updateSettingsFlowWithSamlMethod" totp: "#/components/schemas/updateSettingsFlowWithTotpMethod" webauthn: "#/components/schemas/updateSettingsFlowWithWebAuthnMethod" passkey: "#/components/schemas/updateSettingsFlowWithPasskeyMethod" lookup_secret: "#/components/schemas/updateSettingsFlowWithLookupMethod" - op: add path: /components/schemas/settingsFlowState value: title: Settings flow state (experimental) description: The experimental state represents the state of a settings flow. This field is EXPERIMENTAL and subject to change! type: string enum: - show_form - success # end # Some issues with AdditionalProperties - op: remove path: "#/components/schemas/OAuth2LoginRequest/properties/AdditionalProperties" - op: remove path: "#/components/schemas/OAuth2ConsentRequestOpenIDConnectContext/properties/AdditionalProperties" - op: remove path: "#/components/schemas/OAuth2Client/properties/AdditionalProperties" ================================================ FILE: .schema/openapi/patches/session.yaml ================================================ - op: add path: /paths/~1sessions~1whoami/get/parameters/0/example value: MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj - op: add path: /paths/~1sessions~1whoami/get/parameters/1/example value: ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f== - op: add path: /components/schemas/authenticatorAssuranceLevel/enum value: - aal0 - aal1 - aal2 - aal3 ================================================ FILE: .schema/openapi.json ================================================ { "components": { "responses": { "emptyResponse": { "description": "Empty responses are sent when, for example, resources are deleted. The HTTP status code for empty responses is\ntypically 201." }, "errorContainer": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/errorContainer" } } }, "description": "User-facing error response" }, "identityList": { "content": { "application/json": { "schema": { "items": { "$ref": "#/components/schemas/Identity" }, "type": "array" } } }, "description": "A list of identities." }, "identityResponse": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Identity" } } }, "description": "A single identity." }, "schemaResponse": { "content": { "application/json": { "schema": { "type": "object" } } }, "description": "The raw identity traits schema" } }, "schemas": { "CompleteSelfServiceBrowserSettingsProfileStrategyFlow": { "description": "nolint:deadcode,unused", "properties": { "csrf_token": { "description": "The Anti-CSRF Token\n\nThis token is only required when performing browser flows.", "type": "string" }, "traits": { "description": "Traits contains all of the identity's traits.", "type": "object" } }, "type": "object" }, "CompleteSelfServiceLoginFlowWithPasswordMethod": { "properties": { "csrf_token": { "description": "Sending the anti-csrf token is only required for browser login flows.", "type": "string" }, "identifier": { "description": "Identifier is the email or username of the user trying to log in.", "type": "string" }, "password": { "description": "The user's password.", "type": "string" } }, "type": "object" }, "CompleteSelfServiceSettingsFlowWithPasswordMethod": { "properties": { "csrf_token": { "description": "CSRFToken is the anti-CSRF token\n\ntype: string", "type": "string" }, "password": { "description": "Password is the updated password\n\ntype: string", "type": "string" } }, "required": [ "password" ], "type": "object" }, "CreateIdentity": { "properties": { "schema_id": { "description": "SchemaID is the ID of the JSON Schema to be used for validating the identity's traits.", "type": "string" }, "traits": { "description": "Traits represent an identity's traits. The identity is able to create, modify, and delete traits\nin a self-service manner. The input will always be validated against the JSON Schema defined\nin `schema_url`.", "type": "object" } }, "required": [ "schema_id", "traits" ], "type": "object" }, "CreateRecoveryLink": { "properties": { "expires_in": { "description": "Link Expires In\n\nThe recovery link will expire at that point in time. Defaults to the configuration value of\n`selfservice.flows.recovery.request_lifespan`.", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "type": "string" }, "identity_id": { "$ref": "#/components/schemas/UUID" } }, "required": [ "identity_id" ], "type": "object" }, "CredentialsType": { "description": "and so on.", "title": "CredentialsType represents several different credential types, like password credentials, passwordless credentials,", "type": "string" }, "ID": { "format": "int64", "type": "integer" }, "Identity": { "properties": { "id": { "$ref": "#/components/schemas/UUID" }, "recovery_addresses": { "description": "RecoveryAddresses contains all the addresses that can be used to recover an identity.", "items": { "$ref": "#/components/schemas/RecoveryAddress" }, "type": "array", "x-omitempty": true }, "schema_id": { "description": "SchemaID is the ID of the JSON Schema to be used for validating the identity's traits.", "type": "string" }, "schema_url": { "description": "SchemaURL is the URL of the endpoint where the identity's traits schema can be fetched from.\n\nformat: url", "type": "string" }, "traits": { "$ref": "#/components/schemas/Traits" }, "verifiable_addresses": { "description": "VerifiableAddresses contains all the addresses that can be verified by the user.", "items": { "$ref": "#/components/schemas/VerifiableAddress" }, "type": "array", "x-omitempty": true } }, "required": [ "id", "schema_id", "schema_url", "traits" ], "type": "object" }, "NullTime": { "format": "date-time", "title": "NullTime implements sql.NullTime functionality.", "type": "string" }, "RecoveryAddress": { "properties": { "id": { "$ref": "#/components/schemas/UUID" }, "value": { "type": "string" }, "via": { "$ref": "#/components/schemas/RecoveryAddressType" } }, "required": [ "id", "value", "via" ], "type": "object" }, "RecoveryAddressType": { "type": "string" }, "State": { "type": "string" }, "Traits": { "type": "object" }, "Type": { "description": "The flow type can either be `api` or `browser`.", "title": "Type is the flow type.", "type": "string" }, "UUID": { "format": "uuid4", "type": "string" }, "UpdateIdentity": { "properties": { "schema_id": { "description": "SchemaID is the ID of the JSON Schema to be used for validating the identity's traits. If set\nwill update the Identity's SchemaID.", "type": "string" }, "traits": { "description": "Traits represent an identity's traits. The identity is able to create, modify, and delete traits\nin a self-service manner. The input will always be validated against the JSON Schema defined\nin `schema_id`.", "type": "object" } }, "required": [ "traits" ], "type": "object" }, "VerifiableAddress": { "properties": { "id": { "$ref": "#/components/schemas/UUID" }, "status": { "$ref": "#/components/schemas/VerifiableAddressStatus" }, "value": { "type": "string" }, "verified": { "type": "boolean" }, "verified_at": { "$ref": "#/components/schemas/NullTime" }, "via": { "$ref": "#/components/schemas/VerifiableAddressType" } }, "required": [ "id", "value", "verified", "via", "status" ], "type": "object" }, "VerifiableAddressStatus": { "type": "string" }, "VerifiableAddressType": { "type": "string" }, "completeSelfServiceBrowserSettingsOIDCFlowPayload": { "properties": { "flow": { "description": "Flow ID is the flow's ID.\n\nin: query", "type": "string" }, "link": { "description": "Link this provider\n\nEither this or `unlink` must be set.\n\ntype: string\nin: body", "type": "string" }, "unlink": { "description": "Unlink this provider\n\nEither this or `link` must be set.\n\ntype: string\nin: body", "type": "string" } }, "type": "object" }, "completeSelfServiceRecoveryFlowWithLinkMethod": { "properties": { "csrf_token": { "description": "Sending the anti-csrf token is only required for browser login flows.", "type": "string" }, "email": { "description": "Email to Recover\n\nNeeds to be set when initiating the flow. If the email is a registered\nrecovery email, a recovery link will be sent. If the email is not known,\na email with details on what happened will be sent instead.\n\nformat: email\nin: body", "type": "string" } }, "type": "object" }, "completeSelfServiceVerificationFlowWithLinkMethod": { "properties": { "csrf_token": { "description": "Sending the anti-csrf token is only required for browser login flows.", "type": "string" }, "email": { "description": "Email to Verify\n\nNeeds to be set when initiating the flow. If the email is a registered\nverification email, a verification link will be sent. If the email is not known,\na email with details on what happened will be sent instead.\n\nformat: email\nin: body", "type": "string" } }, "type": "object" }, "errorContainer": { "properties": { "errors": { "description": "Errors in the container", "type": "object" }, "id": { "$ref": "#/components/schemas/UUID" } }, "required": [ "id", "errors" ], "type": "object" }, "form": { "description": "HTMLForm represents a HTML Form. The container can work with both HTTP Form and JSON requests", "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "genericError": { "description": "Error responses are sent when an error (e.g. unauthorized, bad request, ...) occurred.", "properties": { "error": { "$ref": "#/components/schemas/genericErrorPayload" } }, "title": "Error response", "type": "object" }, "genericErrorPayload": { "properties": { "code": { "description": "Code represents the error status code (404, 403, 401, ...).", "example": 404, "format": "int64", "type": "integer" }, "debug": { "description": "Debug contains debug information. This is usually not available and has to be enabled.", "example": "The database adapter was unable to find the element", "type": "string" }, "details": { "additionalProperties": true, "type": "object" }, "message": { "type": "string" }, "reason": { "type": "string" }, "request": { "type": "string" }, "status": { "type": "string" } }, "type": "object" }, "healthNotReadyStatus": { "properties": { "errors": { "additionalProperties": { "type": "string" }, "description": "Errors contains a list of errors that caused the not ready status.", "type": "object" } }, "type": "object" }, "healthStatus": { "properties": { "status": { "description": "Status always contains \"ok\".", "type": "string" } }, "type": "object" }, "loginFlow": { "description": "This object represents a login flow. A login flow is initiated at the \"Initiate Login API / Browser Flow\"\nendpoint by a client.\n\nOnce a login flow is completed successfully, a session cookie or session token will be issued.", "properties": { "active": { "$ref": "#/components/schemas/CredentialsType" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to log in,\na new flow has to be initiated.", "format": "date-time", "type": "string" }, "forced": { "description": "Forced stores whether this login flow should enforce re-authentication.", "type": "boolean" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the flow started.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/loginFlowMethod" }, "description": "List of login methods\n\nThis is the list of available login methods with their required form fields, such as `identifier` and `password`\nfor the password login method. This will also contain error messages such as \"password can not be empty\".", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods" ], "title": "Login Flow", "type": "object" }, "loginFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/loginFlowMethodConfig" }, "method": { "$ref": "#/components/schemas/CredentialsType" } }, "required": [ "method", "config" ], "type": "object" }, "loginFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" }, "providers": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "loginFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" }, "providers": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "loginViaApiResponse": { "description": "The Response for Login Flows via API", "properties": { "session": { "$ref": "#/components/schemas/session" }, "session_token": { "description": "The Session Token\n\nA session token is equivalent to a session cookie, but it can be sent in the HTTP Authorization\nHeader:\n\nAuthorization: bearer ${session-token}\n\nThe session token is only issued for API flows, not for Browser flows!", "type": "string" } }, "required": [ "session_token", "session" ], "type": "object" }, "recoveryFlow": { "description": "This request is used when an identity wants to recover their account.\n\nWe recommend reading the [Account Recovery Documentation](../self-service/flows/password-reset-account-recovery)", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the request expires. If the user still wishes to update the setting,\na new request has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the request occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/recoveryFlowMethod" }, "description": "Methods contains context for all account recovery methods. If a registration request has been\nprocessed, but for example the password is incorrect, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "state": { "$ref": "#/components/schemas/State" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods", "state" ], "title": "A Recovery Flow", "type": "object" }, "recoveryFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/recoveryFlowMethodConfig" }, "method": { "description": "Method contains the request credentials type.", "type": "string" } }, "required": [ "method", "config" ], "type": "object" }, "recoveryFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "recoveryFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "recoveryLink": { "properties": { "expires_at": { "description": "Recovery Link Expires At\n\nThe timestamp when the recovery link expires.", "format": "date-time", "type": "string" }, "recovery_link": { "description": "Recovery Link\n\nThis link can be used to recover the account.", "type": "string" } }, "required": [ "recovery_link" ], "type": "object" }, "registrationFlow": { "properties": { "active": { "$ref": "#/components/schemas/CredentialsType" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to log in,\na new flow has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the flow occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/registrationFlowMethod" }, "description": "Methods contains context for all enabled registration methods. If a registration flow has been\nprocessed, but for example the password is incorrect, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods" ], "type": "object" }, "registrationFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/registrationFlowMethodConfig" }, "method": { "$ref": "#/components/schemas/CredentialsType" } }, "required": [ "method", "config" ], "type": "object" }, "registrationFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" }, "providers": { "description": "Providers is set for the \"oidc\" registration method.", "items": { "$ref": "#/components/schemas/uiNodes" }, "type": "array" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "registrationFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" }, "providers": { "description": "Providers is set for the \"oidc\" registration method.", "items": { "$ref": "#/components/schemas/uiNodes" }, "type": "array" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "registrationViaApiResponse": { "description": "The Response for Registration Flows via API", "properties": { "identity": { "$ref": "#/components/schemas/Identity" }, "session": { "$ref": "#/components/schemas/session" }, "session_token": { "description": "The Session Token\n\nThis field is only set when the session hook is configured as a post-registration hook.\n\nA session token is equivalent to a session cookie, but it can be sent in the HTTP Authorization\nHeader:\n\nAuthorization: bearer ${session-token}\n\nThe session token is only issued for API flows, not for Browser flows!", "type": "string" } }, "required": [ "session_token", "identity" ], "type": "object" }, "revokeSession": { "properties": { "session_token": { "description": "The Session Token\n\nInvalidate this session token.", "type": "string" } }, "required": [ "session_token" ], "type": "object" }, "session": { "properties": { "active": { "type": "boolean" }, "authenticated_at": { "format": "date-time", "type": "string" }, "expires_at": { "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "identity": { "$ref": "#/components/schemas/Identity" }, "issued_at": { "format": "date-time", "type": "string" } }, "required": [ "id", "expires_at", "authenticated_at", "issued_at", "identity" ], "type": "object" }, "settingsFlow": { "description": "This flow is used when an identity wants to update settings\n(e.g. profile data, passwords, ...) in a selfservice manner.\n\nWe recommend reading the [User Settings Documentation](../self-service/flows/user-settings)", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the flow expires. If the user still wishes to update the setting,\na new flow has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "identity": { "$ref": "#/components/schemas/Identity" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the flow occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/settingsFlowMethod" }, "description": "Methods contains context for all enabled registration methods. If a settings flow has been\nprocessed, but for example the first name is empty, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "state": { "$ref": "#/components/schemas/State" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "id", "expires_at", "issued_at", "request_url", "methods", "identity", "state" ], "title": "Flow represents a Settings Flow", "type": "object" }, "settingsFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/settingsFlowMethodConfig" }, "method": { "description": "Method is the name of this flow method.", "type": "string" } }, "required": [ "method", "config" ], "type": "object" }, "settingsFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "settingsFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "settingsProfileFormConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "settingsViaApiResponse": { "description": "The Response for Settings Flows via API", "properties": { "flow": { "$ref": "#/components/schemas/settingsFlow" }, "identity": { "$ref": "#/components/schemas/Identity" } }, "required": [ "flow", "identity" ], "type": "object" }, "uiNode": { "description": "Nodes are represented as HTML elements or their native UI equivalents. For example,\na node can be an `\u003cimg\u003e` tag, or an `\u003cinput element\u003e` but also `some plain text`.", "properties": { "attributes": { "$ref": "#/components/schemas/uiNodeAttributes" }, "group": { "$ref": "#/components/schemas/uiNodeGroup" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "type": { "$ref": "#/components/schemas/uiNodeType" } }, "required": [ "type", "attributes" ], "title": "Node represents a flow's nodes", "type": "object" }, "uiNodeAnchorAttributes": { "properties": { "href": { "description": "The link's href (destination) URL.\n\nformat: uri", "type": "string" }, "title": { "$ref": "#/components/schemas/uiText" } }, "required": [ "href", "title" ], "title": "AnchorAttributes represents the attributes of an anchor node.", "type": "object" }, "uiNodeAttributes": { "oneOf": [ { "$ref": "#/components/schemas/uiNodeInputAttributes" }, { "$ref": "#/components/schemas/uiNodeTextAttributes" }, { "$ref": "#/components/schemas/uiNodeImageAttributes" }, { "$ref": "#/components/schemas/uiNodeAnchorAttributes" } ], "title": "Attributes represents a list of attributes (e.g. `href=\"foo\"` for links)." }, "uiNodeGroup": { "type": "string" }, "uiNodeImageAttributes": { "properties": { "src": { "description": "The image's source URL.\n\nformat: uri", "type": "string" } }, "required": [ "src" ], "title": "ImageAttributes represents the attributes of an image node.", "type": "object" }, "uiNodeInputAttributeType": { "type": "string" }, "uiNodeInputAttributes": { "description": "InputAttributes represents the attributes of an input node", "properties": { "disabled": { "description": "Sets the input's disabled field to true or false.", "type": "boolean" }, "label": { "$ref": "#/components/schemas/uiText" }, "name": { "description": "The input's element name.", "type": "string" }, "pattern": { "description": "The input's pattern.", "type": "string" }, "required": { "description": "Mark this input field as required.", "type": "boolean" }, "type": { "$ref": "#/components/schemas/uiNodeInputAttributeType" }, "value": { "description": "The input's value.", "nullable": true, "oneOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" } ] } }, "required": [ "name", "type", "disabled" ], "type": "object" }, "uiNodeTextAttributes": { "properties": { "text": { "$ref": "#/components/schemas/uiText" } }, "required": [ "text" ], "title": "TextAttributes represents the attributes of a text node.", "type": "object" }, "uiNodeType": { "type": "string" }, "uiNodes": { "items": { "$ref": "#/components/schemas/uiNode" }, "type": "array" }, "uiText": { "properties": { "context": { "description": "The message's context. Useful when customizing messages.", "type": "object" }, "id": { "$ref": "#/components/schemas/ID" }, "text": { "description": "The message text. Written in american english.", "type": "string" }, "type": { "$ref": "#/components/schemas/uiTextType" } }, "required": [ "id", "text", "type" ], "type": "object" }, "uiTextType": { "type": "string" }, "uiTexts": { "items": { "$ref": "#/components/schemas/uiText" }, "type": "array" }, "verificationFlow": { "description": "Used to verify an out-of-band communication\nchannel such as an email address or a phone number.\n\nFor more information head over to: https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation", "properties": { "active": { "description": "Active, if set, contains the registration method that is being used. It is initially\nnot set.", "type": "string" }, "expires_at": { "description": "ExpiresAt is the time (UTC) when the request expires. If the user still wishes to verify the address,\na new request has to be initiated.", "format": "date-time", "type": "string" }, "id": { "$ref": "#/components/schemas/UUID" }, "issued_at": { "description": "IssuedAt is the time (UTC) when the request occurred.", "format": "date-time", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "methods": { "additionalProperties": { "$ref": "#/components/schemas/verificationFlowMethod" }, "description": "Methods contains context for all account verification methods. If a registration request has been\nprocessed, but for example the password is incorrect, this will contain error messages.", "type": "object" }, "request_url": { "description": "RequestURL is the initial URL that was requested from Ory Kratos. It can be used\nto forward information contained in the URL's path or query for example.", "type": "string" }, "state": { "$ref": "#/components/schemas/State" }, "type": { "$ref": "#/components/schemas/Type" } }, "required": [ "methods", "state" ], "title": "A Verification Flow", "type": "object" }, "verificationFlowMethod": { "properties": { "config": { "$ref": "#/components/schemas/verificationFlowMethodConfig" }, "method": { "description": "Method contains the request credentials type.", "type": "string" } }, "required": [ "method", "config" ], "type": "object" }, "verificationFlowMethodConfig": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "verificationFlowMethodConfigPayload": { "properties": { "action": { "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.", "type": "string" }, "messages": { "$ref": "#/components/schemas/uiTexts" }, "method": { "description": "Method is the form method (e.g. POST)", "type": "string" }, "nodes": { "$ref": "#/components/schemas/uiNodes" } }, "required": [ "action", "method", "nodes" ], "type": "object" }, "version": { "properties": { "version": { "description": "Version is the service's version.", "type": "string" } }, "type": "object" } }, "securitySchemes": { "sessionCookie": { "in": "cookie", "name": "ory_kratos_session", "type": "apiKey" }, "sessionToken": { "in": "header", "name": "X-Session-Token", "type": "apiKey" } } }, "info": { "contact": { "email": "hi@ory.sh" }, "description": "Documentation for all public and administrative Ory Kratos APIs. Public and administrative APIs\nare exposed on different ports. Public APIs can face the public internet without any protection\nwhile administrative APIs should never be exposed without prior authorization. To protect\nthe administative API port you should use something like Nginx, Ory Oathkeeper, or any other\ntechnology capable of authorizing incoming requests.\n", "license": { "name": "Apache 2.0" }, "title": "Ory Kratos API", "version": "" }, "openapi": "3.0.3", "paths": { "/health/alive": { "get": { "description": "This endpoint returns a HTTP 200 status code when Ory Kratos is accepting incoming\nHTTP requests. This status does currently not include checks whether the database connection is working.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the health status will never\nrefer to the cluster state, only to a single instance.", "operationId": "isAlive", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/healthStatus" } } }, "description": "Ory Kratos is ready to accept connections." }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Check HTTP Server Status", "tags": [ "admin" ] } }, "/health/ready": { "get": { "description": "This endpoint returns a HTTP 200 status code when Ory Kratos is up running and the environment dependencies (e.g.\nthe database) are responsive as well.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of Ory Kratos, the health status will never\nrefer to the cluster state, only to a single instance.", "operationId": "isReady", "responses": { "200": { "content": { "application/json": { "schema": { "properties": { "status": { "description": "Always \"ok\".", "type": "string" } }, "type": "object" } } }, "description": "Ory Kratos is ready to accept requests." }, "503": { "content": { "application/json": { "schema": { "properties": { "errors": { "additionalProperties": { "type": "string" }, "description": "Errors contains a list of errors that caused the not ready status.", "type": "object" } }, "type": "object" } } }, "description": "Ory Kratos is not yet ready to accept requests." } }, "summary": "Check HTTP Server and Database Status", "tags": [ "admin" ] } }, "/identities": { "get": { "description": "Lists all identities. Does not support search at the moment.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "listIdentities", "parameters": [ { "description": "Items per Page\n\nThis is the number of items per page.", "in": "query", "name": "per_page", "schema": { "default": 100, "format": "int64", "maximum": 500, "minimum": 1, "type": "integer" } }, { "description": "Pagination Page", "in": "query", "name": "page", "schema": { "default": 0, "format": "int64", "minimum": 0, "type": "integer" } } ], "responses": { "200": { "$ref": "#/components/responses/identityList" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "List Identities", "tags": [ "admin" ] }, "post": { "description": "This endpoint creates an identity. It is NOT possible to set an identity's credentials (password, ...)\nusing this method! A way to achieve that will be introduced in the future.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "createIdentity", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateIdentity" } } }, "x-originalParamName": "Body" }, "responses": { "201": { "$ref": "#/components/responses/identityResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "409": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Create an Identity", "tags": [ "admin" ] } }, "/identities/{id}": { "delete": { "description": "Calling this endpoint irrecoverably and permanently deletes the identity given its ID. This action can not be undone.\nThis endpoint returns 204 when the identity was deleted or when the identity was not found, in which case it is\nassumed that is has been deleted already.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "deleteIdentity", "parameters": [ { "description": "ID is the identity's ID.", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "$ref": "#/components/responses/emptyResponse" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Delete an Identity", "tags": [ "admin" ] }, "get": { "description": "Learn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "getIdentity", "parameters": [ { "description": "ID must be set to the ID of identity you want to get", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "$ref": "#/components/responses/identityResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get an Identity", "tags": [ "admin" ] }, "put": { "description": "This endpoint updates an identity. It is NOT possible to set an identity's credentials (password, ...)\nusing this method! A way to achieve that will be introduced in the future.\n\nThe full identity payload (except credentials) is expected. This endpoint does not support patching.\n\nLearn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).", "operationId": "updateIdentity", "parameters": [ { "description": "ID must be set to the ID of identity you want to update", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateIdentity" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "$ref": "#/components/responses/identityResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Update an Identity", "tags": [ "admin" ] } }, "/metrics/prometheus": { "get": { "description": "```\nmetadata:\nannotations:\nprometheus.io/port: \"4434\"\nprometheus.io/path: \"/metrics/prometheus\"\n```", "operationId": "prometheus", "responses": { "200": { "$ref": "#/components/responses/emptyResponse" } }, "summary": "Get snapshot metrics from the Kratos service. If you're using k8s, you can then add annotations to\nyour deployment like so:", "tags": [ "admin" ] } }, "/recovery/link": { "post": { "description": "This endpoint creates a recovery link which should be given to the user in order for them to recover\n(or activate) their account.", "operationId": "createRecoveryLink", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateRecoveryLink" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryLink" } } }, "description": "recoveryLink" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Create a Recovery Link", "tags": [ "admin" ] } }, "/schemas/{id}": { "get": { "description": "Get a Traits Schema Definition", "operationId": "getSchema", "parameters": [ { "description": "ID must be set to the ID of schema you want to get", "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "$ref": "#/components/responses/schemaResponse" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "tags": [ "public", "admin" ] } }, "/self-service/browser/flows/logout": { "get": { "description": "This endpoint initializes a logout flow.\n\n\u003e This endpoint is NOT INTENDED for API clients and only works\nwith browsers (Chrome, Firefox, ...).\n\nOn successful logout, the browser will be redirected (HTTP 302 Found) to the `return_to` parameter of the initial request\nor fall back to `urls.default_return_to`.\n\nMore information can be found at [Ory Kratos User Logout Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-logout).", "operationId": "initializeSelfServiceBrowserLogoutFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Browser-Based Logout User Flow", "tags": [ "public" ] } }, "/self-service/browser/flows/registration/strategies/oidc/settings/connections": { "post": { "description": "This endpoint completes a browser-based settings flow. This is usually achieved by POSTing data to this\nendpoint.\n\n\u003e This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...) and HTML Forms.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "completeSelfServiceBrowserSettingsOIDCSettingsFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete the Browser-Based Settings Flow for the OpenID Connect Strategy", "tags": [ "public" ] } }, "/self-service/errors": { "get": { "description": "This endpoint returns the error associated with a user-facing self service errors.\n\nThis endpoint supports stub values to help you implement the error UI:\n\n`?error=stub:500` - returns a stub 500 (Internal Server Error) error.\n\nMore information can be found at [Ory Kratos User User Facing Error Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-facing-errors).", "operationId": "getSelfServiceError", "parameters": [ { "description": "Error is the container's ID", "in": "query", "name": "error", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "$ref": "#/components/responses/errorContainer" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get User-Facing Self-Service Errors", "tags": [ "public", "admin" ] } }, "/self-service/login/api": { "get": { "description": "This endpoint initiates a login flow for API clients such as mobile devices, smart TVs, and so on.\n\nIf a valid provided session cookie or session token is provided, a 400 Bad Request error\nwill be returned unless the URL query parameter `?refresh=true` is set.\n\nTo fetch an existing login flow call `/self-service/login/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks, including CSRF login attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceLoginViaAPIFlow", "parameters": [ { "description": "Refresh a login session\n\nIf set to true, this will refresh an existing login session by\nasking the user to sign in again. This will reset the\nauthenticated_at time of the session.", "in": "query", "name": "refresh", "schema": { "type": "boolean" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginFlow" } } }, "description": "loginFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Login Flow for API clients", "tags": [ "public" ] } }, "/self-service/login/browser": { "get": { "description": "This endpoint initializes a browser-based user login flow. Once initialized, the browser will be redirected to\n`selfservice.flows.login.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session\nexists already, the browser will be redirected to `urls.default_redirect_url` unless the query parameter\n`?refresh=true` was set.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceLoginViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Login Flow for browsers", "tags": [ "public" ] } }, "/self-service/login/flows": { "get": { "description": "This endpoint returns a login flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "getSelfServiceLoginFlow", "parameters": [ { "description": "The Login Flow ID\n\nThe value for this parameter comes from `flow` URL Query parameter sent to your\napplication (e.g. `/login?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginFlow" } } }, "description": "loginFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get Login Flow", "tags": [ "public", "admin" ] } }, "/self-service/login/methods/password": { "post": { "description": "Use this endpoint to complete a login flow by sending an identity's identifier and password. This endpoint\nbehaves differently for API and browser flows.\n\nAPI flows expect `application/json` to be sent in the body and responds with\nHTTP 200 and a application/json body with the session token on success;\nHTTP 302 redirect to a fresh login flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after login URL or the `return_to` value if it was set and if the login succeeded;\na HTTP 302 redirect to the login UI URL with the flow ID containing the validation errors otherwise.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "completeSelfServiceLoginFlowWithPasswordMethod", "parameters": [ { "description": "The Flow ID", "in": "query", "name": "flow", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceLoginFlowWithPasswordMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceLoginFlowWithPasswordMethod" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginViaApiResponse" } } }, "description": "loginViaApiResponse" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginFlow" } } }, "description": "loginFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Login Flow with Username/Email Password Method", "tags": [ "public" ] } }, "/self-service/recovery/api": { "get": { "description": "This endpoint initiates a recovery flow for API clients such as mobile devices, smart TVs, and so on.\n\nIf a valid provided session cookie or session token is provided, a 400 Bad Request error.\n\nTo fetch an existing recovery flow call `/self-service/recovery/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "initializeSelfServiceRecoveryViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryFlow" } } }, "description": "recoveryFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Recovery Flow for API Clients", "tags": [ "public" ] } }, "/self-service/recovery/browser": { "get": { "description": "This endpoint initializes a browser-based account recovery flow. Once initialized, the browser will be redirected to\n`selfservice.flows.recovery.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session\nexists, the browser is returned to the configured return URL.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "initializeSelfServiceRecoveryViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Recovery Flow for Browser Clients", "tags": [ "public" ] } }, "/self-service/recovery/flows": { "get": { "description": "This endpoint returns a recovery flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "getSelfServiceRecoveryFlow", "parameters": [ { "description": "The Flow ID\n\nThe value for this parameter comes from `request` URL Query parameter sent to your\napplication (e.g. `/recovery?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryFlow" } } }, "description": "recoveryFlow" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get information about a recovery flow", "tags": [ "public", "admin" ] } }, "/self-service/recovery/methods/link": { "post": { "description": "Use this endpoint to complete a recovery flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh recovery flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Recovery UI URL with the Recovery Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another recovery email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a recovery link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Recover UI URL with\na new Recovery Flow ID which contains an error message that the recovery link was invalid.\n\nMore information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery.mdx).", "operationId": "completeSelfServiceRecoveryFlowWithLinkMethod", "parameters": [ { "description": "Recovery Token\n\nThe recovery token which completes the recovery request. If the token\nis invalid (e.g. expired) an error will be shown to the end-user.", "in": "query", "name": "token", "schema": { "type": "string" } }, { "description": "The Flow ID\n\nformat: uuid", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/completeSelfServiceRecoveryFlowWithLinkMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/completeSelfServiceRecoveryFlowWithLinkMethod" } } }, "x-originalParamName": "Body" }, "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recoveryFlow" } } }, "description": "recoveryFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Recovery Flow with Link Method", "tags": [ "public" ] } }, "/self-service/registration/api": { "get": { "description": "This endpoint initiates a registration flow for API clients such as mobile devices, smart TVs, and so on.\n\nIf a valid provided session cookie or session token is provided, a 400 Bad Request error\nwill be returned unless the URL query parameter `?refresh=true` is set.\n\nTo fetch an existing registration flow call `/self-service/registration/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceRegistrationViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationFlow" } } }, "description": "registrationFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Registration Flow for API clients", "tags": [ "public" ] } }, "/self-service/registration/browser": { "get": { "description": "This endpoint initializes a browser-based user registration flow. Once initialized, the browser will be redirected to\n`selfservice.flows.registration.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session\nexists already, the browser will be redirected to `urls.default_redirect_url` unless the query parameter\n`?refresh=true` was set.\n\n:::note\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "initializeSelfServiceRegistrationViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Registration Flow for browsers", "tags": [ "public" ] } }, "/self-service/registration/flows": { "get": { "description": "This endpoint returns a registration flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "getSelfServiceRegistrationFlow", "parameters": [ { "description": "The Registration Flow ID\n\nThe value for this parameter comes from `flow` URL Query parameter sent to your\napplication (e.g. `/registration?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationFlow" } } }, "description": "registrationFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get Registration Flow", "tags": [ "public", "admin" ] } }, "/self-service/registration/methods/password": { "post": { "description": "Use this endpoint to complete a registration flow by sending an identity's traits and password. This endpoint\nbehaves differently for API and browser flows.\n\nAPI flows expect `application/json` to be sent in the body and respond with\nHTTP 200 and a application/json body with the created identity success - if the session hook is configured the\n`session` and `session_token` will also be included;\nHTTP 302 redirect to a fresh registration flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after registration URL or the `return_to` value if it was set and if the registration succeeded;\na HTTP 302 redirect to the registration UI URL with the flow ID containing the validation errors otherwise.\n\nMore information can be found at [Ory Kratos User Login and User Registration Documentation](https://www.ory.sh/docs/next/kratos/self-service/flows/user-login-user-registration).", "operationId": "completeSelfServiceRegistrationFlowWithPasswordMethod", "parameters": [ { "description": "Flow is flow ID.", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } }, "application/x-www-form-urlencoded": { "schema": { "type": "object" } } }, "x-originalParamName": "Payload" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationViaApiResponse" } } }, "description": "registrationViaApiResponse" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/registrationFlow" } } }, "description": "registrationFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Registration Flow with Username/Email Password Method", "tags": [ "public" ] } }, "/self-service/settings/api": { "get": { "description": "This endpoint initiates a settings flow for API clients such as mobile devices, smart TVs, and so on.\nYou must provide a valid Ory Kratos Session Token for this endpoint to respond with HTTP 200 OK.\n\nTo fetch an existing settings flow call `/self-service/settings/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "initializeSelfServiceSettingsViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Initialize Settings Flow for API Clients", "tags": [ "public" ] } }, "/self-service/settings/browser": { "get": { "description": "This endpoint initializes a browser-based user settings flow. Once initialized, the browser will be redirected to\n`selfservice.flows.settings.ui_url` with the flow ID set as the query parameter `?flow=`. If no valid\nOry Kratos Session Cookie is included in the request, a login flow will be initialized.\n\n:::note\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\n:::\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "initializeSelfServiceSettingsViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Initialize Settings Flow for Browsers", "tags": [ "public" ] } }, "/self-service/settings/flows": { "get": { "description": "When accessing this endpoint through Ory Kratos' Public API you must ensure that either the Ory Kratos Session Cookie\nor the Ory Kratos Session Token are set. The public endpoint does not return 404 status codes\nbut instead 403 or 500 to improve data privacy.\n\nYou can access this endpoint without credentials when using Ory Kratos' Admin API.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "getSelfServiceSettingsFlow", "parameters": [ { "description": "ID is the Settings Flow ID\n\nThe value for this parameter comes from `flow` URL Query parameter sent to your\napplication (e.g. `/settings?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "410": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Get Settings Flow", "tags": [ "public", "admin" ] } }, "/self-service/settings/methods/password": { "post": { "description": "Use this endpoint to complete a settings flow by sending an identity's updated password. This endpoint\nbehaves differently for API and browser flows.\n\nAPI-initiated flows expect `application/json` to be sent in the body and respond with\nHTTP 200 and an application/json body with the session token on success;\nHTTP 302 redirect to a fresh settings flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\nHTTP 401 when the endpoint is called without a valid session token.\nHTTP 403 when `selfservice.flows.settings.privileged_session_max_age` was reached.\nImplies that the user needs to re-authenticate.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after settings URL or the `return_to` value if it was set and if the flow succeeded;\na HTTP 302 redirect to the Settings UI URL with the flow ID containing the validation errors otherwise.\na HTTP 302 redirect to the login endpoint when `selfservice.flows.settings.privileged_session_max_age` was reached.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "completeSelfServiceSettingsFlowWithPasswordMethod", "parameters": [ { "description": "Flow is flow ID.", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceSettingsFlowWithPasswordMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/CompleteSelfServiceSettingsFlowWithPasswordMethod" } } }, "x-originalParamName": "Body" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsViaApiResponse" } } }, "description": "settingsViaApiResponse" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "401": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Complete Settings Flow with Username/Email Password Method", "tags": [ "public" ] } }, "/self-service/settings/methods/profile": { "post": { "description": "Use this endpoint to complete a settings flow by sending an identity's updated traits. This endpoint\nbehaves differently for API and browser flows.\n\nAPI-initiated flows expect `application/json` to be sent in the body and respond with\nHTTP 200 and an application/json body with the session token on success;\nHTTP 302 redirect to a fresh settings flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\nHTTP 401 when the endpoint is called without a valid session token.\nHTTP 403 when `selfservice.flows.settings.privileged_session_max_age` was reached and a sensitive field was\nupdated (e.g. recovery email). Implies that the user needs to re-authenticate.\n\nBrowser flows expect `application/x-www-form-urlencoded` to be sent in the body and responds with\na HTTP 302 redirect to the post/after settings URL or the `return_to` value if it was set and if the flow succeeded;\na HTTP 302 redirect to the settings UI URL with the flow ID containing the validation errors otherwise.\na HTTP 302 redirect to the login endpoint when `selfservice.flows.settings.privileged_session_max_age` was reached.\n\nMore information can be found at [Ory Kratos User Settings \u0026 Profile Management Documentation](../self-service/flows/user-settings).", "operationId": "completeSelfServiceSettingsFlowWithProfileMethod", "parameters": [ { "description": "Flow is flow ID.", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } }, "application/x-www-form-urlencoded": { "schema": { "type": "object" } } }, "x-originalParamName": "Payload" }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/settingsFlow" } } }, "description": "settingsFlow" }, "401": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Complete Settings Flow with Profile Method", "tags": [ "public" ] } }, "/self-service/verification/api": { "get": { "description": "This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on.\n\nTo fetch an existing verification flow call `/self-service/verification/flows?flow=\u003cflow_id\u003e`.\n\n:::warning\n\nYou MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server\nPages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make\nyou vulnerable to a variety of CSRF attacks.\n\nThis endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).\n\n:::\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaAPIFlow", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/verificationFlow" } } }, "description": "verificationFlow" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Verification Flow for API Clients", "tags": [ "public" ] } }, "/self-service/verification/browser": { "get": { "description": "This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to\n`selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`.\n\nThis endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...).\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "initializeSelfServiceVerificationViaBrowserFlow", "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Initialize Verification Flow for Browser Clients", "tags": [ "public" ] } }, "/self-service/verification/flows": { "get": { "description": "This endpoint returns a verification flow's context with, for example, error details and other information.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "getSelfServiceVerificationFlow", "parameters": [ { "description": "The Flow ID\n\nThe value for this parameter comes from `request` URL Query parameter sent to your\napplication (e.g. `/verification?flow=abcde`).", "in": "query", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/verificationFlow" } } }, "description": "verificationFlow" }, "403": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "404": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Get Verification Flow", "tags": [ "public", "admin" ] } }, "/self-service/verification/methods/link": { "post": { "description": "Use this endpoint to complete a verification flow using the link method. This endpoint\nbehaves differently for API and browser flows and has several states:\n\n`choose_method` expects `flow` (in the URL query) and `email` (in the body) to be sent\nand works with API- and Browser-initiated flows.\nFor API clients it either returns a HTTP 200 OK when the form is valid and HTTP 400 OK when the form is invalid\nand a HTTP 302 Found redirect with a fresh verification flow if the flow was otherwise invalid (e.g. expired).\nFor Browser clients it returns a HTTP 302 Found redirect to the Verification UI URL with the Verification Flow ID appended.\n`sent_email` is the success state after `choose_method` and allows the user to request another verification email. It\nworks for both API and Browser-initiated flows and returns the same responses as the flow in `choose_method` state.\n`passed_challenge` expects a `token` to be sent in the URL query and given the nature of the flow (\"sending a verification link\")\ndoes not have any API capabilities. The server responds with a HTTP 302 Found redirect either to the Settings UI URL\n(if the link was valid) and instructs the user to update their password, or a redirect to the Verification UI URL with\na new Verification Flow ID which contains an error message that the verification link was invalid.\n\nMore information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).", "operationId": "completeSelfServiceVerificationFlowWithLinkMethod", "parameters": [ { "description": "Verification Token\n\nThe verification token which completes the verification request. If the token\nis invalid (e.g. expired) an error will be shown to the end-user.", "in": "query", "name": "token", "schema": { "type": "string" } }, { "description": "The Flow ID\n\nformat: uuid", "in": "query", "name": "flow", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/completeSelfServiceVerificationFlowWithLinkMethod" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/completeSelfServiceVerificationFlowWithLinkMethod" } } }, "x-originalParamName": "Body" }, "responses": { "302": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/verificationFlow" } } }, "description": "verificationFlow" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Complete Verification Flow with Link Method", "tags": [ "public" ] } }, "/sessions": { "delete": { "description": "Use this endpoint to revoke a session using its token. This endpoint is particularly useful for API clients\nsuch as mobile apps to log the user out of the system and invalidate the session.\n\nThis endpoint does not remove any HTTP Cookies - use the Self-Service Logout Flow instead.", "operationId": "revokeSession", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/revokeSession" } } }, "required": true, "x-originalParamName": "Body" }, "responses": { "204": { "$ref": "#/components/responses/emptyResponse" }, "400": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "summary": "Revoke and Invalidate a Session", "tags": [ "public" ] } }, "/sessions/whoami": { "get": { "description": "Uses the HTTP Headers in the GET request to determine (e.g. by using checking the cookies) who is authenticated.\nReturns a session object in the body or 401 if the credentials are invalid or no credentials were sent.\nAdditionally when the request it successful it adds the user ID to the 'X-Kratos-Authenticated-Identity-Id' header in the response.\n\nThis endpoint is useful for reverse proxies and API Gateways.", "operationId": "whoami", "parameters": [ { "in": "header", "name": "Cookie", "schema": { "type": "string" } }, { "description": "in: authorization", "in": "query", "name": "Authorization", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/session" } } }, "description": "session" }, "401": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" }, "500": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/genericError" } } }, "description": "genericError" } }, "security": [ { "sessionToken": [] } ], "summary": "Check Who the Current HTTP Session Belongs To", "tags": [ "public" ] } }, "/version": { "get": { "description": "This endpoint returns the version of Ory Kratos.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the version will never\nrefer to the cluster state, only to a single instance.", "operationId": "getVersion", "responses": { "200": { "content": { "application/json": { "schema": { "properties": { "version": { "description": "The version of Ory Kratos.", "type": "string" } }, "type": "object" } } }, "description": "Returns the Ory Kratos version." } }, "summary": "Return Running Software Version.", "tags": [ "admin" ] } } }, "servers": [ { "url": "https://{tenant}.tenants.oryapis.com/api/kratos/{api}", "variables": { "api": { "default": "public", "description": "Target the public or administrative API.", "enum": [ "public", "admin" ] }, "tenant": { "default": "demo", "description": "Tenant ID as provided by Ory Cloud." } } } ], "tags": [ { "description": "All administrative API endpoints exposed at the admin API port.", "externalDocs": { "url": "https://www.ory.sh/kratos/docs/reference/api" }, "name": "admin" }, { "description": "All public API endpoints exposed at the public API port.", "externalDocs": { "url": "https://www.ory.sh/kratos/docs/reference/api" }, "name": "public" } ], "x-forwarded-proto": "string", "x-request-id": "string" } ================================================ FILE: .schema/version.schema.json ================================================ { "$id": "https://github.com/ory/kratos/.schema/versions.config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "oneOf": [ { "allOf": [ { "properties": { "version": { "const": "v1.3.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v1.3.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v1.2.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v1.2.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v1.1.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v1.1.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v1.0.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v1.0.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.13.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.13.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.11.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.11.1/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.11.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.11.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.10.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.10.1/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.10.0" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.10.0/.schemastore/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.9.0-alpha.3" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.9.0-alpha.3/embedx/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.9.0-alpha.2" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.9.0-alpha.2/embedx/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.4.6-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.4.6-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.5.0-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.0-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.5.1-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.1-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.5.2-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.2-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.5.3-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.3-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.5.4-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.4-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.5.5-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.5-alpha.1/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.6.0-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.0-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.6.0-alpha.2" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.0-alpha.2/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.6.1-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.1-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.6.2-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.2-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.6.3-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.3-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.7.0-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.0-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.7.1-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.1-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.7.3-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.3-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.7.4-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.4-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.7.5-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.5-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.7.6-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.6-alpha.1/driver/config/.schema/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.8.0-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.0-alpha.1/embedx/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.8.0-alpha.2" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.0-alpha.2/embedx/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.8.0-alpha.3" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.0-alpha.3/embedx/config.schema.json" } ] }, { "allOf": [ { "properties": { "version": { "const": "v0.8.2-alpha.1" } }, "required": [ "version" ] }, { "$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.2-alpha.1/embedx/config.schema.json" } ] }, { "allOf": [ { "oneOf": [ { "properties": { "version": { "type": "string", "maxLength": 0 } }, "required": [ "version" ] }, { "not": { "properties": { "version": {} }, "required": [ "version" ] } } ] }, { "$ref": "#/oneOf/0/allOf/1" } ] } ], "title": "All Versions for Ory Kratos Configuration", "type": "object" } ================================================ FILE: .schemastore/README.md ================================================ The config schema is generated from the internal one at `embedx/config.schema.json`, so in case of changes to the config schema, please edit that internal schema instead. ================================================ FILE: .schemastore/config.schema.json ================================================ { "$id": "https://github.com/ory/kratos/embedx/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Ory Kratos Configuration", "type": "object", "definitions": { "baseUrl": { "title": "Base URL", "description": "The URL where the endpoint is exposed at. This domain is used to generate redirects, form URLs, and more.", "type": "string", "format": "uri-reference", "examples": [ "https://my-app.com/", "https://my-app.com/.ory/kratos/public" ] }, "socket": { "type": "object", "additionalProperties": false, "description": "Sets the permissions of the unix socket", "properties": { "owner": { "type": "string", "description": "Owner of unix socket. If empty, the owner will be the user running Kratos.", "default": "" }, "group": { "type": "string", "description": "Group of unix socket. If empty, the group will be the primary group of the user running Kratos.", "default": "" }, "mode": { "type": "integer", "description": "Mode of unix socket in numeric form", "default": 493, "minimum": 0, "maximum": 511 } } }, "defaultReturnTo": { "title": "Redirect browsers to set URL per default", "description": "Ory Kratos redirects to this URL per default on completion of self-service flows and other browser interaction. Read this [article for more information on browser redirects](https://www.ory.sh/kratos/docs/concepts/browser-redirect-flow-completion).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/dashboard", "/dashboard"] }, "selfServiceSessionRevokerHook": { "type": "object", "properties": { "hook": { "const": "revoke_active_sessions" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceSessionIssuerHook": { "type": "object", "properties": { "hook": { "const": "session" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceRequireVerifiedAddressHook": { "type": "object", "properties": { "hook": { "const": "require_verified_address" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceVerificationHook": { "type": "object", "properties": { "hook": { "const": "verification" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceShowVerificationUIHook": { "type": "object", "properties": { "hook": { "const": "show_verification_ui" } }, "additionalProperties": false, "required": ["hook"] }, "b2bSSOHook": { "type": "object", "properties": { "hook": { "enum": ["b2b_sso", "organization"] }, "config": { "type": "object", "additionalProperties": true } }, "additionalProperties": false, "required": ["hook", "config"] }, "webHookAuthBasicAuthProperties": { "properties": { "type": { "const": "basic_auth" }, "config": { "type": "object", "properties": { "user": { "type": "string", "description": "user name for basic auth" }, "password": { "type": "string", "description": "password for basic auth" } }, "additionalProperties": false, "required": ["user", "password"] } }, "additionalProperties": false, "required": ["type", "config"] }, "httpRequestConfig": { "type": "object", "properties": { "url": { "title": "HTTP address of API endpoint", "description": "This URL will be used to send the emails to.", "examples": ["https://example.com/api/v1/email"], "type": "string", "pattern": "^https?://" }, "method": { "type": "string", "description": "The HTTP method to use (GET, POST, etc). Defaults to POST.", "default": "POST" }, "headers": { "type": "object", "description": "The HTTP headers that must be applied to request", "additionalProperties": { "type": "string" } }, "body": { "type": "string", "format": "uri", "pattern": "^(http|https|file|base64)://", "description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods which support HTTP body payloads", "default": "base64://ZnVuY3Rpb24oY3R4KSB7CiAgcmVjaXBpZW50OiBjdHgucmVjaXBpZW50LAogIHRlbXBsYXRlX3R5cGU6IGN0eC50ZW1wbGF0ZV90eXBlLAogIHRvOiBpZiAidGVtcGxhdGVfZGF0YSIgaW4gY3R4ICYmICJ0byIgaW4gY3R4LnRlbXBsYXRlX2RhdGEgdGhlbiBjdHgudGVtcGxhdGVfZGF0YS50byBlbHNlIG51bGwsCiAgcmVjb3ZlcnlfY29kZTogaWYgInRlbXBsYXRlX2RhdGEiIGluIGN0eCAmJiAicmVjb3ZlcnlfY29kZSIgaW4gY3R4LnRlbXBsYXRlX2RhdGEgdGhlbiBjdHgudGVtcGxhdGVfZGF0YS5yZWNvdmVyeV9jb2RlIGVsc2UgbnVsbCwKICByZWNvdmVyeV91cmw6IGlmICJ0ZW1wbGF0ZV9kYXRhIiBpbiBjdHggJiYgInJlY292ZXJ5X3VybCIgaW4gY3R4LnRlbXBsYXRlX2RhdGEgdGhlbiBjdHgudGVtcGxhdGVfZGF0YS5yZWNvdmVyeV91cmwgZWxzZSBudWxsLAogIHZlcmlmaWNhdGlvbl91cmw6IGlmICJ0ZW1wbGF0ZV9kYXRhIiBpbiBjdHggJiYgInZlcmlmaWNhdGlvbl91cmwiIGluIGN0eC50ZW1wbGF0ZV9kYXRhIHRoZW4gY3R4LnRlbXBsYXRlX2RhdGEudmVyaWZpY2F0aW9uX3VybCBlbHNlIG51bGwsCiAgdmVyaWZpY2F0aW9uX2NvZGU6IGlmICJ0ZW1wbGF0ZV9kYXRhIiBpbiBjdHggJiYgInZlcmlmaWNhdGlvbl9jb2RlIiBpbiBjdHgudGVtcGxhdGVfZGF0YSB0aGVuIGN0eC50ZW1wbGF0ZV9kYXRhLnZlcmlmaWNhdGlvbl9jb2RlIGVsc2UgbnVsbCwKICBzdWJqZWN0OiBjdHguc3ViamVjdCwKICBib2R5OiBjdHguYm9keQp9Cg==", "examples": [ "file:///path/to/body.jsonnet", "file://./body.jsonnet", "base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=", "https://oryapis.com/default_body.jsonnet" ] }, "auth": { "type": "object", "title": "Auth mechanisms", "description": "Define which auth mechanism to use for auth with the HTTP email provider", "oneOf": [ { "$ref": "#/definitions/webHookAuthApiKeyProperties" }, { "$ref": "#/definitions/webHookAuthBasicAuthProperties" } ] }, "additionalProperties": false }, "additionalProperties": false }, "webHookAuthApiKeyProperties": { "properties": { "type": { "const": "api_key" }, "config": { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the api key" }, "value": { "type": "string", "description": "The value of the api key" }, "in": { "type": "string", "description": "How the api key should be transferred", "enum": ["header", "cookie"] } }, "additionalProperties": false, "required": ["name", "value", "in"] } }, "additionalProperties": false, "required": ["type", "config"] }, "selfServiceWebHook": { "type": "object", "properties": { "hook": { "const": "web_hook" }, "config": { "type": "object", "title": "Web-Hook Configuration", "description": "Define what the hook should do", "properties": { "id": { "type": "string", "description": "The ID of the hook. Used to identify the hook in logs and errors. For debugging purposes only." }, "response": { "title": "Response Handling", "description": "How the web hook should handle the response", "type": "object", "additionalProperties": false, "properties": { "ignore": { "type": "boolean", "description": "Ignore the response from the web hook. If enabled the request will be made asynchronously which can be useful if you only wish to notify another system but do not parse the response.", "default": false }, "parse": { "type": "boolean", "default": false, "description": "If enabled parses the response before saving the flow result. Set this value to true if you would like to modify the identity, for example identity metadata, before saving it during registration. When enabled, you may also abort the registration, verification, login or settings flow due to, for example, a validation flow. Head over to the [web hook documentation](https://www.ory.sh/docs/kratos/hooks/configure-hooks) for more information." } }, "not": { "properties": { "ignore": { "const": true }, "parse": { "const": true } }, "required": ["ignore", "parse"] } }, "url": { "type": "string", "description": "The URL the Web-Hook should call", "format": "uri" }, "method": { "type": "string", "description": "The HTTP method to use (GET, POST, etc)." }, "headers": { "type": "object", "description": "The HTTP headers that must be applied to the Web-Hook", "additionalProperties": { "type": "string" } }, "body": { "type": "string", "oneOf": [ { "format": "uri", "pattern": "^(http|https|file|base64)://", "description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods, which support HTTP body payloads", "examples": [ "file:///path/to/body.jsonnet", "file://./body.jsonnet", "base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=", "https://oryapis.com/default_body.jsonnet" ] }, { "description": "DEPRECATED: please use a URI instead (i.e. prefix your filepath with 'file://')", "not": { "pattern": "^(http|https|file|base64)://" } } ] }, "can_interrupt": { "type": "boolean", "default": false, "description": "Deprecated, please use `response.parse` instead. If enabled allows the web hook to interrupt / abort the self-service flow. It only applies to certain flows (registration/verification/login/settings) and requires a valid response format." }, "emit_analytics_event": { "type": "boolean", "default": true, "description": "Emit tracing events for this webhook on delivery or error" }, "auth": { "type": "object", "title": "Auth mechanisms", "description": "Define which auth mechanism the Web-Hook should use", "oneOf": [ { "$ref": "#/definitions/webHookAuthApiKeyProperties" }, { "$ref": "#/definitions/webHookAuthBasicAuthProperties" } ] }, "additionalProperties": false }, "anyOf": [ { "not": { "properties": { "response": { "properties": { "ignore": { "const": true } }, "required": ["ignore"] } }, "required": ["response"] } }, { "properties": { "can_interrupt": { "const": false } }, "require": ["can_interrupt"] } ], "additionalProperties": false, "required": ["url", "method"] } }, "additionalProperties": false, "required": ["hook", "config"] }, "OIDCClaims": { "title": "OpenID Connect claims", "description": "The OpenID Connect claims and optionally their properties which should be included in the id_token or returned from the UserInfo Endpoint.", "type": "object", "examples": [ { "id_token": { "email": null, "email_verified": null } }, { "userinfo": { "given_name": { "essential": true }, "nickname": null, "email": { "essential": true }, "email_verified": { "essential": true }, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": { "essential": true }, "acr": { "values": ["urn:mace:incommon:iap:silver"] } } } ], "patternProperties": { "^userinfo$|^id_token$": { "type": "object", "additionalProperties": false, "patternProperties": { ".*": { "oneOf": [ { "const": null, "description": "Indicates that this Claim is being requested in the default manner." }, { "type": "object", "additionalProperties": false, "properties": { "essential": { "description": "Indicates whether the Claim being requested is an Essential Claim.", "type": "boolean" }, "value": { "description": "Requests that the Claim be returned with a particular value.", "$comment": "There seem to be no constrains on value" }, "values": { "description": "Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.", "type": "array", "items": { "$comment": "There seem to be no constrains on individual items" } } } } ] } } } } }, "selfServiceOIDCProvider": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon, amazon.", "type": "string", "enum": [ "github", "github-app", "gitlab", "generic", "google", "microsoft", "discord", "salesforce", "slack", "facebook", "auth0", "vk", "yandex", "apple", "spotify", "netid", "dingtalk", "patreon", "line", "linkedin", "linkedin_v2", "lark", "x", "fedcm-test", "amazon", "uaepass" ], "examples": ["google"] }, "label": { "title": "Optional string which will be used when generating labels for UI buttons.", "type": "string" }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to Ory Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "microsoft_tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] }, "subject_source": { "title": "Microsoft subject source", "description": "Controls which source the subject identifier is taken from by microsoft provider. If set to `userinfo` (the default) then the identifier is taken from the `sub` field of OIDC ID token or data received from `/userinfo` standard OIDC endpoint. If set to `me` then the `id` field of data structure received from `https://graph.microsoft.com/v1.0/me` is taken as an identifier. If the value is `oid` then the the oid (Object ID) is taken to identify users across different services.", "type": "string", "enum": ["userinfo", "me", "oid"], "default": "userinfo", "examples": ["userinfo"] }, "apple_team_id": { "title": "Apple Developer Team ID", "description": "Apple Developer Team ID needed for generating a JWT token for client secret", "type": "string", "examples": ["KP76DQS54M"] }, "apple_private_key_id": { "title": "Apple Private Key Identifier", "description": "Sign In with Apple Private Key Identifier needed for generating a JWT token for client secret", "type": "string", "examples": ["UX56C66723"] }, "apple_private_key": { "title": "Apple Private Key", "description": "Sign In with Apple Private Key needed for generating a JWT token for client secret", "type": "string", "examples": [ "-----BEGIN PRIVATE KEY-----\n........\n-----END PRIVATE KEY-----" ] }, "requested_claims": { "$ref": "#/definitions/OIDCClaims" }, "organization_id": { "title": "Organization ID", "description": "The ID of the organization that this provider belongs to. Only effective in the Ory Network.", "type": "string", "examples": ["12345678-1234-1234-1234-123456789012"] }, "additional_id_token_audiences": { "title": "Additional client ids allowed when using ID token submission", "type": "array", "items": { "type": "string", "examples": ["12345678-1234-1234-1234-123456789012"] } }, "claims_source": { "title": "Claims source", "description": "Can be either `userinfo` (calls the userinfo endpoint to get the claims) or `id_token` (takes the claims from the id token). It defaults to `id_token`", "type": "string", "enum": ["id_token", "userinfo"], "default": "id_token", "examples": ["id_token", "userinfo"] }, "pkce": { "title": "Proof Key for Code Exchange", "description": "PKCE controls if the OpenID Connect OAuth2 flow should use PKCE (Proof Key for Code Exchange). IMPORTANT: If you set this to `force`, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. Instead of /self-service/methods/oidc/callback/, you must use /self-service/methods/oidc/callback", "type": "string", "enum": ["auto", "never", "force"], "default": "auto" }, "fedcm_config_url": { "title": "Federation Configuration URL", "description": "The URL where the FedCM IdP configuration is located for the provider. This is only effective in the Ory Network.", "type": "string", "format": "uri", "examples": ["https://example.com/config.json"] }, "net_id_token_origin_header": { "title": "NetID Token Origin Header", "description": "Contains the orgin header to be used when exchanging a NetID FedCM token for an ID token", "type": "string", "examples": ["https://example.com"] }, "account_linking_mode": { "title": "Account linking mode", "description": "Controls how account conflicts are resolved for this provider. `confirm_with_existing_credential` (default) requires the user to verify their identity with an existing credential. `automatic` silently links accounts if the provider verifies email ownership. This is only effective in the Ory Network.", "type": "string", "enum": ["confirm_with_existing_credential", "automatic"], "default": "confirm_with_existing_credential" } }, "additionalProperties": false, "required": ["id", "provider", "client_id", "mapper_url"], "allOf": [ { "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["microsoft_tenant"] }, "else": { "not": { "properties": { "microsoft_tenant": {} }, "required": ["microsoft_tenant"] } } }, { "if": { "properties": { "provider": { "const": "apple" } }, "required": ["provider"] }, "then": { "not": { "properties": { "client_secret": { "type": "string", "minLength": 1 } }, "required": ["client_secret"] }, "required": [ "apple_private_key_id", "apple_private_key", "apple_team_id" ] }, "else": { "required": ["client_secret"], "allOf": [ { "not": { "properties": { "apple_team_id": { "type": "string", "minLength": 1 } }, "required": ["apple_team_id"] } }, { "not": { "properties": { "apple_private_key_id": { "type": "string", "minLength": 1 } }, "required": ["apple_private_key_id"] } }, { "not": { "properties": { "apple_private_key": { "type": "string", "minLength": 1 } }, "required": ["apple_private_key"] } } ] } } ] }, "selfServiceHooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false }, "selfServiceAfterRecoveryHooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false }, "selfServiceAfterSettingsProfileMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceShowVerificationUIHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterSettingsAuthMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterDefaultLoginMethodHooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" }, { "$ref": "#/definitions/selfServiceRequireVerifiedAddressHook" }, { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceVerificationHook" }, { "$ref": "#/definitions/selfServiceShowVerificationUIHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false }, "selfServiceAfterDefaultLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethodHooks" } } }, "selfServiceAfterOIDCLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" }, { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceRequireVerifiedAddressHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterRegistrationMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionIssuerHook" }, { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceShowVerificationUIHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "featureRequiredAal": { "title": "Required Authenticator Assurance Level", "description": "Sets what Authenticator Assurance Level (used for 2FA) is required to access this feature. If set to `highest_available` then this endpoint requires the highest AAL the identity has set up. If set to `aal1` then the identity can access this feature without 2FA.", "type": "string", "enum": ["aal1", "highest_available"], "default": "highest_available" }, "selfServiceAfterSettings": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "totp": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "webauthn": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "passkey": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "lookup_secret": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "profile": { "$ref": "#/definitions/selfServiceAfterSettingsProfileMethod" }, "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeLogin": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterLogin": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "webauthn": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "passkey": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterOIDCLoginMethod" }, "code": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "totp": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "lookup_secret": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "hooks": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethodHooks" } } }, "selfServiceBeforeRegistration": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeSettings": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeRecovery": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeVerification": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterRegistration": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "webauthn": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "passkey": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "code": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterVerification": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterRecovery": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "$ref": "#/definitions/selfServiceAfterRecoveryHooks" } }, "additionalProperties": false }, "tlsxSource": { "type": "object", "additionalProperties": false, "properties": { "path": { "title": "Path to PEM-encoded Fle", "type": "string", "examples": ["path/to/file.pem"] }, "base64": { "title": "Base64 Encoded Inline", "description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "type": "string", "examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ] } } }, "tlsx": { "title": "HTTPS", "description": "Configure HTTP over TLS (HTTPS). All options can also be set using environment variables by replacing dots (`.`) with underscores (`_`) and uppercasing the key. For example, `some.prefix.tls.key.path` becomes `export SOME_PREFIX_TLS_KEY_PATH`. If all keys are left undefined, TLS will be disabled.", "type": "object", "additionalProperties": false, "properties": { "key": { "title": "Private Key (PEM)", "allOf": [ { "$ref": "#/definitions/tlsxSource" } ] }, "cert": { "title": "TLS Certificate (PEM)", "allOf": [ { "$ref": "#/definitions/tlsxSource" } ] } } }, "courierTemplates": { "additionalProperties": false, "type": "object", "properties": { "invalid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" } }, "required": ["email"] }, "valid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" }, "sms": { "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] } } }, "smsCourierTemplate": { "additionalProperties": false, "type": "object", "properties": { "body": { "additionalProperties": false, "type": "object", "properties": { "plaintext": { "type": "string", "description": "A template send to the SMS provider.", "format": "uri", "examples": [ "file://path/to/body.plaintext.gotmpl", "https://foo.bar.com/path/to/body.plaintext.gotmpl" ] } } } } }, "emailCourierTemplate": { "additionalProperties": false, "type": "object", "properties": { "body": { "additionalProperties": false, "type": "object", "properties": { "plaintext": { "type": "string", "description": "The fallback template for email clients that do not support html.", "format": "uri", "examples": [ "file://path/to/body.plaintext.gotmpl", "https://foo.bar.com/path/to/body.plaintext.gotmpl", "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQp7eyAuUmVjb3ZlcnlVUkwgfX0K" ] }, "html": { "type": "string", "description": "The default template used for sending out emails. The template can contain HTML ", "format": "uri", "examples": [ "file://path/to/body.html.gotmpl", "https://foo.bar.com/path/to/body.html.gotmpl", "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4" ] } } }, "subject": { "type": "string", "format": "uri", "examples": [ "file://path/to/subject.gotmpl", "https://foo.bar.com/path/to/subject.gotmpl", "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4" ] } } } }, "properties": { "selfservice": { "type": "object", "additionalProperties": false, "required": ["default_browser_return_url"], "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "allowed_return_urls": { "title": "Allowed Return To URLs", "description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "examples": [ [ "https://app.my-app.com/dashboard", "/dashboard", "https://www.my-app.com/", "https://*.my-app.com/" ] ] }, "flows": { "type": "object", "additionalProperties": false, "properties": { "settings": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "URL of the Settings page.", "description": "URL where the Settings UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/user/settings"], "default": "https://www.ory.sh/kratos/docs/fallback/settings" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "privileged_session_max_age": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "required_aal": { "$ref": "#/definitions/featureRequiredAal" }, "after": { "$ref": "#/definitions/selfServiceAfterSettings" }, "before": { "$ref": "#/definitions/selfServiceBeforeSettings" } } }, "logout": { "type": "object", "additionalProperties": false, "properties": { "after": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } } } } }, "registration": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable User Registration", "description": "If set to true will enable [User Registration](https://www.ory.sh/kratos/docs/self-service/flows/user-registration/).", "default": true }, "login_hints": { "type": "boolean", "title": "Provide Login Hints on Failed Registration", "description": "When registration fails because an account with the given credentials or addresses previously signed up, provide login hints about available methods to sign in to the user.", "default": false }, "ui_url": { "title": "Registration UI URL", "description": "URL where the Registration UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/signup"], "default": "https://www.ory.sh/kratos/docs/fallback/registration" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "before": { "$ref": "#/definitions/selfServiceBeforeRegistration" }, "after": { "$ref": "#/definitions/selfServiceAfterRegistration" }, "enable_legacy_one_step": { "type": "boolean", "title": "Disable two-step registration", "description": "Deprecated, please use `style` instead.", "deprecationMessage": "Deprecated, please use `style` instead.", "default": false }, "style": { "title": "Registration Flow Style", "description": "The style of the registration flow. If set to `unified` the login flow will be a one-step process. If set to `profile_first` the registration flow will first ask for the profile information first, and then the credentials.", "type": "string", "enum": ["unified", "profile_first"], "default": "profile_first" } } }, "login": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Login UI URL", "description": "URL where the Login UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/login"], "default": "https://www.ory.sh/kratos/docs/fallback/login" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "style": { "title": "Login Flow Style", "description": "The style of the login flow. If set to `unified` the login flow will be a one-step process. If set to `identifier_first` (experimental!) the login flow will first ask for the identifier and then the credentials.", "type": "string", "enum": ["unified", "identifier_first"], "default": "unified" }, "before": { "$ref": "#/definitions/selfServiceBeforeLogin" }, "after": { "$ref": "#/definitions/selfServiceAfterLogin" } } }, "verification": { "title": "Email and Phone Verification and Account Activation Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Email/Phone Verification", "description": "If set to true will enable [Email and Phone Verification and Account Activation](https://www.ory.sh/kratos/docs/self-service/flows/verify-email-account-activation/).", "default": false }, "ui_url": { "title": "Verify UI URL", "description": "URL where the Ory Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/verification" }, "after": { "$ref": "#/definitions/selfServiceAfterVerification" }, "lifespan": { "title": "Self-Service Verification Request Lifespan", "description": "Sets how long the verification request (for the UI interaction) is valid.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "before": { "$ref": "#/definitions/selfServiceBeforeVerification" }, "use": { "title": "Verification Strategy", "description": "The strategy to use for verification requests", "type": "string", "enum": ["link", "code"], "default": "code" }, "notify_unknown_recipients": { "title": "Notify unknown recipients", "description": "Whether to notify recipients, if verification was requested for their address.", "type": "boolean", "default": false } } }, "recovery": { "title": "Account Recovery Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Account Recovery", "description": "If set to true will enable [Account Recovery](https://www.ory.sh/kratos/docs/self-service/flows/password-reset-account-recovery/).", "default": false }, "ui_url": { "title": "Recovery UI URL", "description": "URL where the Ory Recovery UI is hosted. This is the page where users request and complete account recovery. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/recovery" }, "after": { "$ref": "#/definitions/selfServiceAfterRecovery" }, "lifespan": { "title": "Self-Service Recovery Request Lifespan", "description": "Sets how long the recovery request is valid. If expired, the user has to redo the flow.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "before": { "$ref": "#/definitions/selfServiceBeforeRecovery" }, "use": { "title": "Recovery Strategy", "description": "The strategy to use for recovery requests", "type": "string", "enum": ["link", "code"], "default": "code" }, "notify_unknown_recipients": { "title": "Notify unknown recipients", "description": "Whether to notify recipients, if recovery was requested for their account.", "type": "boolean", "default": false } } }, "error": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Ory Kratos Error UI URL", "description": "URL where the Ory Kratos Error UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/kratos-error"], "default": "https://www.ory.sh/kratos/docs/fallback/error" } } } } }, "methods": { "type": "object", "additionalProperties": false, "properties": { "b2b": { "title": "Single Sign-On for B2B", "description": "Single Sign-On for B2B allows your customers to bring their own (workforce) identity server (e.g. OneLogin). This feature is not available in the open source licensed code.", "type": "object", "properties": { "config": { "type": "object", "additionalProperties": false, "properties": { "organizations": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "The ID of the organization.", "format": "uuid", "examples": ["00000000-0000-0000-0000-000000000000"] }, "label": { "type": "string", "description": "The label of the organization.", "examples": ["ACME SSO"] }, "domains": { "type": "array", "items": { "type": "string", "format": "hostname", "examples": ["my-app.com"], "description": "If this domain matches the email's domain, this provider is shown." } } } } } } } }, "additionalProperties": false }, "profile": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Profile Management Method", "default": true } } }, "link": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Link Method", "default": false }, "config": { "type": "object", "title": "Link Configuration", "description": "Additional configuration for the link strategy.", "properties": { "base_url": { "title": "Override the base URL which should be used as the base for recovery and verification links.", "type": "string", "deprecationMessage": "This option has no effect, because the request URL is now used as the base URL.", "examples": ["https://my-app.com"] }, "lifespan": { "title": "How long a link is valid for", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] } } } } }, "code": { "type": "object", "additionalProperties": true, "anyOf": [ { "properties": { "passwordless_enabled": { "const": true }, "mfa_enabled": { "const": false } } }, { "properties": { "mfa_enabled": { "const": true }, "passwordless_enabled": { "const": false } } }, { "properties": { "mfa_enabled": { "const": false }, "passwordless_enabled": { "const": false } } } ], "properties": { "passwordless_enabled": { "type": "boolean", "title": "Enables login and registration with the code method.", "description": "If set to true, code.enabled will be set to true as well.", "default": false }, "mfa_enabled": { "type": "boolean", "title": "Enables login flows code method to fulfil MFA requests", "default": false }, "enabled": { "type": "boolean", "title": "Enables Code Method", "default": true }, "config": { "type": "object", "title": "Code Configuration", "description": "Additional configuration for the code strategy.", "properties": { "lifespan": { "title": "How long a code is valid for", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "max_submissions": { "type": "integer", "title": "Maximum number of times the code can be submitted before a flow is invalidated", "minimum": 1, "maximum": 255, "default": 5 }, "missing_credential_fallback_enabled": { "type": "boolean", "title": "Enable Code OTP as a Fallback", "description": "Enabling this allows users to sign in with the code method, even if their identity schema or their credentials are not set up to use the code method. If enabled, a verified address (such as an email) will be used to send the code to the user. Use with caution and only if actually needed.", "default": false } } } } }, "password": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Username/Email and Password Method", "default": true }, "config": { "type": "object", "title": "Password Configuration", "description": "Define how passwords are validated.", "properties": { "haveibeenpwned_host": { "title": "Custom haveibeenpwned host", "description": "Allows changing the default HIBP host to a self hosted version.", "type": "string", "default": "api.pwnedpasswords.com" }, "haveibeenpwned_enabled": { "title": "Enable the HaveIBeenPwned API", "description": "If set to false the password validation does not utilize the Have I Been Pwnd API.", "type": "boolean", "default": true }, "max_breaches": { "title": "Allow Password Breaches", "description": "Defines how often a password may have been breached before it is rejected.", "type": "integer", "minimum": 0, "maximum": 100, "default": 0 }, "ignore_network_errors": { "title": "Ignore Lookup Network Errors", "description": "If set to false the password validation fails when the network or the Have I Been Pwnd API is down.", "type": "boolean", "default": true }, "min_password_length": { "title": "Minimum Password Length", "description": "Defines the minimum length of the password.", "type": "integer", "default": 8, "minimum": 6 }, "identifier_similarity_check_enabled": { "title": "Enable password-identifier similarity check", "description": "If set to false the password validation does not check for similarity between the password and the user identifier.", "type": "boolean", "default": true }, "migrate_hook": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Password Migration", "description": "If set to true will enable password migration.", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "url": { "type": "string", "description": "The URL the password migration hook should call", "format": "uri" }, "method": { "type": "string", "description": "The HTTP method to use (GET, POST, etc).", "const": "POST", "default": "POST" }, "headers": { "type": "object", "description": "The HTTP headers that must be applied to the password migration hook.", "additionalProperties": { "type": "string" } }, "emit_analytics_event": { "type": "boolean", "default": true, "description": "Emit tracing events for this hook on delivery or error" }, "auth": { "type": "object", "title": "Auth mechanisms", "description": "Define which auth mechanism the Web-Hook should use", "oneOf": [ { "$ref": "#/definitions/webHookAuthApiKeyProperties" }, { "$ref": "#/definitions/webHookAuthBasicAuthProperties" } ] }, "body": { "type": "string", "format": "uri", "pattern": "^(http|https|file|base64)://", "description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods, which support HTTP body payloads", "examples": [ "file:///path/to/body.jsonnet", "file://./body.jsonnet", "base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=", "https://oryapis.com/default_body.jsonnet" ] }, "additionalProperties": false } } } } }, "additionalProperties": false } } }, "totp": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the TOTP method", "default": false }, "config": { "type": "object", "title": "TOTP Configuration", "properties": { "issuer": { "title": "TOTP Issuer", "description": "The issuer (e.g. a domain name) will be shown in the TOTP app (e.g. Google Authenticator). It helps the user differentiate between different codes.", "type": "string" } }, "additionalProperties": false } } }, "lookup_secret": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the lookup secret method", "default": false } } }, "webauthn": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the WebAuthn method", "default": false }, "config": { "type": "object", "title": "WebAuthn Configuration", "properties": { "passwordless": { "type": "boolean", "title": "Use For Passwordless Flows", "description": "If enabled will have the effect that WebAuthn is used for passwordless flows (as a first factor) and not for multi-factor set ups. With this set to true, users will see an option to sign up with WebAuthn on the registration screen." }, "rp": { "title": "Relying Party (RP) Config", "properties": { "display_name": { "type": "string", "title": "Relying Party Display Name", "description": "An name to help the user identify this RP.", "examples": ["Ory Foundation"] }, "id": { "type": "string", "title": "Relying Party Identifier", "description": "The id must be a subset of the domain currently in the browser.", "examples": ["ory.sh"] }, "origin": { "type": "string", "title": "Relying Party Origin", "description": "An explicit RP origin. If left empty, this defaults to `id`, prepended with the current protocol schema (HTTP or HTTPS).", "format": "uri", "deprecationMessage": "This field is deprecated. Use `origins` instead.", "examples": ["https://www.ory.sh"] }, "origins": { "type": "array", "title": "Relying Party Origins", "description": "A list of explicit RP origins. If left empty, this defaults to either `origin` or `id`, prepended with the current protocol schema (HTTP or HTTPS).", "items": { "type": "string", "format": "uri", "examples": [ "https://www.ory.sh", "https://auth.ory.sh" ] } }, "icon": { "type": "string", "title": "Relying Party Icon", "description": "An icon to help the user identify this RP.", "format": "uri", "deprecationMessage": "This field is deprecated and ignored due to security considerations.", "examples": ["https://www.ory.sh/an-icon.png"] } }, "type": "object", "oneOf": [ { "required": ["id", "display_name"], "properties": { "origin": { "not": {} }, "origins": { "not": {} } } }, { "required": ["id", "display_name", "origin"], "properties": { "origin": { "type": "string" }, "origins": { "not": {} } } }, { "required": ["id", "display_name", "origins"], "properties": { "origin": { "not": {} }, "origins": { "type": "array", "items": { "type": "string" } } } } ] } }, "additionalProperties": false } }, "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] }, "then": { "required": ["config"] } }, "passkey": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the Passkey method", "default": false }, "config": { "type": "object", "title": "Passkey Configuration", "properties": { "rp": { "title": "Relying Party (RP) Config", "properties": { "display_name": { "type": "string", "title": "Relying Party Display Name", "description": "A name to help the user identify this RP.", "examples": ["Ory Foundation"] }, "id": { "type": "string", "title": "Relying Party Identifier", "description": "The id must be a subset of the domain currently in the browser.", "examples": ["ory.sh"] }, "origins": { "type": "array", "title": "Relying Party Origins", "description": "A list of explicit RP origins. If left empty, this defaults to either `origin` or `id`, prepended with the current protocol schema (HTTP or HTTPS).", "items": { "type": "string", "examples": [ "https://www.ory.sh", "https://auth.ory.sh" ] } } }, "type": "object", "required": ["display_name", "id"] } }, "additionalProperties": false } }, "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] }, "then": { "required": ["config"] } }, "oidc": { "type": "object", "title": "Specify OpenID Connect and OAuth2 Configuration", "showEnvVarBlockForObject": true, "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables OpenID Connect Method", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "base_redirect_uri": { "type": "string", "title": "Base URL for OAuth2 Redirect URIs", "description": "Can be used to modify the base URL for OAuth2 Redirect URLs. If unset, the Public Base URL will be used.", "format": "uri", "examples": ["https://auth.myexample.org/"] }, "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers Ory Kratos should integrate with.", "type": "array", "items": { "$ref": "#/definitions/selfServiceOIDCProvider" } } } } } } } } } }, "database": { "type": "object", "title": "Database related configuration", "description": "Miscellaneous settings used in database related tasks (cleanup, etc.)", "properties": { "cleanup": { "type": "object", "title": "Database cleanup settings", "description": "Settings that controls how the database cleanup process is configured (delays, batch size, etc.)", "properties": { "batch_size": { "type": "integer", "title": "Number of records to clean in one iteration", "description": "Controls how many records should be purged from one table during database cleanup task", "minimum": 1, "default": 100 }, "sleep": { "type": "object", "title": "Delays between various database cleanup phases", "description": "Configures delays between each step of the cleanup process. It is useful to tune the process so it will be efficient and performant.", "properties": { "tables": { "type": "string", "title": "Delay between each table cleanups", "description": "Controls the delay time between cleaning each table in one cleanup iteration", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1m" } } }, "older_than": { "type": "string", "title": "Remove records older than", "description": "Controls how old records do we want to leave", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "0s" } } } }, "additionalProperties": false }, "dsn": { "type": "string", "title": "Data Source Name", "description": "DSN is used to specify the database credentials as a connection URI.", "examples": [ "postgres://user: password@postgresd:5432/database?sslmode=disable&max_conns=20&max_idle_conns=4", "mysql://user:secret@tcp(mysqld:3306)/database?max_conns=20&max_idle_conns=4", "cockroach://user@cockroachdb:26257/database?sslmode=disable&max_conns=20&max_idle_conns=4", "sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc" ] }, "courier": { "type": "object", "title": "Courier configuration", "description": "The courier is responsible for sending and delivering messages over email, sms, and other means.", "properties": { "templates": { "additionalProperties": false, "type": "object", "properties": { "recovery": { "$ref": "#/definitions/courierTemplates" }, "recovery_code": { "$ref": "#/definitions/courierTemplates" }, "verification": { "$ref": "#/definitions/courierTemplates" }, "verification_code": { "$ref": "#/definitions/courierTemplates" }, "registration_code": { "additionalProperties": false, "type": "object", "properties": { "valid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" }, "sms": { "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] } } }, "login_code": { "additionalProperties": false, "type": "object", "properties": { "valid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" }, "sms": { "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] } } } } }, "template_override_path": { "type": "string", "title": "Override message templates", "description": "You can override certain or all message templates by pointing this key to the path where the templates are located.", "examples": ["/conf/courier-templates"] }, "message_retries": { "description": "Defines the maximum number of times the sending of a message is retried after it failed before it is marked as abandoned", "type": "integer", "default": 5, "examples": [10, 60] }, "worker": { "description": "Configures the dispatch worker.", "type": "object", "properties": { "pull_count": { "description": "Defines how many messages are pulled from the queue at once.", "type": "integer", "default": 10 }, "pull_wait": { "description": "Defines how long the worker waits before pulling messages from the queue again.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1s" } } }, "delivery_strategy": { "title": "Delivery Strategy", "description": "Defines how emails will be sent, either through SMTP (default) or HTTP.", "type": "string", "enum": ["smtp", "http"], "default": "smtp" }, "http": { "title": "HTTP Configuration", "description": "Configures outgoing emails using HTTP.", "type": "object", "properties": { "request_config": { "$ref": "#/definitions/httpRequestConfig" } }, "additionalProperties": false }, "smtp": { "title": "SMTP Configuration", "description": "Configures outgoing emails using the SMTP protocol.", "type": "object", "properties": { "connection_uri": { "title": "SMTP connection string", "description": "This URI will be used to connect to the SMTP server. Use the scheme smtps for implicit TLS sessions or smtp for explicit StartTLS/cleartext sessions. Please note that TLS is always enforced with certificate trust verification by default for security reasons on both schemes. With the smtp scheme you can use the query parameter (`?disable_starttls=true`) to allow cleartext sessions or (`?disable_starttls=false`) to enforce StartTLS (default behaviour). Additionally, use the query parameter to allow (`?skip_ssl_verify=true`) or disallow (`?skip_ssl_verify=false`) self-signed TLS certificates (default behaviour) on both implicit and explicit TLS sessions.", "examples": [ "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=false", "smtp://foo:bar@my-mailserver:1234/?disable_starttls=true (NOT RECOMMENDED: Cleartext smtp for devel and legacy infrastructure only)", "smtp://foo:bar@my-mailserver:1234/ (Explicit StartTLS with certificate trust verification)", "smtp://foo:bar@my-mailserver:1234/?skip_ssl_verify=true (NOT RECOMMENDED: Explicit StartTLS without certificate trust verification)", "smtps://foo:bar@my-mailserver:1234/ (Implicit TLS with certificate trust verification)", "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=true (NOT RECOMMENDED: Implicit TLS without certificate trust verification)", "smtps://subdomain.my-mailserver:1234/?server_name=my-mailserver (allows TLS to work if the server is hosted on a sudomain that uses a non-wildcard domain certificate)" ], "type": "string", "pattern": "^smtps?://.*" }, "client_cert_path": { "title": "SMTP Client certificate path", "description": "Path of the client X.509 certificate, in case of certificate based client authentication to the SMTP server.", "type": "string", "default": "" }, "client_key_path": { "title": "SMTP Client private key path", "description": "Path of the client certificate private key, in case of certificate based client authentication to the SMTP server", "type": "string", "default": "" }, "from_address": { "title": "SMTP Sender Address", "description": "The recipient of an email will see this as the sender address.", "type": "string", "format": "email", "default": "no-reply@ory.kratos.sh" }, "from_name": { "title": "SMTP Sender Name", "description": "The recipient of an email will see this as the sender name.", "type": "string", "examples": ["Bob"] }, "headers": { "title": "SMTP Headers", "description": "These headers will be passed in the SMTP conversation -- e.g. when using the AWS SES SMTP interface for cross-account sending.", "type": "object", "additionalProperties": { "type": "string" }, "examples": [ { "X-SES-SOURCE-ARN": "arn:aws:ses:us-west-2:123456789012:identity/example.com", "X-SES-FROM-ARN": "arn:aws:ses:us-west-2:123456789012:identity/example.com", "X-SES-RETURN-PATH-ARN": "arn:aws:ses:us-west-2:123456789012:identity/example.com" } ] }, "local_name": { "title": "SMTP HELO/EHLO name", "description": "Identifier used in the SMTP HELO/EHLO command. Some SMTP relays require a unique identifier.", "type": "string", "default": "localhost" } }, "additionalProperties": false }, "channels": { "type": "array", "items": { "title": "Courier channel configuration", "type": "object", "properties": { "id": { "type": "string", "title": "Channel id", "description": "The channel id. Corresponds to the .via property of the identity schema for recovery, verification, etc. Currently only sms is supported.", "maxLength": 32, "enum": ["sms"] }, "type": { "type": "string", "title": "Channel type", "description": "The channel type. Currently only http is supported.", "enum": ["http"] }, "request_config": { "$ref": "#/definitions/httpRequestConfig" } }, "required": ["id", "request_config"], "additionalProperties": false } } }, "additionalProperties": false }, "oauth2_provider": { "title": "OAuth2 Provider Configuration", "type": "object", "properties": { "url": { "title": "OAuth 2.0 Provider URL.", "description": "If set, the login and registration flows will handle the Ory OAuth 2.0 & OpenID `login_challenge` query parameter to serve as an OpenID Connect Provider. This URL should point to Ory Hydra when you are not running on the Ory Network and be left untouched otherwise.", "type": "string", "format": "uri", "examples": [ "https://some-slug.projects.oryapis.com", "https://domain-of-ory-hydra:4445" ] }, "headers": { "title": "HTTP Request Headers", "description": "These headers will be passed in HTTP request to the OAuth2 Provider.", "type": "object", "additionalProperties": { "type": "string" }, "examples": [ { "Authorization": "Bearer some-token" } ] }, "override_return_to": { "title": "Persist OAuth2 request between flows", "type": "boolean", "default": false, "description": "Override the return_to query parameter with the OAuth2 provider request URL when perfoming an OAuth2 login flow." } }, "additionalProperties": false }, "preview": { "title": "Configure Preview Features", "type": "object", "properties": { "default_read_consistency_level": { "type": "string", "title": "Default Read Consistency Level", "description": "The default consistency level to use when reading from the database. Defaults to `strong` to not break existing API contracts. Only set this to `eventual` if you can accept that other read APIs will suddenly return eventually consistent results. It is only effective in Ory Network.", "enum": ["strong", "eventual"], "default": "strong" } } }, "serve": { "type": "object", "properties": { "admin": { "type": "object", "properties": { "request_log": { "type": "object", "properties": { "disable_for_health": { "title": "Disable health endpoints request logging", "description": "Disable request logging for /health/alive and /health/ready endpoints", "type": "boolean", "default": false } }, "additionalProperties": false }, "base_url": { "title": "Admin Base URL", "description": "The URL where the admin endpoint is exposed at.", "type": "string", "format": "uri", "examples": ["https://kratos.private-network:4434/"] }, "host": { "title": "Admin Host", "description": "The host (interface) kratos' admin endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Admin Port", "description": "The port kratos' admin endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4434], "default": 4434 }, "socket": { "$ref": "#/definitions/socket" }, "tls": { "$ref": "#/definitions/tlsx" } }, "additionalProperties": false }, "public": { "type": "object", "properties": { "request_log": { "type": "object", "properties": { "disable_for_health": { "title": "Disable health endpoints request logging", "description": "Disable request logging for /health/alive and /health/ready endpoints", "type": "boolean", "default": false } }, "additionalProperties": false }, "cors": { "type": "object", "additionalProperties": false, "description": "Configures Cross Origin Resource Sharing for public endpoints.", "properties": { "enabled": { "type": "boolean", "description": "Sets whether CORS is enabled.", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "does match all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "type": "string", "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "default": ["*"], "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "default": [ "Authorization", "Content-Type", "Max-Age", "X-Session-Token", "X-XSRF-TOKEN", "X-CSRF-TOKEN" ], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "default": ["Content-Type"], "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug server side CORS issues.", "default": false } } }, "base_url": { "$ref": "#/definitions/baseUrl" }, "host": { "title": "Public Host", "description": "The host (interface) kratos' public endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Public Port", "description": "The port kratos' public endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4433], "default": 4433 }, "socket": { "$ref": "#/definitions/socket" }, "tls": { "$ref": "#/definitions/tlsx" } }, "additionalProperties": false } }, "additionalProperties": false }, "tracing": { "$ref": "https://raw.githubusercontent.com/ory/kratos/c651ecf2e/oryx/otelx/config.schema.json" }, "log": { "title": "Log", "description": "Configure logging using the following options. Logging will always be sent to stdout and stderr.", "type": "object", "properties": { "level": { "description": "Debug enables stack traces on errors. Can also be set using environment variable LOG_LEVEL.", "type": "string", "default": "info", "enum": [ "trace", "debug", "info", "warning", "error", "fatal", "panic" ] }, "leak_sensitive_values": { "type": "boolean", "title": "Leak Sensitive Log Values", "description": "If set will leak sensitive values (e.g. emails) in the logs." }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "format": { "description": "The log format can either be text or JSON.", "type": "string", "enum": ["json", "text"] } }, "additionalProperties": false }, "identity": { "type": "object", "properties": { "default_schema_id": { "title": "The default Identity Schema", "description": "This Identity Schema will be used as the default for self-service flows. Its ID needs to exist in the \"schemas\" list.", "type": "string", "default": "default" }, "schemas": { "type": "array", "title": "All JSON Schemas for Identity Traits", "description": "Note that identities that used the \"default_schema_url\" field in older kratos versions will be corrupted unless you specify their schema url with the id \"default\" in this list.", "examples": [ [ { "id": "customer", "url": "base64://ewogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInR5cGUiOiAib2JqZWN0IiwKICAicHJvcGVydGllcyI6IHsKICAgICJiYXIiOiB7CiAgICAgICJ0eXBlIjogInN0cmluZyIKICAgIH0KICB9LAogICJyZXF1aXJlZCI6IFsKICAgICJiYXIiCiAgXQp9" }, { "id": "employee", "url": "https://foo.bar.com/path/to/employee.traits.schema.json" }, { "id": "employee-v2", "url": "file://path/to/employee.v2.traits.schema.json" } ] ], "minItems": 1, "items": { "type": "object", "properties": { "id": { "title": "The schema's ID.", "type": "string", "examples": ["employee"] }, "url": { "type": "string", "title": "JSON Schema URL for identity traits schema", "description": "URL for JSON Schema which describes a identity's traits. Can be a file path, a https URL, or a base64 encoded string.", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json", "base64://ewogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInR5cGUiOiAib2JqZWN0IiwKICAicHJvcGVydGllcyI6IHsKICAgICJiYXIiOiB7CiAgICAgICJ0eXBlIjogInN0cmluZyIKICAgIH0KICB9LAogICJyZXF1aXJlZCI6IFsKICAgICJiYXIiCiAgXQp9" ] }, "selfservice_selectable": { "type": "boolean", "title": "Is the schema enabled in self-service flows", "description": "If set to true, this schema can be used explicity in self-service flows by setting `identity_schema` query parameter to the schema's ID.", "default": false } }, "required": ["id", "url"] } } }, "required": ["schemas"], "additionalProperties": false }, "secrets": { "type": "object", "properties": { "default": { "type": "array", "title": "Default Encryption Signing Secrets", "description": "The first secret in the array is used for signing and encrypting things while all other keys are used to verify and decrypt older things that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "cookie": { "type": "array", "title": "Signing Keys for Cookies", "description": "The first secret in the array is used for encrypting cookies while all other keys are used to decrypt older cookies that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "pagination": { "type": "array", "title": "Secrets to encrypt the pagination token", "description": "To avoid clients reverse-engineering and relying on the implementation details of the pagination token, it is encrypted with these keys", "items": { "type": "string", "minLength": 16 }, "minItems": 1, "examples": [ [ "secret used for encryption", "old secret kept for decryption", "another old secret kept for decryption" ] ] }, "cipher": { "type": "array", "title": "Secrets to use for encryption by cipher", "description": "The first secret in the array is used for encryption data while all other keys are used to decrypt older data that were signed with.", "items": { "type": "string", "minLength": 32, "maxLength": 32 }, "minItems": 1 } }, "additionalProperties": false }, "hashers": { "title": "Hashing Algorithm Configuration", "type": "object", "properties": { "algorithm": { "title": "Password hashing algorithm", "description": "One of the values: argon2, bcrypt.\nAny other hashes will be migrated to the set algorithm once an identity authenticates using their password.", "type": "string", "default": "bcrypt", "enum": ["argon2", "bcrypt"] }, "argon2": { "title": "Configuration for the Argon2id hasher.", "type": "object", "properties": { "memory": { "type": "string", "pattern": "^[0-9]+(B|KB|MB|GB|TB|PB|EB)", "default": "128MB" }, "iterations": { "type": "integer", "minimum": 1, "default": 1 }, "parallelism": { "type": "integer", "minimum": 1, "description": "Number of parallel workers, defaults to 2*runtime.NumCPU()." }, "salt_length": { "type": "integer", "minimum": 16, "default": 16 }, "key_length": { "type": "integer", "minimum": 16, "default": 32 }, "expected_duration": { "description": "The time a hashing operation (~login latency) should take.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "500ms" }, "expected_deviation": { "description": "The standard deviation expected for hashing operations. If this value is exceeded you will be warned in the logs to adjust the parameters.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "500ms" }, "dedicated_memory": { "description": "The memory dedicated for Kratos. As password hashing is very resource intense, Kratos will monitor the memory consumption and warn about high values.", "type": "string", "pattern": "^[0-9]+(B|KB|MB|GB|TB|PB|EB)", "default": "1GB" } }, "additionalProperties": false }, "bcrypt": { "title": "Configuration for the Bcrypt hasher. Minimum is 4 when --dev flag is used and 12 otherwise.", "type": "object", "additionalProperties": false, "required": ["cost"], "properties": { "cost": { "type": "integer", "minimum": 4, "maximum": 31, "default": 12 } } } }, "additionalProperties": false }, "ciphers": { "title": "Cipher Algorithm Configuration", "type": "object", "properties": { "algorithm": { "title": "ciphering algorithm", "description": "One of the values: noop, aes, xchacha20-poly1305", "type": "string", "default": "noop", "enum": ["noop", "aes", "xchacha20-poly1305"] } } }, "cookies": { "type": "object", "title": "HTTP Cookie Configuration", "description": "Configure the HTTP Cookies. Applies to both CSRF and session cookies.", "properties": { "domain": { "title": "HTTP Cookie Domain", "description": "Sets the cookie domain for session and CSRF cookies. Useful when dealing with subdomains. Use with care!", "type": "string" }, "path": { "title": "HTTP Cookie Path", "description": "Sets the session and CSRF cookie path. Use with care!", "type": "string", "default": "/" }, "secure": { "title": "Session Cookie Secure Flag", "description": "Sets the session secure flag. If unset, defaults to !dev mode.", "type": "string" }, "same_site": { "title": "HTTP Cookie Same Site Configuration", "description": "Sets the session and CSRF cookie SameSite.", "type": "string", "enum": ["Strict", "Lax", "None"], "default": "Lax" } }, "additionalProperties": false }, "session": { "type": "object", "additionalProperties": false, "properties": { "whoami": { "title": "WhoAmI / ToSession Settings", "description": "Control how the `/sessions/whoami` endpoint is behaving.", "type": "object", "properties": { "required_aal": { "$ref": "#/definitions/featureRequiredAal" }, "tokenizer": { "title": "Tokenizer configuration", "description": "Configure the tokenizer, responsible for converting a session into a token format such as JWT.", "type": "object", "properties": { "templates": { "title": "Tokenizer templates", "description": "A list of different templates that govern how a session is converted to a token format.", "type": "object", "patternProperties": { "[a-zA-Z0-9-_.]+": { "type": "object", "required": ["jwks_url"], "properties": { "ttl": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1m", "title": "Token time to live" }, "claims_mapper_url": { "type": "string", "format": "uri", "title": "Jsonnet mapper URL" }, "jwks_url": { "type": "string", "format": "uri", "title": "JSON Web Key Set URL" }, "subject_source": { "type": "string", "title": "Subject source", "description": "The source of the subject claim in the token. Can be one of: `id`, or `external_id`.", "enum": ["id", "external_id"], "default": "id" } } } } } } } }, "additionalProperties": false }, "lifespan": { "title": "Session Lifespan", "description": "Defines how long a session is active. Once that lifespan has been reached, the user needs to sign in again.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "24h", "examples": ["1h", "1m", "1s"] }, "cookie": { "type": "object", "properties": { "domain": { "title": "Session Cookie Domain", "description": "Sets the session cookie domain. Useful when dealing with subdomains. Use with care! Overrides `cookies.domain`.", "type": "string" }, "name": { "title": "Session Cookie Name", "description": "Sets the session cookie name. Use with care!", "type": "string", "default": "ory_kratos_session" }, "persistent": { "title": "Make Session Cookie Persistent", "description": "If set to true will persist the cookie in the end-user's browser using the `max-age` parameter which is set to the `session.lifespan` value. Persistent cookies are not deleted when the browser is closed (e.g. on reboot or alt+f4). This option affects the Ory OAuth2 and OpenID Provider's remember feature as well.", "type": "boolean", "default": true }, "path": { "title": "Session Cookie Path", "description": "Sets the session cookie path. Use with care! Overrides `cookies.path`.", "type": "string" }, "secure": { "title": "Session Cookie Secure Flag", "description": "Sets the session secure flag. If unset, defaults to !dev mode.", "type": "string" }, "same_site": { "title": "Session Cookie SameSite Configuration", "description": "Sets the session cookie SameSite. Overrides `cookies.same_site`.", "type": "string", "enum": ["Strict", "Lax", "None"] } }, "additionalProperties": false }, "earliest_possible_extend": { "title": "Earliest Possible Session Extension", "description": "Sets when a session can be extended. Settings this value to `24h` will prevent the session from being extended before until 24 hours before it expires. This setting prevents excessive writes to the database. We highly recommend setting this value.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "examples": ["1h", "1m", "1s"] } } }, "security": { "type": "object", "properties": { "account_enumeration": { "type": "object", "properties": { "mitigate": { "type": "boolean", "default": false, "description": "Mitigate account enumeration by making it harder to figure out if an identifier (email, phone number) exists or not. Enabling this setting degrades user experience. This setting does not mitigate all possible attack vectors yet." } } } } }, "version": { "title": "The kratos version this config is written for.", "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "type": "string", "pattern": "^(v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|$", "examples": ["v0.5.0-alpha.1"] }, "dev": { "type": "boolean" }, "help": { "type": "boolean" }, "sqa-opt-out": { "type": "boolean", "default": false, "description": "This is a CLI flag and environment variable and can not be set using the config file." }, "watch-courier": { "type": "boolean", "default": false, "description": "This is a CLI flag and environment variable and can not be set using the config file." }, "expose-metrics-port": { "title": "Metrics port", "description": "The port the courier's metrics endpoint listens on (0/disabled by default). This is a CLI flag and environment variable and can not be set using the config file.", "type": "integer", "minimum": 0, "maximum": 65535, "examples": [4434], "default": 0 }, "config": { "type": "array", "items": { "type": "string" }, "description": "This is a CLI flag and environment variable and can not be set using the config file." }, "clients": { "title": "Global outgoing network settings", "description": "Configure how outgoing network calls behave.", "type": "object", "properties": { "http": { "title": "Global HTTP client configuration", "description": "Configure how outgoing HTTP calls behave.", "type": "object", "properties": { "disallow_private_ip_ranges": { "title": "Disallow private IP ranges", "description": "Disallow all outgoing HTTP calls to private IP ranges. This feature can help protect against SSRF attacks.", "type": "boolean", "default": false }, "private_ip_exception_urls": { "title": "Add exempt URLs to private IP ranges", "description": "Allows the given URLs to be called despite them being in the private IP range. URLs need to have an exact and case-sensitive match to be excempt.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "default": [] } } }, "web_hook": { "title": "Global web_hook HTTP client configuration", "description": "Configure the global HTTP client of the web_hook action.", "type": "object", "properties": { "header_allowlist": { "title": "Allowed request headers", "description": "List of request headers that are forwarded to the web hook target in canonical form.", "type": "array", "items": { "type": "string" }, "default": [ "Accept", "Accept-Encoding", "Accept-Language", "Content-Length", "Content-Type", "Origin", "Priority", "Referer", "Sec-Ch-Ua", "Sec-Ch-Ua-Mobile", "Sec-Ch-Ua-Platform", "Sec-Fetch-Dest", "Sec-Fetch-Mode", "Sec-Fetch-Site", "Sec-Fetch-User", "True-Client-Ip", "User-Agent" ] } } } } }, "feature_flags": { "title": "Feature flags", "properties": { "cacheable_sessions": { "type": "boolean", "title": "Enable Ory Sessions caching", "description": "If enabled allows Ory Sessions to be cached. Only effective in the Ory Network.", "default": false }, "cacheable_sessions_max_age": { "title": "Set Ory Session Edge Caching maximum age", "description": "Set how long Ory Sessions are cached on the edge. If unset, the session expiry will be used. Only effective in the Ory Network.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$" }, "use_continue_with_transitions": { "type": "boolean", "title": "Enable new flow transitions using `continue_with` items", "description": "If enabled allows new flow transitions using `continue_with` items.", "default": false }, "choose_recovery_address": { "type": "boolean", "title": "Enable new recovery screens to pick which address to send a recovery code/link to", "description": "If enabled, enable new recovery screens to pick which address to send a recovery code to, and can send a code via SMS. It is safe to toggle it back and forth, existing recovery flows will be handled with their respective logic. That is because it is decided at creation time whether a recovery flow is V1 or V2 and this cannot be changed afterwards. Thus, if a recovery flow is created with this flag enabled, it will be created as a recovery v2 flow. If this flag is disabled while this flow is still active, this flow will still be handled with the correct logic (v2).", "default": false }, "legacy_continue_with_verification_ui": { "type": "boolean", "title": "Always include show_verification_ui in continue_with", "description": "If true, restores the legacy behavior of always including `show_verification_ui` in the registration flow's `continue_with` when verification is enabled. If set to false, `show_verification_ui` is only set in `continue_with` if the `show_verification_ui` hook is used. This flag will be removed in the future.", "deprecationMessage": "This behavior is deprecated and will be removed in the future. Use the `show_verification_hook` in the post-registration hook instead.", "default": false }, "legacy_require_verified_login_error": { "type": "boolean", "title": "Return a form error if the login identifier is not verified", "description": "If true, the login flow will return a form error if the login identifier is not verified, which restores legacy behavior. If this value is false, the `continue_with` array will contain a `show_verification_ui` hook instead.", "deprecationMessage": "This behavior is deprecated and will be removed in the future. Please upgrade your SDKs.", "default": false }, "faster_session_extend": { "type": "boolean", "title": "Enable faster session extension", "description": "If enabled allows faster session extension by skipping the session lookup. Disabling this feature will be deprecated in the future.", "default": false }, "password_profile_registration_node_group": { "title": "Registration node group", "description": "The node group to use for registration flows. Previously, the node group for the password method's profile fields was `password`. Going forward, it will be `default`. This switch can toggle between those two for backwards compatibility.", "enum": ["password", "default"], "default": "default" }, "legacy_oidc_registration_node_group": { "title": "Registration node group for OIDC", "description": "The node group to use for registration flows. Previously, the node group for the oidc method's profile fields was `oidc`. Going forward, it will be `default`. This switch can toggle between those two for backwards compatibility and will be removed in the future.", "default": false, "type": "boolean" } }, "additionalProperties": false }, "organizations": { "title": "Organizations", "description": "Please use selfservice.methods.b2b instead. This key will be removed. Only effective in the Ory Network.", "type": "array", "default": [] }, "enterprise": { "title": "Enterprise features", "description": "Specifies enterprise features. Only effective in the Ory Network or with a valid license.", "type": "object", "properties": { "identity_schema_fallback_url_template": { "type": "string", "title": "Fallback URL template for identity schemas", "description": "A fallback URL template used when looking up identity schemas." } }, "additionalProperties": false }, "revision": { "title": "Config revision", "description": "Set a recognizable revision. This could be the commit time or a random value. This value is exposed at the `/health/config` endpoint and allows you to ensure that the correct config is loaded.", "type": "string" } }, "allOf": [ { "if": { "properties": { "selfservice": { "properties": { "flows": { "oneOf": [ { "properties": { "verification": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["verification"] }, { "properties": { "recovery": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["recovery"] } ] } }, "required": ["flows"] } }, "required": ["selfservice"] }, "then": { "required": ["courier"] } }, { "if": { "properties": { "ciphers": { "properties": { "algorithm": { "oneOf": [ { "const": "aes" }, { "const": "xchacha20-poly1305" } ] } }, "required": ["algorithm"] } }, "required": ["ciphers"] }, "then": { "required": ["secrets"], "properties": { "secrets": { "required": ["cipher"] } } } } ], "required": ["identity", "dsn", "selfservice"], "additionalProperties": false } ================================================ FILE: .trivyignore ================================================ CVE-2022-30065 CVE-2024-2961 CVE-2023-2650 ================================================ FILE: .vscode/launch.json ================================================ { // Launch configuration to build and launch Kratos // It uses a barebones "version": "0.2.0", "configurations": [ { "name": "Debug Kratos", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceFolder}/main.go", "buildFlags": "-tags sqlite", "preLaunchTask": "Kratos: setup", "postDebugTask": "close tasks", // stops mailhog. Needed, because VSCode does not re-use existing isBackground tasks "args": [ "serve", "--dev", "--watch-courier", "-c=${workspaceFolder}/contrib/quickstart/kratos/email-password/kratos.yml" ], "internalConsoleOptions": "openOnSessionStart", "env": { "IDENTITY_SCHEMAS_0_URL": "file://${workspaceFolder}/contrib/quickstart/kratos/email-password/identity.schema.json", "DSN": "sqlite://${workspaceFolder}/.debug.sqlite.db?_fk=true", "COURIER_SMTP_CONNECTION_URI": "smtp://localhost:8026/?disable_starttls=true", "DEV_DISABLE_API_FLOW_ENFORCEMENT": "true", "SELFSERVICE_METHODS_PASSWORD_CONFIG_HAVEIBEENPWNED_ENABLED": "false", // disable locally, as the integration hangs requests, if internet is slow "TRACING_PROVIDER": "jaeger", "TRACING_PROVIDERS_JAEGER_SAMPLING_SERVER_URL": "http://127.0.0.1:5778/sampling", "TRACING_PROVIDERS_JAEGER_LOCAL_AGENT_ADDRESS": "127.0.0.1:6831" } } ] } ================================================ FILE: .vscode/settings.json ================================================ { "gopls": { "formatting.gofumpt": true, "formatting.local": "github.com/ory" } } ================================================ FILE: .vscode/tasks.json ================================================ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "Kratos: build", "type": "shell", "command": "go", "args": [ "build", "-tags", "sqlite", "-o", "${workspaceFolder}/kratos", "${workspaceFolder}/main.go" ] }, { "label": "Kratos debug: setup sqlite", "type": "shell", "presentation": { "echo": false, "reveal": "always" }, "dependsOn": ["Kratos: build"], "command": "rm -f ${workspaceFolder}/.debug.sqlite.db && ${workspaceFolder}/kratos migrate sql up -e --yes", "options": { "env": { "DSN": "sqlite://${workspaceFolder}/.debug.sqlite.db?_fk=true" } } }, { "label": "Kratos: setup", "type": "shell", "presentation": { "reveal": "silent" }, "dependsOn": ["Kratos debug: setup sqlite"], "problemMatcher": [] }, { "label": "close tasks", "command": "echo ${input:terminate}", "type": "shell", "problemMatcher": [] } ], "inputs": [ { "id": "terminate", "type": "command", "command": "workbench.action.tasks.terminate", "args": "terminateAll" } ] } ================================================ FILE: AUTHORS ================================================ # This is the official list of Kratos authors. # If you don't want to be on this list, please contact Ory. Adam Schepis Addison Snelling adrian5 Aeneas Rekkas aenoralanar <38026322+aenoralanar@users.noreply.github.com> Aidan Holland Ajay Kelkar Alano Terblanche Alexander Probst Alexandre Burgoni Alexandre Gagneux <46828169+alexGNX@users.noreply.github.com> Alexey Reshetnik Andreas Andreas Bucksteeg Andrew Banchich <13824577+andrewbanchich@users.noreply.github.com> Andrew Minkin Andrew Tan Andrey Andy Steele angryPopcorn Anirudh Oppiliappan Antoine Beyet Anuar Ustayev Arne Arne aspeteRakete Atreya <44151328+atreya2011@users.noreply.github.com> AVA Monitoring <56961522+avamonitoring@users.noreply.github.com> Avinash Dwarapu b3j0f Bengt Hagemeister <30391176+BengtHagemeister@users.noreply.github.com> Bengt Hagemeister Benjamin Freeman BIKI DAS Bill Monkman Bipin Paul Bedi Brahm Lower Brahm Lower BrentChesny CaptainStandby <18215579+CaptainStandby@users.noreply.github.com> Carlos Chida chlasch <72044019+chlasch@users.noreply.github.com> cwei-wish Cλctysman Daniel Hobbs David ALEXANDRE <9482408+david972@users.noreply.github.com> David Cheung David Laban David Laban David San David van der Sluijs debrutal Dejan Filipovic Dejan Filipovic Denis Palnitsky Dibyajyoti Behera Dimitrij Drus Dimitrij Drus Eifoen <35534229+Eifoen@users.noreply.github.com> emrah emtammaru Eric B ernax78 <65963237+ernax78@users.noreply.github.com> Erol Keskin Erol Keskin Fabian Meyer <3982806+meyfa@users.noreply.github.com> Felix Beuke Ferdi Florian Kramer Florian Nagel Florian Wiech frederikhors <41120635+frederikhors@users.noreply.github.com> Gajewski Dmitriy Geoff Lawler Giacomo Mazzamuto Gibheer goughjo02 Grant Zvolský Harsimran Singh Maan Henning Perl Jacob Lehr Jakob Høgenes <1014990+jakhog@users.noreply.github.com> Jakob Sinclair Jakub Błaszczyk James D. Nurmi James D. Nurmi Jason Ertel JeffreyThijs Jelle Besseling Jhonatan Hulse JiggyDown <84430646+JiggyDown@users.noreply.github.com> jingkai jld3103 Joe Krill Johan Forssell John Binstead <67282336+Bin-fluence@users.noreply.github.com> John Jonas Hungershausen Jonathon Sheffield Jordan Jordan May Juri <1773075+2mol@users.noreply.github.com> Kevin Goslar Kim Neunert Klaus Herrmann <106238709+kmherrmann@users.noreply.github.com> koenmtb1 Koen van Marrewijk kszafran Kun Chong Landon Pattison <67596936+LandonPattison@users.noreply.github.com> Lan Phan Li Ming Lorenzo Baldassarri Łukasz Harasimowicz Łukasz Szcześniak Márk Bartos Mart Aarma Martin Eigenbrodt Martin Eigenbrodt Martin Mathias Polligkeit <13847569+woylie@users.noreply.github.com> Matt Bonnell Matthieu Jacquot Matúš Múčka Maurice Freitag Mike Milano Mike Mikołaj Meller <52668809+mmeller-wikia@users.noreply.github.com> Miłosz <12242002+mszekiel@users.noreply.github.com> MiniDigger | Martin Mitar Mitsuo Heijo <25817501+johejo@users.noreply.github.com> Nanik Natanael Oliva Neil Rutherford <374275+nrutherford@users.noreply.github.com> Nichlas Lendal <62498670+pr1ze@users.noreply.github.com> Nicholas Bush Nick Campbell Nick Malcev <41836660+nmlc@users.noreply.github.com> Nick Ufer Nikita Puzankov nipsufn <17983323+nipsufn@users.noreply.github.com> Ole Petersen <56505957+peteole@users.noreply.github.com> oliverpool <3864879+oliverpool@users.noreply.github.com> Omar A. Hachach Patrik Neu Paweł Hemperek Piotr Mścichowski Przemysław Czaus Quentin Gliech qvamatel Radek Gruchalski RamiBerm <54766858+RamiBerm@users.noreply.github.com> Rauno Viskus reshetnik-alexey Rodrigo Queiro rvo sawadashota sawadashota Seb seremenko-wish <60801091+seremenko-wish@users.noreply.github.com> Sergey Plaunov sherbang shivam <86538330+shivam-909@users.noreply.github.com> Simon Lipp Simon-Pierre Gingras <892367+spg@users.noreply.github.com> Steffen Heidel TannerGabriel <40315960+TannerGabriel@users.noreply.github.com> Theodor Brandt theotherian Thomas Aidan Curran <2030403+tacurran@users.noreply.github.com> Thomas Guillet Thomas Ruiz Tomasz Tomalak <12939493+t-tomalak@users.noreply.github.com> Tom Fenech Toon van Strijp Tsirkin Evgeny ttimonen Tyler Battle VeenaInd <104088519+VeenaInd@users.noreply.github.com> Victor Duarte Vincent vinckr Viz WeiQi <17816875364@163.com> wezzle Yagiz Nizipli yon <38630464+yonbh@users.noreply.github.com> Yorick Holkamp you1996 <45292366+you1996@users.noreply.github.com> Yuvraj <10830562+evalsocket@users.noreply.github.com> Zageron Zhiming Guo znerol 好风 ================================================ FILE: CHANGELOG.md ================================================ # Changelog **Table of Contents** - [ (2026-03-20)](#2026-03-20) - [26.2.0 (2026-03-20)](#2620-2026-03-20) - [Bug Fixes](#bug-fixes) - [Code Refactoring](#code-refactoring) - [Documentation](#documentation) - [Features](#features) - [Tests](#tests) - [Unclassified](#unclassified) - [25.4.0 (2025-11-07)](#2540-2025-11-07) - [Breaking Changes](#breaking-changes) - [Related issue(s)](#related-issues) - [Checklist](#checklist) - [Related issue(s)](#related-issues-1) - [Checklist](#checklist-1) - [Related issue(s)](#related-issues-2) - [Checklist](#checklist-2) - [Bug Fixes](#bug-fixes-1) - [Chores](#chores) - [Code Generation](#code-generation) - [Code Refactoring](#code-refactoring-1) - [Documentation](#documentation-1) - [Features](#features-1) - [Reverts](#reverts) - [Tests](#tests-1) - [Unclassified](#unclassified-1) - [1.3.0 (2024-09-26)](#130-2024-09-26) - [Breaking Changes](#breaking-changes-1) - [Bug Fixes](#bug-fixes-2) - [Code Generation](#code-generation-1) - [Documentation](#documentation-2) - [Features](#features-2) - [Tests](#tests-2) - [Unclassified](#unclassified-2) - [1.2.0 (2024-06-05)](#120-2024-06-05) - [Breaking Changes](#breaking-changes-2) - [Bug Fixes](#bug-fixes-3) - [Code Generation](#code-generation-2) - [Documentation](#documentation-3) - [Features](#features-3) - [Tests](#tests-3) - [Unclassified](#unclassified-3) - [1.1.0 (2024-02-20)](#110-2024-02-20) - [Breaking Changes](#breaking-changes-3) - [Bug Fixes](#bug-fixes-4) - [Code Generation](#code-generation-3) - [Documentation](#documentation-4) - [Features](#features-4) - [Reverts](#reverts-1) - [Tests](#tests-4) - [Unclassified](#unclassified-4) - [1.0.0 (2023-07-12)](#100-2023-07-12) - [Bug Fixes](#bug-fixes-5) - [Code Generation](#code-generation-4) - [Documentation](#documentation-5) - [Features](#features-5) - [Tests](#tests-5) - [Unclassified](#unclassified-5) - [0.13.0 (2023-04-18)](#0130-2023-04-18) - [Breaking Changes](#breaking-changes-4) - [Bug Fixes](#bug-fixes-6) - [Code Generation](#code-generation-5) - [Code Refactoring](#code-refactoring-2) - [Documentation](#documentation-6) - [Features](#features-6) - [Tests](#tests-6) - [Unclassified](#unclassified-6) - [0.11.1 (2023-01-14)](#0111-2023-01-14) - [Breaking Changes](#breaking-changes-5) - [Bug Fixes](#bug-fixes-7) - [Code Generation](#code-generation-6) - [Documentation](#documentation-7) - [Features](#features-7) - [Tests](#tests-7) - [0.11.0 (2022-12-02)](#0110-2022-12-02) - [Code Generation](#code-generation-7) - [Features](#features-8) - [0.11.0-alpha.0.pre.2 (2022-11-28)](#0110-alpha0pre2-2022-11-28) - [Breaking Changes](#breaking-changes-6) - [Bug Fixes](#bug-fixes-8) - [Code Generation](#code-generation-8) - [Code Refactoring](#code-refactoring-3) - [Documentation](#documentation-8) - [Features](#features-9) - [Reverts](#reverts-2) - [Tests](#tests-8) - [Unclassified](#unclassified-7) - [0.10.1 (2022-06-01)](#0101-2022-06-01) - [Bug Fixes](#bug-fixes-9) - [Code Generation](#code-generation-9) - [0.10.0 (2022-05-30)](#0100-2022-05-30) - [Breaking Changes](#breaking-changes-7) - [Bug Fixes](#bug-fixes-10) - [Code Generation](#code-generation-10) - [Code Refactoring](#code-refactoring-4) - [Documentation](#documentation-9) - [Features](#features-10) - [Tests](#tests-9) - [Unclassified](#unclassified-8) - [0.9.0-alpha.3 (2022-03-25)](#090-alpha3-2022-03-25) - [Breaking Changes](#breaking-changes-8) - [Bug Fixes](#bug-fixes-11) - [Code Generation](#code-generation-11) - [Documentation](#documentation-10) - [0.9.0-alpha.2 (2022-03-22)](#090-alpha2-2022-03-22) - [Bug Fixes](#bug-fixes-12) - [Code Generation](#code-generation-12) - [0.9.0-alpha.1 (2022-03-21)](#090-alpha1-2022-03-21) - [Breaking Changes](#breaking-changes-9) - [Bug Fixes](#bug-fixes-13) - [Code Generation](#code-generation-13) - [Code Refactoring](#code-refactoring-5) - [Documentation](#documentation-11) - [Features](#features-11) - [Tests](#tests-10) - [Unclassified](#unclassified-9) - [0.8.3-alpha.1.pre.0 (2022-01-21)](#083-alpha1pre0-2022-01-21) - [Breaking Changes](#breaking-changes-10) - [Bug Fixes](#bug-fixes-14) - [Code Generation](#code-generation-14) - [Code Refactoring](#code-refactoring-6) - [Documentation](#documentation-12) - [Features](#features-12) - [Tests](#tests-11) - [0.8.2-alpha.1 (2021-12-17)](#082-alpha1-2021-12-17) - [Bug Fixes](#bug-fixes-15) - [Code Generation](#code-generation-15) - [Documentation](#documentation-13) - [0.8.1-alpha.1 (2021-12-13)](#081-alpha1-2021-12-13) - [Bug Fixes](#bug-fixes-16) - [Code Generation](#code-generation-16) - [Documentation](#documentation-14) - [Features](#features-13) - [Tests](#tests-12) - [0.8.0-alpha.4.pre.0 (2021-11-09)](#080-alpha4pre0-2021-11-09) - [Breaking Changes](#breaking-changes-11) - [Bug Fixes](#bug-fixes-17) - [Code Generation](#code-generation-17) - [Documentation](#documentation-15) - [Features](#features-14) - [Tests](#tests-13) - [0.8.0-alpha.3 (2021-10-28)](#080-alpha3-2021-10-28) - [Bug Fixes](#bug-fixes-18) - [Code Generation](#code-generation-18) - [0.8.0-alpha.2 (2021-10-28)](#080-alpha2-2021-10-28) - [Code Generation](#code-generation-19) - [0.8.0-alpha.1 (2021-10-27)](#080-alpha1-2021-10-27) - [Breaking Changes](#breaking-changes-12) - [Bug Fixes](#bug-fixes-19) - [Code Generation](#code-generation-20) - [Code Refactoring](#code-refactoring-7) - [Documentation](#documentation-16) - [Features](#features-15) - [Reverts](#reverts-3) - [Tests](#tests-14) - [Unclassified](#unclassified-10) - [0.7.6-alpha.1 (2021-09-12)](#076-alpha1-2021-09-12) - [Code Generation](#code-generation-21) - [0.7.5-alpha.1 (2021-09-11)](#075-alpha1-2021-09-11) - [Code Generation](#code-generation-22) - [0.7.4-alpha.1 (2021-09-09)](#074-alpha1-2021-09-09) - [Bug Fixes](#bug-fixes-20) - [Code Generation](#code-generation-23) - [Documentation](#documentation-17) - [Features](#features-16) - [Tests](#tests-15) - [0.7.3-alpha.1 (2021-08-28)](#073-alpha1-2021-08-28) - [Bug Fixes](#bug-fixes-21) - [Code Generation](#code-generation-24) - [Documentation](#documentation-18) - [Features](#features-17) - [0.7.1-alpha.1 (2021-07-22)](#071-alpha1-2021-07-22) - [Bug Fixes](#bug-fixes-22) - [Code Generation](#code-generation-25) - [Documentation](#documentation-19) - [Tests](#tests-16) - [0.7.0-alpha.1 (2021-07-13)](#070-alpha1-2021-07-13) - [Breaking Changes](#breaking-changes-13) - [Bug Fixes](#bug-fixes-23) - [Code Generation](#code-generation-26) - [Code Refactoring](#code-refactoring-8) - [Documentation](#documentation-20) - [Features](#features-18) - [Tests](#tests-17) - [Unclassified](#unclassified-11) - [0.6.3-alpha.1 (2021-05-17)](#063-alpha1-2021-05-17) - [Breaking Changes](#breaking-changes-14) - [Bug Fixes](#bug-fixes-24) - [Code Generation](#code-generation-27) - [Code Refactoring](#code-refactoring-9) - [0.6.2-alpha.1 (2021-05-14)](#062-alpha1-2021-05-14) - [Code Generation](#code-generation-28) - [Documentation](#documentation-21) - [0.6.1-alpha.1 (2021-05-11)](#061-alpha1-2021-05-11) - [Code Generation](#code-generation-29) - [Features](#features-19) - [0.6.0-alpha.2 (2021-05-07)](#060-alpha2-2021-05-07) - [Bug Fixes](#bug-fixes-25) - [Code Generation](#code-generation-30) - [Features](#features-20) - [0.6.0-alpha.1 (2021-05-05)](#060-alpha1-2021-05-05) - [Breaking Changes](#breaking-changes-15) - [Bug Fixes](#bug-fixes-26) - [Code Generation](#code-generation-31) - [Code Refactoring](#code-refactoring-10) - [Documentation](#documentation-22) - [Features](#features-21) - [Tests](#tests-18) - [Unclassified](#unclassified-12) - [0.5.5-alpha.1 (2020-12-09)](#055-alpha1-2020-12-09) - [Bug Fixes](#bug-fixes-27) - [Code Generation](#code-generation-32) - [Documentation](#documentation-23) - [Features](#features-22) - [Tests](#tests-19) - [Unclassified](#unclassified-13) - [0.5.4-alpha.1 (2020-11-11)](#054-alpha1-2020-11-11) - [Bug Fixes](#bug-fixes-28) - [Code Generation](#code-generation-33) - [Code Refactoring](#code-refactoring-11) - [Documentation](#documentation-24) - [Features](#features-23) - [0.5.3-alpha.1 (2020-10-27)](#053-alpha1-2020-10-27) - [Bug Fixes](#bug-fixes-29) - [Code Generation](#code-generation-34) - [Documentation](#documentation-25) - [Features](#features-24) - [Tests](#tests-20) - [0.5.2-alpha.1 (2020-10-22)](#052-alpha1-2020-10-22) - [Bug Fixes](#bug-fixes-30) - [Code Generation](#code-generation-35) - [Documentation](#documentation-26) - [Tests](#tests-21) - [0.5.1-alpha.1 (2020-10-20)](#051-alpha1-2020-10-20) - [Bug Fixes](#bug-fixes-31) - [Code Generation](#code-generation-36) - [Documentation](#documentation-27) - [Features](#features-25) - [Tests](#tests-22) - [Unclassified](#unclassified-14) - [0.5.0-alpha.1 (2020-10-15)](#050-alpha1-2020-10-15) - [Breaking Changes](#breaking-changes-16) - [Bug Fixes](#bug-fixes-32) - [Code Generation](#code-generation-37) - [Code Refactoring](#code-refactoring-12) - [Documentation](#documentation-28) - [Features](#features-26) - [Tests](#tests-23) - [Unclassified](#unclassified-15) - [0.4.6-alpha.1 (2020-07-13)](#046-alpha1-2020-07-13) - [Bug Fixes](#bug-fixes-33) - [Code Generation](#code-generation-38) - [0.4.5-alpha.1 (2020-07-13)](#045-alpha1-2020-07-13) - [Bug Fixes](#bug-fixes-34) - [Code Generation](#code-generation-39) - [0.4.4-alpha.1 (2020-07-10)](#044-alpha1-2020-07-10) - [Bug Fixes](#bug-fixes-35) - [Code Generation](#code-generation-40) - [Documentation](#documentation-29) - [0.4.3-alpha.1 (2020-07-08)](#043-alpha1-2020-07-08) - [Bug Fixes](#bug-fixes-36) - [Code Generation](#code-generation-41) - [0.4.2-alpha.1 (2020-07-08)](#042-alpha1-2020-07-08) - [Bug Fixes](#bug-fixes-37) - [Code Generation](#code-generation-42) - [0.4.0-alpha.1 (2020-07-08)](#040-alpha1-2020-07-08) - [Breaking Changes](#breaking-changes-17) - [Bug Fixes](#bug-fixes-38) - [Code Generation](#code-generation-43) - [Code Refactoring](#code-refactoring-13) - [Documentation](#documentation-30) - [Features](#features-27) - [Unclassified](#unclassified-16) - [0.3.0-alpha.1 (2020-05-15)](#030-alpha1-2020-05-15) - [Breaking Changes](#breaking-changes-18) - [Bug Fixes](#bug-fixes-39) - [Chores](#chores-1) - [Code Refactoring](#code-refactoring-14) - [Documentation](#documentation-31) - [Features](#features-28) - [Unclassified](#unclassified-17) - [0.2.1-alpha.1 (2020-05-05)](#021-alpha1-2020-05-05) - [Chores](#chores-2) - [Documentation](#documentation-32) - [0.2.0-alpha.2 (2020-05-04)](#020-alpha2-2020-05-04) - [Breaking Changes](#breaking-changes-19) - [Bug Fixes](#bug-fixes-40) - [Chores](#chores-3) - [Code Refactoring](#code-refactoring-15) - [Documentation](#documentation-33) - [Features](#features-29) - [Unclassified](#unclassified-18) - [0.1.1-alpha.1 (2020-02-18)](#011-alpha1-2020-02-18) - [Bug Fixes](#bug-fixes-41) - [Code Refactoring](#code-refactoring-16) - [Documentation](#documentation-34) - [0.1.0-alpha.6 (2020-02-16)](#010-alpha6-2020-02-16) - [Bug Fixes](#bug-fixes-42) - [Code Refactoring](#code-refactoring-17) - [Documentation](#documentation-35) - [Features](#features-30) - [0.1.0-alpha.5 (2020-02-06)](#010-alpha5-2020-02-06) - [Documentation](#documentation-36) - [Features](#features-31) - [0.1.0-alpha.4 (2020-02-06)](#010-alpha4-2020-02-06) - [Continuous Integration](#continuous-integration) - [Documentation](#documentation-37) - [0.1.0-alpha.3 (2020-02-06)](#010-alpha3-2020-02-06) - [Continuous Integration](#continuous-integration-1) - [0.1.0-alpha.2 (2020-02-03)](#010-alpha2-2020-02-03) - [Bug Fixes](#bug-fixes-43) - [Documentation](#documentation-38) - [Features](#features-32) - [Unclassified](#unclassified-19) - [0.1.0-alpha.1 (2020-01-31)](#010-alpha1-2020-01-31) - [Documentation](#documentation-39) - [0.0.3-alpha.15 (2020-01-31)](#003-alpha15-2020-01-31) - [Unclassified](#unclassified-20) - [0.0.3-alpha.14 (2020-01-31)](#003-alpha14-2020-01-31) - [Unclassified](#unclassified-21) - [0.0.3-alpha.13 (2020-01-31)](#003-alpha13-2020-01-31) - [Unclassified](#unclassified-22) - [0.0.3-alpha.11 (2020-01-31)](#003-alpha11-2020-01-31) - [Unclassified](#unclassified-23) - [0.0.3-alpha.10 (2020-01-31)](#003-alpha10-2020-01-31) - [Unclassified](#unclassified-24) - [0.0.3-alpha.7 (2020-01-30)](#003-alpha7-2020-01-30) - [Unclassified](#unclassified-25) - [0.0.3-alpha.5 (2020-01-30)](#003-alpha5-2020-01-30) - [Continuous Integration](#continuous-integration-2) - [Unclassified](#unclassified-26) - [0.0.3-alpha.4 (2020-01-30)](#003-alpha4-2020-01-30) - [Unclassified](#unclassified-27) - [0.0.3-alpha.2 (2020-01-30)](#003-alpha2-2020-01-30) - [Unclassified](#unclassified-28) - [0.0.3-alpha.1 (2020-01-30)](#003-alpha1-2020-01-30) - [Unclassified](#unclassified-29) - [0.0.1-alpha.9 (2020-01-29)](#001-alpha9-2020-01-29) - [Continuous Integration](#continuous-integration-3) - [0.0.2-alpha.1 (2020-01-29)](#002-alpha1-2020-01-29) - [Unclassified](#unclassified-30) - [0.0.1-alpha.6 (2020-01-29)](#001-alpha6-2020-01-29) - [Continuous Integration](#continuous-integration-4) - [0.0.1-alpha.5 (2020-01-29)](#001-alpha5-2020-01-29) - [Continuous Integration](#continuous-integration-5) - [Unclassified](#unclassified-31) - [0.0.1-alpha.3 (2020-01-28)](#001-alpha3-2020-01-28) - [Continuous Integration](#continuous-integration-6) - [Documentation](#documentation-40) - [Unclassified](#unclassified-32) # [](https://github.com/ory/kratos/compare/v26.2.0...v) (2026-03-20) # [26.2.0](https://github.com/ory/kratos/compare/v25.4.0...v26.2.0) (2026-03-20) v26.2.0 ### Bug Fixes - remove more instances of injecting unrecoverable email faults ([81e9151](https://github.com/ory/kratos/commit/81e9151758d89d31a673b2513102ccb9fe46fd1f)): GitOrigin-RevId: 7432f6c605be10cc59b58cf656736d627d7d6f74 - Add missing indices on identity_id ([a085d87](https://github.com/ory/kratos/commit/a085d872fa180f28f22809fc6624cfc1e8318580)): GitOrigin-RevId: 0e9820c93111db7f89f85325a40081737e89978f - Add missing StrategyUsed attribute to Login and registration events ([e72c297](https://github.com/ory/kratos/commit/e72c2972340fa58415d53cc939d3e42e8ea6d6f9)): GitOrigin-RevId: 34d5db665ed61f78a94249d59ea686abce048983 - Add missing transient nodes clear ([ed56dac](https://github.com/ory/kratos/commit/ed56daccaafb6b98f889b61834a8d8d31a055930)): GitOrigin-RevId: 8d262434213c9fbcb483b8fdc8a498e1167ed26f - Add oidc linking/unlinking to api settings flow ([6a6928c](https://github.com/ory/kratos/commit/6a6928c401084443f2475a9361096e9ddfcd1bb0)): GitOrigin-RevId: c3fc0688e531326c0e00b797402d01e324b9abfb - Always retry curl invocations to surmount transient third-party failures ([2473954](https://github.com/ory/kratos/commit/2473954f4e3e8b0d2f8d18a36ddb08c02953cfc2)): GitOrigin-RevId: b23ca94a4e1ba11367012a70a9e528b614f3ceb9 - Base64encoded schemaURL cannot be resolved ([a86c212](https://github.com/ory/kratos/commit/a86c212198369083aaf47ab38e798781d42ceb34)): GitOrigin-RevId: 7b224a32afd2ff5ebf62ef96d797258e8de56afd - Batch identity error propagation ([2f9c3e3](https://github.com/ory/kratos/commit/2f9c3e3490e077b46bcff8e1167ee4204c00e328)): GitOrigin-RevId: 66a8cca953ee45f50adcdee2aabcb1fa6c429e50 - Clarify password import ([849b0de](https://github.com/ory/kratos/commit/849b0def539bbe96ccca950a99998fcd3b0b6618)): GitOrigin-RevId: 8d1bffaae93e9aa484dd67803c2ad86398500b37 - Context passing in jsonnetsecure ([7e33125](https://github.com/ory/kratos/commit/7e33125db8fcbc2163649a03eab20e5e6394c7bb)): GitOrigin-RevId: 5545bf7446208fafbc8f45f88ab2903a7000a318 - Correctly scan SQL `NULL` into go JSON types ([6183672](https://github.com/ory/kratos/commit/6183672902a86988b82c6b4854089df8d6b96915)): GitOrigin-RevId: 7493c9efdb70ceea61e216660b83408bc7a5d367 - Courier should not retry message dispatches in one go ([70f7b38](https://github.com/ory/kratos/commit/70f7b38ab3cf1ac198537379c09e9332f24d4d9f)): GitOrigin-RevId: f7631f29dff77e700e7495376527eca1f56b0e51 - Data race making test flaky ([c651ecf](https://github.com/ory/kratos/commit/c651ecf2ebef1fa6bd43ae57f06c4133b05ce068)): GitOrigin-RevId: 263cada3af66101a06e617077ddd508d8ef86705 - Deadlock when using -parallel 1 ([8adaa02](https://github.com/ory/kratos/commit/8adaa024e8701c974f7644ec08d1be5caeb15822)): GitOrigin-RevId: 467c9c3c2ef22da59b31b7fadc443ca66bf208b9 - Don't attempt to redirect to ory.com in kratos tests ([a06b3c2](https://github.com/ory/kratos/commit/a06b3c2b7d827aa64328825c38132c167044d852)): GitOrigin-RevId: 5ff2118ee1ded9ddfd6f3e8c2d11ebdac46b70cc - Down migrations in newer MySQL versions ([e948a0b](https://github.com/ory/kratos/commit/e948a0bfd4baf2bbb1382cd8a778cafa9697affa)): GitOrigin-RevId: 85c133fa17445d2579918d673db27d9e68e3fc8b - Duplicate credential error placeholder case mismatch ([84ee596](https://github.com/ory/kratos/commit/84ee59662ce43e250b00716c95aa47b37eba82f1)): GitOrigin-RevId: 04b013ad8069ef993d337a0cb97ea7fea84c188e - Failing down migration ([7bb24c5](https://github.com/ory/kratos/commit/7bb24c591704b83ea2fee62029029dd71786dc30)): GitOrigin-RevId: 30d15680f8916d2ec12c2acdad6768cb3c0ed439 - Fetch login challenge after code submissions ([048d315](https://github.com/ory/kratos/commit/048d3150e518c8bf7a9cd4c7e57ea0617ca34ff5)): GitOrigin-RevId: 4ff0402e96d4b8242bbfcbae518f96c77e1fdf09 - Fix benchmark test ([2886abe](https://github.com/ory/kratos/commit/2886abe1a0e39a63dc95dc6e1b009655af9aa4bb)): GitOrigin-RevId: 8cd5d90c7d7124671389cafbb89b87e6c9ced34c - Fix data race in courier test by protecting slice with mutex ([6673982](https://github.com/ory/kratos/commit/66739820c9d45ad4bc465b2ce3e10311967e29e4)): GitOrigin-RevId: ae11f746a59d8c0c35a04c1242e8c23ad4aa2497 - Fix flaky email test ([01e1dd0](https://github.com/ory/kratos/commit/01e1dd04dce4ca08ba8a855866f47d635ed68076)): GitOrigin-RevId: feab815802aa1088bceca0ea7a968d99f674e657 - Handle batch identities errors more gracefully ([952d7ea](https://github.com/ory/kratos/commit/952d7ea7829ba84699d51c52c5ea2eaf86a09c13)): GitOrigin-RevId: 973ed113f3cb0f6b3b43897b67d02fb5caf2533b - Incorrect default value for page_tokens ([9a5f8b9](https://github.com/ory/kratos/commit/9a5f8b9dee2eebfacd7be7440aadfa764c2f1a62)): GitOrigin-RevId: 74d3aa40f77a7b08f7616a5d00dd6097b6441e08 - Incorrect error handling ([1757cdd](https://github.com/ory/kratos/commit/1757cdd2896235acb6d1d11901adfe8ec17a270a)): GitOrigin-RevId: 0fd490b859fdd3df0f628904dc4b9531b28a8f68 - Incorrect usage of database/sql ([590d898](https://github.com/ory/kratos/commit/590d898dc7fcbd90ff6abc94f97cd1795315ba1e)): GitOrigin-RevId: a6bc423928fb9910a76e0b18afa2fc2a56fc052d - **kratos:** Otp fast-path 2fa body error ([95341a8](https://github.com/ory/kratos/commit/95341a83a6b639aeec7db9cbec44a93b55bfca98)): GitOrigin-RevId: 86ef2d84eed29078257d4fc30ca7e278d00e9947 - Lint ([e7045c5](https://github.com/ory/kratos/commit/e7045c55a35c10ba5c9ef42627ee394de0cd2056)): GitOrigin-RevId: 76a6f406c39711497a9029cf8937e3b6d1b58e0e - Pass transient payload to webhooks in API/native OIDC flows ([d023775](https://github.com/ory/kratos/commit/d023775750c0e86f5aef64b91dc976f554f90699)): GitOrigin-RevId: f31711edace2a1515579991437250bbd08fa74a1 - Properly accept login challenge in verification after login flows ([f6d59bb](https://github.com/ory/kratos/commit/f6d59bb1a7d5d9850dac9254bf67166c4e44f023)): GitOrigin-RevId: d71d9a347e6ec39854e291aa00d2a635f480a55c - Recovery code expires_in regression ([8f54814](https://github.com/ory/kratos/commit/8f5481428695071bdb4f09c3e22ebe848cb89a0c)): GitOrigin-RevId: 00c5dfe3b3c2ded1f6622410bb4b7c7f6cfbf129 - Recovery code expiry error ([3447e0a](https://github.com/ory/kratos/commit/3447e0a6f5e5b8b22ef360e989e4944edc8491a2)): GitOrigin-RevId: 9505d3eb2da5ea82b200700483168bb1987966fa - Redact subject codes ([071ad54](https://github.com/ory/kratos/commit/071ad54b0a1fc601b162a63d8f96a7a95c8ad118)): GitOrigin-RevId: 56be74ec84ca7b8d9885c92f89cd1332e7af4238 - Remove flaky test for unused function ([b4d8591](https://github.com/ory/kratos/commit/b4d85919854ee5b888d63519af6d0fe176973559)): GitOrigin-RevId: 736a6ab0e4bbdc424705c3d4589caf61b72d00d1 - Remove redundant ORDER BY in QueryForCredentials ([65b27fd](https://github.com/ory/kratos/commit/65b27fd812b2fe6fe3fd1a2a706b8e38e265f523)): GitOrigin-RevId: 78e60dcdf2ed5082ffa3b7e5489ab9018fc2f5b2 - Remove WithDumpMigrations option to MigrationBox ([7ee85fb](https://github.com/ory/kratos/commit/7ee85fb985b01efcfd7c9d486e1db85c0104648e)): GitOrigin-RevId: ec8bf6fb2d78337c71a158ba2f65dfe2e2d612f6 - Request log config key ([1799e3a](https://github.com/ory/kratos/commit/1799e3a49f56875a3328add32ef90d3bab485caa)): GitOrigin-RevId: d81d89baac28e6ac486b3a0b1ca3683f537eb15e - Resolve incorrect error handling ([9144b55](https://github.com/ory/kratos/commit/9144b554c0feb5fc737f245ac71e99da904f2d98)): GitOrigin-RevId: e8bb70305cf8218b60e89db4d7bf128f7b590aa7 - Resolve null response in OAuth2 flow with existing session ([e7d8bd1](https://github.com/ory/kratos/commit/e7d8bd1f7d13fff2e9aa065444556f27f9a9d688)): GitOrigin-RevId: 54adcc002fa545e2eb4abf1ef819d9e08089a279 - Return a specific error message for email & phone validation errors ([d6b0f49](https://github.com/ory/kratos/commit/d6b0f492c4aa6608b821252ede2ae0175ff89b66)): GitOrigin-RevId: 48ed3f3642f831b1bc58f3238105911ad8a55de0 - Return correct CSRF errors ([b7b7fd4](https://github.com/ory/kratos/commit/b7b7fd4f0bdbca38eb7e3f26c641cb99ad1337de)): GitOrigin-RevId: cab70529e9041391cd406fb96b8ce0b53b1a657f - Return oauth2 login challenge on Bad Request in self-service flows ([bc33d5c](https://github.com/ory/kratos/commit/bc33d5cd99c89a304e6e55b5bc8a3118133e9d9d)): GitOrigin-RevId: ab4e0fd801d619550234afba63614c098bc79fea - Seamlessly migrate existing users to SCIM ([76d35cf](https://github.com/ory/kratos/commit/76d35cfe1c79346628bcbc4adfe540b6099d2a62)): GitOrigin-RevId: b2088ed6bf263a408a124d411c118b1f3fa040b9 - Show captcha on otp submission ([039d5bc](https://github.com/ory/kratos/commit/039d5bccd7955ca3d6ae4bfd656c790778c583c4)): GitOrigin-RevId: e1cee4e841345aec0aae698e25ddefa36a93210b - Stray debug print ([3da622f](https://github.com/ory/kratos/commit/3da622f0194469289f7e1067e42c16a8721b795c)): GitOrigin-RevId: eeb3d3c38faa92f79d2c0b5eb758bdf912a43bc7 - Transfer OAuth2 login challenge in account linking flow ([1ab143c](https://github.com/ory/kratos/commit/1ab143c5f5427032d16fd5c03dfd5ee3dd86184f)): GitOrigin-RevId: b666119e260f3b0b2071161578b5179e49a89616 - Update CONTRIBUTING.md ([95bf33b](https://github.com/ory/kratos/commit/95bf33bb29cc81d880933efa3ab38b8089006a3a)): GitOrigin-RevId: c40330cae452ab009c3a86892c0c2772d82ccfb8 - Update dependencies and replace @ory/client for kratos-selfserivce-ui-react-native ([3d88a43](https://github.com/ory/kratos/commit/3d88a439c5084da333e2f514c6c7a114c9650867)): GitOrigin-RevId: 4d380b9988c7f01acf6b71d30eeb5021cdaef973 - Update packages to fix GHSA-7h2j-956f-4vf2 ([79fb49d](https://github.com/ory/kratos/commit/79fb49d39a400f6e8c67cf2cb7097ce43bb037bf)): GitOrigin-RevId: 55aad6f8b36d1d22a6e149b842d035eae6ccae35 - Upgrade vulnerable dependencies across Go and npm ([c2adee4](https://github.com/ory/kratos/commit/c2adee419e9c6c6c35817193a2a9b45dc0022a26)): Co-authored-by: Deepak Prabhakara GitOrigin-RevId: c3fc33561a0d0eedbc9be03ff551f62452f1c905 - Use correct client authentication method for Apple OIDC ([6c2f8fb](https://github.com/ory/kratos/commit/6c2f8fb90967d69637b5587ff5c31512b8232c92)): GitOrigin-RevId: 499278d7a86a26153f98d1c820f54e69879738f4 - X data race and parallize some tests ([116a66e](https://github.com/ory/kratos/commit/116a66e18adfc10fe2cfc60c46c902d5d680543d)): GitOrigin-RevId: 737267e41d80a6278bcb8cc657208a3a0cf13dcd ### Code Refactoring - Squash merge old backoffice migration and fix up command ([7790322](https://github.com/ory/kratos/commit/77903222ee85a015ffa1caa8504655895817bdb2)): GitOrigin-RevId: 047d524cb71a5f0e7a082c4f3ebec6426935739d ### Documentation - Improve readme and dev instructions ([56be7ba](https://github.com/ory/kratos/commit/56be7baaabe11702b9406900412964028b17762c)): GitOrigin-RevId: 5269520d6b9313732e2a9f13747e8696bb96a8e4 - Update readmes ([bc8dca6](https://github.com/ory/kratos/commit/bc8dca6807e0deb77aa3a9c589a367bc6772ba9d)): GitOrigin-RevId: 936016692123f481c9addb607355ac326edee206 ### Features - Add captcha strategy for recovery flow ([3dee8f5](https://github.com/ory/kratos/commit/3dee8f5f949973373f5e3d4ea9878981c30da07f)): GitOrigin-RevId: 8bf87d0de35855b50cd0c995329b4e40520a4c2b - Add captcha strategy for verification flow ([420f69d](https://github.com/ory/kratos/commit/420f69d5bcecb8358bd24a432d61608cccfd4479)): GitOrigin-RevId: c500a8b1fad779bd3a4da04ebc816ee488d21d25 - Add column identity_id to identity_credential_identifiers and session_devices ([57b099f](https://github.com/ory/kratos/commit/57b099f24ce94ed3c443d2ee3acbc5c5670d1aa8)): GitOrigin-RevId: a8fc43b6bba9119a2d9472343eede30978ee72d7 - Add native api flow support for passkeys ([39c341b](https://github.com/ory/kratos/commit/39c341b9260694bf5ac19874c66ef58e18094b9e)): GitOrigin-RevId: 407616212851156151f786e176be6e6349917c8d - Add ratelimit buckets to swagger definitions ([a14c3f2](https://github.com/ory/kratos/commit/a14c3f222617eef5573534745e957d5c15e4f589)): GitOrigin-RevId: 854dea8de34fc0402fbe1641af7f076f977cbcbc - Add session to all settings hooks payloads ([aebbc2b](https://github.com/ory/kratos/commit/aebbc2be0096e5727c1da0fa936d1a9a0b3273e1)): GitOrigin-RevId: baeb33ceb72409aaa231fee2988734d729115896 - Add support for NULL and more column types to keysetpagination ([3f24dbf](https://github.com/ory/kratos/commit/3f24dbf3b98f51726a94240a5d6de947586c8112)): GitOrigin-RevId: 4e314fd71e85ebeb0fc5ef3bd8d5e892c610f01e - Auto account linking for google and apple ([623742e](https://github.com/ory/kratos/commit/623742e15227c36f85730185fe4230781febdd09)): GitOrigin-RevId: c66e7aeda756b80aee97ee82fed988123da72541 - Automatic transaction retries for postgres ([80dcbac](https://github.com/ory/kratos/commit/80dcbac48d1c9a1882319fa009269692aee1709a)): GitOrigin-RevId: e7d567988621937fb25e9f41af7892b6f0aad65d - Better multi-region queries ([af48288](https://github.com/ory/kratos/commit/af48288839e0dee36f7a4cb86434bf3237cb6fd2)): GitOrigin-RevId: 007a93a33d5fe26ef336c35f3f6100ee8bcb4f61 - Collect external latency data and write to logs ([97ce640](https://github.com/ory/kratos/commit/97ce6402184842f1cfcd79da274ab367a0e8124a)): GitOrigin-RevId: 6ffdbd26c4346ed65646a8f508a4ed44dd4b7637 - Consider Go migrations DirHash when restoring full schema from backups ([99c8cdc](https://github.com/ory/kratos/commit/99c8cdc97bd8d70cef981f2c78fdae186806c389)): GitOrigin-RevId: 3b1e15bede6a985c746e16aee512d778c7bf9743 - Forward (some) user request headers to SMS HTTP channel ([f2ce286](https://github.com/ory/kratos/commit/f2ce286eb11eb3fcd4ef63d2ec5b596c3cfc76df)): GitOrigin-RevId: 2f63cc936d612b530a3b1058656e54716f71559f - Generate events for SSO and SCIM provider revisions ([da8ec11](https://github.com/ory/kratos/commit/da8ec11af3506af8220280727c074cedcdf34f43)): GitOrigin-RevId: 61d08f591404d69e55057d745e0342c17446a713 - Hydra benchmarking tool ([aa3071f](https://github.com/ory/kratos/commit/aa3071fa7a35ea4d6a6bd4f60c147bf571deedcb)): GitOrigin-RevId: a2db2f17a2eaf000ff7ffbfdd71c422721259d39 - Improved tracing ([46c1028](https://github.com/ory/kratos/commit/46c1028e2b57ba17f4de5f79c882f34b19d84822)): GitOrigin-RevId: 98ae16a6f1de4414538c7692758af850aa0caaac - Infer regional-by-row region using foreign key constraints ([46c18eb](https://github.com/ory/kratos/commit/46c18eb372e93b3bcd1d92b5edb0606dbcc10b50)): GitOrigin-RevId: 0b33189034afb6a1090f49b06ec28cb68b335a89 - Keto-cli improvements ([86968f5](https://github.com/ory/kratos/commit/86968f5773c0d7f6ecff71db08bd505b85222b7d)): GitOrigin-RevId: 7e569c384d891743c8b9a5c2cfca635e550cfc89 - **kratos:** Auto-send code when it is the only available method ([86103bc](https://github.com/ory/kratos/commit/86103bcff46b4e4cb0d18c3934bbcef30fb4af68)): GitOrigin-RevId: cedeed8f92b51fdc746807b10937cb633d3c62d0 - Login with uae pass ([1544efe](https://github.com/ory/kratos/commit/1544efe5ea3975f2eb9d61aea4d893eb51b7abaa)): GitOrigin-RevId: 9ea7e86014ca7202b0b54bc1e5707253121db6cb - Make new identity_id column on identifiers and session_devices NOT NULL and establish foreign key ([6bf18bf](https://github.com/ory/kratos/commit/6bf18bf87e02a25bd1f87bb40af71f8439a6c0c5)): GitOrigin-RevId: a837b24e7f0642fcd77b2f5c1c0532de07c49078 - Make SCIM work with MySQL ([a34e951](https://github.com/ory/kratos/commit/a34e9515ad9742cb6dd5f981c248bbed8050a404)): GitOrigin-RevId: a833fe21172d9c218b55d7229e4ed3a131cfcb6c - Rename project revision columns ([e25723e](https://github.com/ory/kratos/commit/e25723e39492fc2c9f7e43970dfb4f08bba0a1b3)): GitOrigin-RevId: b20a153d86d72c43f83f7107b4c3ed602bcb5009 - Speed up OIDC login+registration handling ([6bfbaf5](https://github.com/ory/kratos/commit/6bfbaf5791eddce0a2777b580a62983762314930)): GitOrigin-RevId: bd516d8f488b385e2968d56509123cde78d0f642 - Update GetActiveRecoveryStrategies method ([b94f4c9](https://github.com/ory/kratos/commit/b94f4c9ba5b1178ac8ca16af2ce544c0479956a6)): GitOrigin-RevId: ae8121e4488d8fc332bea1bcea704b15c2cd7987 - Use keysetpagination planner for keto read queries ([85590e8](https://github.com/ory/kratos/commit/85590e8a4ca192ebf8aad6a2ce79f5dc50c5f53f)): GitOrigin-RevId: e2ed9c3eecc60fd26eaedb3f8cca4f222fd1cb1e ### Tests - Add assertions for json response body ([0f5085c](https://github.com/ory/kratos/commit/0f5085c67a3f6f8481246c1d86b080d932a4cfa4)): GitOrigin-RevId: b9ff0ad8e58bf96953980bb8aeb55aeee8138c04 - Deflake and improve performance ([0451169](https://github.com/ory/kratos/commit/04511690ca6702f05ce7c64227b9db028fa5204f)): GitOrigin-RevId: 0f7be9e16ea12f9cb277f8cb3f03058e9db1aaa9 - Deflake directory watcherx ([00c4f9e](https://github.com/ory/kratos/commit/00c4f9e4a67b7c78ff8359d4240a2ddaef05b4bb)): GitOrigin-RevId: 6b1c66f96f46833273490b22179aadcb5c7ce01c - Deflake SAML config assertion ([71da1e3](https://github.com/ory/kratos/commit/71da1e3c590f5001933b28102ffeb454497e1827)): GitOrigin-RevId: 3cad41ed2ded379328e6c0f50d1b27a9da9b0f38 - Faster and more reliable courier tests ([2a552ea](https://github.com/ory/kratos/commit/2a552eacefb0dd385f01c678eae8544d276dfa90)): GitOrigin-RevId: 30c7ebf095fd5c233e173dc0a645d7bcbe69115f - Fix data races ([4014eeb](https://github.com/ory/kratos/commit/4014eeb08c65dee3956881127ce1e5c50d018449)): GitOrigin-RevId: 7dc520ddc926be30393d474734d37a5f58cc4851 - Fix data races ([8482dd5](https://github.com/ory/kratos/commit/8482dd5de02900fd59746313504ee9b81c9ff874)): GitOrigin-RevId: 624aeee77f199dd8c67b67cefe2a61c2f9e7d759 - **hydra:** Add plaintext backups for all DB types ([3369ebd](https://github.com/ory/kratos/commit/3369ebd6f3695c94c41e0bdd49fdff29d79971c7)): GitOrigin-RevId: ee2c8ff9ffacff66d09241827780350979957dba - Minor setup improvements ([b9e094d](https://github.com/ory/kratos/commit/b9e094d674629c8b4f8f4e3cf7679658c5551f55)): GitOrigin-RevId: cc7740e9ef1c5469df10001c6dd1bca50368ee01 ### Unclassified - apply review changes ([e7d5dd2](https://github.com/ory/kratos/commit/e7d5dd272b3618767223732e61b0b9a2f3c752f5)): GitOrigin-RevId: 9d4a25d813cb86f4a42fe879d7d1c346674e5dc6 - storybook snapshots ([3f06c5d](https://github.com/ory/kratos/commit/3f06c5da026861664677472ffec986f90b5e5590)): GitOrigin-RevId: 066f7ecfdb4bc5614a7c238c4e0e21e959b7e6b7 - fixes ([7982b73](https://github.com/ory/kratos/commit/7982b73ae145e5e709ab742c29d784d1a7f85db2)): GitOrigin-RevId: 11ef68918584d7711fca8b20f371ea0c8b86f127 # [25.4.0](https://github.com/ory/kratos/compare/v1.3.0...v25.4.0) (2025-11-07) v25.4.0 ## Breaking Changes The `require_verified_address` hook no longer returns a plain error. Previously, users had to manually start the verification flow, which caused a poor experience. Now, Ory Kratos automatically creates a verification flow and redirects the user using `continue_with` or an HTTP redirect. The verification flow starts with the first verified address found for the user. This aligns the behavior of `require_verified_address` with using the `verification` and `show_verification_ui` hook combination for login. Going forward, the node group of fields that are failing validation during oidc sign up are `default` and no longer `oidc`. For now, you can get the legacy behavior back by turning on `feature_flags.legacy_oidc_registration_node_group=true`. Co-authored-by: Jonas Hungershausen Before this change, `show_verification_ui` would always be included in `continue_with` for the registration flow when verification was enabled. After this change, `show_verification_ui` is only included when the `show_verification_ui` post-registration hook is defined. Account linking incorrectly returned a 200 OK status code even though the login flow was not completed successfully. Going forward, the correct 400 OK status code will be sent when using the API flow or `Accept: application/json`. This patch changes the behavior of configuration item `foo` to do bar. To keep the existing behavior please do baz. ``` --> ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code This patch changes the behavior of configuration item `foo` to do bar. To keep the existing behavior please do baz. ``` --> ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code This patch changes the behavior of configuration item `foo` to do bar. To keep the existing behavior please do baz. ``` --> ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code This patch changes the behavior of configuration item `foo` to do bar. To keep the existing behavior please do baz. ``` --> ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code This patch changes the behavior of configuration item `foo` to do bar. To keep the existing behavior please do baz. ``` --> ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code This patch changes the behavior of configuration item `foo` to do bar. To keep the existing behavior please do baz. ``` --> ## Related issue(s) ## Checklist - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [ ] I have referenced an issue containing the design document if my change introduces a new feature. - [ ] I am following the [contributing code The total count header `x-total-count` will no longer be sent in response to `GET /admin/sessions` requests. Closes https://github.com/ory-corp/cloud/issues/7177 Closes https://github.com/ory-corp/cloud/issues/7175 Closes https://github.com/ory-corp/cloud/issues/7176 ### Bug Fixes - Accept login challenge in session_issuer on SPA flows ([#4288](https://github.com/ory/kratos/issues/4288)) ([e13687a](https://github.com/ory/kratos/commit/e13687ad51cdb889f0e680a005145a0134086fc7)) - Accept login_challenge in SPA verification flows ([#4284](https://github.com/ory/kratos/issues/4284)) ([7ca3b6b](https://github.com/ory/kratos/commit/7ca3b6be14c53e16c3a8f4e7eb83efe0b0e7c88e)) - Account linking should only happen after 2fa when required ([#4174](https://github.com/ory/kratos/issues/4174)) ([8e29b68](https://github.com/ory/kratos/commit/8e29b68a595d2ef18e48c2a01072335cefa36d86)) - Account linking with 2FA ([#4188](https://github.com/ory/kratos/issues/4188)) ([4a870a6](https://github.com/ory/kratos/commit/4a870a678dd3676abda7afc9803399dec4411b05)): This fixes some edge cases with OIDC account linking for accounts with 2FA enabled. - Add default issuer URL for LINE ([#4415](https://github.com/ory/kratos/issues/4415)) ([292f65d](https://github.com/ory/kratos/commit/292f65d6bd1bc70b2f13b92bdbcb8e30256e0a17)): Fixed+expanded relevant comment. Fixed some tracing issues. Added error info and missing res.Body.Close() in courier. - Add exists clause ([#4191](https://github.com/ory/kratos/issues/4191)) ([a313dd6](https://github.com/ory/kratos/commit/a313dd6ba6d823deb40f14c738e3b609dbaad56c)) - Add missing autocomplete attributes to identifier_first strategy ([#4215](https://github.com/ory/kratos/issues/4215)) ([e1f29c2](https://github.com/ory/kratos/commit/e1f29c2d3524f9444ec067c52d2c9f1d44fa6539)) - Add missing csrf_token ([#4363](https://github.com/ory/kratos/issues/4363)) ([f441f41](https://github.com/ory/kratos/commit/f441f41312b81a570e99348f69b88008f4516660)) - Add missing discriminator ([#4365](https://github.com/ory/kratos/issues/4365)) ([c10bb06](https://github.com/ory/kratos/commit/c10bb06bb9125fbc71863c5aa82194da2f2e2888)) - Add missing saml group ([#4268](https://github.com/ory/kratos/issues/4268)) ([44eb305](https://github.com/ory/kratos/commit/44eb305cf91672798f7d57550a026c6b970f7566)) - Add missing submit group ([#4354](https://github.com/ory/kratos/issues/4354)) ([106163d](https://github.com/ory/kratos/commit/106163d15e2eb84c3403d0ce8f829a9d9b3ce94f)) - Add missing values to the session method enum ([a043b43](https://github.com/ory/kratos/commit/a043b43ceb5e7e1ce4fd1ef25f4ba8db72d7b478)): GitOrigin-RevId: 60b31e9f7d7b50dc652efc5f3a385be4adb25ba1 - Add resend node to after registration verification flow ([#4260](https://github.com/ory/kratos/issues/4260)) ([9bc83a4](https://github.com/ory/kratos/commit/9bc83a410b8de9d649b6393f136889dd14098b0d)) - Add transient payload to fedcm ([#4369](https://github.com/ory/kratos/issues/4369)) ([245f5dc](https://github.com/ory/kratos/commit/245f5dc1c83d35d7e228a45ee267d6bcdb705e98)), closes [../blob/master/CONTRIBUTING.md#contributing-code](https://github.com/../blob/master/CONTRIBUTING.md/issues/contributing-code): **Changes:** - Add `LoginStarted` and `RegistrationStarted` events along their required attributes - Sort all event attributes alphabetically - Emit these events when a new login/registration flow is created, _after_ basic validation passed - It is unclear yet how many of these events will be emitted, as such it is suggested that in a first phase, they remain internal and are not yet sent externally to avoid surprises (note: sometimes, these events can be emitted without user action such as simply visiting/being redirected to the sign-in page, etc) **Documentation PR:** [ory/docs#2144](https://github.com/ory/docs/pull/2144) **Issue:** https://github.com/ory-corp/cloud/issues/7895 Examples in Grafana: - LoginStarted: Screenshot 2025-05-06 at 14 54 32 - RegistrationStarted: Screenshot 2025-05-06 at 14
    46 17 - Add migrate sql up|down|status ([#4228](https://github.com/ory/kratos/issues/4228)) ([e6fa520](https://github.com/ory/kratos/commit/e6fa520058ca778e01d4e93a8ab4b31a74dd2e11)): This patch adds the ability to execute down migrations using: ``` kratos migrate sql down -e --steps {num_of_steps} ``` Please read `kratos migrate sql down --help` carefully. Going forward, please use the following commands ``` kratos migrate sql up ... kratos migrate sql status ... ``` instead of the previous, now deprecated ``` kratos migrate sql ... kratos migrate status ... ``` commands. See https://github.com/ory-corp/cloud/issues/7350 - Add new Division ui node attributes ([235af52](https://github.com/ory/kratos/commit/235af527dea47b87ad0f18ff04f9b807e4639ae3)): Division nodes may be used to hook dynamic scripts and are not actively used in the Ory Kratos open source. - Add new endpoint to tokenize JWT with a webhook ([f7fa792](https://github.com/ory/kratos/commit/f7fa792a52af5bddbd2869fc4bc0383c73641dfb)): GitOrigin-RevId: ff93a3daadc993348ff40ee21c28ec0a30c6cfbe - Add oid as subject source for microsoft ([#4171](https://github.com/ory/kratos/issues/4171)) ([77beb4d](https://github.com/ory/kratos/commit/77beb4de5209cee0bea4b63dfec21d656cf64473)), closes [#4170](https://github.com/ory/kratos/issues/4170): In the case of Microsoft, using `sub` as an identifier can lead to problems. Because the use of OIDC at Microsoft is based on an app registration, the content of `sub` changes with every new app registration. `Sub` is therefore not uniquely related to the user. It is therefore not possible to transfer users from one app registration to another without further problems. https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference#payload-claims With the use of `oid` it is possible to identify a user by a unique id. - Add session in settings after hook ([1b57fdf](https://github.com/ory/kratos/commit/1b57fdf375e218ff1cb13fa4888ee01bbb2a986c)): GitOrigin-RevId: 754c057a2d2d5b92a417b429caea524a5d54b184 - Add support for Line v2.1 OIDC provider ([#4240](https://github.com/ory/kratos/issues/4240)) ([729effd](https://github.com/ory/kratos/commit/729effd61e4b08f28099bba09acef87aeb0c7ffd)): For OIDC Line Login, you only need to add id_token_key_type=JWK in the exchange step to issue tokens in ES256 format. https://github.com/ory/kratos/discussions/1116 - Allow deleting password credentials ([#4304](https://github.com/ory/kratos/issues/4304)) ([f2212d4](https://github.com/ory/kratos/commit/f2212d48af47f24ca6e504ca98bc31afe6774241)): The admin API did not allow to delete passwords at all. The restriction is now lifted to only block deletion of the first-factor credential if it is the last one. - Allow extra go migrations in persister ([#4183](https://github.com/ory/kratos/issues/4183)) ([7bec935](https://github.com/ory/kratos/commit/7bec935c33b9adb6033aaecfa9a6dbe6c9c3daa1)) - Allow listing identities by organization ID ([#4115](https://github.com/ory/kratos/issues/4115)) ([b4c453b](https://github.com/ory/kratos/commit/b4c453b0472f67d0a52b345691f66aa48777a897)) - Allow setting the org ID on creation ([#4306](https://github.com/ory/kratos/issues/4306)) ([bccd2fb](https://github.com/ory/kratos/commit/bccd2fb8c8efac96938e564f1f34cd711b41d0a1)) - Autoconfigure kratos-changefeed ([b8bf4c7](https://github.com/ory/kratos/commit/b8bf4c7ca4323adfa936cf2e24fc98e34123150a)): GitOrigin-RevId: 8e684d3c1ed528798c0c81cc4330858c54a39acf - Bump CRDB, establish foreign key, ([d76e70f](https://github.com/ory/kratos/commit/d76e70f275846e598c601b63fc337e5ceefa8f81)): GitOrigin-RevId: ca6d967ddb2e2eeb2d2eaf25e851652dddbc1d47 - Cache OIDC providers ([#4222](https://github.com/ory/kratos/issues/4222)) ([30485c4](https://github.com/ory/kratos/commit/30485c44e61c17231e0c46b321be842b19ea5a5f)): This change significantly reduces the number of requests to `/.well-known/openid-configuration` endpoints. - **changelog-oel:** Choose identity schema in self-service registration and login flows ([53f4b9f](https://github.com/ory/kratos/commit/53f4b9f943495ad265e4f0d87cbb018032f068a3)): GitOrigin-RevId: 8d6ee03cc8181d3277100a4b7412a3a113799964 - **changelog-oel:** Improved tracing and metrics for the high-performance SQL connection pool ([ce1bf9f](https://github.com/ory/kratos/commit/ce1bf9f46810553ffd8e7ff8aff4abc44e4ce1f0)), closes [hi#performance](https://github.com/hi/issues/performance): GitOrigin-RevId: 9480f8997f7641b0f1276ca2ae0f25781428fdbc - **changelog:** Add a new feature flag for the Recovery V2 to ensure backwards-compatibility ([d68736b](https://github.com/ory/kratos/commit/d68736bed28e956e52a54fef5591bd4c88de6594)): GitOrigin-RevId: e630152345321a187bc75ee59a190cc3485556a3 - **changelog:** Add CourierMessageAbandoned & CourierMessageDispatched events ([dfed493](https://github.com/ory/kratos/commit/dfed493184c64d6eee061577549caf7501d017ad)): GitOrigin-RevId: b4a2680d2fc9438b565a1283641b49871d1cbb11 - **changelog:** Find-by and delete SAML credentials ([0c80f61](https://github.com/ory/kratos/commit/0c80f61ccafc24e5bb1a497e652edfc8e951431a)): GitOrigin-RevId: 4a34b9acfc999454a8678c3e520a1bba3fe84b16 - **changelog:** Migrate http router to stdlib router ([48f5adb](https://github.com/ory/kratos/commit/48f5adb9ce720f6906283372515b85f365a7f0b5)): GitOrigin-RevId: ebd7ec330a4f7b9826cb70ba36ba2f727ea64c96 - **changelog:** Reject new password same as old password when changing the password ([a7f50ab](https://github.com/ory/kratos/commit/a7f50abc99ddd7b6dac7dea09004feeb8e84c323)): GitOrigin-RevId: 96efafceac92934eb2ab81f1a1b329b0e777cd74 - Console UI for multiple identity schemas ([1145cda](https://github.com/ory/kratos/commit/1145cda7ce2a7b7b30d78f936d005edaf5060fc3)): GitOrigin-RevId: c235c2874236762c54e619a1c09def1fd713ce78 - Custom page token column extraction ([c5cb85e](https://github.com/ory/kratos/commit/c5cb85eabee29fe32b7b2ed4acae7308c41cb245)): GitOrigin-RevId: 706b836df390da53f8ef3e3800391b206b715949 - Domain telemetry improvements ([93345d7](https://github.com/ory/kratos/commit/93345d7b9f2b302e3b35041191c44f85a6ea0973)): GitOrigin-RevId: 9a0825160976ff16b7a39024e650ecfaf9ce82a5 - Drop unused indices post index migration ([#4201](https://github.com/ory/kratos/issues/4201)) ([1008639](https://github.com/ory/kratos/commit/1008639428a6b72e0aa47bd13fe9c1d120aafb6e)) - Emit admin recovery code event ([#4230](https://github.com/ory/kratos/issues/4230)) ([a7cdc3a](https://github.com/ory/kratos/commit/a7cdc3a6911e265f4e78c780d8e4b8922066875c)) - Emit event on Jsonnet claims mapping error ([#4394](https://github.com/ory/kratos/issues/4394)) ([8caebdb](https://github.com/ory/kratos/commit/8caebdb6eb67c2039251b53804aac6a9f166f578)): We now emit an event containing the Jsonnet input and output in anonymized form when mapping the claims in the OIDC flow fails. - Emit events on jsonnet failure when templating a jwt ([#4409](https://github.com/ory/kratos/issues/4409)) ([959ded5](https://github.com/ory/kratos/commit/959ded5c8bb17b12b2bf242e959802a56f7c43e0)), closes [../blob/master/CONTRIBUTING.md#contributing-code](https://github.com/../blob/master/CONTRIBUTING.md/issues/contributing-code): - Fix typo: parital -> partial - Document with comments why an event is not emitted or not documented - Emit `JsonnetMappingFailed` events on jsonnet failure when templating a jwt (see https://www.ory.sh/docs/identities/session-to-jwt-cors). After review it seems we otherwise always emit events in all the right places, except in this very case. Tested end-to-end manually with the UI. ## Related issue(s) https://github.com/ory-corp/cloud/issues/7291 ## Checklist - [x] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md). - [x] I have referenced an issue containing the design document if my change introduces a new feature. - [x] I am following the [contributing code - Emit oryWebAuthnInitialized event once webauthn is initialized ([b4485f4](https://github.com/ory/kratos/commit/b4485f411651713a673b11e6984a3e467bd75e51)): GitOrigin-RevId: 65bf66553ee2027ce592b1f48741d320e1840de0 - Enable JSONNet templating for password migration hook ([#4390](https://github.com/ory/kratos/issues/4390)) ([b162897](https://github.com/ory/kratos/commit/b1628976a0251a0ad84fd2128d1df23f4dff5e99)): This enables JSONNet body templating for the password migration hook. There is also a significant refactoring of some internals around webhook config handling. - Expose Ory-Error-Id HTTP header ([f2b0cd5](https://github.com/ory/kratos/commit/f2b0cd5a5669e9116d248d1a43a875e5a38d723c)): GitOrigin-RevId: 3fe0ebc17fec11dd8135bfdd8e6facfd99ac2d5a - Fast add credential type lookups ([#4177](https://github.com/ory/kratos/issues/4177)) ([eeb1355](https://github.com/ory/kratos/commit/eeb13552118504f17b48f2c7e002e777f5ee73f4)) - Faster UpdateIdentity ([4c2cfae](https://github.com/ory/kratos/commit/4c2cfaefc777c38fee35d529156629f148b6da85)): GitOrigin-RevId: d3d0ea990908443967a2c576d6b04e5c4f8ba03a - Fewer DB loads when linking credentials, add tracing ([2c5bb21](https://github.com/ory/kratos/commit/2c5bb21224e28d5218354349f77514f4fbe71762)) - Goreleaser ([db10a68](https://github.com/ory/kratos/commit/db10a68529088b7e45489e8ad463661cbdcffbff)): GitOrigin-RevId: c4975f609610e3f05eaff13eb1d07eec90c49a2d - Gracefully handle failing password rehashing during login ([#4235](https://github.com/ory/kratos/issues/4235)) ([3905787](https://github.com/ory/kratos/commit/39057879821b387b49f5d4f7cb19b9e02ec924a7)): This fixes an issue where we would successfully import long passwords (>72 chars), but fail when the user attempts to login with the correct password because we can't rehash it. In this case, we simply issue a warning to the logs, keep the old hash intact, and continue logging in the user. - **hydra:** Split up persister ([910cf9c](https://github.com/ory/kratos/commit/910cf9c0a220f310e08c35701fb721faeb8fb685)): GitOrigin-RevId: 203cf926c1613fcbb20393c5b7d0af25c7aecb15 - Improve domain telemetry for OSS (Hydra & Kratos) ([86ab72a](https://github.com/ory/kratos/commit/86ab72ac7850b84e4608774a4fb98e4bd1d46ca0)): GitOrigin-RevId: b8aebb0ad8bae28ee8295b9052b2f60603244b7e - Improve identity import limits ([#4378](https://github.com/ory/kratos/issues/4378)) ([e38e812](https://github.com/ory/kratos/commit/e38e812314850c80cb18f8e1921dffc7d324aeda)) - Improve kratos courier metrics and debug log message ([c50ffcc](https://github.com/ory/kratos/commit/c50ffcc5cb2210a79953f38af2f99d46f013c780)): GitOrigin-RevId: 077bc86e499ca7be1076c3ae38412384c2777553 - Improve QueryForCredentials ([#4181](https://github.com/ory/kratos/issues/4181)) ([ca0d6a7](https://github.com/ory/kratos/commit/ca0d6a7ea717495429b8bac7fd843ac69c1ebf16)) - Improve secondary indices for self service tables ([#4179](https://github.com/ory/kratos/issues/4179)) ([825aec2](https://github.com/ory/kratos/commit/825aec208d966b54df9eeac6643e6d8129cf2253)) - Improve verification required flows ([#4407](https://github.com/ory/kratos/issues/4407)) ([2014a40](https://github.com/ory/kratos/commit/2014a403e0bc05a10d1f805b9ef81bdd4d8e2223)) - Improved events and identity recent activity ([e47b858](https://github.com/ory/kratos/commit/e47b85851539345abc70468eb8d05db882e19df6)): GitOrigin-RevId: 3ef8d9391a402381025baaf25ba3c8c199805b7e - Improved tracing for courier ([85a7071](https://github.com/ory/kratos/commit/85a7071d20d0f072316c74bee82c76ee690276f8)) - Index hint for CRDB when deleting identity credentials ([#4276](https://github.com/ory/kratos/issues/4276)) ([c703a33](https://github.com/ory/kratos/commit/c703a338894f865c7dc1dcebc6e6980ad98eaa1d)): Ref https://support.cockroachlabs.com/hc/en-us/requests/25430 - Jackson provider ([#4242](https://github.com/ory/kratos/issues/4242)) ([f18d1b2](https://github.com/ory/kratos/commit/f18d1b24539f7d8dcf9c27986af861d0f8cb9683)): This adds a jackson provider to Kratos. - Load session only once when middleware is used ([#4187](https://github.com/ory/kratos/issues/4187)) ([234b6f2](https://github.com/ory/kratos/commit/234b6f2f6435c62b7e161c032b888c4e2b3328d4)) - Monorepo ([31f1894](https://github.com/ory/kratos/commit/31f18944116f6fde546fdd5139faf7e9b68a4c10)): GitOrigin-RevId: dbb48d171fad1f9b4fd31385f0ef4fb01e39e823 - More extension points ([#4272](https://github.com/ory/kratos/issues/4272)) ([373a2e6](https://github.com/ory/kratos/commit/373a2e6552f0da0488638306a58d8bd63a6ca10a)): This adds more extension points to the Kratos registry. - Move config testhelpers to ory/x ([8d43aae](https://github.com/ory/kratos/commit/8d43aae7bc6c003287b03e6f8900a536fd13dcef)): GitOrigin-RevId: fd484445e9715760231f7f86ec212d094e826377 - Optimize identity-related secondary indices ([#4182](https://github.com/ory/kratos/issues/4182)) ([53874c1](https://github.com/ory/kratos/commit/53874c1753940e08e0bf50753a1d3126add77af1)) - Passwordless SMS and expiry notice in code / link templates ([#4104](https://github.com/ory/kratos/issues/4104)) ([462cea9](https://github.com/ory/kratos/commit/462cea91448a00a0db21e20c2c347bf74957dc8f)): This feature allows Ory Kratos to use the SMS gateway for login and registration with code via SMS. Additionally, the default email and sms templates have been updated. We now also expose `ExpiresInMinutes` / `expires_in_minutes` in the templates, making it easier to remind the user how long the code or link is valid for. Closes https://github.com/ory/kratos/issues/1570 Closes https://github.com/ory/kratos/issues/3779 - Recovery with any address including with a code via SMS ([71844dd](https://github.com/ory/kratos/commit/71844dd75fed5c60d29399dc3595ccafcc5f0809)): GitOrigin-RevId: 4fa4ea56feacf71fa7fc84fa2fc33ce94db5a21e - Refactor cmd/daemon ([#4371](https://github.com/ory/kratos/issues/4371)) ([7fe55d9](https://github.com/ory/kratos/commit/7fe55d9fec5e5f4048b211eaa56ac61e29635157)) - Remove duplicate queries during settings flow and use better index hint for credentials lookup ([#4193](https://github.com/ory/kratos/issues/4193)) ([c33965e](https://github.com/ory/kratos/commit/c33965e5735ead3acddac87ef84c3a730874f9ab)): This patch reduces duplicate GetIdentity queries as part of submitting the settings flow, and improves an index to significantly reduce credential lookup. For better debugging, more tracing ha been added to the settings module. - Remove more unused indices ([#4186](https://github.com/ory/kratos/issues/4186)) ([b294804](https://github.com/ory/kratos/commit/b2948044de4eee1841110162fe874055182bd2d2)) - Return field name in generated node text label ([8c7a3dc](https://github.com/ory/kratos/commit/8c7a3dc5eb7e863b7a710cc9256a12e6125e359d)): GitOrigin-RevId: c37d048759b18337eafafbf9cdf8449253b64836 - Rework the OTP code submit count mechanism ([#4251](https://github.com/ory/kratos/issues/4251)) ([4ca4d79](https://github.com/ory/kratos/commit/4ca4d79cff5185caad27eddee7e6f8d0e58463ba)): - feat: rework the OTP code submit count mechanism Unlike what the previous comment suggested, incrementing and checking the submit count inside the database transaction is not actually optimal peformance- or security-wise. We now check atomically increment and check the submit count as the first part of the operation, and abort as early as possible if we detect brute-forcing. This prevents a situation where the check works only on certain transaction isolation levels. - chore: bump dependencies - Support android webauthn origins ([#4155](https://github.com/ory/kratos/issues/4155)) ([a82d288](https://github.com/ory/kratos/commit/a82d288014411ae4eb82c718bfe825ca55b4fab0)): This patch adds the ability to verify Android APK origins used during WebAuthn/Passkey exchange. Upgrades go-webauthn and includes fixes for Go 1.23 and workarounds for Swagger. - Support CRUD OIDC providers through the onboarding portal API ([664fd1a](https://github.com/ory/kratos/commit/664fd1a48d822f29b1ee2164818c560df63b29e2)): GitOrigin-RevId: 76c77654a4dc1150d3edb92c2ba428bd325850bc - Support importing more credentials ([#4361](https://github.com/ory/kratos/issues/4361)) ([9a6dadf](https://github.com/ory/kratos/commit/9a6dadfefaf0d54c227cdbab5a2cbe7da14faa96)): Adds support to import SAML credentials. SAML connections are only available in Ory Enterprise License / Ory Network. - Trace identity id in errors ([772572c](https://github.com/ory/kratos/commit/772572c0d633d5848f12d41947d52df1d391329b)): GitOrigin-RevId: dec38a20911ebf8038ed8eb5a5f286e79430266d - Update only necessary database columns in UpdateVerifiableAddress ([#4292](https://github.com/ory/kratos/issues/4292)) ([168a3f6](https://github.com/ory/kratos/commit/168a3f6c68b1fbc0ddcd455f8762f6de19879442)): This is an optimization to reduce database load. When we specify exactly which columns changed, we should be able to elide updates to the `identity_verifiable_addresses_status_via_uq_idx (nid,via,value)` index. Updating that index requires contacting remote regions. Also fixed a bug where we did not set the `verified_at` timestamp correctly sometimes. - Use one transaction for `/admin/recovery/code` ([#4225](https://github.com/ory/kratos/issues/4225)) ([3e87e0c](https://github.com/ory/kratos/commit/3e87e0c4559736f9476eba943bac8d67cde91aad)) - Use stdlib HTTP router in Kratos ([acfa6ef](https://github.com/ory/kratos/commit/acfa6ef2ec4aa61f9a1a7da6fdda59609f1360e3)): GitOrigin-RevId: 799513e99acbf43a05fe3113ffda45d2fff2a9e0 - Use vendored ory/x ([a9ab800](https://github.com/ory/kratos/commit/a9ab800a4373875a29c58492f174165e0b4592e7)): GitOrigin-RevId: 994f3b754946ca5b2bd1bab0fe20532f5d5ab62f - Webhook header allowlist configuration option ([#4309](https://github.com/ory/kratos/issues/4309)) ([871f5aa](https://github.com/ory/kratos/commit/871f5aab6d7b2a655ebcd6f0f90e79635ffc85f6)), closes [#4290](https://github.com/ory/kratos/issues/4290): Adds a `clients.web_hook.header_allowlist` configuration option for configuring the webhook header allowlist. ### Reverts - Tests: improve randomness in e2e tests ([19a41ec](https://github.com/ory/kratos/commit/19a41ecd505e1a78dcbefb8c1f264268adfd4415)): GitOrigin-RevId: 377b6b2dca8eec59f244d3b0f94883a6b443b772 - Use account.apple.com for oidc discovery and appleid.apple.com for token verification and signing ([c772d8b](https://github.com/ory/kratos/commit/c772d8b87b1e13e761a2f64f545607c70b330b55)): GitOrigin-RevId: 5afe7dc747df323cf9c89d6406d4ea644c36ca68 - Use appleid audience for secret exchange ([620e33e](https://github.com/ory/kratos/commit/620e33e11343b84f80cc1e83ec585fcc375bd65b)): GitOrigin-RevId: 2110e8e47501f01ead389eb5b76bd90bcd12ada7 - Use updated appleid issuer ([9697c45](https://github.com/ory/kratos/commit/9697c453933a3ca33e35a8fbd2c59908603db28b)): GitOrigin-RevId: 56915792c8cdcf0330523a482ca3a8f1f68d95e3 ### Tests - Add golangci-lint config and GHA ([0720950](https://github.com/ory/kratos/commit/0720950361ffa44deb7078c6c842a2d9b49540c0)): GitOrigin-RevId: eb14c9f38e2b98d11a78ee0b90fd8f4f689abd3d - Don't require DB for hasher tests ([41c69db](https://github.com/ory/kratos/commit/41c69db6b2b44cdc8d3a72d3fb5c459bf96c1358)): GitOrigin-RevId: 2fd89b72bf82c88cff030b0df2eaa97fe8d4f095 - **hydra:** Add snapshots for login & consent requests ([ee39bdb](https://github.com/ory/kratos/commit/ee39bdb5bd337dade757ed9f6ac33dd07bd76c35)): GitOrigin-RevId: 47d041cf207af6c3e9e21bf3016e5ea0cf044344 - Improve pgxpool tests ([6297a8f](https://github.com/ory/kratos/commit/6297a8fc22cdb9c9a9301cfbba4496160d2b4aa5)): GitOrigin-RevId: 2008231a7e0eb05276484b7eda885899c67f0a3a - Resturcture and improve integration tests ([6f32d5d](https://github.com/ory/kratos/commit/6f32d5d623882e3e742c305f0eddd81156283173)): GitOrigin-RevId: 83dfe53cfc33f0a974d7b2f7eeed81d017d2518c - Update snapshots ([#4167](https://github.com/ory/kratos/issues/4167)) ([b51f780](https://github.com/ory/kratos/commit/b51f780b7e4abc79a757ac1efe1cb65b3d35c8a4)) ### Unclassified - Improve randomness in e2e tests ([ecfe435](https://github.com/ory/kratos/commit/ecfe43591d6a9e476e22d4a3872d393f8b7179d0)): GitOrigin-RevId: 2dcb862d4375fa22824a5694767329bdea990bde - Run credential validation in its own goroutine when changing the password ([c7fedfe](https://github.com/ory/kratos/commit/c7fedfe21f2e95f89b54ced34bf9b49bd5f64fb9)): GitOrigin-RevId: fbc4ca277a1f3feeb2c6ec9344f498cee6d76dee # [1.3.0](https://github.com/ory/kratos/compare/v1.2.0...v1.3.0) (2024-09-26) We are thrilled to announce the release of [Ory Kratos v1.3.0](https://www.ory.sh/kratos)! This release includes significant updates, enhancements, and fixes to improve your experience with Ory Kratos. ![Ory Kratos 1.3.0 Release](https://www.ory.sh/images/newsletter/kratos-1.3.0/kratos-1.3-release.png) Enhance your sign-in experience with Identifier First Authentication. This feature allows users to first identify themselves (e.g., by providing their email or username) and then proceed with the chosen authentication method, whether it be OTP code, passkeys, passwords, or social login. By streamlining the sign-in process, users can select the authentication method that best suits their needs, reducing friction and enhancing security. Identifier First Authentication improves user flow and reduces the likelihood of errors, resulting in a more user-friendly and efficient login experience. ![Identifier First Authentication](https://www.ory.sh/images/newsletter/kratos-1.3.0/identifier-first-demo.png) The UI for OpenID Connect (OIDC) account linking has been improved to provide better user guidance and error messages during the linking process. As a result, account linking error rates have dropped significantly, making it easier for users to link multiple identities (e.g., social login and email-based accounts) to the same profile. This improvement enhances user convenience, reduces support inquiries, and offers a seamless multi-account experience. You can now use Salesforce as an identity provider, expanding the range of supported identity providers. This integration allows organizations already using Salesforce for identity management to leverage their existing infrastructure, simplifying user management and enhancing the authentication experience. Social sign-in has been enhanced with better detection and handling of double-submit issues, especially for platforms like Facebook and Apple mobile login. These changes make the social login process more reliable, reducing errors and improving the user experience. Additionally, Ory Kratos now supports social providers in credential discovery, offering more flexibility during sign-up and sign-in flows. One-Time Password (OTP) MFA has been improved with more robust handling of code-based authentication. The enhancements ensure a smoother flow when using OTP for multi-factor authentication (MFA), providing clearer guidance to users and improving fallback mechanisms. These updates help to prevent users from being locked out due to misconfigurations or errors during the MFA process, increasing security without compromising user convenience. - **Deprecated `via` Parameter for SMS 2FA**: The `via` parameter is now deprecated when performing SMS 2FA. If not included, users will see all their phone/email addresses to perform the flow. This parameter will be removed in a future version. Ensure your identity schema has the appropriate code configuration for passwordless or 2FA login. - **Endpoint Change**: The `/admin/session/.../extend` endpoint will now return 204 No Content for new Ory Network projects. Returning 200 with the session body will be deprecated in future versions. - **SDK Enhancements**: Added new methods and support for additional actions in the SDK, improving integration capabilities. - **Password Migration Hook**: Added a password migration hook to facilitate migrating passwords where the hash is unavailable, easing the transition to Ory Kratos. - **Partially Failing Batch Inserts:** When batch-inserting multiple identities, conflicts or validation errors of a subset of identities in the batch still allow the rest of the identities to be inserted. The returned JSON contains the error details that led to the failure. - **Security Fixes**: Fixed a security vulnerability where the `code` method did not respect the `highest_available`setting. Refer to the [security advisory](https://github.com/ory/kratos/security/advisories/GHSA-wc43-73w7-x2f5) for more details. - **Session Extension Issues**: Fixed issues related to session extension to prevent long response times on `/session/whoami` when extending sessions simultaneously. - **OIDC and Social Sign-In**: Fixed UI and error handling for OpenID Connect and social sign-in flows, improving the overall experience. - **Credential Identifier Handling**: Corrected handling of code credential identifiers, ensuring proper detection of phone numbers and correct functioning of SMS/email MFA. - **Concurrent Updates for Webhooks**: Fixed concurrent map update issues for webhook headers, improving webhook reliability. - **Passwordless & 2FA Login**: Before upgrading, ensure your identity schema has the appropriate code configuration when using the code method for passwordless or 2FA login. - **Code Method for 2FA**: If you use the code method for 2FA or 1FA login but haven't configured the code identifier, set `selfservice.methods.code.config.missing_credential_fallback_enabled` to `true` to avoid user lockouts. We hope you enjoy the new features and improvements in Ory Kratos v1.3.0. Please remember to leave a [GitHub star](https://github.com/ory/kratos) and check out our other [open-source projects](https://github.com/ory). Your feedback is valuable to us, so join the [Ory community](https://slack.ory.sh/) and help us shape the future of identity management. ## Breaking Changes When using two-step registration, it was previously possible to send `method=profile:back` to get to the previous screen. This feature was not documented in the SDK API yet. Going forward, please instead use `screen=previous`. Please note that the `via` parameter is deprecated when performing SMS 2FA. It will be removed in a future version. If the parameter is not included in the request, the user will see all their phone/email addresses from which to perform the flow. Before upgrading, ensure that your identity schema has the appropriate code configuration when using the code method for passwordless or 2fa login. If you are using the code method for 2FA login already, or you are using it for 1FA login but have not yet configured the code identifier, set `selfservice.methods.code.config.missing_credential_fallback_enabled` to `true` to prevent users from being locked out. Please note that the `via` parameter is deprecated when performing SMS 2FA. It will be removed in a future version. If the parameter is not included in the request, the user will see all their phone/email addresses from which to perform the flow. Before upgrading, ensure that your identity schema has the appropriate code configuration when using the code method for passwordless or 2fa login. If you are using the code method for 2FA login already, or you are using it for 1FA login but have not yet configured the code identifier, set `selfservice.methods.code.config.missing_credential_fallback_enabled` to `true` to prevent users from being locked out. Going forward, the `/admin/session/.../extend` endpoint will return 204 no content for new Ory Network projects. We will deprecate returning 200 + session body in the future. ### Bug Fixes - Add continue with only for json browser requests ([#4002](https://github.com/ory/kratos/issues/4002)) ([e0a4010](https://github.com/ory/kratos/commit/e0a4010b84b43f364be14414a380c872b166274d)) - Add fallback to providerLabel ([#3999](https://github.com/ory/kratos/issues/3999)) ([d26f204](https://github.com/ory/kratos/commit/d26f2042eb5325a8d639c08d95a005724e61cb8e)): This adds a fallback to the provider label when trying to register a duplicate identifier with an oidc. Current error message: `Signing in will link your account to "test@test.com" at provider "". If you do not wish to link that account, please start a new login flow.` The label represents an optional label for the UI, but in my case it's always empty. I suggest we fallback to the provider when the label is not present. In case the label is present, the behaviour won't change. Fallback to provider: `Signing in will link your account to "test@test.com" at provider "google". If you do not wish to link that account, please start a new login flow.` - Add missing JS triggers ([7597bc6](https://github.com/ory/kratos/commit/7597bc6345848b66161d5a9b7a42307bbc85c978)) - Add PKCE config key to config schema ([#4098](https://github.com/ory/kratos/issues/4098)) ([2c7ff3c](https://github.com/ory/kratos/commit/2c7ff3c8baab6aaa105e2d733a483fc07537470f)) - Batch identity created event ([#4111](https://github.com/ory/kratos/issues/4111)) ([340f698](https://github.com/ory/kratos/commit/340f698243bd908e217394710b475a7f686a8cf9)) - Concurrent map update for webhook header ([#4055](https://github.com/ory/kratos/issues/4055)) ([6ceb2f1](https://github.com/ory/kratos/commit/6ceb2f1213e1b28d3aa72380661e4aa985bfa437)) - Do not populate `id_first` first step for account linking flows ([#4074](https://github.com/ory/kratos/issues/4074)) ([6ab2637](https://github.com/ory/kratos/commit/6ab2637652013e0ff377f52355e2025d68c7b3d3)) - Downgrade go-webauthn ([#4035](https://github.com/ory/kratos/issues/4035)) ([4d1954a](https://github.com/ory/kratos/commit/4d1954ac74dee358f9a08e619848dfe94e4934ce)) - Emit SelfServiceMethodUsed in SettingsSucceeded event ([#4056](https://github.com/ory/kratos/issues/4056)) ([76af303](https://github.com/ory/kratos/commit/76af303b20ae5dffb932169a73667a55be3f3f80)) - Filter web hook headers ([#4048](https://github.com/ory/kratos/issues/4048)) ([ddb838e](https://github.com/ory/kratos/commit/ddb838e0e8f7d752cd1708c505e80b6c0ccc0b8a)) - Improve OIDC account linking UI ([#4036](https://github.com/ory/kratos/issues/4036)) ([2b4a618](https://github.com/ory/kratos/commit/2b4a618485c9d79762243f59b35f142083f5492c)) - Include duplicate credentials in account linking message ([#4079](https://github.com/ory/kratos/issues/4079)) ([122b63d](https://github.com/ory/kratos/commit/122b63d68a3ff2ad78107300869c5a6d2aa43354)) - Incorrect append of code credential identifier ([#4102](https://github.com/ory/kratos/issues/4102)) ([3215792](https://github.com/ory/kratos/commit/3215792df4cab494c05ef09e969b2fa0ed95a98b)), closes [#4076](https://github.com/ory/kratos/issues/4076) - Jsonnet timeouts ([#3979](https://github.com/ory/kratos/issues/3979)) ([7c5299f](https://github.com/ory/kratos/commit/7c5299f1f832ebbe0622d0920b7a91253d26b06c)) - Move password migration hook config ([#3986](https://github.com/ory/kratos/issues/3986)) ([b5a66e0](https://github.com/ory/kratos/commit/b5a66e0dde3a8fa6fdeb727482481b6302589631)): This moves the password migration hook to ```yaml selfservice: methods: password: config: migrate_hook: ... ``` - Normalize code credentials and deprecate via parameter ([c417b4a](https://github.com/ory/kratos/commit/c417b4aa76a76d3aebb4474999d7bb072615bd9f)): Before this, code credentials for passwordless and mfa login were incorrectly stored and normalized. This could cause issues where the system would not detect the user's phone number, and where SMS/email MFA would not properly work with the `highest_available` setting. - Passthrough correct organization ID to CompletedLoginForWithProvider ([#4124](https://github.com/ory/kratos/issues/4124)) ([ad1acd5](https://github.com/ory/kratos/commit/ad1acd51d8dd7582b05a3078b92f73970e1e2715)) - Password migration hook config ([#4001](https://github.com/ory/kratos/issues/4001)) ([50deedf](https://github.com/ory/kratos/commit/50deedfeecf7adbc948521371b181306a0c26cf1)): This fixes the config loading for the password migration hook. - Pw migration param ([#3998](https://github.com/ory/kratos/issues/3998)) ([6016cc8](https://github.com/ory/kratos/commit/6016cc88a076eeea71a85d75cfb5191808b69844)) - Refactor internal API to prevent panics ([#4028](https://github.com/ory/kratos/issues/4028)) ([81bc152](https://github.com/ory/kratos/commit/81bc1525f09504729c666192d458cf2eaafab99f)) - Remove flows from log messages ([#3913](https://github.com/ory/kratos/issues/3913)) ([310a405](https://github.com/ory/kratos/commit/310a405202c6b44633b15ad30e1fdb8ebd153e4b)) - Replace submit with continue button for recovery and verification and add maxlength ([04850f4](https://github.com/ory/kratos/commit/04850f45cfbdc89223366ffa3b540d579a3b44be)) - Return credentials in FindByCredentialsIdentifier ([#4068](https://github.com/ory/kratos/issues/4068)) ([f949173](https://github.com/ory/kratos/commit/f949173b3ed3d45167bb4af8b95440d5e4a39636)): Instead of re-fetching the credentials later (expensive), we load them only once. - Return error if invalid UUID is supplied to ids filter ([#4116](https://github.com/ory/kratos/issues/4116)) ([98140f2](https://github.com/ory/kratos/commit/98140f2fd43ccd889e2635e4f3e7582b92fe96ab)) - **security:** Code credential does not respect `highest_available` setting ([b0111d4](https://github.com/ory/kratos/commit/b0111d4bd561d0f0e2f5883f30fac36fcf7135d5)): This patch fixes a security vulnerability which prevents the `code` method to properly report it's credentials count to the `highest_available` mechanism. For more details on this issue please refer to the [security advisory](https://github.com/ory/kratos/security/advisories/GHSA-wc43-73w7-x2f5). - Timestamp precision on mysql ([9a1f171](https://github.com/ory/kratos/commit/9a1f171c1a4a8d20dc2103073bdc11ee3fdc70af)) - Transient_payload is lost when verification flow started as part of registration ([#3983](https://github.com/ory/kratos/issues/3983)) ([192f10f](https://github.com/ory/kratos/commit/192f10f4ad9eb44a612baaccfc71765d52c7e1ed)) - Trigger oidc web hook on sign in after registration ([#4027](https://github.com/ory/kratos/issues/4027)) ([ad5fb09](https://github.com/ory/kratos/commit/ad5fb09687f863e7c5d45868d0b8f5ec2d965372)) - Typo in login link CLI error messages ([#3995](https://github.com/ory/kratos/issues/3995)) ([8350625](https://github.com/ory/kratos/commit/835062542077b9dd8d6a30836d0455adb015265d)) - Validate page tokens for better error codes ([#4021](https://github.com/ory/kratos/issues/4021)) ([32737dc](https://github.com/ory/kratos/commit/32737dc708c1ecf0ec0ceaa4bbc0ac09286186fd)) - Whoami latency ([#4070](https://github.com/ory/kratos/issues/4070)) ([ff6ed5b](https://github.com/ory/kratos/commit/ff6ed5b70b7f715fc38a41cedd17b5323aebd79e)) ### Code Generation - Pin v1.3.0 release commit ([0a49fd0](https://github.com/ory/kratos/commit/0a49fd05245f179501b117163cd574786f287fe8)) ### Documentation - Add google to supported providers in ID Token doc strings ([#4026](https://github.com/ory/kratos/issues/4026)) ([955bd8f](https://github.com/ory/kratos/commit/955bd8fbc1353d7a9f84d8f591c3af31781cf7b7)) - Typo in changelog ([c508980](https://github.com/ory/kratos/commit/c5089801af2a656e9c1fc371a11aeb23918ba359)) ### Features - Add additional messages ([735fc5b](https://github.com/ory/kratos/commit/735fc5b2c5a99746d3012cc38ee2e1b7cc3a67f2)) - Add browser return_to continue_with action ([7b636d8](https://github.com/ory/kratos/commit/7b636d860c6917cb1133d6d1d7401808adb890c7)) - Add if method to sdk ([612e3bf](https://github.com/ory/kratos/commit/612e3bf09dbffd3feba08d5100bffbc39cbd240a)) - Add redirect to continue_with for SPA flows ([99c945c](https://github.com/ory/kratos/commit/99c945c92d0c2745dc8df4402d755afd53e1b9aa)): This patch adds the new `continue_with` action `redirect_browser_to`, which contains the redirect URL the app should redirect to. It is only supported for SPA (not server-side browser apps, not native apps) flows at this point in time. - Add social providers to credential discovery as well ([5f4a2bf](https://github.com/ory/kratos/commit/5f4a2bf619d540d45e96586129c8ee1e7850e745)) - Add support for Salesforce as identity provider ([#4003](https://github.com/ory/kratos/issues/4003)) ([3bf1ca9](https://github.com/ory/kratos/commit/3bf1ca9030555df90ef9903c34313ae4bd1fecae)) - Add tests for two step login ([#3959](https://github.com/ory/kratos/issues/3959)) ([8225e40](https://github.com/ory/kratos/commit/8225e40e3d767e945006b33eebdfc47fd242ff06)) - Allow deletion of an individual OIDC credential ([#3968](https://github.com/ory/kratos/issues/3968)) ([a43cef2](https://github.com/ory/kratos/commit/a43cef23c177acddbf8b03afef087feeaca51981)): This extends the existing `DELETE /admin/identities/{id}/credentials/{type}` API to accept an `?identifier=foobar` query parameter for `{type}==oidc` like such: `DELETE /admin/identities/{id}/credentials/oidc?identifier=github%3A012345` This will delete the GitHub OIDC credential with the identifier `github:012345` (`012345` is the subject as returned by GitHub). To find out which OIDC credentials exist, call `GET /admin/identities/{id}?include_credential=oidc` beforehand. This will allow you to delete individual OIDC credentials for users even if they have several set up. - Allow partially failing batch inserts ([#4083](https://github.com/ory/kratos/issues/4083)) ([4ba7033](https://github.com/ory/kratos/commit/4ba70330cf9e0eda9044b0a5a504c34493ae17ed)): When batch-inserting multiple identities, conflicts or validation errors of a subset of identities in the batch still allow the rest of the identities to be inserted. The returned JSON contains the error details that lead to the failure. - Better detection if credentials exist on identifier first login ([#3963](https://github.com/ory/kratos/issues/3963)) ([42ade94](https://github.com/ory/kratos/commit/42ade94e32a9a7ad6c0bda785e86d7209c46d8bb)) - Change `method=profile:back` to `screen=previous` ([#4119](https://github.com/ory/kratos/issues/4119)) ([2cd8483](https://github.com/ory/kratos/commit/2cd8483e809170d0524fe6a5d13837108d29fa54)) - Clarify session extend behavior ([#3962](https://github.com/ory/kratos/issues/3962)) ([af5ea35](https://github.com/ory/kratos/commit/af5ea35759e74d7a1637823abcc21dc8e3e39a9d)) - Client-side PKCE take 3 ([#4078](https://github.com/ory/kratos/issues/4078)) ([f7c1024](https://github.com/ory/kratos/commit/f7c102456a71b226d8353b9d59cc03fb2ba0af40)): - feat: client-side PKCE This change introduces a new configuration for OIDC providers: pkce with values auto (default), never, force. When auto is specified or the field is omitted, Kratos will perform autodiscovery and perform PKCE when the server advertises support for it. This requires the issuer_url to be set for the provider. never completely disables PKCE support. This is only theoretically useful: when a provider advertises PKCE support but doesn't actually implement it. force always sends a PKCE challenge in the initial redirect URL, regardless of what the provider advertises. This setting is useful when the provider offers PKCE but doesn't advertise it in his ./well-known/openid-configuration. Important: When setting pkce: force, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. Instead of /self-service/methods/oidc/callback/, you must use /self-service/methods/oidc/callback (note missing last path segment). This is to enable the use of the same OAuth client ID+secret when configuring several Kratos OIDC providers, without having to whitelist individual redirect_uris for each Kratos provider config. - chore: regenerate SDK, bump DB versions, cleanup tool install - chore: get final organization ID from provider config during registration and login - chore: fixup OIDC function signatures and improve tests - Emit events in identity persister ([#4107](https://github.com/ory/kratos/issues/4107)) ([20156f6](https://github.com/ory/kratos/commit/20156f651f2faa0a79842de8d2fb4a09ee7094c1)) - Enable new-style OIDC state generation ([#4121](https://github.com/ory/kratos/issues/4121)) ([eb97243](https://github.com/ory/kratos/commit/eb97243d6499e2d9f2338a2ce3f5e39579d19086)) - Identifier first auth ([1bdc19a](https://github.com/ory/kratos/commit/1bdc19ae3e1a3df38234cb892f65de4a2c95f041)) - Identifier first login for all first factor login methods ([638b274](https://github.com/ory/kratos/commit/638b27431312bcd91844ac4a00733a840976aa4f)) - Improve session extend performance ([#3948](https://github.com/ory/kratos/issues/3948)) ([4e3fad4](https://github.com/ory/kratos/commit/4e3fad4b4739b5cf00d658155350cb599f2cd06a)): This patch improves the performance for extending session lifespans. Lifespan extension is tricky as it is often part of the middleware of Ory Kratos consumers. As such, it is prone to transaction contention when we read and write to the same session row at the same time (and potentially multiple times). To address this, we: 1. Introduce a locking mechanism on the row to reduce transaction contention; 2. Add a new feature flag that toggles returning 204 no content instead of 200 + session. Be aware that all reads on the session table will have to wait for the transaction to commit before they return a value. This may cause long(er) response times on `/session/whoami` for sessions that are being extended at the same time. - Password migration hook ([#3978](https://github.com/ory/kratos/issues/3978)) ([c9d5573](https://github.com/ory/kratos/commit/c9d55730a10b71ac61bb5097f5f9c33f144f2a95)): This adds a password migration hook to easily migrate passwords for which we do not have the hash. For each user that needs to be migrated to Ory Network, a new identity is created with a credential of type password with a config of {"use_password_migration_hook": true} . When a user logs in, the credential identifier and password will be sent to the password_migration web hook if all of these are true: The user’s identity’s password credential is {"use_password_migration_hook": true} The password_migration hook is configured After calling the password_migration hook, the HTTP status code will be inspected: On 200, we parse the response as JSON and look for {"status": "password_match"}. The password credential config will be replaced with the hash of the actual password. On any other status code, we assume that the password is not valid. - **sdk:** Add missing profile discriminator to update registration ([0150795](https://github.com/ory/kratos/commit/0150795d902dcc7cfb2298c3b5a98da1c2541e46)) - **sdk:** Avoid eval with javascript triggers ([dd6e53d](https://github.com/ory/kratos/commit/dd6e53d62f343a317edf403218b20599539218c6)): Using `OnLoadTrigger` and `OnClickTrigger` one can now map the trigger to the corresponding JavaScript function. For example, trigger `{"on_click_trigger":"oryWebAuthnRegistration"}` should be translated to `window.oryWebAuthnRegistration()`: ``` if (attrs.onClickTrigger) { window[attrs.onClickTrigger]() } ``` - Separate 2fa refresh from 1st factor refresh ([#3961](https://github.com/ory/kratos/issues/3961)) ([89355d8](https://github.com/ory/kratos/commit/89355d86258ace19c03fcb38dd3861f88e28af59)) - Set maxlength for totp input ([51042d9](https://github.com/ory/kratos/commit/51042d99fab301f0bb44665e56c5a2364e7d8866)) ### Tests - Add form hydration tests for code login ([37781a9](https://github.com/ory/kratos/commit/37781a93dda9b8f0127217a6b0ac2434dda1cc58)) - Add form hydration tests for idfirst login ([633b0ba](https://github.com/ory/kratos/commit/633b0ba7f724374f4c02128a5b0f748bd2e9413e)) - Add form hydration tests for oidc login ([df0cdcb](https://github.com/ory/kratos/commit/df0cdcb424cae6c49143ef2ef2d0b2c95f14fffb)) - Add form hydration tests for passkey login ([a777854](https://github.com/ory/kratos/commit/a777854e8d99336ab8f5755fdbc9d257e5edd1c0)) - Add form hydration tests for password login ([7186e7e](https://github.com/ory/kratos/commit/7186e7e060e04a4918e22e0b03fefbf4eb9f4a4b)) - Add form hydration tests for webauthn login ([8b68163](https://github.com/ory/kratos/commit/8b68163a3f293f7dceb58397f0ef555f1d8fd7c3)) - Add tests for idfirst ([5f76c15](https://github.com/ory/kratos/commit/5f76c1565e89bfb99f23c3f0f3a9beadbdfa270c)) - Additional code credential test case ([#4122](https://github.com/ory/kratos/issues/4122)) ([4f2c854](https://github.com/ory/kratos/commit/4f2c8542ab04b88c7112d7b564d91bcfd8f5791a)) - Deflake and parallelize persister tests ([#3953](https://github.com/ory/kratos/issues/3953)) ([61f87d9](https://github.com/ory/kratos/commit/61f87d90bd67e5bb1f00ee110d986e4f72fc4c91)) - Deflake session extend config side-effect ([#3950](https://github.com/ory/kratos/issues/3950)) ([b192c92](https://github.com/ory/kratos/commit/b192c92d6c969d470d6479bc33dbc351d327c1f9)) - Enable server-side config from context ([#3954](https://github.com/ory/kratos/issues/3954)) ([e0001b0](https://github.com/ory/kratos/commit/e0001b0db784457652581366bd7ead7cdf6b3898)) - Improve stability of refresh test ([#4037](https://github.com/ory/kratos/issues/4037)) ([68693a4](https://github.com/ory/kratos/commit/68693a43e4e1e3028f17789e72d0b79f6298d139)) - Resolve CI failures ([#4067](https://github.com/ory/kratos/issues/4067)) ([dbf7274](https://github.com/ory/kratos/commit/dbf7274f7a4be56c33b06559875c42725bf4a351)) - Resolve issues and update snapshots for all selfservice strategies ([e2e81ac](https://github.com/ory/kratos/commit/e2e81ac16726b180d33c57913e3cac099daf946b)) - Update incorrect usage of Auth0 in Salesforce tests ([#4007](https://github.com/ory/kratos/issues/4007)) ([6ce3068](https://github.com/ory/kratos/commit/6ce306824cec81890c50dcf23c2b8a5825f20a10)) - Verify redirect continue_with in hook executor for browser clients ([7b0b94d](https://github.com/ory/kratos/commit/7b0b94d30ec9069de6978427814d55a30e62adb8)) ### Unclassified - Merge commit from fork ([123e807](https://github.com/ory/kratos/commit/123e80782b392095631ee2e0d1bd6ec337c1fb79)): - fix(security): code credential does not respect `highest_available` setting This patch fixes a security vulnerability which prevents the `code` method to properly report it's credentials count to the `highest_available` mechanism. For more details on this issue please refer to the [security advisory](https://github.com/ory/kratos/security/advisories/GHSA-wc43-73w7-x2f5). - fix: normalize code credentials and deprecate via parameter Before this, code credentials for passwordless and mfa login were incorrectly stored and normalized. This could cause issues where the system would not detect the user's phone number, and where SMS/email MFA would not properly work with the `highest_available` setting. - Update .github/workflows/ci.yaml ([2d60772](https://github.com/ory/kratos/commit/2d60772062a684c3a27f28b8836c3548f5b8cea9)) - Update Code QL action to v2 ([#4008](https://github.com/ory/kratos/issues/4008)) ([e3f1da0](https://github.com/ory/kratos/commit/e3f1da0f4bf41a8a8733758fcd9edb9910c55cfa)) # [1.2.0](https://github.com/ory/kratos/compare/v1.1.0...v1.2.0) (2024-06-05) Ory Kratos v1.2 is the most complete, scalable, and secure open-source identity server available. We are thrilled to announce its release! ![Ory Kratos 1.2 released](https://www.ory.sh/images/newsletter/kratos-1.2.0/banner.png) This release introduces two major features: two-step registration and full PassKey with resident key support. Passkeys provide a secure and convenient authentication method, eliminating the need for passwords while ensuring strong security. With this release, we have added support for resident keys, enabling offline authentication. Credential discovery allows users to link existing passkeys to their Ory account seamlessly. [Watch the PassKey demo video](https://github.com/aeneasr/web-next-deprecated/assets/3372410/e676c518-c82a-42a6-821e-28aecadb270c) Two-step registration improves the user experience by dividing the registration process into two steps. Users first enter their identity traits, and then choose a credential method for authentication, resulting in a streamlined process. This feature is especially useful when enabling multiple authentication strategies, as it eliminates the need to repeat identity traits for each strategy. ![Two-Step Registration](https://ik.imagekit.io/launchnotes/production/tr:w-1640,c-at_max,f-auto/ngul9dzfjdt3pe8benegjjeeagi1) The 107 commits since v1.1 include several improvements: - **Webhooks** now carry session information if available. - **Transient Payloads** are now available across all self-service flows. - **Sign in with Twitter** is now available. - **Sign in with LinkedIn** now includes an additional v2 provider compatible with LinkedIn's new SSO API. - **Two-Step Registration**: An improved registration experience that separates entering profile information from choosing authentication methods. - **User Credentials Meta-Information** can now be included on the list endpoint. - **Social Sign-In** is now resilient to double-submit issues common with Facebook and Apple mobile login. **Two-Step Registration Enabled by Default**: This is now the default setting. To disable, set `selfservice.flows.registration.enable_legacy_flow` to `true`. - Improved account linking and credential discovery during sign-up. - The `return_to` parameter is now respected in OIDC API flows. - Adjustments to database indices. - Enhanced error messages for security violations. - Improved SDK types. - The `verification` and `verification_ui` hooks are now available in the login flow. - Webhooks now contain the correct identity state in the after-verification hook chain. We are doing this survey to find out how we can support self-hosted Ory users better. We strive to provide you with the best product and service possible and your feedback will help us understand what we're doing well and where we can improve to better meet your needs. We truly value your opinion and thank you in advance for taking the time to share your thoughts with us! Fill out the [survey now](https://share-eu1.hsforms.com/15DiCnJpcRuijnpAdnDhxxwextgn)! ## Breaking Changes This feature enables two-step registration per default. Two-step registration is a significantly improved sign up flow and recommended when using more than one sign up methods. To disable two-step registration, set `selfservice.flows.registration.enable_legacy_flow` to `true`. This value defaults to `false`. ### Bug Fixes - Add login succeeded event to post registration hook ([#3739](https://github.com/ory/kratos/issues/3739)) ([b685fa5](https://github.com/ory/kratos/commit/b685fa5477be2ba099fd2420b27b2411fafc7e51)) - Add missing env vars to set up guide ([#3855](https://github.com/ory/kratos/issues/3855)) ([da90502](https://github.com/ory/kratos/commit/da90502dc3bf8e3d34fb4ecc531834b1919989ad)): Closes https://github.com/ory/kratos/issues/3828 - Add missing indexes and remove unused index ([6d7372e](https://github.com/ory/kratos/commit/6d7372ee3d88ee4fc552b969dd0ff338dcc0544c)) - Add missing indexes and remove unused index ([#3756](https://github.com/ory/kratos/issues/3756)) ([c905f02](https://github.com/ory/kratos/commit/c905f02473c5d77ab309a45f10251b1ba7e88584)) - Add sms mfa via parameter to spec ([#3766](https://github.com/ory/kratos/issues/3766)) ([b291c95](https://github.com/ory/kratos/commit/b291c959c18c72f5edc55607ab23b4592faf8d53)) - Allow updating just the verified_at timestamp of addresses ([#3880](https://github.com/ory/kratos/issues/3880)) ([696cc1b](https://github.com/ory/kratos/commit/696cc1b59b18627fec63915070f4d8c5b3e3250d)) - Always issue session last ([#3876](https://github.com/ory/kratos/issues/3876)) ([e942507](https://github.com/ory/kratos/commit/e94250705e999567e2ed58cebdb3f6a9d589e3ef)): In post persist hooks, the session issuance hook always needs to come last. This fixes the getHooks function to ensure this. - Audit issues ([#3797](https://github.com/ory/kratos/issues/3797)) ([7017490](https://github.com/ory/kratos/commit/7017490caa9c70e22d5c626773c0266521813ff5)) - Change return urls in quickstarts ([#3928](https://github.com/ory/kratos/issues/3928)) ([9730e09](https://github.com/ory/kratos/commit/9730e099a656d211389d8e993c64d8082784c929)) - Close res body ([#3870](https://github.com/ory/kratos/issues/3870)) ([cc39f8d](https://github.com/ory/kratos/commit/cc39f8df7c235af0df616432bc4f88681896ad85)) - CVEs in dependencies ([#3902](https://github.com/ory/kratos/issues/3902)) ([e5d3b0a](https://github.com/ory/kratos/commit/e5d3b0afde3c80c6c9cf8815c56d82e291ede663)) - Db index and duplicate credentials error ([#3896](https://github.com/ory/kratos/issues/3896)) ([9f34a21](https://github.com/ory/kratos/commit/9f34a21ea2035a5d33edd96753023a3c8c6c054c)): - fix: don't return password cred type if empty - fix: better index for config.user_handle on identity_credentials - Do not require method to be passkey in settings schema ([#3862](https://github.com/ory/kratos/issues/3862)) ([660f330](https://github.com/ory/kratos/commit/660f330ab69ef0e6fd21501fbc9dfed693d4a715)) - Don't require connection_uri in SMTP ([#3861](https://github.com/ory/kratos/issues/3861)) ([800f8f1](https://github.com/ory/kratos/commit/800f8f1036ef46a561d24dcdec45dd48803978d7)) - Don't treat passkeys as AAL2 ([#3853](https://github.com/ory/kratos/issues/3853)) ([8eee972](https://github.com/ory/kratos/commit/8eee972d89accb02b3caa053fca2f16ed2c876f1)) - Drop index if exists ([#3846](https://github.com/ory/kratos/issues/3846)) ([ad0619d](https://github.com/ory/kratos/commit/ad0619d803cd2842a67c56a545ec5ab252501b0f)) - Drop trigram index on identifiers ([#3827](https://github.com/ory/kratos/issues/3827)) ([8f8fd90](https://github.com/ory/kratos/commit/8f8fd90304886ecd689a85fc60c4712e47526cdd)) - Enum type of session expandables ([#3891](https://github.com/ory/kratos/issues/3891)) ([63d785e](https://github.com/ory/kratos/commit/63d785e5e73ff067ec804ecc2107fac1525d3688)) - Enum type of session expandables ([#3895](https://github.com/ory/kratos/issues/3895)) ([c435727](https://github.com/ory/kratos/commit/c435727c1e3c70c040b7fc7648ce621b136e5fc2)) - Execute verification & verification_ui properly in login flows ([#3847](https://github.com/ory/kratos/issues/3847)) ([5aad1c1](https://github.com/ory/kratos/commit/5aad1c1e6cc92f72af56511dacb9812edb600813)) - Ignore decrypt errors in WithDeclassifiedCredentials ([#3731](https://github.com/ory/kratos/issues/3731)) ([8f5192f](https://github.com/ory/kratos/commit/8f5192fbb74c4b952029a6856284de8d59027770)) - Improve SDK discriminators ([#3844](https://github.com/ory/kratos/issues/3844)) ([c08b3ad](https://github.com/ory/kratos/commit/c08b3ad76c5adb712c945cdbd92a9a51832e94b9)) - Include all creds in duplicate credential err ([#3881](https://github.com/ory/kratos/issues/3881)) ([e06c241](https://github.com/ory/kratos/commit/e06c241ffe3f0e696bb1cbc1d1080f9d4e09fbd2)) - Linkedin issuer override ([#3875](https://github.com/ory/kratos/issues/3875)) ([11d221a](https://github.com/ory/kratos/commit/11d221a4d33878930ca7025ae1b5c18b25dd1add)) - Make sure emails can still be sent with SMS enabled ([#3795](https://github.com/ory/kratos/issues/3795)) ([7c68c5a](https://github.com/ory/kratos/commit/7c68c5aa69ed76a84a37a37a3555277ddc772cf8)) - Missing indices and foreign keys ([#3800](https://github.com/ory/kratos/issues/3800)) ([0b32ce1](https://github.com/ory/kratos/commit/0b32ce113be47aa724d3468062ced09f8f60c52a)) - **oidc:** Grace period for continuity container on oidc callbacks ([#3915](https://github.com/ory/kratos/issues/3915)) ([1a9a096](https://github.com/ory/kratos/commit/1a9a096d619925dd3718ad9dd9daf77387572ece)) - Passing transient payloads ([#3838](https://github.com/ory/kratos/issues/3838)) ([d01b670](https://github.com/ory/kratos/commit/d01b6705bf36efb6e0f3d71ed22d0574ab8a98a4)) - Prevent SMTP URL leak on unparsable URL ([#3770](https://github.com/ory/kratos/issues/3770)) ([c5f39f4](https://github.com/ory/kratos/commit/c5f39f4bc481e400f736ede7f8f0be546a55eebf)) - Respect return_to in OIDC API flow error case ([#3893](https://github.com/ory/kratos/issues/3893)) ([e8f1bcb](https://github.com/ory/kratos/commit/e8f1bcb1342af994b8e08282aa4066ee00ffe7d4)): - fix: respect return_to in OIDC API flow error case This fix ensures that we redirect the user to the return_to URL when an error occurs during the OIDC login for native flows. Native flows are initialized through the API, and the browser URL is retrieved from a 422 response after a POST to submit the login flow. Successful OIDC flows already returned the `code` to the `return_to` URL. Now, unsuccessful flows return the `flow` with the current flow ID (which might have changed), so that the caller can retrieve the full flow and act accordingly. - fix: ignore trivvy CVE report Bump in distroless is still open - **sdk:** Expand identity in session extension ([#3843](https://github.com/ory/kratos/issues/3843)) ([04f0231](https://github.com/ory/kratos/commit/04f02318d4de5290cbf100e9b301284d5ee40fe7)), closes [#3842](https://github.com/ory/kratos/issues/3842) - **sdk:** Improve discriminators for node and Go ([#3821](https://github.com/ory/kratos/issues/3821)) ([9ddf7cc](https://github.com/ory/kratos/commit/9ddf7cc7c52313c4ee13ccdc2886ad94b5d1317f)) - Show error page on identity mismatch ([#3790](https://github.com/ory/kratos/issues/3790)) ([e6db689](https://github.com/ory/kratos/commit/e6db689e0de41067e6e78889c3dab9637a96236e)) - Test assertions on declassifying OIDC tokens ([#3773](https://github.com/ory/kratos/issues/3773)) ([7f8a7f1](https://github.com/ory/kratos/commit/7f8a7f142a91c8c74f32eadb41224fc4f69c2109)) - Tolerate more "truthy" values when creating new flows ([#3841](https://github.com/ory/kratos/issues/3841)) ([49d93c0](https://github.com/ory/kratos/commit/49d93c0e3383f602fe6be3c7bf749b54f344aa72)), closes [#3839](https://github.com/ory/kratos/issues/3839): Use strconv.ParseBool to accept multiple "truthy" values for the `refresh` and `return_session_token_exchange_code` query parameters when creating a new login flow. For some SDKs (e.g.: Python), these stringification of booleans is not user-controlled and these endpoints could not be used fully due to the backend ignoring any value other than `true` (all lowercase). - Tweaks to UpsertSessions ([#3878](https://github.com/ory/kratos/issues/3878)) ([da51dcd](https://github.com/ory/kratos/commit/da51dcdb8c82a5dbd290ab2f48ad74a1c6dd18f0)) - Use correct post-verification identity state in post-hooks ([#3863](https://github.com/ory/kratos/issues/3863)) ([6e63d06](https://github.com/ory/kratos/commit/6e63d06db1cd1ab62f8a2d0b202ec74572420204)) - Webhook transient payload in OIDC login flows ([#3857](https://github.com/ory/kratos/issues/3857)) ([2cdfc70](https://github.com/ory/kratos/commit/2cdfc70c726a166790b98d419895f0396d13176f)): - fix: transient payload with OIDC login ### Code Generation - Pin v1.2.0 release commit ([1a70648](https://github.com/ory/kratos/commit/1a70648c4d5b9b8d135dd7bea3842057e67b574e)) ### Documentation - Remove delete reference from batch patch identity ([#3906](https://github.com/ory/kratos/issues/3906)) ([cd01cb9](https://github.com/ory/kratos/commit/cd01cb9fb23a24e52d46538a9ea63c2144c3b145)) ### Features - Add `include_credential` query param to `/admin/identities` list call ([#3343](https://github.com/ory/kratos/issues/3343)) ([d94530a](https://github.com/ory/kratos/commit/d94530a716358895b01b65babd77226fab69f494)) - Add headers to web hooks ([#3849](https://github.com/ory/kratos/issues/3849)) ([4642de0](https://github.com/ory/kratos/commit/4642de0cfd1fb15bc48c7093be9449abd488755c)) - Add session to post login webhook ([#3877](https://github.com/ory/kratos/issues/3877)) ([386078e](https://github.com/ory/kratos/commit/386078e0b5c74c54ce2c7dc6fd12fd865817b87a)) - Add transient payloads to all flows ([#3738](https://github.com/ory/kratos/issues/3738)) ([b8b747b](https://github.com/ory/kratos/commit/b8b747b2adc59c8cf938a0ee30accdb4135634b8)) - Add twitter SSO ([#3778](https://github.com/ory/kratos/issues/3778)) ([930fb19](https://github.com/ory/kratos/commit/930fb19842e527e5e9c415efa983b36e02829516)) - Add verification hook to login flow ([#3829](https://github.com/ory/kratos/issues/3829)) ([43e4ead](https://github.com/ory/kratos/commit/43e4eadce7fa6e66bf1f9c03136d141bffd3094f)) - Allow admin to create API code recovery flows ([#3939](https://github.com/ory/kratos/issues/3939)) ([25d1ecd](https://github.com/ory/kratos/commit/25d1ecd90317193095e01b97ff21d92920035b02)) - Control edge cache ttl ([#3808](https://github.com/ory/kratos/issues/3808)) ([c9dcce5](https://github.com/ory/kratos/commit/c9dcce5a41137937df1aad7ac81170b443740f88)) - Linkedin v2 provider ([#3804](https://github.com/ory/kratos/issues/3804)) ([a6ad983](https://github.com/ory/kratos/commit/a6ad983ac83aa3ea65c4dc0c46b582096574c25a)): - feat: add linkedin-v2 provider - docs: document linkedin special-case - PassKeys with Resident Keys and two-step registration ([#3748](https://github.com/ory/kratos/issues/3748)) ([3621411](https://github.com/ory/kratos/commit/3621411dc4386d841bc6766a5ab8d03e65812073)) - Send OIDC claim keys to tracing ([#3798](https://github.com/ory/kratos/issues/3798)) ([04390be](https://github.com/ory/kratos/commit/04390bee426befe51af2ee8177afabaa9ce4fa80)) - Use authenticate endpoint for x ([#3833](https://github.com/ory/kratos/issues/3833)) ([3d9ba5d](https://github.com/ory/kratos/commit/3d9ba5df85e0d0c4d8002365987e536b37678104)): Improves the "Log in with X" experience by not asking the user to re-authenticate every time. ### Tests - Deflake session test ([#3864](https://github.com/ory/kratos/issues/3864)) ([6b275f3](https://github.com/ory/kratos/commit/6b275f35a0732ffb723d47df5b6afbdc06eaf71f)) - Resolve failing test for empty tokens ([#3775](https://github.com/ory/kratos/issues/3775)) ([7277368](https://github.com/ory/kratos/commit/7277368bc28df8f0badffc7e739cef20f05e9a02)) - Resolve flaky e2e tests ([#3935](https://github.com/ory/kratos/issues/3935)) ([a14927d](https://github.com/ory/kratos/commit/a14927dfa5f8d0fbda7e5a831f0a09a42369e06c)): - test: resolve flaky code registration tests - chore: don't fail logout if cookie is not found - chore: remove .only - chore: reduce wait - chore: u - chore: u - chore: u ### Unclassified - Remove unnecessary COPY command from Dockerfile (#3771) ([087748c](https://github.com/ory/kratos/commit/087748c0651ff0fc93259f7ab6b10668c09f5eba)), closes [#3771](https://github.com/ory/kratos/issues/3771) # [1.1.0](https://github.com/ory/kratos/compare/v1.0.0...v1.1.0) (2024-02-20) ![Ory Kratos v1.1.0](https://www.ory.sh/images/newsletter/kratos-1.1.0/banner.png) Ory Kratos v1.1 is the most complete, most scalable, and most secure open-source identity server on the planet, and we are thrilled to announce its release! This release comes with over 270 commits and an incredible amount of new features and capabilities! - **Phone Verification & 2FA with SMS**: Enhance convenient security with phone verification and two-factor authentication (2FA) via SMS, integrating easily with SMS gateways like Twilio. This feature not only adds a convenient layer of security but also offers a straightforward method for user verification, increasing your trust in user accounts. - **Translations & Internationalization**: Ory Kratos now supports multiple languages, making it accessible to a global audience. This improvement enhances the user experience by providing a localized interface, ensuring users interact with the system in their preferred language. - **Native Support for Sign in with Google and Apple on Android/iOS**: Get more sign-ups with native support for "Sign in with Google" and "Sign in with Apple" on mobile platforms. Great user experience matters! - **Account Linking**: Simplify user management with new features that facilitate account linking. If a user registers with a password and later signs in with a social account sharing the same email, new screens make account linking straightforward, enhancing user convenience and reducing support inquiries. - **Passwordless "Magic Code"**: Introduce a passwordless login method with "Magic Code," which sends a one-time code to the user's email for sign-up and login. This method can also serve as a fallback when users forget their password or their social login is unavailable, streamlining the login process and improving user accessibility. - **Session to JWT Conversion**: Convert an Ory Session Cookie or Ory Session Token into a JSON Web Token (JWT), providing more flexibility in handling sessions and integrating with other systems. This feature allows for seamless authentication and authorization processes across different platforms and services. **Note:** To ensure a seamless upgrade experience with minimal impact, some of these features are gated behind the `feature_flags` config parameter, allowing controlled deployment and testing. The following features have been shipped exclusively to Ory Network for this version: - **[B2B SSO](https://www.ory.sh/docs/kratos/organizations)** allows your customers to connect their LDAP / Okta / AD / … to your login. Ory selects the correct login provider based on the user’s email domain. - [\*\*Significantly better API performance](https://www.ory.sh/docs/api/eventual-consistency)\*\* for expensive API operations by specifying the desired consistency (`strong`, `eventual`). - **Finding users effortlessly** with our new fuzzy search for credential identifiers available for the [Identity List API](https://www.ory.sh/docs/kratos/reference/api#tag/identity/operation/listIdentities). - Better reliability when sending out emails across different providers. - Streamlining the HTTP API and improving related SDK methods. - Better performance when calling the whoami API endpoint, updating identities, and listing identities. - The performance of listing identities has significantly improved with the introduction of keyset pagination. Page pagination is still available but will be fully deprecated soon. - Ability to list multiple identities in a batch call. - Passkeys and WebAuthn now support multiple origins, useful when working with subdomains. - The logout flow now redirects the user back to the `return_to` parameter set in the API call. - When updating their settings, the user was sometimes incorrectly asked to confirm the changes by providing their password. This issue has now been fixed. - When signing up with an account that already exists, the user will be shown a hint helping them sign in to their existing account. - CORS configuration can now be hot-reloaded. - The integration with Ory OAuth2 / Ory Hydra has improved for logout, login session management, verification, and recovery flows. - A new passwordless method has been added: "Magic code". It sends a one-time code to the user's email during sign-up and log-in. This method can additionally be used as a fallback login method when the user forgets their password. - Integration with social sign-in has improved, and it is now possible to use the email verified status from the social sign-in provider. - Ory Elements and the default Ory Account Experience are now internationalized with translations. - It is now possible to convert an Ory Session Cookie or Ory Session Token into a JSON Web Token. - Recovery on native apps has improved significantly and no longer requires the user to switch to a browser for the recovery step. - Administrators can now find users by their identifiers with fuzzy search - this feature is still in preview. - Importing HMAC-hashed passwords is now possible. - Webhooks can now update identity admin metadata. - New screens have been added to make account linking possible when a user has registered with a password and later tries signing in with a social account sharing the same email. - Ability to revoke all sessions of a user when they change their password. - Webhooks are now available for all login, registration, and login methods, including Passkeys, TOTP, and others. - The login screen now longer shows “ID” for the primary identifier, but instead extracts the correct label - for example, “Email” or “Username” from the Identity Schema. - Login hints help users with guidance when they are unable to sign in (wrong social sign-in provider) but have an active account. - Phone numbers can now be verified via an SMS gateway like Twilio. - SMS OTP is now a two-factor option. Ory Kratos 1.1 is a major release that marks a significant milestone in our journey. We sincerely hope that you find these new features and improvements in Ory Kratos 1.1 valuable for your projects. To experience the power of the latest release, we encourage you to get the latest version of Ory Kratos [here](https://github.com/ory/kratos) or leverage Ory Kratos in [Ory Network](https://www.ory.sh/network/) — the easiest, simplest, and most cost-effective way to run Ory. For organizations seeking to upgrade their self-hosted solution, **Ory offers enterprise support services to ensure a smooth transition**. Our team is ready to assist you throughout the migration process, ensuring uninterrupted access to the latest features and improvements. Additionally, we provide various [support plans](https://www.ory.sh/support/) specifically tailored for self-hosting organizations. These plans offer comprehensive assistance and guidance to optimize your Ory deployments and meet your unique requirements. We extend our heartfelt gratitude to the vibrant and supportive Ory Community. Without your constant support, feedback, and contributions, reaching this significant milestone would not have been possible. As we continue on this journey, your feedback and suggestions are invaluable to us. Together, we are shaping the future of identity management and authentication in the digital landscape. Contributors to this release in no particular order: [moose115](https://github.com/ory/kratos/commits?author=moose115), [K3das](https://github.com/ory/kratos/commits?author=K3das), [sidartha](https://github.com/ory/kratos/commits?author=sidartha), [efesler](https://github.com/ory/kratos/commits?author=efesler), [BrandonNoad](https://github.com/ory/kratos/commits?author=BrandonNoad) ,[Saancreed](https://github.com/ory/kratos/commits?author=Saancreed), [jpogorzelski](https://github.com/ory/kratos/commits?author=jpogorzelski), [dreksx](https://github.com/ory/kratos/commits?author=dreksx), [martinloesethjensen](https://github.com/ory/kratos/commits?author=martinloesethjensen), [cpoyatos1](https://github.com/ory/kratos/commits?author=cpoyatos1), [misamu](https://github.com/ory/kratos/commits?author=misamu), [tristankenney](https://github.com/ory/kratos/commits?author=tristankenney), [nxy7](https://github.com/ory/kratos/commits?author=nxy7), [anhnmt](https://github.com/ory/kratos/commits?author=anhnmt) Are you passionate about security and want to make a meaningful impact in one of the biggest open-source communities? Join the [Ory community](https://slack.ory.sh/) and become a part of the new ID stack. Together, we are building the next generation of IAM solutions that empower organizations and individuals to secure their identities effectively. Want to check out Ory Kratos yourself? Use these commands to get your Ory Kratos project running on the Ory Network: ``` brew install ory/tap/cli scoop bucket add ory scoop install ory bash <(curl ) -b . ory sudo mv ./ory /usr/local/bin/ ory auth login ory create project --name "My first Kratos project" ory open account-experience registration ory patch identity-config \ --replace '/identity/default_schema_id="preset://username"' \ --replace '/identity/schemas=[{"id":"preset://username","url":"preset://username"}]' \ --format yaml ory open account-experience registration ``` ## Breaking Changes Pagination parameters for the `list identities` CLI command have changed from arguments to flags `--page-token` and `page-size`: ``` - kratos list identities 1 100 + kratos list identities --page-size 100 --page-token ... ``` Furthermore, the JSON / JSON pretty output of `list identities` has changed: ```patch -[ - { "id": "..." }, - { /* ... */ }, - // ... -] +{ + "identities": [ + {"id": "..."}, + { /* ... */ }, + // ... + ], + "next_page_token": "..." +} ``` Closes https://github.com/ory/sdk/issues/284 Closes https://github.com/ory/kratos/pull/3480 ### Bug Fixes - `oidc` does not require a method in the payload ([#3564](https://github.com/ory/kratos/issues/3564)) ([b299abc](https://github.com/ory/kratos/commit/b299abcfa1ebdb8bbb6bb9339f61873d5c77c44f)): - fix: `oidc` does not require a method in the payload - refactor: only update strategies order in test - chore: update audit messages and comments - Accept all 200 responses as OK in courier ([#3401](https://github.com/ory/kratos/issues/3401)) ([88237e2](https://github.com/ory/kratos/commit/88237e25b080a9643f6cbf7eedbf23988ba9ba7c)), closes [#3399](https://github.com/ory/kratos/issues/3399): - fix: accept all 200 responses as OK in courier - Accept login_challenge after verification ([#3427](https://github.com/ory/kratos/issues/3427)) ([6b02350](https://github.com/ory/kratos/commit/6b02350c21aa65decd1bb16e559e1cc7dae42d55)): Part of https://github.com/ory/network/issues/320 - Add caching to Jsonnet snippet during session JWT tokenization ([#3699](https://github.com/ory/kratos/issues/3699)) ([1da8180](https://github.com/ory/kratos/commit/1da818072154baa5c0921134919afde595031e94)) - Add consistency flag ([#3733](https://github.com/ory/kratos/issues/3733)) ([fd79950](https://github.com/ory/kratos/commit/fd7995077307cc101550eda5d7724ea1f68fa98a)) - Add max-age to default cors headers ([#3584](https://github.com/ory/kratos/issues/3584)) ([c5b4aaa](https://github.com/ory/kratos/commit/c5b4aaa2df5d010b62a99ccf45850583daad3a66)) - Add missing tracing & attributes in oidc strategy ([#3429](https://github.com/ory/kratos/issues/3429)) ([09bcb71](https://github.com/ory/kratos/commit/09bcb71f1f0b3238e2d0f4376a1a2290d062c6c1)) - Add return_to parameter to API spec of createRecoveryLinkForIdentity ([#3711](https://github.com/ory/kratos/issues/3711)) ([757a5e4](https://github.com/ory/kratos/commit/757a5e43257e9ff28a16bfe76f8e737b656d3696)) - Add value code to authentication method enum ([#3546](https://github.com/ory/kratos/issues/3546)) ([95dc7a2](https://github.com/ory/kratos/commit/95dc7a20f49aa682f324b70e507ec56c20159ebb)): - fix: add value code to authentication method enum - chore: generate sdk - Additional_id_token_audiences key in config schema ([#3622](https://github.com/ory/kratos/issues/3622)) ([9396bb0](https://github.com/ory/kratos/commit/9396bb0b586d1d1e74a85c0ae3bcf9de81214f1b)) - Adjust tracing verbosity ([976cd0d](https://github.com/ory/kratos/commit/976cd0dc3dd95c2c1992bfa82394e9fad39f34f2)) - Allow post recovery hooks to interrupt the flow ([#3393](https://github.com/ory/kratos/issues/3393)) ([6c1d2f1](https://github.com/ory/kratos/commit/6c1d2f1e4173cfb9a7abe2bfe4f20e47b7568d3b)) - Allow updating admin metadata from webhook responses ([#3569](https://github.com/ory/kratos/issues/3569)) ([22f61f0](https://github.com/ory/kratos/commit/22f61f015495c55e58db4f31ee6882444b9a3caf)) - Always return relative URLs in the Link header for pagination ([fb229c9](https://github.com/ory/kratos/commit/fb229c982c6f7d7a4f5f0f84ffc971a576906160)) - Auto migrate old accounts to use code credential ([#3581](https://github.com/ory/kratos/issues/3581)) ([569b14a](https://github.com/ory/kratos/commit/569b14aba864761236bd3d5a48e4e69f10ea6c86)) - Carry `oauth2_login_challenge` over to registration flow ([#3419](https://github.com/ory/kratos/issues/3419)) ([76241be](https://github.com/ory/kratos/commit/76241bee3dc7fec4690346ee85bc4b9f897fdd34)): Fixes https://github.com/ory/kratos/issues/3321 - Change ListIdentities to keyset pagination ([e16fed1](https://github.com/ory/kratos/commit/e16fed1f8563509aac30886386668bb85e6dc797)) - Change shebangs and makefile from /bin/bash to /usr/bin/env bash ([#3597](https://github.com/ory/kratos/issues/3597)) ([1343bbb](https://github.com/ory/kratos/commit/1343bbbfa11ff3e7fcbc0f233b858d13fd40c66d)): - makefile fix - shebangs changed to /usr/bin/env bash Signed-off-by: nxy7 - Check whoami aal before accepting hydra login request ([#3669](https://github.com/ory/kratos/issues/3669)) ([a2f79c3](https://github.com/ory/kratos/commit/a2f79c31f3208b88024897fc8bf1307ccac6f895)) - Code method on registration and 2fa ([#3481](https://github.com/ory/kratos/issues/3481)) ([7aa2e29](https://github.com/ory/kratos/commit/7aa2e293175d0f4b6c13552cc3781f54f8caf3a0)) - Consider OIDC registration flows errored with duplicate credential to be completed by strategy ([#3525](https://github.com/ory/kratos/issues/3525)) ([3e3c789](https://github.com/ory/kratos/commit/3e3c78967523676cbce9a227d574c2f7f4ea314d)): Returning anything else here may cause Kratos to respond with two concatenated JSON objects: new login flow with actual error message as the first one and a very confusing '500, aborted registration hook execution' as the second one. - Csrf token regenerate on browser flows ([#3706](https://github.com/ory/kratos/issues/3706)) ([e4908db](https://github.com/ory/kratos/commit/e4908dbe4a42fad5a80c4d46004e1e3710cabeb7)), closes [#3705](https://github.com/ory/kratos/issues/3705) - Data race in test ([ab6dc31](https://github.com/ory/kratos/commit/ab6dc3121535d27668fed58804a218b17b17ae43)) - Do not encode full config in multiple places ([#3500](https://github.com/ory/kratos/issues/3500)) ([57a3273](https://github.com/ory/kratos/commit/57a3273055c6e8627dd0b736e881dba3fb0fe75d)) - Do not generate CSRF token for api flows ([#3704](https://github.com/ory/kratos/issues/3704)) ([d93570d](https://github.com/ory/kratos/commit/d93570d330155c27a9315d1f530a0002a459910a)) - Do not initialize parts of the registry in parallel ([#3534](https://github.com/ory/kratos/issues/3534)) ([ff177db](https://github.com/ory/kratos/commit/ff177db8a97f27abc3e883e79832685348602334)) - Don't list org SSOs in settings ([#3637](https://github.com/ory/kratos/issues/3637)) ([6c7068c](https://github.com/ory/kratos/commit/6c7068cf41df51cde5fe9fc79cca84ec6124d38a)) - Don't require code credential for MFA flows ([#3753](https://github.com/ory/kratos/issues/3753)) ([40ed809](https://github.com/ory/kratos/commit/40ed809db631149874864f216a106c43ea5df670)) - Don't require session for OIDC verification ([#3443](https://github.com/ory/kratos/issues/3443)) ([e08f831](https://github.com/ory/kratos/commit/e08f831c2715e515bf58dc2dbb47fc3576421a5c)) - Don't return 500 on conflict for POST /admin/identities ([#3437](https://github.com/ory/kratos/issues/3437)) ([1429949](https://github.com/ory/kratos/commit/142994932e449d9948148804502c98ef73daafff)) - Don't return nil if code is invalid ([#3662](https://github.com/ory/kratos/issues/3662)) ([df8ec2b](https://github.com/ory/kratos/commit/df8ec2b9b77a53beb32e3f94a8fccb711896d8e7)): - fix: don't return nil if code is invalid - chore: add test - Error handling on identity import ([#3520](https://github.com/ory/kratos/issues/3520)) ([83bfb2d](https://github.com/ory/kratos/commit/83bfb2d2a9c69bf3a3442500b9484c1a69f8c794)): When importing identities without any traits, or with malformed traits, 500s are returned. This improves the error handling and messaging. - False-positives for requiring re-authentication on update ([#3421](https://github.com/ory/kratos/issues/3421)) ([ce8139f](https://github.com/ory/kratos/commit/ce8139f2325a8317388cbcaaa98f3f83d626657b)) - Http courier using should use lower case json ([#3740](https://github.com/ory/kratos/issues/3740)) ([84149c4](https://github.com/ory/kratos/commit/84149c4b420ea89f0a16a579c017a8e7e1670204)) - Identity list pagination in CLI command and SDK ([#3482](https://github.com/ory/kratos/issues/3482)) ([1e8b1ae](https://github.com/ory/kratos/commit/1e8b1aeb4bf866892788986f62a31255372de999)): Adds correct pagination parameters to the SDK methods for listing identities and sessions. - Ignore CSRF middleware on Apple OIDC callback ([309c506](https://github.com/ory/kratos/commit/309c50694c11162cad070337f9b1d4e0fcdf444b)) - Ignore more cloudflare cookies ([#3499](https://github.com/ory/kratos/issues/3499)) ([f124ab5](https://github.com/ory/kratos/commit/f124ab5586781cdbfc0a0cfd11b4355bfc8a115c)) - Improved SSRF protection ([#3629](https://github.com/ory/kratos/issues/3629)) ([6d08576](https://github.com/ory/kratos/commit/6d08576bbc2c06014192f05e0129b95eb6c9fd80)): This also improves tracing in the OIDC strategy. - Incorrect login accept challenge ([#3658](https://github.com/ory/kratos/issues/3658)) ([b5dede3](https://github.com/ory/kratos/commit/b5dede329247d0962688b15872a6caf027cf910f)) - Incorrect sdk generator path ([#3488](https://github.com/ory/kratos/issues/3488)) ([ed996c0](https://github.com/ory/kratos/commit/ed996c0d25e68e8a2c7de861c546f0b0e42e9e6e)) - Incorrect SMTP error handling ([#3636](https://github.com/ory/kratos/issues/3636)) ([ee138ec](https://github.com/ory/kratos/commit/ee138ec4e1ba55ef077858653220db9e6b0c7254)) - Incorrect swagger spec for filter parameter ([#3684](https://github.com/ory/kratos/issues/3684)) ([2c1470a](https://github.com/ory/kratos/commit/2c1470ab3556e639f06a01ac1646a6b90c7ecac7)), closes [#3676](https://github.com/ory/kratos/issues/3676) [#3675](https://github.com/ory/kratos/issues/3675) - Increase connection-level timeouts and shutdown timeouts ([#3570](https://github.com/ory/kratos/issues/3570)) ([200b413](https://github.com/ory/kratos/commit/200b4138a429d113ee045d16031bb0a6312c1c01)): The admin API is generally expected to require longer timeouts, for example during bulk identity import. - Issue session after verification after registration with OIDC SSO ([#3467](https://github.com/ory/kratos/issues/3467)) ([a28b523](https://github.com/ory/kratos/commit/a28b523238743f3873b51479eea3b86d684092f9)) - Lint ([e8740c3](https://github.com/ory/kratos/commit/e8740c3498446dcaeab2990604a317e61dc170df)) - Lower-case recovery & verification emails on import ([#3571](https://github.com/ory/kratos/issues/3571)) ([e2ac9ff](https://github.com/ory/kratos/commit/e2ac9ff4e2101788f1fca1b8c83f8791cce446e2)): Emails that contained upper-case characters would be overwritten by the identity schema extension runner, because there all emails are lower-cased. - Mark identity as optional in session struct ([#3463](https://github.com/ory/kratos/issues/3463)) ([7ae02ba](https://github.com/ory/kratos/commit/7ae02ba697f68c9cfae5fe8f696b2c55a3ba9ddc)), closes [#3461](https://github.com/ory/kratos/issues/3461): The identity is not always available in the session struct, for example when AAL2 is required. - Omit irrelevant OIDC providers in forced refresh login flows ([#3608](https://github.com/ory/kratos/issues/3608)) ([912dccd](https://github.com/ory/kratos/commit/912dccdf04a550604c5bfeb53ccf79c5f1133ef2)): Whenever an user is asked to reauthenticate (e.g. because they wish to execute settings flow touching their credentials and their session is no longer privileged) they are asked to provide their credentials again. The forced-refresh login flow generated for such cases already excludes some strategies that are enabled in Kratos but cannot be used to authenticate as current identity, and for example the form presented to the user will not have a password field if the identity does not have a password credential. This, however, does not currently apply to OIDC providers; the user will always see the full set even if some of them can't be used to sign in as current identity. This change causes forced refresh login flows to also omit irrelevant OIDC providers in generated form in order to avoid confunding the user about which strategies/providers are valid and can actually be used to reauthenticate. - On verification required after registration, preserve return_to ([#3589](https://github.com/ory/kratos/issues/3589)) ([6a0a914](https://github.com/ory/kratos/commit/6a0a9149b9828ba994bec9b48a43f9d70245f43f)): - fix: on verification required after registration, preserve return_to - test: return_to on verification flow - chore: refactor - Panic in recovery ([#3639](https://github.com/ory/kratos/issues/3639)) ([c25ddff](https://github.com/ory/kratos/commit/c25ddffd2270a8d0861e2fc78cd0ba26e63af4eb)) - Pass context ([#3452](https://github.com/ory/kratos/issues/3452)) ([c492bdc](https://github.com/ory/kratos/commit/c492bdcd0c5dbdf527ae523d879a6c1eeb9c4cdf)) - Properly normalize OIDC verified emails ([#3450](https://github.com/ory/kratos/issues/3450)) ([703b910](https://github.com/ory/kratos/commit/703b910927d879558bfeb0fd2c3339b1d301fac8)) - Redirect to verification URL even if login_challenge is set ([#3412](https://github.com/ory/kratos/issues/3412)) ([cd9e6a0](https://github.com/ory/kratos/commit/cd9e6a0e1e4cb4957d2a50ae3d288ebb0591e42d)): Fixes https://github.com/ory/network/issues/320 - Reduce db lookups in whoami for aal check ([#3372](https://github.com/ory/kratos/issues/3372)) ([d814a48](https://github.com/ory/kratos/commit/d814a4864d5c25c4f320daca733873577d517331)): Significantly improves performance by reducing the amount of queries we need to do when checking for the different AAL levels. - Registration code ui nodes group ([#3505](https://github.com/ory/kratos/issues/3505)) ([6220184](https://github.com/ory/kratos/commit/622018459ddb16c182da49dfd91fd1c6ef8c6b73)): - fix: registration code ui nodes group - style: format - Registration should accept hydra login ([#3592](https://github.com/ory/kratos/issues/3592)) ([7a47827](https://github.com/ory/kratos/commit/7a47827cfd58ef68ebfbbeaf5ed86c394ba2bd5e)): - fix: registration should accept hydra login - fix: oauth2 registration flow with session - wip: registration oauth flow tests - wip: refactor oauth flows test - wip: refactor op_registration_test - wip: oauth provider registration test - wip: refactor oauth flows test - fix(test): oauth provider login - style: format - Registration with verification ([#3451](https://github.com/ory/kratos/issues/3451)) ([77c3196](https://github.com/ory/kratos/commit/77c3196fd60c5927b84e9a7f6546f80ac2d78ee5)) - Reject obviously invalid email addresses from courier ([8cb9e4c](https://github.com/ory/kratos/commit/8cb9e4cae9dffd4c25d52920186f9c5fbe2bd0fe)) - Remove `earliest_possible_extend` default in schema ([#3464](https://github.com/ory/kratos/issues/3464)) ([7e05b7d](https://github.com/ory/kratos/commit/7e05b7db3c01efc96185ac18042e971e33da37c8)) - Remove duplicate message ID usage ([#3468](https://github.com/ory/kratos/issues/3468)) ([dfcbe22](https://github.com/ory/kratos/commit/dfcbe226bc53b91f3a6c9837496a159b85c2e68a)) - Remove requirement for smtp section ([#3405](https://github.com/ory/kratos/issues/3405)) ([59a3f14](https://github.com/ory/kratos/commit/59a3f1469b8412e49846a500493cb02fc6eb34b1)) - Remove slow queries from update identities ([#3553](https://github.com/ory/kratos/issues/3553)) ([d138abb](https://github.com/ory/kratos/commit/d138abb6278ebb232e120bee0fb956a0f2816b8d)) - Rename "phone" courier channel to "sms" ([#3680](https://github.com/ory/kratos/issues/3680)) ([eb8d1b9](https://github.com/ory/kratos/commit/eb8d1b9abd6d2b3eb86ab11d48d9ebd059586b67)) - Respect gomail.SendError in mail queue ([#3600](https://github.com/ory/kratos/issues/3600)) ([9c608b9](https://github.com/ory/kratos/commit/9c608b991874d839782d9219f2fc27d0d4a398af)) - Respond with 422 when SPA identity requires AAL2 ([#3572](https://github.com/ory/kratos/issues/3572)) ([df18c09](https://github.com/ory/kratos/commit/df18c09e0089743e8aee17540d277b9572252e06)): If you submit a browser login flow with an `Accept` header of `application/json`, but the login flow requires AAL2, then there is no way for the code to know it needs to redirect the user to the 2FA page. Instead of responding with the `Session` in this scenario, this PR changes the behaviour to respond with a `browser_location_change_required` error (status `422`) to indicate that the browser needs to open a specific URL, /self-service/login/browser?aal=aal2. - Return 400 bad request for invalid login challenge ([#3404](https://github.com/ory/kratos/issues/3404)) ([ca34e9b](https://github.com/ory/kratos/commit/ca34e9b744482b41d65082f3bed52e9c4ebd7ba4)) - Return HTTP 400 if key unmarshal fails ([#3594](https://github.com/ory/kratos/issues/3594)) ([fdf4956](https://github.com/ory/kratos/commit/fdf4956d9218cfa1d2227c4880e48f9bbdaeb95d)): - fix: return HTTP 400 if key unmarshal fails - fix: apply reviewer's suggestion, prepare for bump - fix: follow up reviewer suggestion from ory/x - chore: bump ory/x - Schema test errors ([#3528](https://github.com/ory/kratos/issues/3528)) ([bee0341](https://github.com/ory/kratos/commit/bee0341c5bf5708a2210146fc59f050a1b9df663)) - Set iss from userinfo claims if missing ([#3744](https://github.com/ory/kratos/issues/3744)) ([241a911](https://github.com/ory/kratos/commit/241a911af74e8ad7353d6e3cab86db20758b86fc)) - Specify correct minimum versions in migratest ([18b89ea](https://github.com/ory/kratos/commit/18b89ea588d129fa88379f7b0d7f4fd00ec6023d)) - Tracing context passing in /sessions/whoami ([1254bf5](https://github.com/ory/kratos/commit/1254bf5a38dbe2c0e2798e07dd0ee5e4b2f63d6e)) - Tracing improvements ([c804cb2](https://github.com/ory/kratos/commit/c804cb2bebbefc97073cf3b8fa250c3eefc58894)) - Type-assert all interfaces that WebHook implements ([ffda1a0](https://github.com/ory/kratos/commit/ffda1a0dab661c5f11ad849b9287094313561b79)) - Ui node input attributes key added ([#3561](https://github.com/ory/kratos/issues/3561)) ([9eff0f3](https://github.com/ory/kratos/commit/9eff0f3a611f32af7aa7f27587b3d3f4448ce915)): - fix: ui node InputAttributes.Key added - fix: selfservice recovery flow add React unique key and numeric pattern - fix: remove React related key addition - test: update snapshot - Use ID label on login with multiple identifiers ([#3657](https://github.com/ory/kratos/issues/3657)) ([be907db](https://github.com/ory/kratos/commit/be907dbbd841025fd854344b77d3368b2ff8089f)) - Use org ID from session if available in login flow ([#3545](https://github.com/ory/kratos/issues/3545)) ([1b3647c](https://github.com/ory/kratos/commit/1b3647c2acdad966f920c2b9e6e657c52aa50c6e)) - Use provider label in link message ([#3661](https://github.com/ory/kratos/issues/3661)) ([fa5ec93](https://github.com/ory/kratos/commit/fa5ec93e8ae7d971d07f0e9b3acaa0840b9ac7de)) - Use registry client for schema loading ([#3471](https://github.com/ory/kratos/issues/3471)) ([3a57726](https://github.com/ory/kratos/commit/3a577269980213e4415fd5fa713882990e2e7640)) - Using first name as last name ([#3556](https://github.com/ory/kratos/issues/3556)) ([df80377](https://github.com/ory/kratos/commit/df80377f5fe6180fba5904baa5be1ba1d68eb2aa)) - Wrong continue_with enum declaration ([#3522](https://github.com/ory/kratos/issues/3522)) ([4c34c24](https://github.com/ory/kratos/commit/4c34c2417db0cb1f79b42db5f33544c90b38ad87)) ### Code Generation - Pin v1.1.0 release commit ([f47675b](https://github.com/ory/kratos/commit/f47675b82012e0ff74b05b9b7e713b3aa2fdda54)) ### Documentation - Add example for `allowed_return_urls` to include wildcard url ([#3533](https://github.com/ory/kratos/issues/3533)) ([39b0c3c](https://github.com/ory/kratos/commit/39b0c3c03df0aec254b32c840730452d4856872b)), closes [#1528](https://github.com/ory/kratos/issues/1528) - Improve enum handling and completeness ([#3714](https://github.com/ory/kratos/issues/3714)) ([4b881ca](https://github.com/ory/kratos/commit/4b881cae4359bfa068261d2d0765ce3daadcbcf2)) - Remove experimental warnings ([#3406](https://github.com/ory/kratos/issues/3406)) ([d4d26e6](https://github.com/ory/kratos/commit/d4d26e6e1510c8e09346e95251f420f95ec54998)): See https://github.com/ory/kratos/discussions/3388 - Update link to hashed password formats ([#3484](https://github.com/ory/kratos/issues/3484)) ([8ca3adc](https://github.com/ory/kratos/commit/8ca3adcb8a5db2906fbeb92f4b74aa4242fabdef)) ### Features - Add ability to convert session to JWT when calling whoami ([#3472](https://github.com/ory/kratos/issues/3472)) ([57b7bb8](https://github.com/ory/kratos/commit/57b7bb846c8072f786ea6b80cd688fdee75805da)), closes [#2487](https://github.com/ory/kratos/issues/2487): This patch adds a query parameter `tokenize_as` to `/session/whoami` which encodes the session to a JWT. It is possible to customize the JWT claims by using a JsonNet template, and furthermore change the expiry of the token. The tokenize feature supports multiple templates, which makes it easy to use the resulting JWT in a variety of use cases. - Add event ([#3524](https://github.com/ory/kratos/issues/3524)) ([75031e6](https://github.com/ory/kratos/commit/75031e67bc82a820a6aba134115e8d5f93303638)) - Add GetID member functions to RecoveryAddress and Credentials ([#3474](https://github.com/ory/kratos/issues/3474)) ([085d500](https://github.com/ory/kratos/commit/085d5002df27d455057d33bd2d93dfbca0de4872)) - Add ID Token sign in with Google Android/iOS SDK ([#3515](https://github.com/ory/kratos/issues/3515)) ([055ed92](https://github.com/ory/kratos/commit/055ed9226d9d12f5142542be2e18438ff708c2e2)) - Add OpenTelemetry span for password hash comparison ([#3383](https://github.com/ory/kratos/issues/3383)) ([e3fcf0c](https://github.com/ory/kratos/commit/e3fcf0c31db9742ed61bcf783e37ee119ed19d42)) - Add request URL to email and SMS templates ([bf5f8c3](https://github.com/ory/kratos/commit/bf5f8c3cfb2eb523a77239addb8249adf9f8b31d)) - Add sms verification for phone numbers ([#3649](https://github.com/ory/kratos/issues/3649)) ([e3a3c4f](https://github.com/ory/kratos/commit/e3a3c4fe0d6697f6864283daf4be8a8f8971c7b4)) - Add support for recovery on native flows ([#3273](https://github.com/ory/kratos/issues/3273)) ([e363889](https://github.com/ory/kratos/commit/e363889732c0a1cb801fd12b2e0e8546006e9714)) - Add WebhookSucceeded event ([aa8c936](https://github.com/ory/kratos/commit/aa8c93677a8f682f7693afe69f1baf1887355e0a)) - Added various new text messages ([ea91483](https://github.com/ory/kratos/commit/ea914834e6bb626de2977e228af2b40935ccc980)): To improve i18n and message customization, we added a bunch of new messages. Integrations that do message customization should probably handle those new message codes: - 1010014 - 1010015 - 1040005 - 1040006 - 1070012 - 1070013 - 4000028 - 4000029 - 4000030 - 4000031 - 4000032 - 4000033 - 4000034 - 4000035 - 4000036 - 4010007 - 4010008 - 4040002 - 4040003 Additionally, these messages got more context: - 1050014 - 1050018 - 1070002 - 4000001 - 4000003 - 4000004 - 4000017 - 4000018 - 4000019 - 4000020 - 4000021 - 4000022 - 4000023 - 4000024 - 4000025 - 4000026 - 4010001 - 4040001 - 4050001 - 4060005 - 4070005 - 5000001 - Allow additional id token audiences ([#3616](https://github.com/ory/kratos/issues/3616)) ([0fa648d](https://github.com/ory/kratos/commit/0fa648d9f7b837a35de9b230a05b5951e95d5874)) - Allow extra migrations in NewPersister ([96c1ff7](https://github.com/ory/kratos/commit/96c1ff7747ea38e23a3892f74b75ee555ed49c88)) - Allow fuzzy-search on credential identifiers ([#3526](https://github.com/ory/kratos/issues/3526)) ([2cb3ea2](https://github.com/ory/kratos/commit/2cb3ea2eaff909ac936611d5653f69e713f41b64)): This PR adds the ability to search for sub-strings and similar strings in credential identifiers. Note that the **postgres** and **CRDB** migrations create special indexes useful for this feature. To use [online schema changes](https://www.cockroachlabs.com/docs/v23.1/online-schema-changes) with cockroach, we recommend to manually copy the index definition and run it before applying migrations. The migration will then be a no-op. If you run on **mysql** (or **sqlite**), no special index is created. If desired, you can create such an index manually, and it would be highly appreciated if you could contribute its definition. This feature is a preview and will change in behavior! Similarity search is not expected to return deterministic results but are useful for humans. - Allow importing hmac hashed passwords ([#3544](https://github.com/ory/kratos/issues/3544)) ([0a0e1f7](https://github.com/ory/kratos/commit/0a0e1f7200e226ef24de062811a05bcdd02b6acd)), closes [#2422](https://github.com/ory/kratos/issues/2422): The basic format is `$hmac-$$`: ``` # password = test; key=key; hash function=sha $hmac-sha1$NjcxZjU0Y2UwYzU0MGY3OGZmZTFlMjZkY2Y5YzJhMDQ3YWVhNGZkYQ==$a2V5 ``` - Allow marking OIDC provider-verified addresses as verified during registration ([#3448](https://github.com/ory/kratos/issues/3448)) ([e7b33a1](https://github.com/ory/kratos/commit/e7b33a168bf0c0fe0492901abd3df8b6d6a08a68)), closes [#3445](https://github.com/ory/kratos/issues/3445) [#3424](https://github.com/ory/kratos/issues/3424) [#1057](https://github.com/ory/kratos/issues/1057): This feature allows marking emails provided by social sign in providers as verified. - Batch list identities ([#3598](https://github.com/ory/kratos/issues/3598)) ([8ad54f1](https://github.com/ory/kratos/commit/8ad54f1be53b30fdb24b616be0c52fd66829f201)), closes [#2448](https://github.com/ory/kratos/issues/2448): This change allows to filter `GET /admin/identities` by ID with the following syntax: ``` /admin/identities?ids=id1&ids=id2&ids=id3 ``` - **changelog:** Add support for native recovery ([#3624](https://github.com/ory/kratos/issues/3624)) ([492808c](https://github.com/ory/kratos/commit/492808cae0e804793aef9a02a902fce988f9fc6d)): Adds the ability to complete the recovery flow properly on API flows. This PR also streamlines the behavior for SPA flows to not return 422 errors anymore. To enable this new behavior, set the features.use_continue_with_transitions flag in the config to `true`. See also https://github.com/ory/kratos/pull/3273 - Claims from userinfo endpoint ([#3718](https://github.com/ory/kratos/issues/3718)) ([90bdc61](https://github.com/ory/kratos/commit/90bdc61d28466f10e4e609df014b220afbee0478)): - feat: claims from userinfo endpoint - chore: update libraries - test: improve coverage - Emit error details when we find stray cookies in an API flow ([#3496](https://github.com/ory/kratos/issues/3496)) ([df74339](https://github.com/ory/kratos/commit/df74339802d98a292abb32806eca35fb2554960b)) - Eventually consistency API controls ([#3558](https://github.com/ory/kratos/issues/3558)) ([00cf11c](https://github.com/ory/kratos/commit/00cf11c071344103c603c078f07196401d091780)): Adds a feature used in Ory Network which enables trading faster reads for slightly stale data. This feature depends on Cockroach functionality and configuration, and is not possible for MySQL or PostgreSQL. - Extend Microsoft Graph API capabilities ([#3609](https://github.com/ory/kratos/issues/3609)) ([4a7bcc9](https://github.com/ory/kratos/commit/4a7bcc9322be37e6fd141e411bd65e3977eeb692)): This change queries for all user information available with the `User.Read` scope during OIDC, and populates the `RawClaims` field. - Extract identifier label for login from default identity schema ([#3645](https://github.com/ory/kratos/issues/3645)) ([180828e](https://github.com/ory/kratos/commit/180828eb507ab239a9c6589f747a6816b6e50074)) - Fine-grained hooks for all available flow methods ([#3519](https://github.com/ory/kratos/issues/3519)) ([a37f6bd](https://github.com/ory/kratos/commit/a37f6bddc48443b2fc464699fa5c2922f64d81f6)): Adds fine-grained hook configurations to the post-settings flow for methods totp, webauthn, lookup_secret and the post-login flow for totp, lookup_secret, and code. - Hook to revoke sessions after password changed ([#3514](https://github.com/ory/kratos/issues/3514)) ([e6af6db](https://github.com/ory/kratos/commit/e6af6db37ff5de33a656ce7804c813451395459d)), closes [#3513](https://github.com/ory/kratos/issues/3513): Currently, the Kratos system does not automatically log out or invalidate other active sessions when a user changes their password. This poses a significant security risk as it allows potentially unauthorized individuals to maintain access to the account even after the password has been updated. This PR provides the option to add the `revoke_active_sessions` hook to the actions sections of the selfservice settings. - Hot-reload CORS origins ([#3423](https://github.com/ory/kratos/issues/3423)) ([157d934](https://github.com/ory/kratos/commit/157d9345aeb04f371f9d85b70c89e8646e781333)) - Improve messages for easier i18n ([#3457](https://github.com/ory/kratos/issues/3457)) ([37f1657](https://github.com/ory/kratos/commit/37f16577d92ba88869bf15fb1ea54e819b062724)) - Improve performance by computing password hashes while validating ([#3508](https://github.com/ory/kratos/issues/3508)) ([a9786c5](https://github.com/ory/kratos/commit/a9786c599d09f61e2e07df5066ce94feb2d99bac)) - Improved webhook tracing ([#3746](https://github.com/ory/kratos/issues/3746)) ([9d7021d](https://github.com/ory/kratos/commit/9d7021d87f47690c2c1f8000e87b425e49bc9496)) - Jsonnet caching for OIDC claims mapper, webhooks, JWT session tokenizer ([#3701](https://github.com/ory/kratos/issues/3701)) ([1d26e09](https://github.com/ory/kratos/commit/1d26e097b273aeda36f73637765da5bdb2aa4a66)) - Link oidc credentials when login ([#3563](https://github.com/ory/kratos/issues/3563)) ([b784949](https://github.com/ory/kratos/commit/b784949d03b849d9d1d594977f75f5843b7b5da8)), closes [#2727](https://github.com/ory/kratos/issues/2727) [#3222](https://github.com/ory/kratos/issues/3222): When user tries to login with OIDC for the first time but has already registered before with email/password a credentials identifier conflict may be detected by Kratos. In this case user needs to login with email/password first and then link OIDC credentials on a settings screen. This PR simplifies UX and allows user to link OIDC credentials to existing account right in the login flow, without switching to settings flow. - List by OIDC cred ([#3721](https://github.com/ory/kratos/issues/3721)) ([bff9c61](https://github.com/ory/kratos/commit/bff9c61b147648ab139e7e86cda4336b5d1cfd39)) - Login with code on any credential type ([#3549](https://github.com/ory/kratos/issues/3549)) ([ceed7d5](https://github.com/ory/kratos/commit/ceed7d5478c5cca894587698c57f676dda100b27)): Should be able to login with the `code` credential even if the user did not register on the `code` credential. Only `identifier` matching is done and validation based on the identity schema. - One-time code native flows ([#3516](https://github.com/ory/kratos/issues/3516)) ([9b0fee3](https://github.com/ory/kratos/commit/9b0fee30f980d860fd548e7589fa6a06e593537a)) - Order sessions by created_at ([#3696](https://github.com/ory/kratos/issues/3696)) ([688111c](https://github.com/ory/kratos/commit/688111c9a6bf9872657cf6aada77f55fa2520e00)) - Parametrize courier worker ([#3601](https://github.com/ory/kratos/issues/3601)) ([0e4be57](https://github.com/ory/kratos/commit/0e4be57e41e1152f4be22f490541c2c099cfe3fe)): Allows one to parametrize how many messages the courier will fetch and how often it will fetch messages. - Passwordless browser login and registration via code to email ([#3378](https://github.com/ory/kratos/issues/3378)) ([eaaf375](https://github.com/ory/kratos/commit/eaaf37519917612671238412a633847386d7c613)), closes [#2029](https://github.com/ory/kratos/issues/2029) [ory-corp/cloud#3573](https://github.com/ory-corp/cloud/issues/3573): This feature adds passwordless email code login. When a user signs up, or signs in, a code is sent to their email address which they can use to complete the authentication process. This feature is currently only working for browser facing APIs. - Pooled process-isolated Jsonnet VM ([9a52ddf](https://github.com/ory/kratos/commit/9a52ddfbe7c24c41b6aa3ddc3c79c6fcbfb8db02)) - Provide login hints when registration fails due to duplicate credentials/addresses ([#3430](https://github.com/ory/kratos/issues/3430)) ([8b28469](https://github.com/ory/kratos/commit/8b284697e4a26fb01ad57d2e9ebd8f714be49f33)): - feat: provide login hints when registration fails due to duplicate credentials or identifiers - feat: identify edge cases and write tests - chore: synchronize workspaces - feat: make login hints configurable - chore: synchronize workspaces - chore: synchronize workspaces - chore: synchronize workspaces - chore: synchronize workspaces - Support auth_type parameter ([#3487](https://github.com/ory/kratos/issues/3487)) ([fc30304](https://github.com/ory/kratos/commit/fc303040b71139f512fd1491ce30f80837b940b9)): The Facebook OIDC provider supports an auth_type parameter that when set to "reauthenticate" will force the user to reauthenticate (similar to `prompt=login` for other Providers). - Support for B2B SSO ([#3489](https://github.com/ory/kratos/issues/3489)) ([0ec037a](https://github.com/ory/kratos/commit/0ec037ab298ed28fb0ac84db6a4d2b14b81e57df)) - Support MFA via SMS ([#3682](https://github.com/ory/kratos/issues/3682)) ([1516cf6](https://github.com/ory/kratos/commit/1516cf64e346819dccace1cc25aaccac38b9e47c)) - Support multiple origins for WebAuthN ([#3380](https://github.com/ory/kratos/issues/3380)) ([013f335](https://github.com/ory/kratos/commit/013f335881831bbf90ac31b219b57118fc089fe6)): Users can now supply a list of origins for webauthn in the configuration. - Support native social sign using apple sdk ([#3476](https://github.com/ory/kratos/issues/3476)) ([f561013](https://github.com/ory/kratos/commit/f561013dd737dadcc82c4ec049fde12861e91e43)) - Transmit current session ID to Hydra when accepting the login ([#3426](https://github.com/ory/kratos/issues/3426)) ([610c76d](https://github.com/ory/kratos/commit/610c76d9140f2f43217ac55094051a994ea83ecc)): - chore: change react-native port to 19006 - feat: transmit current session ID when accepting login - fix: upgrade hydra in tests - Webhook analytic events ([9c8a25e](https://github.com/ory/kratos/commit/9c8a25eb0d3e06df182565d3d959d57e5dccfed8)) ### Reverts - Revert "chore: simplify courier code (#3603)" ([7c54c9f](https://github.com/ory/kratos/commit/7c54c9f36c86142c8e071a5359c71cf6213a1a69)), closes [#3603](https://github.com/ory/kratos/issues/3603): This reverts commit 316cd4aacfe31efafa7d737a7c476e2c794e9c9b. ### Tests - Add test for link + oidc challenge ([#3720](https://github.com/ory/kratos/issues/3720)) ([67360cf](https://github.com/ory/kratos/commit/67360cf39482b935604f088a4b7a83cc4deab375)) - **e2e:** Logout return_to ([#3418](https://github.com/ory/kratos/issues/3418)) ([c348c12](https://github.com/ory/kratos/commit/c348c12ab3c9cdb4ce8159fe774ed179ff6a4d8a)) - Fix cypress setup ([#3527](https://github.com/ory/kratos/issues/3527)) ([70c8ddd](https://github.com/ory/kratos/commit/70c8ddd49c8abb9c10f2ca349e01061b791c5e7b)) - Fix e2e failures and speed up e2e tests ([#3483](https://github.com/ory/kratos/issues/3483)) ([70a6171](https://github.com/ory/kratos/commit/70a617194d61763f4b75691b22cfa76ba71ab019)) - Fix hydra tests on master ([#3737](https://github.com/ory/kratos/issues/3737)) ([12166b4](https://github.com/ory/kratos/commit/12166b4370d607a069f268227752bb7b18a50b57)) - Reduce logging in go tests ([#3562](https://github.com/ory/kratos/issues/3562)) ([05de3a2](https://github.com/ory/kratos/commit/05de3a29fed020593c44ea7a7b29e45197fef4f7)) - Resolve cypress issues ([#3531](https://github.com/ory/kratos/issues/3531)) ([4206d26](https://github.com/ory/kratos/commit/4206d2605dfa30b19e132be31b85b1a35f8dca78)) ### Unclassified - Revert "feat: extend Microsoft Graph API capabilities (#3609)" (#3717) ([549308d](https://github.com/ory/kratos/commit/549308db1f7dca42004631ed6156cae5f827b8fe)), closes [#3609](https://github.com/ory/kratos/issues/3609) [#3717](https://github.com/ory/kratos/issues/3717): This reverts commit 4a7bcc9322be37e6fd141e411bd65e3977eeb692. # [1.0.0](https://github.com/ory/kratos/compare/v0.13.0...v1.0.0) (2023-07-12) We are thrilled to announce Ory Kratos v1.0, the powerful Identity, User Management, and Authentication system! With this major update, Ory Kratos brings a host of enhancements and fixes that greatly improve the user experience and overall performance. Several compelling reasons led to label Ory Kratos as a major release, like successfully processing over 100 million API requests daily and having about 100 million Docker Pulls. We have maintained stability within the Ory Kratos APIs for nearly two years, demonstrating their robustness and reliability. No breaking changes mean that developers can trust the stability of Ory Kratos in production. Ory Kratos 1.0 introduces a variety of new features while focusing on stability, robustness, and improved performance. Major enhancements include support for social login and single-sign-on via OpenID connect in native apps, emails sent through HTTP rather than SMTP, and full compatibility with Ory Hydra v2.2.0. Users will also find multi-region support in the Ory Network for broader geographic reach, improved export functionality for all credential types, and enhanced session management with the introduction of the "provider ID" parameter. Other additions comprise distroless images for leaner resource utilization and faster deployment and support for the Lark OIDC provider. Significant improvements and fixes accompany these new features. Enhanced OIDC flows now include the ability to forward prompt upstream parameters, offering developers increased flexibility and customization options. The logout flow also supports the `return_to` parameter, facilitating more flexible redirection post-user logout. Performance has been a key focus, with Ory Kratos 1.0 now capable of handling hundreds of millions of active users monthly. Critical bug fixes have been applied to prevent users from being redirected to incorrect destinations, ensuring smoother authentication and authorization. Additionally, there's more support for legacy systems via implemented crypt(3) hashers and a fix for metadata patching has been deployed to ensure consistent user metadata management. For a detailed view of all changes, refer to the [changelog on GitHub](https://github.com/ory/kratos/blob/master/CHANGELOG.md). Feedback and support are, as always, greatly appreciated. Ory Kratos 1.0 is a major release that marks a significant milestone in our journey. We sincerely hope that you find these new features and improvements in Ory Kratos 1.0 valuable for your projects. To experience the power of the latest release, we encourage you to get the latest version of Ory Kratos [here](https://github.com/ory/kratos) or leverage Kratos in [Ory Network](https://www.ory.sh/network/) — the easiest, simplest, and most cost-effective way to run Ory. For organizations seeking to upgrade their self-hosted solution, **Ory offers dedicated support services to ensure a smooth transition**. Our team is ready to assist you throughout the migration process, ensuring uninterrupted access to the latest features and improvements. Additionally, we provide various [support plans](https://www.ory.sh/support/) specifically tailored for self-hosting organizations. These plans offer comprehensive assistance and guidance to optimize your Ory deployments and meet your unique requirements. We extend our heartfelt gratitude to the vibrant and supportive Ory Community. Without your constant support, feedback, and contributions, reaching this significant milestone would not have been possible. As we continue on this journey, your feedback and suggestions are invaluable to us. Together, we are shaping the future of identity management and authentication in the digital landscape. Contributors to this release in alphabetical order: [borisroman](https://github.com/ory/kratos/commits?author=borisroman), [ci42](https://github.com/ory/kratos/commits?author=ci42), [CNLHC](https://github.com/ory/kratos/commits?author=CNLHC), [David-Wobrock](https://github.com/ory/kratos/commits?author=David-Wobrock), [giautm](https://github.com/ory/kratos/commits?author=giautm), [IchordeDionysos](https://github.com/ory/kratos/commits?author=IchordeDionysos), [indietyp](https://github.com/ory/kratos/commits?author=indietyp), [jossbnd](https://github.com/ory/kratos/commits?author=jossbnd), [kralicky](https://github.com/ory/kratos/commits?author=kralicky), [PhakornKiong](https://github.com/ory/kratos/commits?author=PhakornKiong), [sunakan](https://github.com/ory/kratos/commits?author=sunakan), [steverusso](https://github.com/ory/kratos/commits?author=steverusso) Are you passionate about security and want to make a meaningful impact in one of the biggest open-source communities? Join the [Ory community](https://slack.ory.sh) and become a part of the new ID stack. Together, we are building the next generation of IAM solutions that empower organizations and individuals to secure their identities effectively. Want to check out Ory Kratos yourself? Use these commands to get your Ory Kratos project running on the Ory Network: ```shell brew install ory/tap/cli scoop bucket add ory https://github.com/ory/scoop.git scoop install ory bash <(curl ) -b . ory sudo mv ./ory /usr/local/bin/ ory auth ory create project --name "My first Kratos project" ory open account-experience registration ory patch identity-config \\ --replace '/identity/default_schema_id="preset://username"' \\ --replace '/identity/schemas=[{"id":"preset://username","url":"preset://username"}]' \\ --format yaml ory open account-experience registration ``` ### Bug Fixes - Ability to patch metadata even if it is `null` ([#3304](https://github.com/ory/kratos/issues/3304)) ([3c04d8f](https://github.com/ory/kratos/commit/3c04d8fb63cacf91774864450b02d6d1eb90d856)) - Accept OIDC login request in browser+JSON login flow ([#3271](https://github.com/ory/kratos/issues/3271)) ([ad54093](https://github.com/ory/kratos/commit/ad540930df96e84fb65a36616d5081ec0bb46df5)): - fix: OIDC login in browser JSON flow - test: add test for OIDC+JSON continuity cookie - Add error checking when creating verification code ([#3328](https://github.com/ory/kratos/issues/3328)) ([7182eca](https://github.com/ory/kratos/commit/7182eca074c8e84be325d62c75b62d22698878be)) - Add missing SessionIssued event for api flows ([#3348](https://github.com/ory/kratos/issues/3348)) ([adf78e0](https://github.com/ory/kratos/commit/adf78e09f336b2ac83f8ff1ba5ca382c7cfbec23)): - fix: missing SessionIssued event for api flows - chore: add SessionIssued event to post registration hook - chore: format - chore: move sessionissued event to persister - Bump quickstart version ([#3257](https://github.com/ory/kratos/issues/3257)) ([6db70a8](https://github.com/ory/kratos/commit/6db70a81afac5860a86c31881a6fc988096ff0e4)) - Cypress TOTP test ([eac908c](https://github.com/ory/kratos/commit/eac908c4fc14831288e6fd5b3c65ac197d2f58e1)) - Do not require items to be unique ([#3349](https://github.com/ory/kratos/issues/3349)) ([17be30d](https://github.com/ory/kratos/commit/17be30dd84c667e5d1ae13bd79827b7ca9cdd2de)) - Don't assume the login challenge to be a UUID ([#3317](https://github.com/ory/kratos/issues/3317)) ([3172862](https://github.com/ory/kratos/commit/3172862929ad68011fc940a6e0876fa07187a275)): For compatibility with https://github.com/ory/hydra/pull/3515, which now encodes the whole flow in the login challenge, we cannot further assume that the challenge is a UUID. - **e2e:** Install kratos-selfservice-ui-node peer deps ([#3354](https://github.com/ory/kratos/issues/3354)) ([ce20063](https://github.com/ory/kratos/commit/ce20063a858acecb5d9124792fe6d3899bf95c1c)) - Identity list pagination ([#3325](https://github.com/ory/kratos/issues/3325)) ([9d3ef0d](https://github.com/ory/kratos/commit/9d3ef0df9333aff2c587005df0cdd263028029f3)): Resolves a pesky issue that would skip the last page. - IdentityCreated event ([#3314](https://github.com/ory/kratos/issues/3314)) ([78e31cb](https://github.com/ory/kratos/commit/78e31cb82a28e240a6176c8d3d9ef3bc64559e75)) - Incorrect override in identity hydrate ([#3368](https://github.com/ory/kratos/issues/3368)) ([eaa3f3c](https://github.com/ory/kratos/commit/eaa3f3c19feaf9048e800cc5a5f1e28d3708c624)) - Increase size for request url ([#3366](https://github.com/ory/kratos/issues/3366)) ([10713cc](https://github.com/ory/kratos/commit/10713cc703457cb6f4a1b38482c836e54a0cb224)) - Minor refactorings in package hash ([#3186](https://github.com/ory/kratos/issues/3186)) ([831fb19](https://github.com/ory/kratos/commit/831fb19e1c98b9fade3ff61d26ad249c548292d6)) - Missing id for login event ([#3315](https://github.com/ory/kratos/issues/3315)) ([b6b80a3](https://github.com/ory/kratos/commit/b6b80a3af1162e4009fa8c7c5e9ae7225e941849)) - Properly normalize uppercase mail addresses ([4984e0f](https://github.com/ory/kratos/commit/4984e0fb329291484a54344255f797008142b7cc)): Fixes https://github.com/ory/kratos/issues/3187 Fixes https://github.com/ory/kratos/issues/3289 - Provide index hint in QueryForCredentials ([#3329](https://github.com/ory/kratos/issues/3329)) ([4ba530e](https://github.com/ory/kratos/commit/4ba530ef593272d3cc0a9e1d354e81db495e8686)): - fix: provide index hint in QueryForCredentials - feat: remove customizable join predicate in QueryForCredentials - chore: remove obsolete config tracer - Reduce lookups in whoami call ([#3364](https://github.com/ory/kratos/issues/3364)) ([5bb7b0c](https://github.com/ory/kratos/commit/5bb7b0c83b330ee893bdeb4e636655179bd29e39)) - Reintroduce ExpandAll ([#3369](https://github.com/ory/kratos/issues/3369)) ([8f9bff5](https://github.com/ory/kratos/commit/8f9bff527528780b623bf8e4801f7f3c37a5a6f3)) - Remove codeball ([aa29606](https://github.com/ory/kratos/commit/aa296067e2736cad329814f7acffd816ce0d74a3)) - Remove duplicate SessionIssued event ([#3351](https://github.com/ory/kratos/issues/3351)) ([b1e78ad](https://github.com/ory/kratos/commit/b1e78ad3e39418695639e521ddceb64589455d87)) - Return HTTP 400 instead of 500 for bad query parameters ([58258eb](https://github.com/ory/kratos/commit/58258eba99aa15f2ac852123c0200f56518ecb2a)) - **sdk:** Add cookie for updateLogoutFlow ([#3284](https://github.com/ory/kratos/issues/3284)) ([95ed2b9](https://github.com/ory/kratos/commit/95ed2b94cc99d40af6bbe57e5356ec0f28cb9b78)): Closes https://github.com/ory/sdk/issues/255 - **sdk:** Update the API spec to reflect the 204 NoContent in DeleteIdentityCredentials ([#3347](https://github.com/ory/kratos/issues/3347)) ([f3dee86](https://github.com/ory/kratos/commit/f3dee869bef0e0dd2d36541823ae57d54ba5788e)) - Settings should persist `return_to` after required mfa login flow ([#3263](https://github.com/ory/kratos/issues/3263)) ([0ed1abd](https://github.com/ory/kratos/commit/0ed1abd391b6b5369862ee5db8faa4f4aaf68b09)): - fix: get settings should persist `return_to` when redirecting to aal2 - feat(e2e): verify `return_to` persists in recovery flows - test: recovery strategy with mfa account - test: code recovery return to persists to settings with aal2 - u - fix: return to settings flow after mfa login - fix(test): login handler - fix: flow between settings and mfa - fix: get settings endpoint should redirect to settings ui instead of to itself - feat(test): preserve URL from various settings flows through login mfa flow - chore: cleanup - fix(e2e): recovery return to spa tests - fix: e2e proxy - fix: do not always redirect back to settings on mfa - fix: new settings flow with required mfa shouldn't be added to login flow return_to unless it contains a return_to parameter - fix(e2e): let test dynamically handle required_aal - chore: cleanup unused code - test: `DoesSessionSatisfy` with method options - test: recovery strategy with aal2 - String to enum for updateVerificationFlowWithLinkMethod Method ([#3279](https://github.com/ory/kratos/issues/3279)) ([34ff1d2](https://github.com/ory/kratos/commit/34ff1d2912e7f7aefb35dae759dce2eb37ecb790)), closes [#2943](https://github.com/ory/kratos/issues/2943) - Update correct typo ([#3281](https://github.com/ory/kratos/issues/3281)) ([0fea75c](https://github.com/ory/kratos/commit/0fea75c4093d2c7edc84c14f0ab5bebf33a58970)): The text for verification code input should be `Verification code` not `Verify code`. - Update README ([#3363](https://github.com/ory/kratos/issues/3363)) ([c426014](https://github.com/ory/kratos/commit/c4260140966489a05169a0197e209ff98181bc2e)) - Use RETURNING clause for batch create ([#3293](https://github.com/ory/kratos/issues/3293)) ([8ae8783](https://github.com/ory/kratos/commit/8ae8783935292fb011b1018ac7417ed77eb6abb7)) - Use the correct redirect_uri for linkedin social login ([#3269](https://github.com/ory/kratos/issues/3269)) ([27ccecc](https://github.com/ory/kratos/commit/27ccecc1cd490eaa71da7f8235b4b0057b8f14fe)) - Webhook config parse for settings flow ([#3305](https://github.com/ory/kratos/issues/3305)) ([95ad94d](https://github.com/ory/kratos/commit/95ad94d08efdbb369caecaa64cd0a30058c34ed3)) ### Code Generation - Pin v1.0.0 release commit ([41b7c51](https://github.com/ory/kratos/commit/41b7c51c1c6b3bdff9e9ea8bb5e455e3c15c5256)) ### Documentation - Fix typo in readme ([#3299](https://github.com/ory/kratos/issues/3299)) ([b40544e](https://github.com/ory/kratos/commit/b40544e427891f20cea6838e79f4dee5b52ea5d1)) ### Features - Add “provider id” parameter to kratos session ([#3292](https://github.com/ory/kratos/issues/3292)) ([387f5a2](https://github.com/ory/kratos/commit/387f5a2711ca8eee97ad0f6bb2575ec9ba4797d9)), closes [#3283](https://github.com/ory/kratos/issues/3283) - Add distroless and static images ([#3350](https://github.com/ory/kratos/issues/3350)) ([1e65662](https://github.com/ory/kratos/commit/1e65662c92b107290466c20de38bbdc0571b596a)) - Add return_to parameters to the `createLogout` handler ([#3336](https://github.com/ory/kratos/issues/3336)) ([08fed36](https://github.com/ory/kratos/commit/08fed36973274ef294491d00811bc867f1537d62)): - feat: add return_to parameters to the `createLogout` handler - test: logout take over return_to from create to update - test(e2e): logout return to - test(e2e): logout return to - test: logout return_to isnt applicable to react - Allow customization of JOIN predicate in QueryForCredentials ([#3253](https://github.com/ory/kratos/issues/3253)) ([8785166](https://github.com/ory/kratos/commit/87851668e776404aabbfbc67af73a43ea3ee28fc)) - Emit events for login/logout and registration ([#3235](https://github.com/ory/kratos/issues/3235)) ([c784b7e](https://github.com/ory/kratos/commit/c784b7e7ed2834ca83c6db2326b735e78e5a75f2)) - Forward `prompt` upstream parameter during OIDC flow ([#3276](https://github.com/ory/kratos/issues/3276)) ([d290cb0](https://github.com/ory/kratos/commit/d290cb05bb4f63d04ec3763db127060e13c350dc)), closes [#2709](https://github.com/ory/kratos/issues/2709) - Implement `crypt(3)` hashers ([#3303](https://github.com/ory/kratos/issues/3303)) ([afe06db](https://github.com/ory/kratos/commit/afe06db95663cc0cb9704ba4f7014ed9bfb4de09)), closes [#3291](https://github.com/ory/kratos/issues/3291): This PR implements md5crypt, sha256crypt, sha512crypt, which are considered legacy (like md5), but are used in legacy systems looking to convert to ory. They use the existing format of crypt(5) (which is compliant to PHC). - Improve event types and capture more events ([#3297](https://github.com/ory/kratos/issues/3297)) ([835fe13](https://github.com/ory/kratos/commit/835fe13d9ce81f7c0ed91dd2863a740fbb0c6209)) - Lark OIDC provider ([#2925](https://github.com/ory/kratos/issues/2925)) ([f884dfb](https://github.com/ory/kratos/commit/f884dfbaa8aeba58b3b1595bd45e41f9b3e5a0e0)) - Return to oauth flow after switching from login to other flows ([#3212](https://github.com/ory/kratos/issues/3212)) ([a1fea6c](https://github.com/ory/kratos/commit/a1fea6c353768bbf154900766fbbe51f2a148554)): - feat: return to oauth flow after switching from login to other flows - feat(e2e): flows should have return_to set to hydra request_url - u - fix: override return_to URL on OAuth flows - style: format - fix: TestOAuth2Provider - feat: config to opt into using OAuth request url as return_to - chore: cleanup - fix(e2e): oauth2 login flow switching to recovery - feat(test): oauth2 login flow to recovery through oidc provider - fix(e2e): oidc-provider registration - chore: rename `oauth2_provider.return_to_enabled` to `oauth2_provider.override_return_to` - style: format - chore: nit config description - Sort sessions by authenticated_at ([#3324](https://github.com/ory/kratos/issues/3324)) ([46f92ff](https://github.com/ory/kratos/commit/46f92ffebf14d1cf4133ca37a2151e8c3aef9d2d)): Closes https://github.com/ory/network/issues/295 - Sqa metrics v2 ([#3300](https://github.com/ory/kratos/issues/3300)) ([98fe73f](https://github.com/ory/kratos/commit/98fe73faa75c56be47c19c61a780578ef24e7267)) - Support exporting of all credential types ([#3290](https://github.com/ory/kratos/issues/3290)) ([de6c857](https://github.com/ory/kratos/commit/de6c8574c9c6070458303f9b5caf7e8533f06b69)): It's now possible to export all credential types (including passwords) when calling the `getIdentity` SDK method. - Support OIDC flows for native apps ([#3216](https://github.com/ory/kratos/issues/3216)) ([cb10609](https://github.com/ory/kratos/commit/cb106097210ac9a146738d06c20a4306c2345923)), closes [#707](https://github.com/ory/kratos/issues/707): Implements Social Sign In and OpenID Connect for native apps. ### Tests - Run Playwright in CI ([#3259](https://github.com/ory/kratos/issues/3259)) ([342edec](https://github.com/ory/kratos/commit/342edeced4080a1b914000dfb8427196abebc596)): - run Playwright in CI - add cleanup for session token exchangers - fixup: ci - fix: compatibility between OIDC+code and other flows This improves the compatibility between OIDC+code and other flows such as TOTP, settings, password auth. - Update persistence/sql/persister_cleanup_test.go - fix: error handling with OIDC+Code - fix: increase playwright timeout ### Unclassified - @barnarddt @hperl feat: send emails via http api endpoint instead of smtp (#1030) (#3341) ([28b7b04](https://github.com/ory/kratos/commit/28b7b04a34eeba2d84de5c543f5ba8b41b38a129)), closes [#1030](https://github.com/ory/kratos/issues/1030) [#3341](https://github.com/ory/kratos/issues/3341) [#1030](https://github.com/ory/kratos/issues/1030) [#3008](https://github.com/ory/kratos/issues/3008): This change adds a new delivery method to the courier called `mailer`. Similar to SMS functionality it posts a templated Data model to a API endpoint. This API can then send emails via a CRM or any other mechanism that it wants. `Mailer` still uses the existing email data models so any new email added will automatically be sent to the API/CRM as well. ## Related issue(s) Resolves https://github.com/ory/kratos/issues/2825 # [0.13.0](https://github.com/ory/kratos/compare/v0.11.1...v0.13.0) (2023-04-18) We’re excited to announce the release of Ory Kratos v0.13.0! This update brings many enhancements and fixes, improving the user experience and overall performance. Here are the highlights: - We’ve added new social sign-in options with Patreon OIDC and LinkedIn providers, making it even easier for your users to register and log in. Furthermore, we’ve introduced a new admin API that allows you to remove specific 2nd factor credentials, giving you more control over your user accounts. - Performance has been a key focus in this release. We’ve optimized the whoami calls, parallelized the getIdentity and getSession calls, and made asynchronous webhooks fully async. These improvements will result in faster response times and a smoother experience for your users. Additionally, we’ve implemented better tracing to help you diagnose and resolve issues more effectively. - We’ve also made several updates to the webhook system. A new response.parse configuration has been introduced, allowing you to update identity data during registration. This includes admin/public metadata, identity traits, enabling/disabling identity, and modifying verified/recovery addresses. Please note that can_interrupt is now deprecated in favor of response.parse. - Lastly, we’ve made several important fixes, such as resolving the wrong message ID on resend code buttons, implementing the offline scope as Google expects, and improving the OIDC flow on duplicate account registration. We’ve also added the ability to configure whether the system should notify unknown recipients when attempting to recover an account or verify an address, enhancing security with “anti-account-enumeration measures.” We hope you enjoy these new features and improvements in Ory Kratos v0.13.0! All features are already live on the Ory Network - the simplest, fastest and most scalable way to run Ory. Please note that the v0.12.0 release was skipped due to CI issues. Head over to the changelog at [https://github.com/ory/kratos/blob/master/CHANGELOG.md](https://github.com/ory/kratos/blob/master/CHANGELOG.md) to read all the details. As always, we appreciate your feedback and support! ## Breaking Changes By default, Kratos no longer sends out these Emails. If you want to keep notifying unknown addresses (keep the current behavior), set `selfservice.flows.recovery.notify_unknown_recipients` to `true` for recovery, or `selfservice.flows.verification.notify_unknown_recipients` for verification flows. ### Bug Fixes - Access rules example ([#3178](https://github.com/ory/kratos/issues/3178)) ([a206772](https://github.com/ory/kratos/commit/a206772d78efed6febe783ee88dae92de80063d0)) - Account experience redirects to verification page ([#3195](https://github.com/ory/kratos/issues/3195)) ([2e96d75](https://github.com/ory/kratos/commit/2e96d75c2e0a1c9a884e2d3342725fb1983b495d)) - Account settings broken on OIDC removal ([#3185](https://github.com/ory/kratos/issues/3185)) ([61ae531](https://github.com/ory/kratos/commit/61ae531ba86636e1ad4d63e37df47ef76dfa5f29)), closes [ory-corp/cloud#3514](https://github.com/ory-corp/cloud/issues/3514) - Add `after_verification_return_to` to sdk and api docs ([#3097](https://github.com/ory/kratos/issues/3097)) ([c70704c](https://github.com/ory/kratos/commit/c70704cebafff7a92f32928273e4570abb3b1c3d)), closes [#3096](https://github.com/ory/kratos/issues/3096) - Add `HydraLoginRequest` on flow creation ([#3152](https://github.com/ory/kratos/issues/3152)) ([09312dd](https://github.com/ory/kratos/commit/09312dd2d7f89eadbae603e4c8891f39630a2570)), closes [#3108](https://github.com/ory/kratos/issues/3108): The oauth2_login_request field was missing when initially creating the login flow. - Add missing `code` discriminator in updateVerificationFlow ([#3213](https://github.com/ory/kratos/issues/3213)) ([21576be](https://github.com/ory/kratos/commit/21576bebc0d8c3796a4a16b1972ff42889814d61)) - Add missing index ([#3181](https://github.com/ory/kratos/issues/3181)) ([756bed4](https://github.com/ory/kratos/commit/756bed4db3789428117ec105ac0713a52d610938)) - Add mutex to test SMTP server setup/teardown ([20c2359](https://github.com/ory/kratos/commit/20c2359407044c81850759e27b03c371cb0e4886)) - Avoid unchecked casts from IdentityPool to PrivilegedIdentityPool ([71d35dd](https://github.com/ory/kratos/commit/71d35ddd582b3c7081f66e0cdc0c43457816ab25)) - Correctly apply patches to identity metadata ([#3103](https://github.com/ory/kratos/issues/3103)) ([1193a56](https://github.com/ory/kratos/commit/1193a5681fbc25d03c1e26a4296fa0b9abd2452b)), closes [#2950](https://github.com/ory/kratos/issues/2950) - Do not omit last page on identity list ([#3169](https://github.com/ory/kratos/issues/3169)) ([f95f48a](https://github.com/ory/kratos/commit/f95f48a79395b7b99c7482c0974bc5188e007cc0)) - Don't return 500 if active strategy is disabled ([#3197](https://github.com/ory/kratos/issues/3197)) ([3a734c2](https://github.com/ory/kratos/commit/3a734c2dc2bd848033dbdc7d6116b8b6db6fa760)) - Don't reuse ports in courier/SMTP tests ([#3156](https://github.com/ory/kratos/issues/3156)) ([e260fcf](https://github.com/ory/kratos/commit/e260fcf06181ce9339edc729ab74826aa4be78cf)) - Don't treat missing session as error in tracing ([290d28a](https://github.com/ory/kratos/commit/290d28ada1a55b599af7e41e638de699a474f1d8)) - Error messages in OpenAPI/Swagger / improve error messages from failed webhooks and client timeouts ([#3218](https://github.com/ory/kratos/issues/3218)) ([b1bdcd3](https://github.com/ory/kratos/commit/b1bdcd32828fcdbf65bc43b85b64df210ba4c646)) - Handle upstream errors in patreon provider ([#3032](https://github.com/ory/kratos/issues/3032)) ([39fa31f](https://github.com/ory/kratos/commit/39fa31f85deb3f015aa0f1b30b4a17e4b51d461b)) - Identity.CopyWithoutCredentials ([989c99d](https://github.com/ory/kratos/commit/989c99d6a32e02759a8a7a07606a90832afec460)) - Implement offline scope in the way google expects ([#3088](https://github.com/ory/kratos/issues/3088)) ([39043d4](https://github.com/ory/kratos/commit/39043d451e154af44123ba031381f0e3c10fbb00)) - Improve webhook resilience ([#3200](https://github.com/ory/kratos/issues/3200)) ([0a05d99](https://github.com/ory/kratos/commit/0a05d9941c6be549acfe65a78f4a8b21d6efbcdc)): - fix: improve webhook logging - chore: bump x - feat: decouple context in PostRegistrationPostPersist hook - Invalid SQL syntax in ListIdentities ([#3202](https://github.com/ory/kratos/issues/3202)) ([162ab9b](https://github.com/ory/kratos/commit/162ab9b5634329135b1b729ad401701019aca222)): PostgresQL does not support `... WHERE x IN ( )` with an empty argument list. - Issuer missing from netid claims ([#3080](https://github.com/ory/kratos/issues/3080)) ([dec7cbc](https://github.com/ory/kratos/commit/dec7cbc4286cbbe2d787b1f8998ee57054d7c95b)): The NetID provider omits the issuer claim in the userinfo response. To resolve this issue, the ID token returned by NetID is now validated and its `sub` and `iss` values are used. - Lint errors and unused code ([ae49ef0](https://github.com/ory/kratos/commit/ae49ef04ed24c23406a5639d34c2e81ab0130c75)) - Make async webhooks fully async ([#3111](https://github.com/ory/kratos/issues/3111)) ([342bfb0](https://github.com/ory/kratos/commit/342bfb0332d235a2d535493d586192815b7d4974)) - Make session AAL satisfaction check resilient against a nil identity in the session ([5ab1a56](https://github.com/ory/kratos/commit/5ab1a56cfd41e95fbb30b8f93426a27e510c62c7)): Also fix tracing. - Missing issuer regression in OIDC ([#3220](https://github.com/ory/kratos/issues/3220)) ([52f0740](https://github.com/ory/kratos/commit/52f07402edac2624cb37c72c768737a785658d29)): Closes https://github.com/ory/kratos/issues/3182 Closes https://github.com/ory/kratos/issues/3040 - Nolint comment ([93e6501](https://github.com/ory/kratos/commit/93e6501c63a253336c081f156ada58458b83ef92)) - Only return one result set for credentials_identifier ([#3107](https://github.com/ory/kratos/issues/3107)) ([59f35d1](https://github.com/ory/kratos/commit/59f35d11e61a246d1079ac02cb8958ba81b37f75)), closes [#3105](https://github.com/ory/kratos/issues/3105) - Orphaned webhook spans ([a7f9414](https://github.com/ory/kratos/commit/a7f9414460eb214a8f2b2ff96a2b6b303721f806)) - Re-use existing CSRF token in verification flows ([#3188](https://github.com/ory/kratos/issues/3188)) ([08a3447](https://github.com/ory/kratos/commit/08a344761e049c64cffafca2f94c942468201d24)): - fix: re-use existing CSRF token in verification flows - chore: fix if/else - Reduce SQL tracing noise ([1650426](https://github.com/ory/kratos/commit/1650426a2b59cd46035e5556ff8f69994602e88e)) - Remove `http.Redirect` from `show_verification_ui` hook ([#3238](https://github.com/ory/kratos/issues/3238)) ([054705b](https://github.com/ory/kratos/commit/054705b8c6c933d20b8fb45fcb2593a451cee685)) - Remove network omit flag ([#3066](https://github.com/ory/kratos/issues/3066)) ([c629b72](https://github.com/ory/kratos/commit/c629b72be42001e3e1671d61cc8348373b686844)) - Report correct errors for json schema validation ([#3085](https://github.com/ory/kratos/issues/3085)) ([9477ea4](https://github.com/ory/kratos/commit/9477ea4a7bde6efa73ed94f61c2d4ed66fd43a08)): - Implemented the translation of `jsonschema.ValidationError` to errors codes documented [here](https://www.ory.sh/docs/kratos/concepts/ui-user-interface#machine-readable-format) - Added missing error codes for relevant schema errors | Validation | Name | ID | | ------------------ | ------------------------------- | ------- | | `maxLength` | ErrorValidationMaxLength | 4000017 | | `minimum` | ErrorValidationMinimum. | 4000018 | | `exclusiveMinimum` | ErrorValidationExclusiveMinimum | 4000019 | | `maximum` | ErrorValidationMaximum | 4000020 | | `exclusiveMaximum` | ErrorValidationExclusiveMaximum | 4000021 | | `multipleOf` | ErrorValidationMultipleOf | 4000022 | | `maxItems` | ErrorValidationMaxItems | 4000023 | | `minItems` | ErrorValidationMinItems | 4000024 | | `uniqueItems` | ErrorValidationUniqueItems | 4000025 | | `type` | ErrorValidationWrongType | 4000026 | - Updated e2e tests to check these IDs explicitly - Respect the after recovery return to URL from config ([#3141](https://github.com/ory/kratos/issues/3141)) ([3467fd3](https://github.com/ory/kratos/commit/3467fd3b860dd2ad915449e3fff7e4da2d2c61ca)): Fixes https://github.com/ory-corp/cloud/issues/1405 - Set DB connection max idle time ([8d4762c](https://github.com/ory/kratos/commit/8d4762c1bffad14c94ac69575e488fc67d3f5dde)) - Set proper maxAge for session cookies ([#3209](https://github.com/ory/kratos/issues/3209)) ([1180c05](https://github.com/ory/kratos/commit/1180c051b34eb5de786d6b4e4bd94e863f60d06a)), closes [#3208](https://github.com/ory/kratos/issues/3208) - Sqa config values unified across projects ([#3237](https://github.com/ory/kratos/issues/3237)) ([523b93f](https://github.com/ory/kratos/commit/523b93fd1fe8715d06aeedc2db0ac072dfcafb71)) - Test contract names ([e9ac00b](https://github.com/ory/kratos/commit/e9ac00b3941641a955f5d8f32f25a4031c87a726)) - Use correct names in WebAuthN dialogs ([#3215](https://github.com/ory/kratos/issues/3215)) ([3bc1ff0](https://github.com/ory/kratos/commit/3bc1ff0e63c885c1db08e3d1332d959799edb0a8)) - Use type alias instead of type definition ([#3148](https://github.com/ory/kratos/issues/3148)) ([dba3803](https://github.com/ory/kratos/commit/dba38032d5939ff7286560ec19d83a89fe0410ce)) - Webhook tracing and missing defers ([#3145](https://github.com/ory/kratos/issues/3145)) ([46eb063](https://github.com/ory/kratos/commit/46eb063f414a0ad9b901407cf781002ccb97ad93)) - Wrong context in logout trace span ([#3168](https://github.com/ory/kratos/issues/3168)) ([b9ccccf](https://github.com/ory/kratos/commit/b9ccccf0f1b6a5ba903293133b2be15b528c8308)) ### Code Generation - Pin v0.13.0 release commit ([349d0ee](https://github.com/ory/kratos/commit/349d0ee1899e2ff0f81587b528c04fa0287e5546)) ### Code Refactoring - Identity persistence ([#3101](https://github.com/ory/kratos/issues/3101)) ([ceb5cc2](https://github.com/ory/kratos/commit/ceb5cc2b8a78be2f5b65d9a026c01ff0afe106af)) ### Documentation - Fix broken docs links and code example to get verification flow ([#3170](https://github.com/ory/kratos/issues/3170)) ([bdbddcc](https://github.com/ory/kratos/commit/bdbddcce2909b290e2e04dee493519b842715ab4)) - Update security email ([#3164](https://github.com/ory/kratos/issues/3164)) ([9252f5a](https://github.com/ory/kratos/commit/9252f5a3c746927a2f537efc39cb1eb0aba167a5)) ### Features - Add a new admin API to remove a specific 2nd factor credential ([#2962](https://github.com/ory/kratos/issues/2962)) ([44556a4](https://github.com/ory/kratos/commit/44556a468ef233b18fd0f16a83a4e1b2e5f05dcf)), closes [#2505](https://github.com/ory/kratos/issues/2505) - Add API to batch insert identities ([#3157](https://github.com/ory/kratos/issues/3157)) ([829bda7](https://github.com/ory/kratos/commit/829bda701acfd6706ffd72845414d177895ff8fe)), closes [ory/network#266](https://github.com/ory/network/issues/266) - Add Inspect option to driver ([8aa75e9](https://github.com/ory/kratos/commit/8aa75e97e4bfee37e7cf551173b516c6244786ff)) - Add patreon oidc provider ([#3021](https://github.com/ory/kratos/issues/3021)) ([20ea29e](https://github.com/ory/kratos/commit/20ea29e018b33231cf6b2743de74d2233f756c2a)) - Add test to verify GetIdentityConfidential expands everything ([#3217](https://github.com/ory/kratos/issues/3217)) ([f088ccd](https://github.com/ory/kratos/commit/f088ccdf462f5e6373aceb142caa181d98975a09)) - Add token prefixes to session and logout tokens ([#3132](https://github.com/ory/kratos/issues/3132)) ([8210cd0](https://github.com/ory/kratos/commit/8210cd09200d370b101072649fddd1ad9a7f32a9)): This feature adds token prefixes to Ory session and logout tokens: - `ory_st_`: Ory session token prefix - `ory_lt_`: Logout token prefix - Add upstream parameters to oidc provider ([#3138](https://github.com/ory/kratos/issues/3138)) ([b6b1679](https://github.com/ory/kratos/commit/b6b1679c3bd053cd08ff8f26c762735e380fed67)), closes [#3127](https://github.com/ory/kratos/issues/3127) [#2069](https://github.com/ory/kratos/issues/2069): This PR introduces the upstream OIDC query parameters `login_hint` and `hd`. To send additional upstream parameters the form can post this on a login, registration or settings link submit. For example the form below does an OIDC flow to Google. We can now add additional parameters such as `login_hint` and `hd` to the upstream request to Google login with a pre-filled email `email@example.com`: ```html
``` - Allow importing (salted) SHA hashing algorithms ([#2741](https://github.com/ory/kratos/issues/2741)) ([132255e](https://github.com/ory/kratos/commit/132255eff24a3f5a7fc2249a0ecf9b8716a8f1e7)), closes [#2422](https://github.com/ory/kratos/issues/2422) - Allow passing transient data from registration to webhook ([#3104](https://github.com/ory/kratos/issues/3104)) ([4a3a076](https://github.com/ory/kratos/commit/4a3a07657d2eb2a39d777565b58882cb48e928fa)) - Don't pre-generate UUIDs for transient objects ([e17f307](https://github.com/ory/kratos/commit/e17f307732f8ced34727d5f3a70929866a0595e0)) - Drop unused index ([#3165](https://github.com/ory/kratos/issues/3165)) ([852dea9](https://github.com/ory/kratos/commit/852dea90881a7c9abdbfc127a2e8d1cc0aacb166)) - Even more tracing of hidden HTTP requests ([9d8b1e2](https://github.com/ory/kratos/commit/9d8b1e223072e66d284c9e7890060678b77c1d4f)) - Identity by identifier ([#3077](https://github.com/ory/kratos/issues/3077)) ([c288d4d](https://github.com/ory/kratos/commit/c288d4d136bca1a9ed3931b4827967eb44e80ede)) - Improve tracing span naming in hooks ([bf828d3](https://github.com/ory/kratos/commit/bf828d3f5d56a963529e98958f4039f0dc569979)) - Improve webhook diagnostics ([d4eb2f6](https://github.com/ory/kratos/commit/d4eb2f6b728a211f1e1454559c2eff73f2f77936)) - Improved oidc flow on duplicate account registration ([#3151](https://github.com/ory/kratos/issues/3151)) ([4d2fda4](https://github.com/ory/kratos/commit/4d2fda453b16349589e941af06fcce312c2e5c37)): This PR improves the OIDC registration flow when a duplicate account error happens. Currently the flow looks as follows: 1. User registers with password (or other credentials) 2. User forgot they registered with password and tries to login through an OIDC provider (e.g. Google) 3. Kratos attempts a registration since the OIDC credentials do not exist 4. (optional) User needs to add missing traits (e.g. full name) which could not be retrieved from the OIDC provider 5. User gets a duplicate account error with a "Continue" button. 6. After submitting the "Continue" button the flow continues again to the OIDC provider, back to Kratos and redirects to UI with duplicate error (Steps 3 to 5) Instead of causing a confusing redirect loop we should show the user the error with a fresh login flow (since the account exists). This also gives the user the option to do a recovery flow. 1. User registers with password (or other credentials) 2. User forgot they registered with password and tries to login through an OIDC provider (e.g. Google) 3. Kratos attempts a registration since the OIDC credentials do not exist 4. (optional) User needs to add missing traits 5. User is returned to a Login flow with the duplication error - Let DB generate ID for session devices ([62402c7](https://github.com/ory/kratos/commit/62402c7bed3c57ef5b957572e4b84f56d9c530ae)) - Make notification to unknown recipients configurable ([#3075](https://github.com/ory/kratos/issues/3075)) ([1a5ead4](https://github.com/ory/kratos/commit/1a5ead43a60e7a0388617877a9f16d1dec61459b)), closes [#2345](https://github.com/ory/kratos/issues/2345) [#2585](https://github.com/ory/kratos/issues/2585): Added the ability to configure whether the system should notify unknown recipients, if some tries to recover their account or verify their address ("anti-account-enumeration measures"). - Make password validator (HIBP check) cancelable and add tracing ([28f8914](https://github.com/ory/kratos/commit/28f8914bfb8276d38e08b9be9a3ad1c59d1410bb)) - Parallelize get identity and session calls ([#3023](https://github.com/ory/kratos/issues/3023)) ([6393519](https://github.com/ory/kratos/commit/6393519977bc3d804673b5669166e07c561f1c79)) - Refactor credentials fetching ([#3183](https://github.com/ory/kratos/issues/3183)) ([590269f](https://github.com/ory/kratos/commit/590269f91e24203f987124cfbf11d31c04c1d35c)): This change revamps the way we fetch identity credentials. We no longer need most of the helper fields for gobuffalo/pop inside the `Identity` and `Credentials` structures, and we collect all the credentials in one joined query rather than using pop's `EagerPreload` functionality. - Return hydra error messages ([b3d037b](https://github.com/ory/kratos/commit/b3d037b33b248f1873f09d641e5d61376bcfde80)) - Return verification flow ID after registration flow ([#3144](https://github.com/ory/kratos/issues/3144)) ([eb854be](https://github.com/ory/kratos/commit/eb854becd9fe75213fba6ebe4283cc4ed2c9d128)), closes [#2975](https://github.com/ory/kratos/issues/2975) - Show "continue" screen after successful verification ([#3090](https://github.com/ory/kratos/issues/3090)) ([fb6b160](https://github.com/ory/kratos/commit/fb6b1600d3d75e5d11fb98445c499a6218e6b869)): The `link` strategy for verification now shows a confirmation screen with a "continue" link after successful verification, aligning its behavior to the `code` strategy. Also fixes a bug, where the `default_browser_return_url` of the verification flow was not respected when using the code strategy. Closes https://github.com/ory-corp/cloud#3925 Fixes https://github.com/ory/network#228 Fixes https://github.com/ory/network/issues/224 - Social sign in via linkedin ([#3079](https://github.com/ory/kratos/issues/3079)) ([5de6bf4](https://github.com/ory/kratos/commit/5de6bf46aba6c13f927ef1c4c425322a34063ca9)), closes [#2856](https://github.com/ory/kratos/issues/2856): Adds LinkedIn as a social sign in provider. - Webhooks that update identities ([2cbee3e](https://github.com/ory/kratos/commit/2cbee3e8eea6bac376faf9382bf5b15acb732f03)), closes [#2161](https://github.com/ory/kratos/issues/2161): Introduces a new configuration `response.parse` in webhooks. This enables updating of identity data during registration, including admin/public metadata, identity traits, enabling/disabling identity, and modifying verified/recovery addresses. Please note that `can_interrupt` is being deprecated in favor of `response.parse`. ### Tests - **e2e:** Fix compile errors in commands ([#3179](https://github.com/ory/kratos/issues/3179)) ([0002668](https://github.com/ory/kratos/commit/00026682b548b1f33e255a8ee865d90ea127a254)) - Parallelize several unit tests ([#3081](https://github.com/ory/kratos/issues/3081)) ([5403f86](https://github.com/ory/kratos/commit/5403f863d21a6fb5ba4b8572fb054d52e5a8205d)) ### Unclassified - Revert "fix: do not omit last page on identity list (#3169)" (#3184) ([73b5f13](https://github.com/ory/kratos/commit/73b5f13935ef051aae5538cf3d189bb430ea49ae)), closes [#3169](https://github.com/ory/kratos/issues/3169) [#3184](https://github.com/ory/kratos/issues/3184): This reverts commit f95f48a79395b7b99c7482c0974bc5188e007cc0. # [0.11.1](https://github.com/ory/kratos/compare/v0.11.0...v0.11.1) (2023-01-14) - Fixed several bugs to improve overall stability. - Optimized performance for faster load times and smoother operation. - Improved tracing capabilities for better debugging and issue resolution. We are constantly working to improve Ory Kratos and this release is no exception. Thank you for using Ory and please let us know if you have any feedback or encounter any issues. ## Breaking Changes The `/admin/courier/messages` endpoint now uses `keysetpagination` instead. ### Bug Fixes - Add missing indexes ([#2973](https://github.com/ory/kratos/issues/2973)) ([bbb3995](https://github.com/ory/kratos/commit/bbb399572926bd433928b22764f7b3558bb0c21d)) - Add missing indexes for identity delete ([#2952](https://github.com/ory/kratos/issues/2952)) ([dc311f9](https://github.com/ory/kratos/commit/dc311f9a9dc0dbb26e2375b3cd4232a4e8cccb61)): This significantly improves the performance of identity deletes. - Cors headers not added to the response [#2922](https://github.com/ory/kratos/issues/2922) ([#2934](https://github.com/ory/kratos/issues/2934)) ([1ed6839](https://github.com/ory/kratos/commit/1ed6839369baeecc99610d9f04d78dfee53ad72a)) - Dont reset to false ([#2965](https://github.com/ory/kratos/issues/2965)) ([ae8ad7b](https://github.com/ory/kratos/commit/ae8ad7be5b6f3dbb9142bee55448a71c7df44e52)) - Flaky test now stable ([4e5dcd0](https://github.com/ory/kratos/commit/4e5dcd0df6baffda8b15eda37fd7a247793f3297)) - Listing sessions query ([#2958](https://github.com/ory/kratos/issues/2958)) ([3e06c99](https://github.com/ory/kratos/commit/3e06c991ad557f4629ef7412c256ede2386a7bed)), closes [#2930](https://github.com/ory/kratos/issues/2930) - Missing index on courier list count ([#3002](https://github.com/ory/kratos/issues/3002)) ([3b50711](https://github.com/ory/kratos/commit/3b507110d6e0296e90d3c495515bf2a066b7c09b)) - Pin geckodriver version to bypass GitHub API quota ([#2972](https://github.com/ory/kratos/issues/2972)) ([585cb9e](https://github.com/ory/kratos/commit/585cb9e79be5de8b3d684313edb72bb703ffaa78)) - Quickstart demos ([#2940](https://github.com/ory/kratos/issues/2940)) ([a7720b2](https://github.com/ory/kratos/commit/a7720b2ba389c08c83c4f3118b83e1fc044773cc)) - Remove duplicate query in GetIdentity ([#2987](https://github.com/ory/kratos/issues/2987)) ([33b01bb](https://github.com/ory/kratos/commit/33b01bbb0e53fc8ac0127531de72ee1b680be656)) - Remove unused x-session-cookie parameter ([#2983](https://github.com/ory/kratos/issues/2983)) ([56b5c26](https://github.com/ory/kratos/commit/56b5c26e666af2442b3e99449b62b2f76a3a4677)): This patch removes the undocumented and experimental `X-Session-Cookie` header from the `/sessions/whoami` endpoint. - Resilient social sign in ([#3011](https://github.com/ory/kratos/issues/3011)) ([ca35b45](https://github.com/ory/kratos/commit/ca35b45a26c6781be81086a7677344fc165dac9f)) - Respect `return_to` URL parameter in registration flow when the user is already registered ([#2957](https://github.com/ory/kratos/issues/2957)) ([3462ce1](https://github.com/ory/kratos/commit/3462ce1512d03529b613421a69bcf4c1d5e98e08)) - Set accept header for GitLab ([#2998](https://github.com/ory/kratos/issues/2998)) ([e892113](https://github.com/ory/kratos/commit/e892113cc00a010490492def7f128bfb5c15b8de)) - Set config at the start ([e58bc6e](https://github.com/ory/kratos/commit/e58bc6e9bacd5c9c6ee9369beb843a4c54059ae2)) - Spurious cancelation of async webhooks, better tracing ([#2969](https://github.com/ory/kratos/issues/2969)) ([72de640](https://github.com/ory/kratos/commit/72de640bad75da29424222bd613a21d10e1811ec)): Previously, async webhooks (response.ignore=true) would be canceled early once the incoming Kratos request was served and it's associated context released. We now dissociate the cancellation of async hooks from the normal request processing flow. - TOTP internal context after saving settings ([#2960](https://github.com/ory/kratos/issues/2960)) ([8b647b1](https://github.com/ory/kratos/commit/8b647b1f54bb674982b982ce483fbd877e42c43a)), closes [#2680](https://github.com/ory/kratos/issues/2680) - Update pquerna/otp to fix TOTP URL encoding ([#2951](https://github.com/ory/kratos/issues/2951)) ([7248636](https://github.com/ory/kratos/commit/72486368f5403c02772e4a99ed9edc34e84c217c)): v1.4.0 fixes generating TOTP URLs. Query params now use %20 instead of + to encode spaces. + was not correctly interpreted by some Android authenticator apps, and would show up in the issuer name, e.g. "My+Issuer" instead of "My Issuer". - Update year ([d77e2cf](https://github.com/ory/kratos/commit/d77e2cf56ceab4c73e1c2fd579d43ae25a19d345)) - Webhook tracing instrumentation+memory leak ([f0044a3](https://github.com/ory/kratos/commit/f0044a365b39a5f940d6d268977744f8fcb2e49b)) ### Code Generation - Pin v0.11.1 release commit ([41595c5](https://github.com/ory/kratos/commit/41595c52cf48e2bae81b1a901577062cc6e3dc06)) ### Documentation - Improve api headline ([#2989](https://github.com/ory/kratos/issues/2989)) ([fc2787b](https://github.com/ory/kratos/commit/fc2787ba9a5cb9088a76b7ec25752d75ef399281)) ### Features - Add client IP to span events ([7ce3a74](https://github.com/ory/kratos/commit/7ce3a7471243898e111ca3e2b5d1346131c55dae)) - Add NID to logs in courier ([#2956](https://github.com/ory/kratos/issues/2956)) ([b407aa9](https://github.com/ory/kratos/commit/b407aa9427382f38dd8a992a6998202a7b6ba83a)) - Improve error message when no session is found ([#2988](https://github.com/ory/kratos/issues/2988)) ([7ad2b97](https://github.com/ory/kratos/commit/7ad2b970089cee2209b3afeaaffd7e04f803918d)) - Improve tracing ([#2992](https://github.com/ory/kratos/issues/2992)) ([04d0280](https://github.com/ory/kratos/commit/04d0280ca1338b93ac6e3026a8a2d852fbb46ef2)) - Remove duplicate queries from whoami calls ([#2995](https://github.com/ory/kratos/issues/2995)) ([b50a222](https://github.com/ory/kratos/commit/b50a22298eedef30a45979866163921604bc698a)), closes [#2402](https://github.com/ory/kratos/issues/2402): Introduces an expand API to the identity persister which greatly improves whoami performance. - Require verification on login ([#2927](https://github.com/ory/kratos/issues/2927)) ([efb8ae8](https://github.com/ory/kratos/commit/efb8ae89cbc31477c2696a0df4c89d6dbf856d27)) - Store errors of courier message ([#2914](https://github.com/ory/kratos/issues/2914)) ([fc7aa86](https://github.com/ory/kratos/commit/fc7aa86545f9e74c22738891af92abafe0030d7f)) ### Tests - Improve parallelization ([e8e8ce5](https://github.com/ory/kratos/commit/e8e8ce5eb3713f28ce1c9a05564ec7f74b48ab4d)) - Regenerate csrf if verification flow expired ([#2455](https://github.com/ory/kratos/issues/2455)) ([7025081](https://github.com/ory/kratos/commit/7025081b76171ce0a8f312a7b671aead1bb21215)) - Update integrity snapshots ([#3000](https://github.com/ory/kratos/issues/3000)) ([6d26e5c](https://github.com/ory/kratos/commit/6d26e5c735a28ecb8b2d8cd142751ef679e19e86)) # [0.11.0](https://github.com/ory/kratos/compare/v0.11.0-alpha.0.pre.2...v0.11.0) (2022-12-02) The 2022 winter release of Ory Kratos is here, and we are extremely excited to share with you some of the highlights included: - Ory Kratos now supports verification and recovery codes, which replace are now the default strategy and should be used instead of magic links. - Import of MD5-hashed passwords is now supported. - Ory Kratos can now act as the login app for the Ory Hydra Consent & Login Flow using the `oauth2_provider.url` configuration value. - Ory Kratos' SDK is now released as version 1. Learn more in the [upgrade guide](https://www.ory.sh/docs/guides/upgrade/sdk-v1). - New APIs are available to manage Ory Sessions. - Ory Sessions now contain device information. - Added all claims to the Social Sign-In data mapper as well as the option to customize admin and public metadata. - Add webhooks that can block the request, useful to do some additional validation. - Add asynchronous webhooks which do not block the request. - A CLI helper to clean up stale data. Please read the changelog carefully to identify changes which might affect you. Always test upgrading with a copy of your production system before applying the upgrade in production. ### Code Generation - Pin v0.11.0 release commit ([59c30b6](https://github.com/ory/kratos/commit/59c30b6860b56990e132416366e0ae6abe7a275f)) ### Features - Forward parsed request cookies to webhook Jsonnet snippet ([#2917](https://github.com/ory/kratos/issues/2917)) ([70ed068](https://github.com/ory/kratos/commit/70ed068debe7a711ba36e2eb4fcf60be8cae4681)): Request cookies were already available in raw form in the ctx.request_headers top-level argument to the Jsonnet snippet. Parsing cookies in Jsonnet is tedious and error-prone, though, so we parse them internally for convenience. # [0.11.0-alpha.0.pre.2](https://github.com/ory/kratos/compare/v0.10.1...v0.11.0-alpha.0.pre.2) (2022-11-28) autogen: pin v0.11.0-alpha.0.pre.2 release commit ## Breaking Changes This patch changes the behavior of the recovery flow. It introduces a new strategy for account recovery that sends out short "one-time passwords" (`code`) that a user can use to prove ownership of their account and recovery access to it. This PR also updates the default recovery strategy to `code`. This patch invalidates recovery flows initiated using the Admin API. Please re-generate any admin-generated recovery flows and tokens. This is a breaking change, as it removes the `courier.message_ttl` config key and replaces it with a counter `courier.message_retries`. Closes https://github.com/ory/kratos/issues/402 Closes https://github.com/ory/kratos/issues/1598 SDK Method `getJsonSchema` was renamed to `getIdentitySchema`. ### Bug Fixes - Active attribute based off IsActive checks ([#2901](https://github.com/ory/kratos/issues/2901)) ([bcbf68e](https://github.com/ory/kratos/commit/bcbf68e716aa62f684acbe91e8c35f6c006a4706)) - Add issuerURL for apple id ([#2565](https://github.com/ory/kratos/issues/2565)) ([2aeb0a2](https://github.com/ory/kratos/commit/2aeb0a210e6e6433f1a9d9e6a75b21b8e3083239)): No issuer url was specified when using the Apple ID provider, this forced usersers to manually enter it in the provider config. This PR adds the Apple ID issuer url to the provider simplifying the setup. - Add missing go.mod to docker build ([7c4964e](https://github.com/ory/kratos/commit/7c4964ef65769b40f1ec572a87c2c4106a800bf9)) - Add support for verified Graph API calls for facebook oidc provider ([#2547](https://github.com/ory/kratos/issues/2547)) ([1ba7c66](https://github.com/ory/kratos/commit/1ba7c66fc4897b676690f0ac701a0b68aee4f151)) - Admin recovery CSRF & duplicate form elements ([#2846](https://github.com/ory/kratos/issues/2846)) ([de80b7f](https://github.com/ory/kratos/commit/de80b7f508afdd56f5d8396f03919bd9a98e49d3)) - Bump docker image ([#2594](https://github.com/ory/kratos/issues/2594)) ([071c885](https://github.com/ory/kratos/commit/071c885d8231a1a66051002ecfcff5c8e5237085)) - Bump graceful to deal with http header timeouts ([9ce2d26](https://github.com/ory/kratos/commit/9ce2d260338f020e2da077e81464e520883f582b)) - Cache migration status ([#2631](https://github.com/ory/kratos/issues/2631)) ([9020738](https://github.com/ory/kratos/commit/902073836e4dcf6dc87776921e7988d795943718)): See https://github.com/ory-corp/cloud/issues/2691 - Check return code of ms graphapi /me request. ([#2647](https://github.com/ory/kratos/issues/2647)) ([3f490a3](https://github.com/ory/kratos/commit/3f490a31cddc53ce5d9958454f41c352580904c9)) - **cli:** Dry up code ([#2572](https://github.com/ory/kratos/issues/2572)) ([d1b6b40](https://github.com/ory/kratos/commit/d1b6b40aa9dcc7a3ec9237eec28c4fa55f0b8627)) - Codecov ([#2879](https://github.com/ory/kratos/issues/2879)) ([e446c5a](https://github.com/ory/kratos/commit/e446c5a53dbe9963e8047a3e9ca443fa6a7e64eb)) - Correct name of span on recovery code deletion ([#2823](https://github.com/ory/kratos/issues/2823)) ([44f775f](https://github.com/ory/kratos/commit/44f775f45d47eff63379d77a2339b824a6ede235)) - Correctly calculate `expired_at` timestamp for FlowExpired errors ([#2836](https://github.com/ory/kratos/issues/2836)) ([ddde43e](https://github.com/ory/kratos/commit/ddde43ec0d77a1214cd03e1f3e48ab4c34193779)) - Debugging Docker setup ([#2616](https://github.com/ory/kratos/issues/2616)) ([aaabe75](https://github.com/ory/kratos/commit/aaabe754659b96d2a5b727c4cada3ec300624434)) - Disappearing title label on verification and recovery flow ([#2613](https://github.com/ory/kratos/issues/2613)) ([29aa3b6](https://github.com/ory/kratos/commit/29aa3b6c37b3a173dcfeb02fdad4abc83774bc0b)), closes [#2591](https://github.com/ory/kratos/issues/2591) - Distinguish credential types properly when collecting identifiers ([#2873](https://github.com/ory/kratos/issues/2873)) ([705f7b1](https://github.com/ory/kratos/commit/705f7b105c98b1d68b3e35d6e6893e9cfb661548)) - Do not crash process on invalid smtp url ([#2890](https://github.com/ory/kratos/issues/2890)) ([c5d3ebc](https://github.com/ory/kratos/commit/c5d3ebc6927f7293ee05b65aee745a19ec96ce77)): Closes https://github.com/ory-corp/cloud/issues/3321 - Do not double-commit webhooks on registration ([#2888](https://github.com/ory/kratos/issues/2888)) ([88e75d9](https://github.com/ory/kratos/commit/88e75d997348450b1a2a3e4619bcbd614a5582e8)) - Do not invalidate recovery addr on update ([#2699](https://github.com/ory/kratos/issues/2699)) ([1689bb9](https://github.com/ory/kratos/commit/1689bb9f0a52387f699568da6bc773929b1201ae)) - **docker:** Add missing dependencies ([#2643](https://github.com/ory/kratos/issues/2643)) ([c589520](https://github.com/ory/kratos/commit/c589520ff865cefdb287e597b9e858851a778755)) - **docker:** Update images ([b5f80c1](https://github.com/ory/kratos/commit/b5f80c1198e4bb9ed392521daca934548eb21ee6)) - Duplicate messages in recovery flow ([#2592](https://github.com/ory/kratos/issues/2592)) ([43fcc51](https://github.com/ory/kratos/commit/43fcc51b9bf6996fc4f7b0ef797189eb8f3978dc)) - Express e2e tests for new account experience ([#2708](https://github.com/ory/kratos/issues/2708)) ([84ea0cf](https://github.com/ory/kratos/commit/84ea0cf4c72b14f246835d435d22a31f96d9e644)) - Format ([0934def](https://github.com/ory/kratos/commit/0934defff7a0d56e712af98c1cec87c60b3c934b)) - Format check stage in the CI ([#2737](https://github.com/ory/kratos/issues/2737)) ([bbe4463](https://github.com/ory/kratos/commit/bbe44632de77cfb3d4983b68647107d914cd4c46)) - Gosec false positives ([e3e7ed0](https://github.com/ory/kratos/commit/e3e7ed08f5ce47fc794bd5c093018cee51baf689)) - Identity sessions list response includes pagination headers ([#2763](https://github.com/ory/kratos/issues/2763)) ([0c2efa2](https://github.com/ory/kratos/commit/0c2efa2d4345c035649208a71332a64c225313c3)), closes [#2762](https://github.com/ory/kratos/issues/2762) - **identity:** Migrate identity_addresses to lower case ([#2517](https://github.com/ory/kratos/issues/2517)) ([c058e23](https://github.com/ory/kratos/commit/c058e23599d994e12b676e87f7282c1f2b2e089c)), closes [#2426](https://github.com/ory/kratos/issues/2426) - Ignore commata in HIBP response ([0856bd7](https://github.com/ory/kratos/commit/0856bd719b7e06a6d2163bf428ff6513d86376db)) - Ignore CSRF for session extension on public route ([866b472](https://github.com/ory/kratos/commit/866b472750fba7bf498d359796f24867af7270ad)) - Ignore error explicitly ([772d596](https://github.com/ory/kratos/commit/772d5968d5a0cb7ac9415cfb2b1e9e86ae3a3131)) - Improve migration status speed ([#2637](https://github.com/ory/kratos/issues/2637)) ([a2e3c41](https://github.com/ory/kratos/commit/a2e3c41f9e513e1de47f6320f6a10acd1fed5eea)) - Include flow id in use recovery token query ([#2679](https://github.com/ory/kratos/issues/2679)) ([d56586b](https://github.com/ory/kratos/commit/d56586b028d79387886f880c1455edb5e4df2209)): This PR adds the `selfservice_recovery_flow_id` to the query used when "using" a token in the recovery flow. This PR also adds a new enum field for `identity_recovery_tokens` to distinguish the two flows: admin versus self-service recovery. - Include metadata_admin in admin identity list response ([#2791](https://github.com/ory/kratos/issues/2791)) ([aa698e0](https://github.com/ory/kratos/commit/aa698e03a3a96abf1563aea24273735bd9cc412d)), closes [#2711](https://github.com/ory/kratos/issues/2711) - Incorrect swagger annotation for `getSession` ([#2891](https://github.com/ory/kratos/issues/2891)) ([797ea68](https://github.com/ory/kratos/commit/797ea6857e29e5477e0769af5dd51dd7e43080b2)) - **lint:** Fixed lint error causing ci failures ([4aab5e0](https://github.com/ory/kratos/commit/4aab5e0114dd02b8b0ce45376a0fe4bf11e38221)) - Make `courier.TemplateType` an enum ([#2875](https://github.com/ory/kratos/issues/2875)) ([65aeb0a](https://github.com/ory/kratos/commit/65aeb0a7fd90bfbc81f68b77141f8271aef011fe)) - Make hydra consistently localhost ([70211a1](https://github.com/ory/kratos/commit/70211a17a452d5ced8317822afda3f8e6185cc71)) - Make ID field in VerifiableAddress struct optional ([#2507](https://github.com/ory/kratos/issues/2507)) ([0844b47](https://github.com/ory/kratos/commit/0844b47c30851c548d46273927afee103cdc0e97)), closes [#2506](https://github.com/ory/kratos/issues/2506) - Make servicelocator explicit ([4f841da](https://github.com/ory/kratos/commit/4f841dae5423acf3514d50add9e99d28bc339fbb)) - Make swagger/openapi go 1.19 compatible ([fec6772](https://github.com/ory/kratos/commit/fec6772739129e0d5bb4103c717b1ac60df45aa8)) - Mark gosec false positives ([13eaddb](https://github.com/ory/kratos/commit/13eaddb7babe630750361c6d8f3ffc736898ddec)) - Metadata should not be required ([05afd68](https://github.com/ory/kratos/commit/05afd68381abe58c5e7cdd51cbf0ae409f5f0eb0)) - Migration error detection ([a115486](https://github.com/ory/kratos/commit/a11548603a4c9b46ba238d2a7ee58fffb7f6d857)) - Missing usage to recovery_code_invalid template ([#2798](https://github.com/ory/kratos/issues/2798)) ([5ac7553](https://github.com/ory/kratos/commit/5ac7553d191885957215b5a63f3bbdc2d020f3fe)) - Not cleared field validation message ([#2800](https://github.com/ory/kratos/issues/2800)) ([cdaf68d](https://github.com/ory/kratos/commit/cdaf68db8e6dd7bacfdb5fc6ff28e5d960f75c2c)) - Panic ([1182278](https://github.com/ory/kratos/commit/11822789c1561b27c2d769c9ea53a81835702f4a)) - Patch invalidates credentials ([#2721](https://github.com/ory/kratos/issues/2721)) ([c4d95af](https://github.com/ory/kratos/commit/c4d95afac590136acd14efa093f48c301fd07164)), closes [ory/cloud#148](https://github.com/ory/cloud/issues/148) - Potentially resolve tx issue in crdb ([#2595](https://github.com/ory/kratos/issues/2595)) ([9d22035](https://github.com/ory/kratos/commit/9d22035695b6a793ac4bc5e2bd0a68b3aeea039c)) - Preserve return_to param between flows ([#2644](https://github.com/ory/kratos/issues/2644)) ([f002649](https://github.com/ory/kratos/commit/f002649d45658a1486fac551d8ca6b37b3d03026)) - Proper annotation for patch ([#2784](https://github.com/ory/kratos/issues/2784)) ([0cbfe41](https://github.com/ory/kratos/commit/0cbfe410c50cfe551693683881b4145d115c1aa3)) - Re-add service to quickstart ([8c52c33](https://github.com/ory/kratos/commit/8c52c33cf277eda82c9b00b77cd9e03f1e5b4602)) - Re-issue outdated cookie in /whoami ([#2598](https://github.com/ory/kratos/issues/2598)) ([bf6f27e](https://github.com/ory/kratos/commit/bf6f27e37b8aa342ae002e0a9f227a31e0f7c279)), closes [#2562](https://github.com/ory/kratos/issues/2562) - Remove jackc rewrites ([#2634](https://github.com/ory/kratos/issues/2634)) ([fe00c5b](https://github.com/ory/kratos/commit/fe00c5be72b0cdcc8d462a97aa04c413f758e8e3)) - Remove jsonnet import support ([d708c81](https://github.com/ory/kratos/commit/d708c81abbec424e4376a68140e5008bdba4eaaf)) - Remove newline sign from email subject ([#2576](https://github.com/ory/kratos/issues/2576)) ([ca3d9c2](https://github.com/ory/kratos/commit/ca3d9c24e25ce501e9eae23547f87e1c35b2ea97)) - Remove rust workaround ([355ec43](https://github.com/ory/kratos/commit/355ec431a304eef236a088571e2414f96c49d862)) - Replace io/util usage by io and os package ([e2d805b](https://github.com/ory/kratos/commit/e2d805b7e336d202f7cf3c2e0ce586d78ac03cc0)) - Resolve bug where 500s in web hooks are not properly retried ([e572e81](https://github.com/ory/kratos/commit/e572e8185e17839addabf2a72f4e9921bda8b47a)) - Respect more http sources for computing request URL ([66a9448](https://github.com/ory/kratos/commit/66a94488eb2fc778a00a5c69916e7958b3535440)) - Return browser to 'return_to' when logging in without registered account using oidc. ([#2496](https://github.com/ory/kratos/issues/2496)) ([a4194f5](https://github.com/ory/kratos/commit/a4194f58dd4ccecca6698d5b43284d857a70a221)), closes [#2444](https://github.com/ory/kratos/issues/2444) - Return empty array not null when there are no sessions ([#2548](https://github.com/ory/kratos/issues/2548)) ([fffba47](https://github.com/ory/kratos/commit/fffba473440fec3118a3951b697d5a0d2d4e30d6)) - Revert Go 1.19 formatting changes ([7fb085b](https://github.com/ory/kratos/commit/7fb085b6ca4fbfe2978998bea868959966ae193d)) - Revert removal of required field in uiNodeInputAttributes ([#2623](https://github.com/ory/kratos/issues/2623)) ([fee154b](https://github.com/ory/kratos/commit/fee154b28dfb3007f8d20a807cfd6d362c3bd9e7)) - **sdk:** Identity metadata is nullable ([#2841](https://github.com/ory/kratos/issues/2841)) ([4c70578](https://github.com/ory/kratos/commit/4c7057823b5292cb38f43bd5a96041aed178ad0a)): Closes https://github.com/ory/sdk/issues/218 - **sdk:** Make InputAttributes.Type an enum ([ff6190f](https://github.com/ory/kratos/commit/ff6190f31f538cf8ed735dfd1bb3b7afcd944c36)) - **sdk:** Rust compile issue with required enum ([#2619](https://github.com/ory/kratos/issues/2619)) ([8800085](https://github.com/ory/kratos/commit/8800085d5bde32367217170d00f7141b7ea46733)) - Send out correct verification invalid email in code strategy ([#2908](https://github.com/ory/kratos/issues/2908)) ([d2bb67a](https://github.com/ory/kratos/commit/d2bb67af64d031613f2516b4848208d4f709e7b4)) - Set cache default to false ([#2906](https://github.com/ory/kratos/issues/2906)) ([e407f92](https://github.com/ory/kratos/commit/e407f92572b7823f70df17d463400807f14c8ae8)) - Take over return_to param from unauthorized settings to login flow ([#2787](https://github.com/ory/kratos/issues/2787)) ([504fb36](https://github.com/ory/kratos/commit/504fb36b6e72900808666dde778906a069f3c48b)) - Unable to find JSON Schema ID: default ([#2393](https://github.com/ory/kratos/issues/2393)) ([f43396b](https://github.com/ory/kratos/commit/f43396bdc03f89812f026c2a94b0b50100134c23)) - Use correct download location for golangci-lint ([c36ca53](https://github.com/ory/kratos/commit/c36ca53d4552596e62ec323795c3bf21438d4f26)) - Use errors instead of fatal for serve cmd ([02f7e9c](https://github.com/ory/kratos/commit/02f7e9cfd17ab60c3f38aab3ae977c427b26990d)) - Use full URL for webhook payload ([72595ad](https://github.com/ory/kratos/commit/72595adcb68a1a2d350c4687328653e28d888847)) - Use process-isolated Jsonnet VM ([#2869](https://github.com/ory/kratos/issues/2869)) ([9eeedc0](https://github.com/ory/kratos/commit/9eeedc06408c447077b630fff65e9ca4ed1ec59a)) - Verification redirect & continue label ([#2905](https://github.com/ory/kratos/issues/2905)) ([e1119e8](https://github.com/ory/kratos/commit/e1119e8f2e0372152d7d8367e7843fd5a49bf728)): This PR resolves an issue with the redirect after a successful verification, if not specified. - Wrap migration error in WithStack ([#2636](https://github.com/ory/kratos/issues/2636)) ([4ce9f1e](https://github.com/ory/kratos/commit/4ce9f1ebb39cccfd36c4f0fb4a2ae2a17fbc18cc)) - Wrong config key in admin recovery documentation ([#2815](https://github.com/ory/kratos/issues/2815)) ([154b61b](https://github.com/ory/kratos/commit/154b61b9ff50306c540eb0904ae012195e735da4)) - X-forwarded-for header parsing ([#2807](https://github.com/ory/kratos/issues/2807)) ([4682afa](https://github.com/ory/kratos/commit/4682afaca3655dc809582b775a5a1c56205a4b4a)) ### Code Generation - Pin v0.11.0-alpha.0.pre.2 release commit ([624e1f0](https://github.com/ory/kratos/commit/624e1f0d23b1c58bc28b2eaf845d4ef63e64bdba)) ### Code Refactoring - Hot reloading ([b0d8f38](https://github.com/ory/kratos/commit/b0d8f3853886228a64e82437643a82b3970d6ff7)) - Make embedding easier with internal sdk ([e9aa21f](https://github.com/ory/kratos/commit/e9aa21f02b4bb7b09e268197334beb9c5772d13d)) - SDK v1 naming ([11f9d30](https://github.com/ory/kratos/commit/11f9d30a5d245b4dfc922a766853eaac2a20a8f5)): Find the full [upgrade guide in our documentation](https://www.ory.sh/docs/guides/upgrade/sdk). - **sdk:** Rename `getJsonSchema` to `getIdentitySchema` ([#2606](https://github.com/ory/kratos/issues/2606)) ([8dc2ecf](https://github.com/ory/kratos/commit/8dc2ecf4919c9a14ef0bd089677de66ab3cfed92)) - Use gotemplates for command usage ([baa84c6](https://github.com/ory/kratos/commit/baa84c681b0c7fa29d653bd7226e792a5f44cb4c)) - Use gotemplates for command usage ([#2770](https://github.com/ory/kratos/issues/2770)) ([1d22b23](https://github.com/ory/kratos/commit/1d22b235291ce7102dd186a53a431b55780973d3)) ### Documentation - Cleanup v0alpha2 endpoint summaries ([db9a95b](https://github.com/ory/kratos/commit/db9a95b6d28f7db3416c9d1530be4fd63a17ac6b)) - Cypress on arm based mac ([#2795](https://github.com/ory/kratos/issues/2795)) ([d8514b5](https://github.com/ory/kratos/commit/d8514b50b5df9c098c77c5cb817602657b2a02ea)) - Enable 2FA methods in docker-compose quickstart setup ([#2828](https://github.com/ory/kratos/issues/2828)) ([8f52e8b](https://github.com/ory/kratos/commit/8f52e8b728bf8e2a99807f4d4899c2eaaca9e7e5)) - Fix badge ([dbb7506](https://github.com/ory/kratos/commit/dbb7506ec1a5a2b5bef21cb7838b6c86e755f0f9)) - Importing credentials supported ([4e8b5cf](https://github.com/ory/kratos/commit/4e8b5cf775c1bfe4c2eb5588bfebe900d1c390eb)) - **sdk:** Identifier is actually required ([#2593](https://github.com/ory/kratos/issues/2593)) ([f89d279](https://github.com/ory/kratos/commit/f89d2794d8a2122e3f86eeb8aa5d554da32e753e)) - **sdk:** Incorrect URL ([#2521](https://github.com/ory/kratos/issues/2521)) ([ac6c4cc](https://github.com/ory/kratos/commit/ac6c4ccfc1901d38855ecd9991ef8de80e9d7c40)) - Update README ([5da4c6b](https://github.com/ory/kratos/commit/5da4c6b934b1b820d4a6ca67621855e87ecef773)) - Update readme badges ([7136e94](https://github.com/ory/kratos/commit/7136e94028dc64877e887776a1ccafb8826ce23c)) - Write messages as single json document ([#2519](https://github.com/ory/kratos/issues/2519)) ([3d8cf38](https://github.com/ory/kratos/commit/3d8cf38ef05c6ca5edf1161846c63bd3a23d9adc)), closes [#2498](https://github.com/ory/kratos/issues/2498) ### Features - Add "success" UITextType ([#2900](https://github.com/ory/kratos/issues/2900)) ([2ff34b6](https://github.com/ory/kratos/commit/2ff34b604757c46aae5cf3cbb23f39f982341486)) - Add admin get api for session ([#2855](https://github.com/ory/kratos/issues/2855)) ([1aa1321](https://github.com/ory/kratos/commit/1aa13211d1459e7453c2ba8fec69fee1c79aecbc)) - Add api endpoint to fetch messages ([#2651](https://github.com/ory/kratos/issues/2651)) ([5fddcbf](https://github.com/ory/kratos/commit/5fddcbf6554264766301e63ed3889ba746f0cd1a)): Closes https://github.com/ory/kratos/issues/2639 - Add autocomplete attributes ([#2523](https://github.com/ory/kratos/issues/2523)) ([6284a9a](https://github.com/ory/kratos/commit/6284a9a5152924018d85f306e5758e9d8d759283)), closes [#2396](https://github.com/ory/kratos/issues/2396) - Add cache headers ([#2817](https://github.com/ory/kratos/issues/2817)) ([71e2449](https://github.com/ory/kratos/commit/71e2449d7038594e107f39934e4716f845be7bb7)) - Add codecov yaml ([90da0bb](https://github.com/ory/kratos/commit/90da0bb4aeb50ed697c998342300cc56de5d5e1c)) - Add DingTalk social login ([#2494](https://github.com/ory/kratos/issues/2494)) ([7b966bd](https://github.com/ory/kratos/commit/7b966bd16333f419b2a57f2a0b8684d6d86b34e6)) - Add flow id check to use verification token ([#2695](https://github.com/ory/kratos/issues/2695)) ([54c64fc](https://github.com/ory/kratos/commit/54c64fcea40ede17a87253042259fd97eeb780fe)) - Add handler with openapi def for admin revoke session ([#2867](https://github.com/ory/kratos/issues/2867)) ([2438ca0](https://github.com/ory/kratos/commit/2438ca0c9aed997870dcf60d41dad783838dd840)) - Add identity id to "account disabled" error ([#2557](https://github.com/ory/kratos/issues/2557)) ([f09b1b3](https://github.com/ory/kratos/commit/f09b1b3701c6deda4d25cebb7ccf2e97089be32a)) - Add missing config entry ([8fe9de6](https://github.com/ory/kratos/commit/8fe9de6d60a381611e07226614241a83b0010126)) - Add missing cookie headers to SDK methods ([#2720](https://github.com/ory/kratos/issues/2720)) ([32e32d1](https://github.com/ory/kratos/commit/32e32d1b98404ac14a44b2f0ccefa8c02d38c5f7)): See https://github.com/ory/kratos/discussions/2583 - Add OpenTelemetry span events ([#2858](https://github.com/ory/kratos/issues/2858)) ([37b1a3b](https://github.com/ory/kratos/commit/37b1a3bb0cf2ea859d672674ca0e95893e63301b)) - Add PATCH to adminUpdateIdentity ([#2380](https://github.com/ory/kratos/issues/2380)) ([#2471](https://github.com/ory/kratos/issues/2471)) ([94a3741](https://github.com/ory/kratos/commit/94a37416011086582e309f62dc2c45ca84083a33)) - Add pre-hooks to settings, verification, recovery ([c0ceaf3](https://github.com/ory/kratos/commit/c0ceaf31f9327cca903c19b77597cae4587737e6)) - Add session cache header feature flag ([#2899](https://github.com/ory/kratos/issues/2899)) ([02a92b4](https://github.com/ory/kratos/commit/02a92b4d8ab5ced5d0d9387b38491990fa7cb724)), closes [ory-corp/cloud#3283](https://github.com/ory-corp/cloud/issues/3283) - Add support for firebase scrypt hashes on identity import and login hash upgrade ([#2734](https://github.com/ory/kratos/issues/2734)) ([3852eb4](https://github.com/ory/kratos/commit/3852eb460251a079bad68d08bee2aef23516d168)), closes [#2422](https://github.com/ory/kratos/issues/2422) - Add verification via `code` ([#2838](https://github.com/ory/kratos/issues/2838)) ([a82ee92](https://github.com/ory/kratos/commit/a82ee9295681b8dde96c3c6fb156e791df68613c)), closes [#2824](https://github.com/ory/kratos/issues/2824): The new `code` strategy is now supported as a verification strategy. If enabled, the strategy sends a code, instead of a magic link to the user's address, which they can use to verify their address. - Adding admin session listing api ([#2818](https://github.com/ory/kratos/issues/2818)) ([59588d2](https://github.com/ory/kratos/commit/59588d2e290a8b72125021fa899661622e4cd946)) - Adding device information to the session ([#2715](https://github.com/ory/kratos/issues/2715)) ([82bc9ce](https://github.com/ory/kratos/commit/82bc9ce00d44085287e6d8d9e3fb67e107be2503)): Closes https://github.com/ory/kratos/issues/2091 See https://github.com/ory-corp/cloud/issues/3011 Co-authored-by: Patrik - Allow importing scrypt hashing algorithm ([#2689](https://github.com/ory/kratos/issues/2689)) ([3e3b59e](https://github.com/ory/kratos/commit/3e3b59e53de8cb89e9fd01cfec75a0f8a601035b)), closes [#2422](https://github.com/ory/kratos/issues/2422): It is now possible to import scrypt-hashed passwords. - Allow setting public and admin metadata with the jsonnet data mapper ([#2569](https://github.com/ory/kratos/issues/2569)) ([aa6eb13](https://github.com/ory/kratos/commit/aa6eb13c1c42c11354074553fac9c90ee0a8999e)), closes [#2552](https://github.com/ory/kratos/issues/2552) - Automatic TLS certificate reloading ([#2744](https://github.com/ory/kratos/issues/2744)) ([09751e6](https://github.com/ory/kratos/commit/09751e6a03783701af60ce606633694ef67deacc)) - Change code length to 6 numbers ([#2894](https://github.com/ory/kratos/issues/2894)) ([56feb07](https://github.com/ory/kratos/commit/56feb079c3b99856c03cd8beb950673c10310520)) - **cli:** Helper for cleaning up stale records ([#2406](https://github.com/ory/kratos/issues/2406)) ([29d6376](https://github.com/ory/kratos/commit/29d6376e22e4de617ec63ca0a5dcb4dbf34c7c37)), closes [#952](https://github.com/ory/kratos/issues/952) - Handler for update API with credentials ([#2423](https://github.com/ory/kratos/issues/2423)) ([561187d](https://github.com/ory/kratos/commit/561187dafe2fea324d55c4efe3ffa6b65f9bed72)), closes [#2334](https://github.com/ory/kratos/issues/2334) - Immutable cookie session values ([#2761](https://github.com/ory/kratos/issues/2761)) ([a6f2793](https://github.com/ory/kratos/commit/a6f27935ce17a7ff5b3deaa4973d72a7d83454fb)), closes [#2701](https://github.com/ory/kratos/issues/2701) - Implement blocking webhooks ([#1585](https://github.com/ory/kratos/issues/1585)) ([e48e9fa](https://github.com/ory/kratos/commit/e48e9fac7ab6a982e0e941bfea1d15569eb53582)), closes [#1724](https://github.com/ory/kratos/issues/1724) [#1483](https://github.com/ory/kratos/issues/1483) - Improve cache handling ([6e8579b](https://github.com/ory/kratos/commit/6e8579b835d54d5ebb5371297ea60f24e915882d)) - Improve state generation logic ([546ee3d](https://github.com/ory/kratos/commit/546ee3dc900874bc0614923b10697388c4e7676b)) - Ingest hydra bugfix ([3c11216](https://github.com/ory/kratos/commit/3c112165e553161696cf746befb9e03c2e6e07fb)) - OAuth2 integration ([#2804](https://github.com/ory/kratos/issues/2804)) ([7c6eb2a](https://github.com/ory/kratos/commit/7c6eb2a5128c6bc76ac7306edafaa54c4893ea82)): This feature allows Ory Kratos to act as a login provider for Ory Hydra using the `oauth2_provider.url` configuration value. Closes https://github.com/ory/kratos/issues/273 Closes https://github.com/ory/kratos/discussions/2293 See https://github.com/ory/kratos-selfservice-ui-node/pull/50 See https://github.com/ory/kratos-selfservice-ui-node/pull/68 See https://github.com/ory/kratos-selfservice-ui-node/pull/108 See https://github.com/ory/kratos-selfservice-ui-node/pull/111 See https://github.com/ory/kratos-selfservice-ui-node/pull/149 See https://github.com/ory/kratos-selfservice-ui-node/pull/170 See https://github.com/ory/kratos-selfservice-ui-node/pull/198 See https://github.com/ory/kratos-selfservice-ui-node/pull/207 - Parse all id token claims into raw_claims ([#2765](https://github.com/ory/kratos/issues/2765)) ([1da0cf6](https://github.com/ory/kratos/commit/1da0cf62b3f0ed8a81bca22123474baa7cf6de65)), closes [#2528](https://github.com/ory/kratos/issues/2528): All ID Token claims resulting from the Social Sign In flow are now available in `raw_claims` and can be used in the Social Sign In JsonNet Mapper. - Replace magic links with one time codes in recovery flow ([#2645](https://github.com/ory/kratos/issues/2645)) ([a1532ba](https://github.com/ory/kratos/commit/a1532ba79722ccfc9c8608ef6f51a6d9ecb24a8e)), closes [#1451](https://github.com/ory/kratos/issues/1451): This feature introduces a new `code` strategy to recover an account. Currently, if a user needs to initiate a recovery flow to recover a lost password/MFA/etc., they’ll receive an email containing a “magic link”. This link contains a flow_id and a recovery_token. This is problematic because some antivirus software opens links in emails to check for malicious content, etc. Instead of the magic link, we send an 8-digit code that is clearly displayed in the email or SMS. A user can now copy/paste or type it manually into the text-field that is shown after the user clicks “submit” on the initiate flow page. - Replace message_ttl with static max retry count ([#2638](https://github.com/ory/kratos/issues/2638)) ([b341756](https://github.com/ory/kratos/commit/b341756130ee808ddcc003163884f09e3f006d0a)): This PR replaces the `courier.message_ttl` configuration option with a `courier.message_retries` option to limit how often the sending of a message is retried before it is marked as `abandoned`. - Standardize license headers ([#2790](https://github.com/ory/kratos/issues/2790)) ([8406eaf](https://github.com/ory/kratos/commit/8406eaf92006d9812108bd3ae57245f01e627bfc)) - Support ip exceptions ([de46c08](https://github.com/ory/kratos/commit/de46c08534dfae6165f6a570cc59829f367c0b57)) - Support md5 hash import ([#2725](https://github.com/ory/kratos/issues/2725)) ([d1b4e17](https://github.com/ory/kratos/commit/d1b4e1748f66c0dc8033235f1a9c155aac0d5caa)) - Trace WebHooks ([#2911](https://github.com/ory/kratos/issues/2911)) ([665605b](https://github.com/ory/kratos/commit/665605bbc4f6ca838f0180680cdd68905f07d482)): Previously the context was not propagated to the http client. As a result the (instrumented) client did not find the existing span and the sapns for outgoing http request have been orphains. With this simple Fix they are now children of the corresponding webhook spans. - Update for the Ory Network ([#2814](https://github.com/ory/kratos/issues/2814)) ([3e09e58](https://github.com/ory/kratos/commit/3e09e58a695cf5d9d57b9f773e0f50b1fd794915)) - Upgrade hydra to v2 ([fdb108f](https://github.com/ory/kratos/commit/fdb108fe2542569202bfb39ef55e1a7e8c5b5ebf)) ### Reverts - Revert "autogen(openapi): regenerate swagger spec and internal client" ([24eddfb](https://github.com/ory/kratos/commit/24eddfb2adc67e22d34efdc6b6a6723c7be64237)): This reverts commit 4159b93ae3f8175cf7ccf77d34e4a7a2d0181d4f. ### Tests - **e2e:** Add typescript ([37018c0](https://github.com/ory/kratos/commit/37018c0161d0affe88c9f2574d043f337579e4a9)) - **e2e:** Fix flaky assertions ([21a8487](https://github.com/ory/kratos/commit/21a8487f984168abbc7279c590c66822414c718e)) - **e2e:** Fix issuer config ([32454d2](https://github.com/ory/kratos/commit/32454d2fbd169a7839fc3d02786376ef4c7c986d)) - **e2e:** Fix webauthn regression ([26001e7](https://github.com/ory/kratos/commit/26001e7544b60ad0004153773a21c1d04abf9987)) - **e2e:** Improve webauthn test reliability ([4d323d0](https://github.com/ory/kratos/commit/4d323d01b53b9f7b0dc346211ac4fda0626d357a)) - **e2e:** Migrate to cypress 10.x ([317fab0](https://github.com/ory/kratos/commit/317fab0fe76a2762a77b3d2f8a75735598cb1c0e)) - **e2e:** Resolve flaky hydra configuration ([d8c82da](https://github.com/ory/kratos/commit/d8c82dabad4f04874647c48ecbf0eda91c7c90fa)) - **e2e:** Resolve max-age and issuer regression ([0ee4cf0](https://github.com/ory/kratos/commit/0ee4cf058cbda2bef52b3fa830f3db411f442197)) - **e2e:** Resolve max-age regression ([904f75d](https://github.com/ory/kratos/commit/904f75d254e9513aa3edad4fa3f9ead4d80e46df)) - **e2e:** Use correct dir ([907dbe3](https://github.com/ory/kratos/commit/907dbe3f605d5be5038ddc06029082b2df0914e2)) - Fix broken assertions ([e5f1311](https://github.com/ory/kratos/commit/e5f131138243ad5806c7927dd5a642d029cfad6c)) - Fix oidc test regression ([6c14b68](https://github.com/ory/kratos/commit/6c14b682d0984175495051308985281d72c0988e)) - Improve e2e tooling ([390ccaa](https://github.com/ory/kratos/commit/390ccaac18023979ff36bc7ee2df6c0d4a90d8c8)) - Parallelize and speed up config tests ([#2611](https://github.com/ory/kratos/issues/2611)) ([d8dea01](https://github.com/ory/kratos/commit/d8dea0138b09d4dff3c30aa14e0e99e423b355fe)) - Resolve builder regression ([934c30d](https://github.com/ory/kratos/commit/934c30d6064d1e7dfc59f4eef43d096e977c113e)) - Try and recover from allocated port error ([3b5ac5f](https://github.com/ory/kratos/commit/3b5ac5ff03b653191c1979fe1e4e9a4ea3ed7d36)) - Update snapshots ([#2877](https://github.com/ory/kratos/issues/2877)) ([cbaaceb](https://github.com/ory/kratos/commit/cbaaceb9ef73a91e1b4ce5e4f7b9d7bac04d4c03)) ### Unclassified - Revert "refactor: use gotemplates for command usage (#2770)" (#2778) ([d612612](https://github.com/ory/kratos/commit/d612612313dc26f1ddaaa84dbca65139b967d52c)), closes [#2770](https://github.com/ory/kratos/issues/2770) [#2778](https://github.com/ory/kratos/issues/2778): This reverts commit 1d22b235291ce7102dd186a53a431b55780973d3. - Remove empty script (#2739) ([1515b83](https://github.com/ory/kratos/commit/1515b839f52044d6c9674d4a2df43dfeda3bb15b)), closes [#2739](https://github.com/ory/kratos/issues/2739) # [0.10.1](https://github.com/ory/kratos/compare/v0.10.0...v0.10.1) (2022-06-01) Re-release the SDK. ### Bug Fixes - Bump ory cli ([12ceae0](https://github.com/ory/kratos/commit/12ceae005749c5dd01959720925418d643f13070)) ### Code Generation - Pin v0.10.1 release commit ([ab16580](https://github.com/ory/kratos/commit/ab16580b4326250885b920198b280456eb873a6b)) # [0.10.0](https://github.com/ory/kratos/compare/v0.9.0-alpha.3...v0.10.0) (2022-05-30) We achieved a major milestone - Ory Kratos is out of alpha! Ory Kratos had no major changes in the APIs for the last months and feel confident that no large breaking changes will need to be introduced in the near future. This release focuses on quality-of-live improvements, resolves several bugs, irons out developer experience issues, and introduces session renew capabilities! ## Breaking Changes Please be aware that the SDK method signatures for `submitSelfServiceRecoveryFlow`, `submitSelfServiceRegistrationFlow`, `submitSelfServiceLoginFlow`, `submitSelfServiceSettingsFlow`, `submitSelfServiceVerificationFlow` might have changed in your SDK. This patch moves several CLI command to comply with the Ory CLI command structure: ```patch - ory identities get ... + ory get identity ... - ory identities delete ... + ory delete identity ... - ory identities import ... + ory import identity ... - ory identities list ... + ory list identities ... - ory identities validate ... + ory validate identity ... - ory jsonnet format ... + ory format jsonnet ... - ory jsonnet lint ... + ory lint jsonnet ... ``` This patch moves several CLI command to comply with the Ory CLI command structure: ```patch - ory identities get ... + ory get identity ... - ory identities delete ... + ory delete identity ... - ory identities import ... + ory import identity ... - ory identities list ... + ory list identities ... - ory identities validate ... + ory validate identity ... - ory jsonnet format ... + ory format jsonnet ... - ory jsonnet lint ... + ory lint jsonnet ... ``` ### Bug Fixes - Add flow id when return_to is passed to the verification ([#2482](https://github.com/ory/kratos/issues/2482)) ([c2b1c23](https://github.com/ory/kratos/commit/c2b1c2303cd0587b9419d500f2e3d5f9c9c80ad4)) - Add indices for slow queries ([e0cdbc9](https://github.com/ory/kratos/commit/e0cdbc9ab3389de0f65b37758d86bea56d294d64)) - Add legacy session value ([ecfd052](https://github.com/ory/kratos/commit/ecfd05216f5ebb70f1617595d2d398cf1fa3c660)), closes [#2398](https://github.com/ory/kratos/issues/2398) - **auth0:** Created_at workaround ([#2492](https://github.com/ory/kratos/issues/2492)) ([52a965d](https://github.com/ory/kratos/commit/52a965dc7e4ac868d21261cb44576846426bffa5)), closes [#2485](https://github.com/ory/kratos/issues/2485) - Avoid excessive memory allocations in HIBP cache ([#2389](https://github.com/ory/kratos/issues/2389)) ([ee2d410](https://github.com/ory/kratos/commit/ee2d41057a7e6cb2c57c6304c2e7bbf5ad7c56da)), closes [#2354](https://github.com/ory/kratos/issues/2354) - Change SQLite database mode to 0600 ([#2344](https://github.com/ory/kratos/issues/2344)) ([0e5d3b7](https://github.com/ory/kratos/commit/0e5d3b7726a8923fbc2a4c10ec18f0ba97ffbcff)): The default mode is 0644, which is allows broader access than necessary. - Compile issues from merge conflict ([#2419](https://github.com/ory/kratos/issues/2419)) ([85a90c8](https://github.com/ory/kratos/commit/85a90c892d785b834cbdf8d029315550210444e2)) - Correct location ([b249aaa](https://github.com/ory/kratos/commit/b249aaad97eabc88c269265359a33cea920ef7f2)) - **courier:** Add ability to specify backoff ([#2349](https://github.com/ory/kratos/issues/2349)) ([bf970f3](https://github.com/ory/kratos/commit/bf970f32f571164b8081f09f602a3473e079194e)) - Do not expose debug in a response when a schema is not found ([#2348](https://github.com/ory/kratos/issues/2348)) ([aee2b1e](https://github.com/ory/kratos/commit/aee2b1ed1189b57fcbb1aaa456444d5121be94b1)) - Do not fail release if no changes needed ([114c93e](https://github.com/ory/kratos/commit/114c93eb48c242702b72d7785da70bd31d858214)) - **Dockerfile:** Use existing builder base image ([#2390](https://github.com/ory/kratos/issues/2390)) ([37de25a](https://github.com/ory/kratos/commit/37de25a541a24e03407ecf344fb750775e48c782)) - Embed schema ([b797bba](https://github.com/ory/kratos/commit/b797bba5910dfd925a11fb86e2dbd14b5dd839d9)) - Get user first name and last name from Apple ([#2331](https://github.com/ory/kratos/issues/2331)) ([4779909](https://github.com/ory/kratos/commit/47799098b35ea1cf5a1163f57d872a5bb2242d97)) - Improve error reporting from OpenAPI ([8a1009b](https://github.com/ory/kratos/commit/8a1009b16653df13485bab8e33926967c449bf4e)) - Improve performance of identity schema call ([af28de2](https://github.com/ory/kratos/commit/af28de267f21cd72953f3f353d8fd587937b2249)) - Internal Server Error on Empty PUT /identities/id body ([#2417](https://github.com/ory/kratos/issues/2417)) ([5a50231](https://github.com/ory/kratos/commit/5a50231b553aaa64bd90a3d2cd1be9d2e3aba9ac)) - Load return_to and append to errors ([#2333](https://github.com/ory/kratos/issues/2333)) ([5efe4a3](https://github.com/ory/kratos/commit/5efe4a33e35e74d248d4eec43dc901b7b6334037)), closes [#2275](https://github.com/ory/kratos/issues/2275) [#2279](https://github.com/ory/kratos/issues/2279) [#2285](https://github.com/ory/kratos/issues/2285) - Make delete formattable ([0005f35](https://github.com/ory/kratos/commit/0005f357a049ecbf94d76a1e73434837753a04ea)) - Mark body as required ([#2479](https://github.com/ory/kratos/issues/2479)) ([c9ae117](https://github.com/ory/kratos/commit/c9ae1175340993cfc93db436c06462c80935ea2a)) - New issue templates ([b9ad684](https://github.com/ory/kratos/commit/b9ad684311ee8c654b2fa382010315e892581f5c)) - Openapi regression ([#2465](https://github.com/ory/kratos/issues/2465)) ([37a3369](https://github.com/ory/kratos/commit/37a3369cea8ed5af34e8324a291a7d7dba0eb43a)) - Quickstart docker-compose ([#2490](https://github.com/ory/kratos/issues/2490)) ([9717762](https://github.com/ory/kratos/commit/97177629c715028affbc294bdd432fd6c954d5ad)), closes [#2488](https://github.com/ory/kratos/issues/2488) - Refresh is always false when session exists ([d3436d7](https://github.com/ory/kratos/commit/d3436d7fa17589d91e25c9f0bd66bc3bb5b150fa)), closes [#2341](https://github.com/ory/kratos/issues/2341) - Remove required legacy field ([#2410](https://github.com/ory/kratos/issues/2410)) ([638d45c](https://github.com/ory/kratos/commit/638d45caf480b7287c9762cbf3c593217f40e3e8)) - Remove wrong templates ([4fe2d25](https://github.com/ory/kratos/commit/4fe2d25dd68033a8d7b3dd5f62d87b23a7ba361d)) - Reorder transactions ([78ca4c6](https://github.com/ory/kratos/commit/78ca4c6ca5a49b0800d9c34954638a926d80078b)) - Resolve index naming issues ([d5550b5](https://github.com/ory/kratos/commit/d5550b5ddc4e1677e4c4f808578f573760c6581e)) - Resolve MySQL index issues ([50bdba9](https://github.com/ory/kratos/commit/50bdba9f1117c60e80e153416bc997187b4a60b7)) - Resolve otelx panics ([6613a02](https://github.com/ory/kratos/commit/6613a02b8fd5f6f06e9b6301bdc39037771b3d9b)) - **sdk:** Improved OpenAPI specifications for UI nodes ([#2375](https://github.com/ory/kratos/issues/2375)) ([a42a0f7](https://github.com/ory/kratos/commit/a42a0f772af3625c457032d6dcc34289a62acc61)), closes [#2357](https://github.com/ory/kratos/issues/2357) - Serve.admin.request_log.disable_for_health behaviour ([#2399](https://github.com/ory/kratos/issues/2399)) ([0a381fa](https://github.com/ory/kratos/commit/0a381fa3d702f77e614d0492dafa3ac2cd102c7e)) - **sql:** Add additional join argument to resolve MySQL query issue ([854e5cb](https://github.com/ory/kratos/commit/854e5cba80cad52b58571587980c00c038ff6596)), closes [#2262](https://github.com/ory/kratos/issues/2262) - Unreliable HIBP caching strategy ([#2468](https://github.com/ory/kratos/issues/2468)) ([93bf1e2](https://github.com/ory/kratos/commit/93bf1e2cd53f3a4de3ff414017c17813d36b56da)) - Use `path` instead of `filepath` to join http route paths ([16b1244](https://github.com/ory/kratos/commit/16b12449c841bf7a237fe436b884b4b5012cd022)), closes [#2292](https://github.com/ory/kratos/issues/2292) - Use JOIN instead of iterative queries ([0998cfb](https://github.com/ory/kratos/commit/0998cfb2fdda27ba8baeebcc603aae5fbe5c901f)), closes [#2402](https://github.com/ory/kratos/issues/2402) - Use pointer of string for PasswordIdentifier in example code ([#2421](https://github.com/ory/kratos/issues/2421)) ([61f12e7](https://github.com/ory/kratos/commit/61f12e7579c7c337d0f415ac2b4029790c659c3d)) - Use predictable SQLite in memory DSNs ([#2415](https://github.com/ory/kratos/issues/2415)) ([51a13f7](https://github.com/ory/kratos/commit/51a13f712d38a942772b3f4c014971ecb4658d7a)), closes [#2059](https://github.com/ory/kratos/issues/2059) ### Code Generation - Pin v0.10.0 release commit ([87e0de7](https://github.com/ory/kratos/commit/87e0de7a10b2a7478d8113ca028bfdb6525bc8e5)) ### Code Refactoring - Deprecate fizz renderer ([5277668](https://github.com/ory/kratos/commit/5277668b1324173df95db5e9e4b96ed841ff088b)) - Move CLI commands to match Ory CLI structure ([d11a9a9](https://github.com/ory/kratos/commit/d11a9a9dafdebb53ed9a8359496eb70b8adb99dd)) - Move CLI commands to match Ory CLI structure ([73910a3](https://github.com/ory/kratos/commit/73910a329b1ee46de2607c7ab1958ef2fb6de5f4)) ### Documentation - Add docs about change in default schema ([#2447](https://github.com/ory/kratos/issues/2447)) ([5093cd4](https://github.com/ory/kratos/commit/5093cd47f22311c2e1fdbffd82f0494806076f08)) - Remove notice importing credentials not possible ([#2418](https://github.com/ory/kratos/issues/2418)) ([b80ed69](https://github.com/ory/kratos/commit/b80ed6955518003ae6b7f647dffd2d49cc999fbc)) ### Features - Add certificate based authentication for smtp client ([#2351](https://github.com/ory/kratos/issues/2351)) ([7200037](https://github.com/ory/kratos/commit/72000375c028f5f7f9cb0d0b1b02f8aa09503e4f)) - Add ID to the recovery error when already logged in ([#2483](https://github.com/ory/kratos/issues/2483)) ([29e4a51](https://github.com/ory/kratos/commit/29e4a51cc5344dcb44839f8aa57197c41aeeb78d)) - Add localName to smtp config ([#2445](https://github.com/ory/kratos/issues/2445)) ([27336b6](https://github.com/ory/kratos/commit/27336b63b0c11c1667d5a07230bed82283475aa4)), closes [#2425](https://github.com/ory/kratos/issues/2425) - Add render-schema script ([a0c006e](https://github.com/ory/kratos/commit/a0c006e40fb00608d682b74f44725883b9c7bf4f)) - Add session renew capabilities ([#2146](https://github.com/ory/kratos/issues/2146)) ([4348b86](https://github.com/ory/kratos/commit/4348b8640a282cd61fe30961faba5753e2af8bb0)), closes [#615](https://github.com/ory/kratos/issues/615) - Add support for netID provider ([#2394](https://github.com/ory/kratos/issues/2394)) ([ee7fc79](https://github.com/ory/kratos/commit/ee7fc79d49cd6d8f2985809585d1675c8e2ed376)) - Add tracing to persister ([391c54e](https://github.com/ory/kratos/commit/391c54eb3ba721e4912a7a4676acc2f630be2a72)) - **identity:** Add admin and public metadata fields ([562e340](https://github.com/ory/kratos/commit/562e340fe980e7c65ab3fc41f82a2a8899a33bfa)), closes [#2388](https://github.com/ory/kratos/issues/2388) [#47](https://github.com/ory/kratos/issues/47): This patch adds two new keys to identities, `metadata_public` and `metadata_admin` that can be used to store additional metadata about identities in Ory. - Read subject id from https://graph.microsoft.com/v1.0/me for microsoft ([#2347](https://github.com/ory/kratos/issues/2347)) ([852f24f](https://github.com/ory/kratos/commit/852f24fb5cd8576f3f6d35017ce85e4fa1c51c95)): Adds the ability to read the OIDC subject ID from the `https://graph.microsoft.com/v1.0/me` endpoint. This introduces a new field `subject_source` to the OIDC configuration. Closes https://github.com/ory/kratos/pull/2153 - **sdk:** Add cookie headers to all form submissions ([#2467](https://github.com/ory/kratos/issues/2467)) ([9a969fd](https://github.com/ory/kratos/commit/9a969fd927ae8436a863e91ecb6574cb3bb1c3a6)), closes [#2003](https://github.com/ory/kratos/issues/2003) [#2454](https://github.com/ory/kratos/issues/2454) - **sdk:** Add csrf cookie for login flow submission ([#2454](https://github.com/ory/kratos/issues/2454)) ([2bffee8](https://github.com/ory/kratos/commit/2bffee81f0e8a98851a3e11b4fc4969d95e9b445)) - Support argon2i password ([#2395](https://github.com/ory/kratos/issues/2395)) ([8fdadf9](https://github.com/ory/kratos/commit/8fdadf9d1724d28ae11996304703e06671549660)) - Switch to opentelemetry tracing ([#2318](https://github.com/ory/kratos/issues/2318)) ([121a4d3](https://github.com/ory/kratos/commit/121a4d3fc0f396e8da50ad1985cacf68a5c85a12)) - **tracing:** Improved tracing for requests ([#2475](https://github.com/ory/kratos/issues/2475)) ([b90a558](https://github.com/ory/kratos/commit/b90a5582284f1ceb0e97575e3b3562603b65ec5f)) - Upgrade to Go 1.18 ([725d202](https://github.com/ory/kratos/commit/725d202e6ae15b3b5c3282e03c03a40480a2e310)) ### Tests - Fix incorrect assertion ([b5b1361](https://github.com/ory/kratos/commit/b5b1361defa8faa6ea36d50a8d940c76f70c4ddd)) - Resolve regressions ([dd44593](https://github.com/ory/kratos/commit/dd44593a51a9277c717170360f9794837e4f910c)) ### Unclassified - BREAKING CHANGES: This patch group updates the tracing provider from OpenTracing to OpenTelemetry. Due to these changes, tracing providers Zipkin, DataDog, Elastic APM have been deactivated temporarily. The best way to re-add support for them is to make a pull request at https://github.com/ory/x/tree/master/otelx and check the status of https://github.com/ory/x/issues/499 ([7165fa0](https://github.com/ory/kratos/commit/7165fa04fa1c9442cad8da5c5814453e1ca0ba7b)): The configuration has not changed, and thus no changes to your system are required if you use Jaeger. # [0.9.0-alpha.3](https://github.com/ory/kratos/compare/v0.9.0-alpha.2...v0.9.0-alpha.3) (2022-03-25) Resolves an issue in the quickstart. ## Breaking Changes Calling /self-service/recovery without flow ID or with an invalid flow ID while authenticated will now respond with an error instead of redirecting to the default page. Closes https://github.com/ory-corp/cloud/issues/2173 Co-authored-by: aeneasr <3372410+aeneasr@users.noreply.github.com> ### Bug Fixes - Accept recovery link from authenticated users ([#2195](https://github.com/ory/kratos/issues/2195)) ([0fa64dd](https://github.com/ory/kratos/commit/0fa64dd7fdaaadf92bddb600bbf201fb6e9d1fed)): When a recovery link is opened while the user already has a session cookie (possibly for another account), the endpoint will now correctly complete the recovery process and issue new cookies. - Quickstart ([73b461c](https://github.com/ory/kratos/commit/73b461c6ea45e0feaab734d0eb0ce380993e95d4)): Closes https://github.com/ory/kratos/issues/2339 - Resolve issue where CF cookies would mingle with CSRF detection in API flows ([011219a](https://github.com/ory/kratos/commit/011219a40027d2c1b06c2797951a55e2f07c0845)) - Typo in error message ([#2332](https://github.com/ory/kratos/issues/2332)) ([b075a5b](https://github.com/ory/kratos/commit/b075a5b30b47e79af1330238a3b5ea97a3c2ac4b)) - Update v0.9.0-alpha.2 config schema path ([#2328](https://github.com/ory/kratos/issues/2328)) ([55705c7](https://github.com/ory/kratos/commit/55705c7ce0ff76dc7ddda24524db919dcb51225a)) - **version schema:** Require version or fall back to latest ([52c9824](https://github.com/ory/kratos/commit/52c98247d4c170f79fa25a019d7f4a73b3e5fdc4)) ### Code Generation - Pin v0.9.0-alpha.3 release commit ([32e36d4](https://github.com/ory/kratos/commit/32e36d4e75f888e69653625a52171200b4968a6c)) ### Documentation - Add missing error codes ([b854bb8](https://github.com/ory/kratos/commit/b854bb8a33794bba684abbfe5abc6b8da1c54f44)) - Clarify 410 error for api payloads ([2c7ac3b](https://github.com/ory/kratos/commit/2c7ac3b15a65e629ba25c0170fce68aa9eb3a80a)) # [0.9.0-alpha.2](https://github.com/ory/kratos/compare/v0.9.0-alpha.1...v0.9.0-alpha.2) (2022-03-22) Resolves an issue in the SDK release pipeline. ### Bug Fixes - Swag location ([5b51bfb](https://github.com/ory/kratos/commit/5b51bfbb10592c9e7dce14689f48530427c34edc)) ### Code Generation - Pin v0.9.0-alpha.2 release commit ([f5501cf](https://github.com/ory/kratos/commit/f5501cf575a74884555e0e1e4cba39c552f4868f)) # [0.9.0-alpha.1](https://github.com/ory/kratos/compare/v0.8.3-alpha.1.pre.0...v0.9.0-alpha.1) (2022-03-21) Ory Kratos v0.9 is here! We're extremely happy to announce that the new release is out and once again it's been made even better thanks to the incredible contributions from our awesome community. <3 Enjoy! Here's an overview of things you can expect from the v0.9 release: 1. We introduced 1:1 compatibility between self-hosting Ory Kratos and using Ory Cloud. The configuration works the same across all modes of operation and deployment! 2. Passwordless login with WebAuthn is now available! Authentication with YubiKeys, TouchID, FaceID, Microsoft Hello, and other WebAuthn-supported methods is now available. The refactored infrastructure lays a foundation for more passwordless flows to come. 3. All the docs are now available in a single repo. Go to the [ory/docs](https://github.com/ory/docs) repository to find docs for all Ory projects. 4. You can now load custom email templates that'll make your essential messaging like project invitations or password recovery emails look slick. 5. We've laid the foundation for adding SMS-dependant flows. 6. Security is always a top priority. We've made changes and updates such as CSP nonces, SSRF defenses, session invalidation hooks, and more. 7. Kratos now gracefully handles cookie errors. 8. Password policies are now configurable. 9. Added configuration to control the flow of webhooks. Now you can cancel flows & run them in the background. 10. You can import identities along with their credentials (password, social sign-in connections, WebAuthn, ...). 11. Infra: we migrated all of our CIs from CircleCI to GitHub Actions. 12. We moved the admin API from `/` to `admin`. **This is a breaking change**. Please read the explanation and proceed with caution! 13. Bugfix: fixed a bug in the handling of secrets. **This is a breaking change**. Please read the explanation and proceed with caution! 14. Bugfix: several bugs in different self-service flows are no more. As you can see, this release introduces breaking changes. We tried to keep the HTTP API as backward-compatible as possible by introducing HTTP redirects and other measures, but this update requires you to take extra care. Make sure you've read the release notes and understand the risk before updating. You must apply SQL migrations for this release. **Make sure to create backup before you start!** ## Breaking Changes Configuration key `selfservice.whitelisted_return_urls` has been renamed to `allowed_return_urls`. All endpoints at the Admin API are now exposed at `/admin/`. For example, endpoint `https://kratos:4434/identities` is now exposed at `https://kratos:4434/admin/identities`. This change makes it easier to configure reverse proxies and API Gateways. Additionally, it introduces 1:1 compatibility between Ory Cloud's APIs and self-hosted Ory Kratos. Please note that nothing has changed in terms of the port. To make the migration less painful, we have set up redirects from the old endpoints to the new `/admin` endpoints, so your APIs, SDKs, and clients should continue working as they were working before. This change is marked as a breaking change as it touches many endpoints and might be confusing when encountering the redirect for the first time. If you are using two or more secrets for the `secrets.session`, this patch might break existing Ory Session Cookies. This has the effect that users will need to re-authenticate when visiting your app. The `password_identifier` form field of the password login strategy has been renamed to `identifier` to make compatibility with passwordless flows possible. Field name `password_identifier` will still be accepted. Please note that the UI node for displaying the "username" / "email" field has this `name="identifier"` going forward. Additionally, the `traits` of the password strategy are no longer within group `password` but instead in group `profile` going forward! The following OpenID Connect configuration keys have been renamed to better explain their purpose: ```patch - private_key_id + apple_private_key_id - private_key + apple_private_key - team_id + apple_team_id - tenant + microsoft_tenant ``` A major issue has been lingering in the configuration for a while. What happens to your identities when you update a schema? The answer was, it depends on the change. If the change is incompatible, some things might break! To resolve this problem we changed the way you define schemas. Instead of having a global `default_schema_url` which developers used to update their schema, you now need to define the `default_schema_id` which must reference schema ID in your config. To update your existing configuration, check out the patch example below: ```patch identity: - default_schema_url: file://stub/identity.schema.json + default_schema_id: default + schemas: + - id: default + url: file://stub/identity.schema.json ``` Ideally, you would version your schema and update the `default_schema_id` with every change to the new version: ```yaml identity: default_schema_id: user_v1 schemas: - id: user_v0 url: file://path/to/user_v0.json - id: user_v1 url: file://path/to/user_v1.json ``` ### Bug Fixes - Add CourierConfig to default registry ([#2243](https://github.com/ory/kratos/issues/2243)) ([2e1fba3](https://github.com/ory/kratos/commit/2e1fba3ca88e273362978fe29197fe44a879813e)) - Add DispatchMessage to interface ([df2ca7a](https://github.com/ory/kratos/commit/df2ca7a7c97a28d40c6a8af082f99ff7706ee9db)) - Add missing enum ([#2223](https://github.com/ory/kratos/issues/2223)) ([4b7d7d0](https://github.com/ory/kratos/commit/4b7d7d0011207614ab12f52bb3a911b62581ebe9)): Closes https://github.com/ory/sdk/issues/147 - Add output-dir input to cli-next ([#2230](https://github.com/ory/kratos/issues/2230)) ([1eb3f18](https://github.com/ory/kratos/commit/1eb3f189f29cc032c44cbd9803acbf99362e5a62)) - Added malformed config test ([5a3c9c1](https://github.com/ory/kratos/commit/5a3c9c162bd1da5c7bb938192a5e82789bac52cc)) - Appropriately pass context around ([#2241](https://github.com/ory/kratos/issues/2241)) ([668f6b2](https://github.com/ory/kratos/commit/668f6b246db1f61b9800f7581bedba4fa25318c4)): Closes https://github.com/ory/cloud/issues/56 - Base redirect URL decoding ([acdefa7](https://github.com/ory/kratos/commit/acdefa7464825e5307132eab5cd2752e1841c3de)) - Base64 encode identity schema URLs ([ad44e4d](https://github.com/ory/kratos/commit/ad44e4d5f2cea86a95cc376c94fb5f5ac5bc1b82)): Previously, identity schema IDs with special characters could lead to broken URLs. This patch introduces a change where identity schema IDs are base64 encoded to address this issue. Schema IDs that are not base64 encoded will continue working. - Broken links API spec ([e1e7516](https://github.com/ory/kratos/commit/e1e75165785f48f5a154c899e1c4168bcbb7d8c3)) - Cloud config issue ([135b29c](https://github.com/ory/kratos/commit/135b29c647c87569cc85e8a72babb8d6777ebd24)) - Correct recovery hook ([c7682a8](https://github.com/ory/kratos/commit/c7682a8fd97fdac87d59d3e7fb798384b018c40f)) - **courier:** Improve composability ([d47150e](https://github.com/ory/kratos/commit/d47150e8440a03ce34d6085fb693bddf2c02620b)) - Do not error when HIBP behaves unexpectedly ([#2251](https://github.com/ory/kratos/issues/2251)) ([a431c1e](https://github.com/ory/kratos/commit/a431c1e1976f740bedb2fec4ce88b7d1b832e42c)), closes [#2145](https://github.com/ory/kratos/issues/2145) - Do not remove all credentials when remove all security keys ([#2233](https://github.com/ory/kratos/issues/2233)) ([ecd715a](https://github.com/ory/kratos/commit/ecd715a0437c0b068aa0c6a17cd2ba53fe034354)) - Don't inherit flow type in recovery and verification flows ([#2250](https://github.com/ory/kratos/issues/2250)) ([c5b444a](https://github.com/ory/kratos/commit/c5b444aa2bf46b3a86d08f693ab200a30bd4a609)), closes [#2049](https://github.com/ory/kratos/issues/2049) - **embed:** Disallow additional props ([b2018ce](https://github.com/ory/kratos/commit/b2018ce3b1667fffc9d0a2c4c82cfafed7f3cac5)) - **embed:** Do not require plaintext/html in email config ([dfe4140](https://github.com/ory/kratos/commit/dfe4140dda44d4b64988b94272b4776e362abde5)) - Ensure no internal networks can be called in SMS sender ([65e42e5](https://github.com/ory/kratos/commit/65e42e5cb3a9a3a81e3c623fa066a7651dfb0699)) - **identity:** Slow query performance on MySQL ([731b3c7](https://github.com/ory/kratos/commit/731b3c7ba48271e2fb6bbd53b0281d5269012332)), closes [#2278](https://github.com/ory/kratos/issues/2278) - Improve password error resilience on settings flow ([e614f6e](https://github.com/ory/kratos/commit/e614f6e94e1d0f66f48bd058b015ab467d6b1b07)) - Improve soundness of credential identifier normalization ([e475163](https://github.com/ory/kratos/commit/e475163330d06ca02cd0419e4b7216f03218e8c5)) - Incorrect makefile rule ([#2222](https://github.com/ory/kratos/issues/2222)) ([83a0ce7](https://github.com/ory/kratos/commit/83a0ce7d20e59c2fb1a35fa071a3d11a9280bcad)) - **login:** Put passwordless login before password ([df9245f](https://github.com/ory/kratos/commit/df9245fbc403e1b8f2dd1378678963cc0d71ef1a)) - **lookup:** Resolve credentials counting regression ([50782c6](https://github.com/ory/kratos/commit/50782c68c77ce1c0d8c092678a6710e0be6fa18d)) - Lower-case jsonnet context for sms ([8c58e94](https://github.com/ory/kratos/commit/8c58e94707122a9b50873ca1acaa32659b5b8416)) - Mark struct as used ([33f3dfe](https://github.com/ory/kratos/commit/33f3dfeba5af3808f34b16241d74993ceed788be)) - Mark width and height as required ([#2322](https://github.com/ory/kratos/issues/2322)) ([37f2f22](https://github.com/ory/kratos/commit/37f2f220ce699e031018777c9976cafa22faa984)): Closes https://github.com/ory/sdk/issues/157 - Move to new post-release steps ([#2206](https://github.com/ory/kratos/issues/2206)) ([10778fd](https://github.com/ory/kratos/commit/10778fdd16a116b5dc8f4c2bdc96a895728d9aec)) - Mr comment fix ([96c917e](https://github.com/ory/kratos/commit/96c917e3c1b02b13be55056bfd94b517007fc206)) - **oidc:** Improve empty credential handling ([124d4ce](https://github.com/ory/kratos/commit/124d4ce9fe949dcea4fd5ff8e45530835d38cb3c)) - **oidc:** Incorrect error handling ([c8d789c](https://github.com/ory/kratos/commit/c8d789c10e2be11dfc8c3eea01a339637f89ea63)) - Order regression ([2cb5d2b](https://github.com/ory/kratos/commit/2cb5d2bf2d645a0e63cf289c966ee8557edbf333)) - Pass context to registration flow ([c8d55b3](https://github.com/ory/kratos/commit/c8d55b339647cdca3c9beace760dc3a9beac31c1)) - Pass docs output dir as a separate argument ([78c69a2](https://github.com/ory/kratos/commit/78c69a2790c957bf8102260150d69b1844899ed9)) - Pass token to render-version-schema ([#2246](https://github.com/ory/kratos/issues/2246)) ([4d117e5](https://github.com/ory/kratos/commit/4d117e51abef739d686e48dede63a030a753be41)) - **password:** Schema regressions ([271d5fa](https://github.com/ory/kratos/commit/271d5fa93f96721d7bf8aa841c700dfec1de4104)) - Properly check for not found ([77ac199](https://github.com/ory/kratos/commit/77ac199f00f04eb7fd40db6fb546921271026e20)) - Properly pass context ([#2300](https://github.com/ory/kratos/issues/2300)) ([fab8a93](https://github.com/ory/kratos/commit/fab8a939c97e61c028143e37e2a78d3edd569da0)) - Provide access to root path and error page ([#2317](https://github.com/ory/kratos/issues/2317)) ([f360ee8](https://github.com/ory/kratos/commit/f360ee8e65dc64983181746d1059eac53588e029)) - Rebase regressions ([d1c5085](https://github.com/ory/kratos/commit/d1c508570032c620a654b896111215a76a811517)) - **registration:** Order for passwordless webauthn ([8427322](https://github.com/ory/kratos/commit/8427322b31fb5206a55e9f62823745fcc6983a22)) - Remove non-hermetic sprig functions ([#2201](https://github.com/ory/kratos/issues/2201)) ([17e0acc](https://github.com/ory/kratos/commit/17e0acc527cfbb703d9d44b776138da23b217ca4)): Closes https://github.com/ory/kratos/issues/2087 - Resolve issues with the CI pipeline ([d15bd90](https://github.com/ory/kratos/commit/d15bd90433ed191c2eb41f119ed288906827334e)) - Resolve merge regression ([d8ca4f3](https://github.com/ory/kratos/commit/d8ca4f327499f94c811c55237f210288fb6a9dd5)) - Resolve prettier issues ([32bf052](https://github.com/ory/kratos/commit/32bf052f0084860623ea815ed913e94261c89070)) - Resolve remaining passwordless regressions ([151c8cf](https://github.com/ory/kratos/commit/151c8cfb53402aaf2518a471579c25c3785b13d2)) - Resovle lint errors ([afb7aaf](https://github.com/ory/kratos/commit/afb7aaf7b019756a624e7f1b2e35fd575882570a)) - Return 400 instead of 404 on admin recovery ([ae2509c](https://github.com/ory/kratos/commit/ae2509cf7a95f940d33945271ac1fe8fc255506b)), closes [#1664](https://github.com/ory/kratos/issues/1664) - **sdk:** Add all available discriminators ([5d70f9c](https://github.com/ory/kratos/commit/5d70f9c70a39067c2d6c0b1f127ff28ca39e77a9)), closes [#2287](https://github.com/ory/kratos/issues/2287) [#2288](https://github.com/ory/kratos/issues/2288) - **sdk:** Add webauth and lookup_secret to identityCredentialsType ([#2276](https://github.com/ory/kratos/issues/2276)) ([61ce3c0](https://github.com/ory/kratos/commit/61ce3c0c35366f587bfee5c89496fa15432bb241)) - **sdk:** Correct minimum page to 1 ([a28362e](https://github.com/ory/kratos/commit/a28362e054cf12441ed25d8927cd63e3264bfed6)), closes [#2286](https://github.com/ory/kratos/issues/2286) - **selfservice:** Cannot login after remove security keys and all other 2FA settings ([#2181](https://github.com/ory/kratos/issues/2181)) ([5ff6773](https://github.com/ory/kratos/commit/5ff6773ab8512bdfb8d2c7b650970711cbb012ba)), closes [#2180](https://github.com/ory/kratos/issues/2180) - **selfservice:** Login self service flow with TOTP does not pass on return_to URL ([#2175](https://github.com/ory/kratos/issues/2175)) ([3eaa88e](https://github.com/ory/kratos/commit/3eaa88e74e1540b14b6e41df2881346c60b92046)), closes [#2172](https://github.com/ory/kratos/issues/2172) - **session:** Correctly calculate aal for passwordless webauthn ([c7eb970](https://github.com/ory/kratos/commit/c7eb970ed252577e06d3d769d2545d5e8e98175a)) - **session:** Properly declare session secrets ([6312afd](https://github.com/ory/kratos/commit/6312afd2eb0d1dc808d600a902eb1e16b07fd9cb)), closes [#2272](https://github.com/ory/kratos/issues/2272): Previously, a misconfiguration of Gorilla's session store caused incorrect handling of the configured secrets. From now on, cookies will also be properly encrypted at all times. - Snapshot regression ([6481441](https://github.com/ory/kratos/commit/6481441fe7df1a2fc43ff153697e9bd2160c49b3)) - Static analysis ([a1d3254](https://github.com/ory/kratos/commit/a1d3254346ec0bcc0a8c42bf66a8171e027f0d97)) - **test:** Parallelization issues ([dbcf3fb](https://github.com/ory/kratos/commit/dbcf3fb616db64e1b1f4cb5066113f703ca0b2ee)) - **text:** Incorrect IDs for different messages ([0833321](https://github.com/ory/kratos/commit/0833321e04e9865046294b051376bed415a41441)), closes [#2277](https://github.com/ory/kratos/issues/2277) - **totp:** Resolve credentials counting regression ([737bb3f](https://github.com/ory/kratos/commit/737bb3f71e91f7c735231d0131072aca4f5622ea)) - Typo ([fbc8b4f](https://github.com/ory/kratos/commit/fbc8b4f9901e7761bef9a7f74a483cb077007cf8)) - Typo ([3bb0d41](https://github.com/ory/kratos/commit/3bb0d41e3696be90cfc12f1bf00a546536e283b6)) - Unstable ordering ([bee26c6](https://github.com/ory/kratos/commit/bee26c65c9511af82b9ed2051ab4f45b9570602d)) - Unstable webauthn order ([6262160](https://github.com/ory/kratos/commit/626216098fcd9411c1b4b7cb3b42784146b29924)) - Updated oathkeeper+kratos example ([#2273](https://github.com/ory/kratos/issues/2273)) ([567a3d7](https://github.com/ory/kratos/commit/567a3d765aa2115951f6af5b4ed4d2c791231de0)) - URL with hash sign in after_verification_return_to stays encoded ([#2173](https://github.com/ory/kratos/issues/2173)) ([fb1cb8a](https://github.com/ory/kratos/commit/fb1cb8a993cbf6cb050d7dce91672b05efd53224)), closes [#2068](https://github.com/ory/kratos/issues/2068) - Use actions/checkout for ui repos ([f0136ca](https://github.com/ory/kratos/commit/f0136cac639862bf50933063b7dc38973739139b)) - Use correct dir for clidoc ([8c8a1ab](https://github.com/ory/kratos/commit/8c8a1ab7b41fa026189cec8d1f77e2e89c696d11)) - Use HTTP 303 instead of 302 for selfservice redirects ([#2215](https://github.com/ory/kratos/issues/2215)) ([50b6bd8](https://github.com/ory/kratos/commit/50b6bd892ae6efba34773811ef488f15fc95154f)), closes [#1969](https://github.com/ory/kratos/issues/1969) - Use latest hydra version ([ffb3f20](https://github.com/ory/kratos/commit/ffb3f20e67d357160c024f5e58ebf63a9aec41ff)) - **webauthn:** Resolve missing identifier bug ([93a1ae4](https://github.com/ory/kratos/commit/93a1ae4fe98487a0bca00d2afdc5e7b07c0e1c46)) - **webauthn:** Schema regressions ([970e861](https://github.com/ory/kratos/commit/970e861714ec01c5cfe19545871798d9ad0ae70c)) - **webauth:** SPA regressions for login ([be378ff](https://github.com/ory/kratos/commit/be378ffa5ddbd56a00b471dce861ec074eed5192)) - Yq version ([41b6f18](https://github.com/ory/kratos/commit/41b6f1879f23866c070100dd1767f841bff3a815)) ### Code Generation - Pin v0.9.0-alpha.1 release commit ([72bd2ed](https://github.com/ory/kratos/commit/72bd2ed67559a64415b2686e8f67c42df888e49e)) ### Code Refactoring - All admin endpoints are now exposed under `/admin/` on the admin port ([8acb4cf](https://github.com/ory/kratos/commit/8acb4cfaa61ef52619e889b8c862191c6b92e5eb)) - Distinguish between first and multi factor credentials ([8de9d01](https://github.com/ory/kratos/commit/8de9d01d9edae485f5a6ea7c68584ba4019a24d6)) - Identity.default_schema_url is now `identity.default_schema_id` ([#1964](https://github.com/ory/kratos/issues/1964)) ([e4f205d](https://github.com/ory/kratos/commit/e4f205d69bec07a71bf1d34d97ab3a6b99a4cc46)) - **identity:** Move credentials counter ([c9875a7](https://github.com/ory/kratos/commit/c9875a7582accc740061e6a19d7b4b0998899f3f)) - Mimic credentials config on import ([c3eb7ce](https://github.com/ory/kratos/commit/c3eb7ce60597954a60b8903ac011a643d0facf12)) - Move credential configs for oidc and password ([50ac851](https://github.com/ory/kratos/commit/50ac851cc4534aa474a76c208f15483548ec8631)) - Move docs to ory/docs ([57151da](https://github.com/ory/kratos/commit/57151da6adc85753d54c108637298642ccbc8347)) - **oidc:** Credentials counting ([b75a639](https://github.com/ory/kratos/commit/b75a6390de85e10db8e9e17a74e95dd6dd716442)) - **password:** DRY up registration helpers ([8a51839](https://github.com/ory/kratos/commit/8a51839ba85ddb5a345fef65f30b4325103ce38a)) - **password:** Internals and deprecated fields ([a7784bd](https://github.com/ory/kratos/commit/a7784bdb52aff0ac171e59b2301755b65c842813)) - Rename `password_identifier` field to `identifier` ([4dbe0ea](https://github.com/ory/kratos/commit/4dbe0ea41f49e198840292fc101258a4bdca826e)) - Rename `whitelisted_return_urls` to `allowed_return_urls` ([#2299](https://github.com/ory/kratos/issues/2299)) ([686c9ba](https://github.com/ory/kratos/commit/686c9ba08ff1db8a310eaed5c4b3aec69e0f84da)) - **session:** Aal computation ([a136de9](https://github.com/ory/kratos/commit/a136de99a0f8fe78ee344f2243359c781b166378)) - Update apple and microsoft config key names ([#2261](https://github.com/ory/kratos/issues/2261)) ([6da2370](https://github.com/ory/kratos/commit/6da2370b4e6833ef61ca03214261e45c4786cb44)), closes [#1979](https://github.com/ory/kratos/issues/1979) ### Documentation - Add debug tip ([#2186](https://github.com/ory/kratos/issues/2186)) ([a1ada22](https://github.com/ory/kratos/commit/a1ada2255d132b1f3ea8cb494620b9c17b42f161)) - Add react example code ([#2185](https://github.com/ory/kratos/issues/2185)) ([0689cc7](https://github.com/ory/kratos/commit/0689cc73ccc9a472c5610f1e011c6ccbc5e0c20d)) - Cloud ([8d1d65d](https://github.com/ory/kratos/commit/8d1d65d9d12a894bd25c82394e0392e228fe383d)) - Fix broken links ([d88c56f](https://github.com/ory/kratos/commit/d88c56fc0ebf042d1270d04a2382784e5200654d)) - Fix broken links API doc ([#2296](https://github.com/ory/kratos/issues/2296)) ([47eaae5](https://github.com/ory/kratos/commit/47eaae575023469834c0c3a4aac64dc6d880e164)) - Fix versions ([7186ff3](https://github.com/ory/kratos/commit/7186ff354b9c3d0fbd3fb809546075fcfcd0c57f)) - Replace all mentions of Ory Kratos SDK with Ory SDK ([#2187](https://github.com/ory/kratos/issues/2187)) ([4e6897f](https://github.com/ory/kratos/commit/4e6897ff2220b5668d784a16dd1f48db30f271f0)) - Update readme ([e7d9da1](https://github.com/ory/kratos/commit/e7d9da199825fb15ae720c0496a257590b353a26)) ### Features - Abandon courier messages after configurable timeout ([#2257](https://github.com/ory/kratos/issues/2257)) ([bff92f7](https://github.com/ory/kratos/commit/bff92f73b3f12d2dffa2061eb0e51e746eba2185)) - Add `webauthn` to list of identifiers ([1a8b256](https://github.com/ory/kratos/commit/1a8b256cca33aa9cbb143e7e8fc1efc8217e9b8a)): This patch adds the key `webauthn` to the list of possible identifiers in the Identity JSON Schema. Use this key to specify what field is used to find the WebAuthn credentials on passwordless login flows. - Add credential migrator pattern ([77afc6f](https://github.com/ory/kratos/commit/77afc6f8ea868eaba7853adfcb9ed159b44ecbc8)) - Add message for missing webauthn credentials ([303dc6b](https://github.com/ory/kratos/commit/303dc6bc33c20cd619d2542180247bd7b7f02092)) - Add new messages ([09e6fd1](https://github.com/ory/kratos/commit/09e6fd16bb6be0ff3ee209bbfe69e967546f70da)) - Add npm install step ([3d253e5](https://github.com/ory/kratos/commit/3d253e58ec7d4464d9749efe6ecc4a5c1d9be789)) - Add versioning and improve compatibility for credential migrations ([78ce668](https://github.com/ory/kratos/commit/78ce668a38c914939028be42cd30eefa566ed09a)) - Added sms sending support to courier ([687eca2](https://github.com/ory/kratos/commit/687eca24aac7a7b89cc949693271343573107898)) - Allow empty version string ([419f94b](https://github.com/ory/kratos/commit/419f94bc1065771e49982faf56f8ef90a30bc306)) - Cancelable web hooks ([44a5323](https://github.com/ory/kratos/commit/44a5323f835860dccd11460d666f620026e8b58d)): Introduces the ability to cancel web hooks by calling `error "cancel"` in JsonNet. - **config:** Add option to mark webauthn as passwordless-able ([0455e3f](https://github.com/ory/kratos/commit/0455e3fe901cff6ff314fd59a35864886672327c)): Adds option `passwordless` to `selfservice.methods.webauthn.config`, making it possible to use WebAuthn for first-factor authentication, or so-called "passwordless" authentication. - Courier template configs ([#2156](https://github.com/ory/kratos/issues/2156)) ([799b6a8](https://github.com/ory/kratos/commit/799b6a81add747d3001a1758e08ee7b4c6463d64)), closes [#2054](https://github.com/ory/kratos/issues/2054): It is now possible to override individual courier email templates using the configuration system! - **courier:** Expose setters again ([598dc3a](https://github.com/ory/kratos/commit/598dc3a4d7c27838e9058382378972a1c0330bde)) - **e2e:** Add passwordless flows and fix bugs ([ef3871b](https://github.com/ory/kratos/commit/ef3871bd9b3e7e5f4360da8d1b7749cc005b4e19)) - **identity:** Add identity credentials helpers ([b7be327](https://github.com/ory/kratos/commit/b7be327a370368932ff390968acffaa1ce6d55a0)) - **identity:** Add versioning to credentials ([aaf779a](https://github.com/ory/kratos/commit/aaf779ac1c29b24ece6d5f3d7892a3bf08277653)) - Ignore web hook response ([ae87914](https://github.com/ory/kratos/commit/ae87914512025c05d814a1200eda66d8f931ce44)): Introduces the ability to ignore responses from web hooks in favor of faster and non-blocking execution. - Make sensitive log value redaction text configurable ([#2321](https://github.com/ory/kratos/issues/2321)) ([9b66e43](https://github.com/ory/kratos/commit/9b66e437d0aeed61643b76aea7d49cad001dc8cf)) - **oidc:** Customizable base redirect uri ([fa1f234](https://github.com/ory/kratos/commit/fa1f23469f2fecfa82fa38147f601d969bd9aaa4)): Closes https://github.com/ory-corp/cloud/issues/2003 - Password, social sign, verified email in import ([41a27b1](https://github.com/ory/kratos/commit/41a27b1e15e090d3e99cdcfc3c1ba8eac76097a4)), closes [#605](https://github.com/ory/kratos/issues/605): This patch introduces the ability to import passwords (cleartext, PKBDF2, Argon2, BCrypt) and Social Sign In connections when creating identities! - **recovery:** Allow invalidation of existing sessions ([5029884](https://github.com/ory/kratos/commit/502988474e2bce46752f7fc7885bc1b91423bbdd)), closes [#1077](https://github.com/ory/kratos/issues/1077): You can now use the `revoke_active_sessions` hook in the recovery flow. It invalidates all of an identity's sessions on successful account recovery. - **schema:** Add functionality to disallow internal HTTP requests ([6e08416](https://github.com/ory/kratos/commit/6e08416235bd821493df4d9cda2e8bd76d507871)): See https://github.com/ory-corp/cloud/issues/1261 - **security:** Add e2e tests for various private network SSRF defenses ([b049bc3](https://github.com/ory/kratos/commit/b049bc304cd79568ee82f1423e583949f63d3377)) - **security:** Add SSRF defenses in OIDC ([d37dc5d](https://github.com/ory/kratos/commit/d37dc5d7946252783463bc9e99f7f792e2735614)) - **session:** Add webauthn to extension validation ([049fd8e](https://github.com/ory/kratos/commit/049fd8edc382f344018398027a4e0b3915116ff2)) - **session:** Webauthn can now be a first factor as well ([861bee0](https://github.com/ory/kratos/commit/861bee0f029e3bb3f6b7218be19eaf6c26562b76)) - Trace web hook calls ([#2154](https://github.com/ory/kratos/issues/2154)) ([98ee300](https://github.com/ory/kratos/commit/98ee300e065c6e81e6128a509af3f48612cda88a)) - **webauthn:** Add error preventing deleting last webauthn credential ([1209eda](https://github.com/ory/kratos/commit/1209edacaf1b7dea32bd1bd124c86910bc2553c6)) - **webauthn:** Add new decoder schemas ([c3e1501](https://github.com/ory/kratos/commit/c3e1501bf5170416a034130eb68d1db456a47239)) - **webauthn:** Add passwordless credentials indicator ([6e3057a](https://github.com/ory/kratos/commit/6e3057a96a34d22cac193e5c17b4a3c01d2ca045)) - **webauthn:** Add swagger type ([14c2b74](https://github.com/ory/kratos/commit/14c2b745e951a185dee600f6f2e8f93788c67285)) - **webauthn:** Count passwordless credentials ([145af23](https://github.com/ory/kratos/commit/145af23aef8f5c9ffdcec47bac5758da709d4646)) - **webauthn:** Implement refresh using webauth ([bf10868](https://github.com/ory/kratos/commit/bf108688ed146211da3cc2ec4bf0df015e535220)), closes [#2284](https://github.com/ory/kratos/issues/2284): This change introduces the ability to refresh a session (for example when entering "sudo" mode") using WebAuthn credentials. In this case, it does not matter whether the WebAuthN credentials are for MFA or passwordless flows. - **webauthn:** Improve schema ([790dcf3](https://github.com/ory/kratos/commit/790dcf3a7079d57a088d399c03d040af1019a3aa)) - **webauthn:** Manage webauthn passwordless keys ([5a62ced](https://github.com/ory/kratos/commit/5a62ced175248a85b1e843b4017757aa86d62d23)) - **webauthn:** Passwordless login ([b4c4fd2](https://github.com/ory/kratos/commit/b4c4fd2c25ae5d55350ce573df8295fe6d8c42a1)) - **webauthn:** Update messages and nodes ([22534d8](https://github.com/ory/kratos/commit/22534d8253384f2002033a5b2bbdcf573779a49c)) - **webauthn:** Use plain bytes for wrapped user ([97c8c9e](https://github.com/ory/kratos/commit/97c8c9e25234847622f1ab508cd5d50758d323c0)) ### Tests - Add data for new migration ([b0488ef](https://github.com/ory/kratos/commit/b0488efa600024f40b2c019fa0f492dd39c8bfa9)) - Add tests for new sms options ([799fa10](https://github.com/ory/kratos/commit/799fa106cd0fed33afbe76903911df9292d49bf6)) - **cmd:** Fix regressions ([4b92be9](https://github.com/ory/kratos/commit/4b92be9325d02e605e12d96c7990774234ed1d1d)) - **driver:** Fix regressions ([c6f5137](https://github.com/ory/kratos/commit/c6f51377f253275bf7321c67a5e949699ac12adb)) - **e2e:** Add import tests ([ed90f39](https://github.com/ory/kratos/commit/ed90f394d32ee0a3e42c3a9c1c066f94a05d02c1)) - **e2e:** Reenable hydra ([055a491](https://github.com/ory/kratos/commit/055a4912d3e7712d4bc3a3f5cf9c68d1834998dc)) - **e2e:** Resolve privileged regression ([f7dd5ab](https://github.com/ory/kratos/commit/f7dd5aba26b43aa9f60d8429a7d256f48f228578)) - **e2e:** Resolve regression ([b5053c9](https://github.com/ory/kratos/commit/b5053c902331ae166824eb92b89295e693bf0dc7)) - **e2e:** Resolve regressions ([da154c5](https://github.com/ory/kratos/commit/da154c5e549f79ca5703209852981ded07281f43)) - **e2e:** Resolve regressions ([d46d435](https://github.com/ory/kratos/commit/d46d435c40c383bbd844af8fead283ee46a137fb)) - **e2e:** Resolve regressions and flakes ([a607385](https://github.com/ory/kratos/commit/a60738510875f770f9dbb0b3449dbcf2d473ada3)) - **e2e:** Wait for initial network requests ([#2242](https://github.com/ory/kratos/issues/2242)) ([c5a04b5](https://github.com/ory/kratos/commit/c5a04b5f174e06faca99ebc7461c8ebe8e1f694d)) - Extract common registration helpers to library ([5c1f11b](https://github.com/ory/kratos/commit/5c1f11b2ae65dd73d572e456b522a7d83ac1f473)) - Fix concurrent database access ([46f6fb7](https://github.com/ory/kratos/commit/46f6fb7d246b384e561bdf8952185855f25cce56)) - Fix regression ([f96e48f](https://github.com/ory/kratos/commit/f96e48fa6d4d8b341bcd3f52228b7abff8b934fb)) - **identity:** Ensure migrations run when fetching identities ([322d467](https://github.com/ory/kratos/commit/322d467ac11dcdf4e3210f947b80029c77662065)) - **identity:** Fix regressions ([f492f0e](https://github.com/ory/kratos/commit/f492f0e1d112813d926eac48b5ad5d2e1857a382)) - Re-enable MySQL ([cbe8f6e](https://github.com/ory/kratos/commit/cbe8f6ea4fe48fe84a5cbc8915754f83e7eff428)) - Remove obsolete test ([cd644ae](https://github.com/ory/kratos/commit/cd644aef9175fe21024c37a381722503fcd88555)) - Remove obsolete test failure ([f8fd480](https://github.com/ory/kratos/commit/f8fd48041404344636c51b63d55a668209bed0e0)) - Remove only ([87b3bce](https://github.com/ory/kratos/commit/87b3bce3433601dd918f76c0bc2d25ea4af6e482)) - Remove unnecessary test ([2fa33e4](https://github.com/ory/kratos/commit/2fa33e4f28759b5dc5de78e00e42ed8cc4ccce89)) - Resolve potential panic ([d44af28](https://github.com/ory/kratos/commit/d44af289e9c09a981e80b6f69d22a5cce6b1dbfa)) - **schema:** Resolve regressions ([c6d0810](https://github.com/ory/kratos/commit/c6d08105a270fafd21a14a19e412d7081dedc754)) - Significantly reduce persister run time ([647d6ef](https://github.com/ory/kratos/commit/647d6ef73797462020c2f59ece15e645561182b0)) - Update fixtures ([21462b7](https://github.com/ory/kratos/commit/21462b7eb8cbac719d8ae531969b0fd9d42b5e0c)) - Update fixtures ([299c6e3](https://github.com/ory/kratos/commit/299c6e3be7c120bb769a4b2572ebe42c5ab3ddb1)) - **webauthn:** Add passwordless profile ([88199ea](https://github.com/ory/kratos/commit/88199ea28e8b3460ccc585e5fd1713d398cae15c)) - **webauthn:** Passwordless registration ([c9b6280](https://github.com/ory/kratos/commit/c9b6280720c2fd08191994c86e85ceb1f52a27d2)) ### Unclassified - Move login hinting to own package ([1eb2604](https://github.com/ory/kratos/commit/1eb260423491af917edb1256d260ca3d3fb198dc)) # [0.8.3-alpha.1.pre.0](https://github.com/ory/kratos/compare/v0.8.2-alpha.1...v0.8.3-alpha.1.pre.0) (2022-01-21) autogen: pin v0.8.3-alpha.1.pre.0 release commit ## Breaking Changes This patch removes the ability to use domain aliases, an obscure feature rarely used that had several issues and inconsistencies. ### Bug Fixes - Add `identity_id` index to `identity_verifiable_addresses` table ([#2147](https://github.com/ory/kratos/issues/2147)) ([86fd942](https://github.com/ory/kratos/commit/86fd942e9a80e36dd65ef4ac57c5a5546f94995a)): The verifiable addresses are loaded eagerly into the identity. When that happens, the `identity_verifiable_addresses` table is queried by `nid` and `identity_id`. This index should greatly improve performance, especially of the `/sessions/whoami` endpoint. - Add ability to resume continuity sessions from several cookies ([#2131](https://github.com/ory/kratos/issues/2131)) ([8b87bdb](https://github.com/ory/kratos/commit/8b87bdb1967654b5fbfbf9799948485b2a9a6af0)), closes [#2016](https://github.com/ory/kratos/issues/2016) [#1786](https://github.com/ory/kratos/issues/1786) - Add hiring notice to README ([#2074](https://github.com/ory/kratos/issues/2074)) ([0c1e816](https://github.com/ory/kratos/commit/0c1e816693ad4a6c3fdb7206bbc95c81cdfdf3c0)) - Add missing version tag in quickstart.yml ([#2110](https://github.com/ory/kratos/issues/2110)) ([1d281ea](https://github.com/ory/kratos/commit/1d281ea69e551cc3d40415f5405690f445891bb6)) - Adjust scan configuration ([#2140](https://github.com/ory/kratos/issues/2140)) ([8506fcf](https://github.com/ory/kratos/commit/8506fcf59d572851b24041b48af6a04b31520a32)), closes [#2083](https://github.com/ory/kratos/issues/2083) - Admin endpoint `/schemas` not redirecting to public endpoint ([#2133](https://github.com/ory/kratos/issues/2133)) ([413833f](https://github.com/ory/kratos/commit/413833f128c0674f4e8dbb9e73698a9df04cfc1a)), closes [#2084](https://github.com/ory/kratos/issues/2084) - Choose correct CSRF cookie when multiple are set ([633076b](https://github.com/ory/kratos/commit/633076be008104afd50186ebe60722ef21999d5d)), closes [ory/kratos#2121](https://github.com/ory/kratos/issues/2121) [ory-corp/cloud#1786](https://github.com/ory-corp/cloud/issues/1786): Resolves an issue where, when multiple CSRF cookies are set, a random one would be used to verify the CSRF token. Now, regardless of how many conflicting CSRF cookies exist, if one of them is valid, the request will pass and clean up the cookie store. - **continuity:** Properly reset cookies that became invalid ([8e4b4fb](https://github.com/ory/kratos/commit/8e4b4fb3d6dbe668cf0166f4cff49eae753d481c)), closes [#2121](https://github.com/ory/kratos/issues/2121) [ory-corp/cloud#1786](https://github.com/ory-corp/cloud/issues/1786): Resolves several reports related to incorrect handling of invalid continuity issues. - **continuity:** Remove cookie on any error ([428ac03](https://github.com/ory/kratos/commit/428ac03b582184dbbbc0c9c3ffd399273fd8e1a5)) - Do not send session after registration without hook ([#2094](https://github.com/ory/kratos/issues/2094)) ([3044229](https://github.com/ory/kratos/commit/3044229227229e81a4ba770eec241a748dd0945c)), closes [#2093](https://github.com/ory/kratos/issues/2093) - Docker-compose standalone definition ([3c7065a](https://github.com/ory/kratos/commit/3c7065ad32ff314c8cbdad8ed89fd9a9f5928f72)) - Explain mitigations in cookie error messages ([ef4b01a](https://github.com/ory/kratos/commit/ef4b01a80ea91114b182ff26759d98cd5ba2cd02)) - Expose network wrapper ([a570607](https://github.com/ory/kratos/commit/a570607d460e7c5f9d49ce38ba7a4e06ae172359)) - Faq ([#2101](https://github.com/ory/kratos/issues/2101)) ([311f906](https://github.com/ory/kratos/commit/311f9066a524308b970afc81d98d1a14b78bf63d)): This patch - moves the FAQ to the Debug & Help section - renames it to Tips & Troubleshooting - moves many of the questions to documents where they fit better, reformatted and with added information where needed. - also some other spelling/format fixes See also https://github.com/ory/docusaurus-template/pull/87 - Ignore whitespace around identifier with password strategy ([#2160](https://github.com/ory/kratos/issues/2160)) ([45335c5](https://github.com/ory/kratos/commit/45335c50f719af504974fe54e504d7653db03c78)), closes [#2158](https://github.com/ory/kratos/issues/2158) - Improve courier test signature ([b8888e3](https://github.com/ory/kratos/commit/b8888e3c93a602635b396503b7301396ce740ff8)) - Include missing type string in config schema ([#2142](https://github.com/ory/kratos/issues/2142)) ([ec2c88a](https://github.com/ory/kratos/commit/ec2c88ac2d65ea1db1146101519cdbb709ebdbbb)): Inside the config.schema.json under the CORS setting, add the missing type (string) for the items of the allowed_origins array - **login:** Error handling when failed to prepare for an expired flow ([#2120](https://github.com/ory/kratos/issues/2120)) ([fdad834](https://github.com/ory/kratos/commit/fdad834e7577e298887b83b693ddf20632cd7c43)) - Minor fixes in FAQ update ([#2130](https://github.com/ory/kratos/issues/2130)) ([b53eec7](https://github.com/ory/kratos/commit/b53eec721489514a80719b73bc5c758dc2adedfd)) - Quickstart standalone service definition ([#2149](https://github.com/ory/kratos/issues/2149)) ([872b06e](https://github.com/ory/kratos/commit/872b06e1f798deacfef101edc3ab33fd75af9b29)) - Resolve configx regression ([672c0ff](https://github.com/ory/kratos/commit/672c0ffc7f5edd1fd238dcdd0c5d0430b30966c6)) - **selfservice:** Recovery self service flow passes on return_to URL ([#1920](https://github.com/ory/kratos/issues/1920)) ([b925d35](https://github.com/ory/kratos/commit/b925d351dd0ce48cb6aed046dcf2698796453751)), closes [#914](https://github.com/ory/kratos/issues/914) - Send 404 instead of null response for unknown verification flows ([#2102](https://github.com/ory/kratos/issues/2102)) ([c9490c8](https://github.com/ory/kratos/commit/c9490c8927209b686aafe54b8a16207a8ef47ebe)), closes [#2099](https://github.com/ory/kratos/issues/2099): Fixes the verification handler to write the error, instead of nil object, when the flow does not exist. Adds tests for every handler to check proper behavior in that regard. - Support setting complex configs from the environment ([c45bf83](https://github.com/ory/kratos/commit/c45bf83a9e6744a0b3f2f24e3b07a6f0131d9a40)): Closes https://github.com/ory/kratos/issues/1535 Closes https://github.com/ory/kratos/issues/1792 Closes https://github.com/ory/kratos/issues/1801 - Update download urls according to the new names ([#2078](https://github.com/ory/kratos/issues/2078)) ([86ae016](https://github.com/ory/kratos/commit/86ae0166c8893b809929c7c45a2ba84416ddf228)) ### Code Generation - Pin v0.8.3-alpha.1.pre.0 release commit ([b1f1da2](https://github.com/ory/kratos/commit/b1f1da2c0b4fbf6e6b4259c58b39a3e88e990142)) ### Code Refactoring - Deprecate domain aliases ([894a2cc](https://github.com/ory/kratos/commit/894a2cc39671fbc9d2c13b1fc1b45b217da5145d)) ### Documentation - Fix incorrect port ([c9a3587](https://github.com/ory/kratos/commit/c9a358717a99af436c6802f45c9c1f6edc77585f)), closes [#2095](https://github.com/ory/kratos/issues/2095) - Fix link ([c245ed4](https://github.com/ory/kratos/commit/c245ed40d443e3068bc5eee902e6b14f6ae777c6)): Closes https://github.com/ory/kratos-selfservice-ui-node/issues/164 - Ory cloud mentions + spelling ([#2100](https://github.com/ory/kratos/issues/2100)) ([0c2fa5b](https://github.com/ory/kratos/commit/0c2fa5bdb98b95877ef740297b6d96a931a3430f)) - Pagination ([#2143](https://github.com/ory/kratos/issues/2143)) ([0807a03](https://github.com/ory/kratos/commit/0807a03fba8ff9a3123cd038a472e90895502e82)), closes [#2039](https://github.com/ory/kratos/issues/2039) - Typo ([#2073](https://github.com/ory/kratos/issues/2073)) ([e1a54f9](https://github.com/ory/kratos/commit/e1a54f9129d41b34cc8864c8ac38d1448e1f9372)) - Typo ([#2114](https://github.com/ory/kratos/issues/2114)) ([a7a16d7](https://github.com/ory/kratos/commit/a7a16d7c91d89e274ea5fd79787cd4671d825532)) - Update docker guide ([072ca4d](https://github.com/ory/kratos/commit/072ca4d990cf4060555c8b2626f39ff18172d064)), closes [#2086](https://github.com/ory/kratos/issues/2086) - Upgrade guide ([#2132](https://github.com/ory/kratos/issues/2132)) ([4a4ab05](https://github.com/ory/kratos/commit/4a4ab05573ebb20f82f62bfd38767de68d7708e9)): Closes https://github.com/ory/kratos/discussions/2104 ### Features - Add preset CSP nonce ([#2096](https://github.com/ory/kratos/issues/2096)) ([8913292](https://github.com/ory/kratos/commit/8913292c1193c416e5a54997e3635bef87affc01)): Closes https://github.com/ory/kratos-selfservice-ui-node/issues/162 - Added phone number identifier ([#1938](https://github.com/ory/kratos/issues/1938)) ([294dfa8](https://github.com/ory/kratos/commit/294dfa85b4552b9266c44bb3376b8610c1ff5521)), closes [#137](https://github.com/ory/kratos/issues/137) - Allow registration to be disabled ([#2081](https://github.com/ory/kratos/issues/2081)) ([864b00d](https://github.com/ory/kratos/commit/864b00d6ecddefdb06ac22fda04670bfa43f2fd5)), closes [#882](https://github.com/ory/kratos/issues/882) - Courier templates fs support ([#2164](https://github.com/ory/kratos/issues/2164)) ([13689a7](https://github.com/ory/kratos/commit/13689a7135311a05b17383486f5fdab2e7a412d0)) - **courier:** Override default link base URL ([cc99096](https://github.com/ory/kratos/commit/cc99096d07408c8b713ef9a7b17b8345597a9129)): Added a new configuration value `selfservice.methods.link.config.base_url` which allows to change the default base URL of recovery and verification links. This is useful when the email should send a link which does not match the globally configured base URL. See https://github.com/ory-corp/cloud/issues/1766 - **docker:** Add jaeger ([27ec2b7](https://github.com/ory/kratos/commit/27ec2b74ee42697102c6a9a79bc5ca3c09756d94)) - Enable Buildkit ([#2079](https://github.com/ory/kratos/issues/2079)) ([f40df5c](https://github.com/ory/kratos/commit/f40df5cd932aa3185b2155368db51a49b7f05991)): Looks like this was attempted before but the magic comment was not on the first line. - Expose courier template load ([#2082](https://github.com/ory/kratos/issues/2082)) ([790716e](https://github.com/ory/kratos/commit/790716e58a4be06f04f3cbc5b974f16d873ae0d8)) - Generalise courier tests ([#2125](https://github.com/ory/kratos/issues/2125)) ([75c6053](https://github.com/ory/kratos/commit/75c60537e366760fe87b7b8978e9854873b7f702)) - Make the password policy more configurable ([#2118](https://github.com/ory/kratos/issues/2118)) ([70c627b](https://github.com/ory/kratos/commit/70c627b9feb3ec55765070b7c6c3fd64f2640e59)), closes [#970](https://github.com/ory/kratos/issues/970) - **security:** Add option to disallow private IP ranges in webhooks ([05f1e5a](https://github.com/ory/kratos/commit/05f1e5a99426ed54cb70514554e64d851f0ba8d6)), closes [#2152](https://github.com/ory/kratos/issues/2152) - Selfservice and administrative session management ([#2011](https://github.com/ory/kratos/issues/2011)) ([0fe4155](https://github.com/ory/kratos/commit/0fe4155b878102b77f7f13de5f0754ff75961498)), closes [#655](https://github.com/ory/kratos/issues/655) [#2007](https://github.com/ory/kratos/issues/2007) ### Tests - Update cypress ([#2090](https://github.com/ory/kratos/issues/2090)) ([883a1b1](https://github.com/ory/kratos/commit/883a1b1ea33a1d3ef8b33342328382b59e4f18c3)) # [0.8.2-alpha.1](https://github.com/ory/kratos/compare/v0.8.1-alpha.1...v0.8.2-alpha.1) (2021-12-17) This release addresses further important security updates in the base Docker Images. We also resolved all issues related to ARM support on both Linux and macOS and fixed a bug that prevent the binary from compiling on FreeBSD. This release also makes use of our new build architecture which means that the Docker Images names have changed. We removed the "scratch" images as we received frequent complaints about them. Additionally, all Docker Images have now, per default, SQLite support built-in. If you are relying on the SQLite images, update your Docker Pull commands as follows: ```patch - docker pull oryd/kratos:{version}-sqlite + docker pull oryd/kratos:{version} ``` Additionally, all passwords now have to be at least 8 characters long, following recommendations from Microsoft and others. In v0.8.1-alpha.1 we failed to include all the exciting things that landed, so we'll cover them now! 1. Advanced E-Mail templating support with sprig - makes it possible to translate emails as well! 2. Support wildcards for allowing redirection targets. 3. Account Recovery initiated by the Admin API now works even if identities have no email address. Enjoy this release! ### Bug Fixes - Add missing sample app paths to oathkeeper config ([#2058](https://github.com/ory/kratos/issues/2058)) ([a527db4](https://github.com/ory/kratos/commit/a527db4487c4efd2e96f8bf84d48a3cca30a14a1)): Add "welcome,registration,login,verification" and "\*\*.png" to the paths oathkeeper forwards to self service ui. - Add section on webauthn constraints ([#2072](https://github.com/ory/kratos/issues/2072)) ([23663b5](https://github.com/ory/kratos/commit/23663b50afce59cec2cfcaa4d3f50ae0abcf6310)) - After release hooks ([56c2e61](https://github.com/ory/kratos/commit/56c2e61195b6e6808ed76b9fd5dee0da1f489ce9)) - Dockerfile clean up ([52420cc](https://github.com/ory/kratos/commit/52420ccc17a8d395f0b13c0ad03ac334434c4b0e)), closes [#2070](https://github.com/ory/kratos/issues/2070) - Goreleaser after hook ([c763f2b](https://github.com/ory/kratos/commit/c763f2b394543a142f35b022d9c9d154c8e8489c)) - Goreleaser config ([7099af2](https://github.com/ory/kratos/commit/7099af20929ad003968e7fc9e47a4fe745984fbb)): See https://github.com/goreleaser/goreleaser/issues/2762 - Release hook ([90bd769](https://github.com/ory/kratos/commit/90bd7698380168b88ee301d9f343054052b208fd)) ### Code Generation - Pin v0.8.2-alpha.1 release commit ([627f4a1](https://github.com/ory/kratos/commit/627f4a1ddb378db84510a85013c4580a9d8024ad)) ### Documentation - Fix bodged release ([032b23a](https://github.com/ory/kratos/commit/032b23aba3fa04e5e2a638b78b806ca49a6a8e1c)) - Quickstart update ([#2060](https://github.com/ory/kratos/issues/2060)) ([3387cf6](https://github.com/ory/kratos/commit/3387cf6f111db5944fbff536fd0a9a67bc388f9a)), closes [#2032](https://github.com/ory/kratos/issues/2032) [#1916](https://github.com/ory/kratos/issues/1916) # [0.8.1-alpha.1](https://github.com/ory/kratos/compare/v0.8.0-alpha.4.pre.0...v0.8.1-alpha.1) (2021-12-13) This maintenance release important security updates for the base Docker Images (e.g. Alpine). Additionally, several hiccups with the new ARM support have been resolved and the binaries are now downloadable for all major platforms. Please note that passwords now have to be at least 8 characters long, following recommendations from Microsoft and others. Enjoy this release! ### Bug Fixes - Bodget docs commit ([f9d2f82](https://github.com/ory/kratos/commit/f9d2f8245bc94aaf21ddc9e5516b64e7887dae4b)) - Build docs on release ([2cf137a](https://github.com/ory/kratos/commit/2cf137a0540b81f4e405920cafd251db71d2f9fa)) - De-duplicate message IDs ([#1973](https://github.com/ory/kratos/issues/1973)) ([9d8e197](https://github.com/ory/kratos/commit/9d8e19720fcc2e5b5371c2ddea4e2501304a93fd)) - Docs links ([#2008](https://github.com/ory/kratos/issues/2008)) ([8515e17](https://github.com/ory/kratos/commit/8515e17938570770ca4cbf93028782925e28f431)) - Require minimum length of 8 characters password ([#2009](https://github.com/ory/kratos/issues/2009)) ([bb5846e](https://github.com/ory/kratos/commit/bb5846ecb446b9e58b2a4949c678fddac4bbac4f)): Kratos follows [NIST Digital Identity Guidelines - 5.1.1.2 Memorized Secret Verifiers](https://pages.nist.gov/800-63-3/sp800-63b.html) and [password policy](https://www.ory.sh/kratos/docs/concepts/security#password-policy) says > Passwords must have a minimum length of 8 characters and all characters > (unicode, ASCII) must be allowed. - Resolve freebsd build issue ([#2004](https://github.com/ory/kratos/issues/2004)) ([9c75fe9](https://github.com/ory/kratos/commit/9c75fe9e7ab4ff27f8d1f2399a58baaadefaaa0d)), closes [#1645](https://github.com/ory/kratos/issues/1645) - Revert tag ([f1d7b9e](https://github.com/ory/kratos/commit/f1d7b9e2db2cab4acdcaacbae06a85c42417b334)), closes [#1945](https://github.com/ory/kratos/issues/1945) - Set dockerfile ([c860b99](https://github.com/ory/kratos/commit/c860b992aee6a63d9696377ed9047e8cdeef0098)) - Skip docs publishing for pre releases ([eb6d8cd](https://github.com/ory/kratos/commit/eb6d8cdb2d3d400eb3b9398a15825ecdb10d3cf8)) - Support complex lifespans ([#2050](https://github.com/ory/kratos/issues/2050)) ([0edbebe](https://github.com/ory/kratos/commit/0edbebed896e79fd2979a54756932ea27c2ddb99)) - Update docs after release ([850be90](https://github.com/ory/kratos/commit/850be9065b64bcf268b42e4018f60b25a7a73da5)) - Verification error code ([#1967](https://github.com/ory/kratos/issues/1967)) ([44411ab](https://github.com/ory/kratos/commit/44411ab4ac5f184c7f42e6ece0ccb2ae7cbdc42c)), closes [#1956](https://github.com/ory/kratos/issues/1956) ### Code Generation - Pin v0.8.1-alpha.1 release commit ([8247416](https://github.com/ory/kratos/commit/82474161f61a3a22afad478838ffe8fe837d41ac)) ### Documentation - Add `Content-Type` to recommended CORS allowed headers ([#2015](https://github.com/ory/kratos/issues/2015)) ([dd890ab](https://github.com/ory/kratos/commit/dd890ab96727d7a2c8c2f52279dc3516096213f0)) - **debug:** Fix typo ([#1976](https://github.com/ory/kratos/issues/1976)) ([0647554](https://github.com/ory/kratos/commit/0647554179d7b0119ed01d353cd0ea9eb8317752)) - Fix incorrect tag ([bbd2355](https://github.com/ory/kratos/commit/bbd2355bbb220389021b596eec339a25652d932a)), closes [#2032](https://github.com/ory/kratos/issues/2032) [#2028](https://github.com/ory/kratos/issues/2028) - Fixed date format example ([#2038](https://github.com/ory/kratos/issues/2038)) ([fc4703a](https://github.com/ory/kratos/commit/fc4703aa34066a56fa3cf3b664a0d032157e477a)) - Improve text around bcrypt ([#2037](https://github.com/ory/kratos/issues/2037)) ([ba6981e](https://github.com/ory/kratos/commit/ba6981e344e880936b5e995c433dae85659ba780)) - Levenshtein-Distance has been released ([#2040](https://github.com/ory/kratos/issues/2040)) ([393b6b3](https://github.com/ory/kratos/commit/393b6b38cdc4758e838eec20e81d486662f7b4a7)) - Minor fixes ([#2010](https://github.com/ory/kratos/issues/2010)) ([12918db](https://github.com/ory/kratos/commit/12918dbf4b0edb2857e06736aee9cccf1a5f76ff)) - Password-strength meter has been dropped ([#2041](https://github.com/ory/kratos/issues/2041)) ([9848fb3](https://github.com/ory/kratos/commit/9848fb3b40c12799eafc73d2ec0f410bf5b22aa8)) - This has been done ([#2045](https://github.com/ory/kratos/issues/2045)) ([7e8c91a](https://github.com/ory/kratos/commit/7e8c91ace5229fdc394461b3453acb3f01da0a6c)) - Totp unlink image in 2fa docs ([#1957](https://github.com/ory/kratos/issues/1957)) ([7afb731](https://github.com/ory/kratos/commit/7afb731c15ebbd6bab54a133f2e80e938dd937d4)) - Update email template docs ([#1960](https://github.com/ory/kratos/issues/1960)) ([#1968](https://github.com/ory/kratos/issues/1968)) ([b0f25a9](https://github.com/ory/kratos/commit/b0f25a9a6013f1e450163f5c08b221d328c210be)) - Webhooks have landed ([#2035](https://github.com/ory/kratos/issues/2035)) ([80e53eb](https://github.com/ory/kratos/commit/80e53eb83d0dc84d2082ee343bfcecd2bfd99e13)) ### Features - Add alpine dockerfile ([587eaee](https://github.com/ory/kratos/commit/587eaeee60cab2f539af8f309800f5a6e9cdfe6f)) - Add x-total-count to paginated pages ([b633ec3](https://github.com/ory/kratos/commit/b633ec3da6ccca196cd9d78c3c43d9797bd8d982)) - Buildkit with multi stage build ([#2025](https://github.com/ory/kratos/issues/2025)) ([57ab7f7](https://github.com/ory/kratos/commit/57ab7f784674c2cef2b1cef4b6922e9834213e3d)) - **cmd:** Add OIDC credential include ([#2017](https://github.com/ory/kratos/issues/2017)) ([1482844](https://github.com/ory/kratos/commit/148284485db8a86aa10c5aefb34373f9a8c7d95a)): With this change, the `kratos identities get` CLI can additionally fetch OIDC credentials. - Generalise courier ([#2019](https://github.com/ory/kratos/issues/2019)) ([1762a73](https://github.com/ory/kratos/commit/1762a730886707be3549bc6789f65c66d755e1d0)) - **oidc:** Add spotify provider ([#2024](https://github.com/ory/kratos/issues/2024)) ([0064e35](https://github.com/ory/kratos/commit/0064e350ccb417fefee6f48ca5895f3d75247bb3)) ### Tests - Add web hook test cases ([#2051](https://github.com/ory/kratos/issues/2051)) ([316e940](https://github.com/ory/kratos/commit/316e940a70684084c857e80a2ffaf334a64aee94)) - **e2e:** Split e2e script into setup and test phase ([#2027](https://github.com/ory/kratos/issues/2027)) ([1761418](https://github.com/ory/kratos/commit/176141860f3aa946519073d0e35bf3acacd6c685)) - Fix changed message ID ([#2013](https://github.com/ory/kratos/issues/2013)) ([0bb66de](https://github.com/ory/kratos/commit/0bb66de582ebcb501c161655ae00e276a1d7d5d2)) # [0.8.0-alpha.4.pre.0](https://github.com/ory/kratos/compare/v0.8.0-alpha.3...v0.8.0-alpha.4.pre.0) (2021-11-09) autogen: pin v0.8.0-alpha.4.pre.0 release commit ## Breaking Changes To celebrate this change, we cleaned up the ways you install Ory software, and will roll this out to all other projects soon: There is now one central brew / bash curl repository: ```patch -brew install ory/kratos/kratos +brew install ory/tap/kratos -bash <(curl https://raw.githubusercontent.com/ory/kratos/master/install.sh) +bash <(curl https://raw.githubusercontent.com/ory/meta/master/install.sh) kratos ``` ### Bug Fixes - Add base64 to ReadSchema ([#1918](https://github.com/ory/kratos/issues/1918)) ([8c8815b](https://github.com/ory/kratos/commit/8c8815b7ced0051eb0120198ae75b8fcf0fce2ba)), closes [#1529](https://github.com/ory/kratos/issues/1529) - Add error.id to invalid cookie/token settings flow ([#1919](https://github.com/ory/kratos/issues/1919)) ([73610d4](https://github.com/ory/kratos/commit/73610d4cfb16789385d2660e278419664b1ea3f3)), closes [#1888](https://github.com/ory/kratos/issues/1888) - Adds missing webauthn authentication method ([#1914](https://github.com/ory/kratos/issues/1914)) ([44892f3](https://github.com/ory/kratos/commit/44892f379c1aa9ffd7f5c92c9c1b32cc34a0dada)) - Allow use of relative URLs in config ([#1754](https://github.com/ory/kratos/issues/1754)) ([5f73bb0](https://github.com/ory/kratos/commit/5f73bb0784aeb7c4f3b1ed949926f9d9aed968d1)), closes [#1446](https://github.com/ory/kratos/issues/1446) - Do not use csrf for meta endpoints ([#1927](https://github.com/ory/kratos/issues/1927)) ([fd14798](https://github.com/ory/kratos/commit/fd147989a55357248a37a30548c5d4c104bcf0f7)) - E2e test regression ([#1937](https://github.com/ory/kratos/issues/1937)) ([c9be009](https://github.com/ory/kratos/commit/c9be009112b03291ea76dd4de0911f495cf1e1ac)) - Include text label for link email field ([07a1dbb](https://github.com/ory/kratos/commit/07a1dbb95156ca50116219dc837ca61e3d597df1)), closes [#1909](https://github.com/ory/kratos/issues/1909) - Panic on webhook with nil body ([#1890](https://github.com/ory/kratos/issues/1890)) ([4bf1825](https://github.com/ory/kratos/commit/4bf18250373b7255e26e95d51a257e5280ad3148)), closes [#1885](https://github.com/ory/kratos/issues/1885) - Paths ([8c852c7](https://github.com/ory/kratos/commit/8c852c73136e130d163e2c9c5e0ca8a3449f4e26)) - Speed up git clone ([d3e4bde](https://github.com/ory/kratos/commit/d3e4bdefd252131b6a1b84917962ff07284e3f9f)) - Update sdk orb ([94e12e6](https://github.com/ory/kratos/commit/94e12e6d767ffa46d9060fdfb463adb83806990b)) - Use bcrypt for password hashing in example ([a9196f2](https://github.com/ory/kratos/commit/a9196f27791c30d32743e6b69a86595d76362f29)) - Use new ory installation method ([09cfc7e](https://github.com/ory/kratos/commit/09cfc7e2c23885270ef02193b4fdddc5550f3c23)) ### Code Generation - Pin v0.8.0-alpha.4.pre.0 release commit ([3e443b7](https://github.com/ory/kratos/commit/3e443b77ef63d72e5bf0b806790c86841a140afc)) ### Documentation - Add subdomain configuration in csrf page ([#1896](https://github.com/ory/kratos/issues/1896)) ([681750f](https://github.com/ory/kratos/commit/681750f92d7fe517e7cc184cb4b65e6a21903ee9)): Add some instructions as to how kratos can be configured to work across subdomains. - Remove unintended characters in subdomain section in csrf page ([#1897](https://github.com/ory/kratos/issues/1897)) ([dfb9007](https://github.com/ory/kratos/commit/dfb900797fc98ca7900631ccf8018858c4e43e85)) ### Features - Add new goreleaser build chain ([#1932](https://github.com/ory/kratos/issues/1932)) ([cf1714d](https://github.com/ory/kratos/commit/cf1714dafaa0cda98640c772106620586dae7763)): This patch adds full compatibility with ARM architectures, including Apple Silicon (M1). We additionally added cryptographically signed signatures verifiable using [cosign](https://github.com/sigstore/cosign) for both binaries as well as docker images. - Add quickstart mimicking hosted ui ([813fb4c](https://github.com/ory/kratos/commit/813fb4cf48df1154ea334cca751cb55f7b3c77eb)) - Advanced e-mail templating support ([#1859](https://github.com/ory/kratos/issues/1859)) ([54b97b4](https://github.com/ory/kratos/commit/54b97b45506eff9cfafe338842ddf818b0c81f62)), closes [#834](https://github.com/ory/kratos/issues/834) [#925](https://github.com/ory/kratos/issues/925) - Allow wildcard domains for redirect_to checks ([#1528](https://github.com/ory/kratos/issues/1528)) ([349cdcf](https://github.com/ory/kratos/commit/349cdcf4b1298d9e544344705ecd8e7b5eada48c)), closes [#943](https://github.com/ory/kratos/issues/943): Support wildcard domains in redirect_to checks. - Configurable health endpoints access logging ([#1934](https://github.com/ory/kratos/issues/1934)) ([1301f68](https://github.com/ory/kratos/commit/1301f689bb0f1f44b66a057c8915f77ac71f30cc)): This PR introduces a new boolean configuration parameter that allows turning off logging of health endpoints requests in the access log. The implementation is basically a rip-off from Ory Hydra and the configuration parameter is the same: ``` serve.public.request_log.disable_for_health serve.admin.request_log.disable_for_health ``` The default value is _false_. - Integrate sbom generation to goreleaser ([#1850](https://github.com/ory/kratos/issues/1850)) ([305bb28](https://github.com/ory/kratos/commit/305bb28d689dabc4d211baac5e6babd34862af5f)) - Make admin recovery to work without emails [#1419](https://github.com/ory/kratos/issues/1419) ([#1750](https://github.com/ory/kratos/issues/1750)) ([db00e85](https://github.com/ory/kratos/commit/db00e85e65c31b2bc497f0f4b4a28684b9f8bb9a)) ### Tests - **e2e:** Improved SDK set up and arm fix ([#1933](https://github.com/ory/kratos/issues/1933)) ([c914ba1](https://github.com/ory/kratos/commit/c914ba10a85e89c031e7acfb73bf22c53201e287)) - Update snapshots ([a820653](https://github.com/ory/kratos/commit/a820653718475656b7ae44a1bc7235a8fb97b8b5)) # [0.8.0-alpha.3](https://github.com/ory/kratos/compare/v0.8.0-alpha.2...v0.8.0-alpha.3) (2021-10-28) Resolves issues in the quickstart. ### Bug Fixes - Resolve quickstart issues ([#1900](https://github.com/ory/kratos/issues/1900)) ([d047009](https://github.com/ory/kratos/commit/d0470095f3263e287f76e8be0abb8df332492dd9)): Closes https://github.com/ory/kratos/discussions/1899 ### Code Generation - Pin v0.8.0-alpha.3 release commit ([a307deb](https://github.com/ory/kratos/commit/a307deb6779dacd2ce54e161a00d347600d2c583)) # [0.8.0-alpha.2](https://github.com/ory/kratos/compare/v0.8.0-alpha.1...v0.8.0-alpha.2) (2021-10-28) Resolves an issue in the SDK release pipeline. ### Code Generation - Pin v0.8.0-alpha.2 release commit ([2178929](https://github.com/ory/kratos/commit/217892978c4fa9897a88b140276c2d27622c5de4)) # [0.8.0-alpha.1](https://github.com/ory/kratos/compare/v0.7.6-alpha.1...v0.8.0-alpha.1) (2021-10-27) We are extremely excited to share this next generation of Ory Kratos! The project is truly maturing and the community is getting larger by the hour. On this special occasion, we would like to bring to your attention that the [**Ory Summit is happening tomorrow and on Friday!**](https://events.hubilo.com/ory-summit/register?mtm_campaign=ory-summit-2021&mtm_kwd=banner-landingpage) You will hear gripping talks from the Ory Community and Ory maintainers! And the best part, tickets are free and we are covering multiple time zones! This release is truly the best version of Ory Kratos to date and we want to give you a tl;dr of the 345 commits and 1152 files changed, and what you can expect from this release: - Full multi-factor authentication with different enforcement policies (soft/hard MFA). - Support for WebAuthn (FIDO2 / U2F) two-factor authentication - from fingerprints to hardware tokens every FIDO2 device is supported! - Ability to fetch the initial OAuth2 Access and Refresh and OpenID Connect ID Tokens an identity receives when performing social sign up. Optionally, these tokens are stored encrypted in the database (XChaCha20Poly1305 or AES-GCM)! - Support for TOTP (Google Authenticator) two-factor verification/authentication. - Advanced two-factor recovery with lookup secrets. - [A complete reference implementation of the Ory Kratos end-user (self-service) facing UI in ReactJS & VercelJS](https://github.com/ory/kratos-react-nextjs-ui). - "Native" support for Single-Page App Single Sign-On. - Much improved single-page app and native app APIs for all self-service flows. - Support for PKBDF2 password hashing, which will help import user passwords from other systems in the future. - Bugfixes and improvements to the OpenAPI spec and auto-generated SDKs. - ARM Docker Images. - Greatly improved internal e2e test pipeline using Cypress 8.x. - Improved functional tests with cupaloy snapshot testing. - Documentation on different error codes and message identifiers to easier translate messages in your own UI. - Better form decoding and ability to mark required JSON Schema fields as required in the UI. - Bug fixes that could result in users ending up in irrecoverable UI states. - Better support for `return_to` across flows (e.g. OIDC) and in custom UIs. - SBOM Software Supply Chain scanning & reporting. - Docker Image vulnerability checking as part of the release pipeline. - Support sending emails via AWS SES SMTP. - A REST endpoint to invalidate all an identity's sessions. As you can see, much has happened and we are grateful for all the great interactions we have with you, every day! Let's take a look at some of the breaking changes. Even though much was added, little has changed in breaking ways! This is a testament that Ory Kratos' internals and APIs are becoming more stable! This release requires you to run SQL migrations. Please, as always, create a backup of your database first! The SDKs are now generated with tag v0alpha2 to reflect that some signatures have changed in a breaking fashion. Please update your imports from `v0alpha1` to `v0alpha2`. The SMTPS scheme used in courier config URL with cleartext/StartTLS/TLS SMTP connection types is now only supporting implicit TLS. For StartTLS and cleartext SMTP, please use the SMTP scheme instead. Example: - SMTP Cleartext: `smtp://foo:bar@my-mailserver:1234/?disable_starttls=true` - SMTP with StartTLS: `smtps://foo:bar@my-mailserver:1234/` -> `smtp://foo:bar@my-mailserver:1234/` - SMTP with implicit TLS: `smtps://foo:bar@my-mailserver:1234/?legacy_ssl=true` -> `smtps://foo:bar@my-mailserver:1234/We are extremely excited to share this next generation of Ory Kratos! The project is truly maturing and the community is getting larger by the hour. On this special occasion, we would like to bring to your attention that the [**Ory Summit is happening tomorrow and on Friday!**](https://events.hubilo.com/ory-summit/register?mtm_campaign=ory-summit-2021&mtm_kwd=banner-landingpage) You will hear gripping talks from the Ory Community and Ory maintainers! And the best part, tickets are free and we are covering multiple time zones! This release is truly the best version of Ory Kratos to date and we want to give you a tl;dr of the 345 commits and 1152 files changed, and what you can expect from this release: - Full multi-factor authentication with different enforcement policies (soft/hard MFA). - Support for WebAuthn (FIDO2 / U2F) two-factor authentication - from fingerprints to hardware tokens every FIDO2 device is supported! - Ability to fetch the initial OAuth2 Access and Refresh and OpenID Connect ID Tokens an identity receives when performing social sign up. Optionally, these tokens are stored encrypted in the database (XChaCha20Poly1305 or AES-GCM)! - Support for TOTP (Google Authenticator) two-factor verification/authentication. - Advanced two-factor recovery with lookup secrets. - [A complete reference implementation of the Ory Kratos end-user (self-service) facing UI in ReactJS & VercelJS](https://github.com/ory/kratos-react-nextjs-ui). - "Native" support for Single-Page App Single Sign-On. - Much improved single-page app and native app APIs for all self-service flows. - Support for PKBDF2 password hashing, which will help import user passwords from other systems in the future. - Bugfixes and improvements to the OpenAPI spec and auto-generated SDKs. - ARM Docker Images. - Greatly improved internal e2e test pipeline using Cypress 8.x. - Improved functional tests with cupaloy snapshot testing. - Documentation on different error codes and message identifiers to easier translate messages in your own UI. - Better form decoding and ability to mark required JSON Schema fields as required in the UI. - Bug fixes that could result in users ending up in irrecoverable UI states. - Better support for `return_to` across flows (e.g. OIDC) and in custom UIs. - SBOM Software Supply Chain scanning & reporting. - Docker Image vulnerability checking as part of the release pipeline. - Support sending emails via AWS SES SMTP. - A REST endpoint to invalidate all an identity's sessions. As you can see, much has happened and we are grateful for all the great interactions we have with you, every day! Let's take a look at some of the breaking changes. Even though much was added, little has changed in breaking ways! This is a testament that Ory Kratos' internals and APIs are becoming more stable! This release requires you to run SQL migrations. Please, as always, create a backup of your database first! The SDKs are now generated with tag v0alpha2 to reflect that some signatures have changed in a breaking fashion. Please update your imports from `v0alpha1` to `v0alpha2`. The SMTPS scheme used in courier config URL with cleartext/StartTLS/TLS SMTP connection types is now only supporting implicit TLS. For StartTLS and cleartext SMTP, please use the SMTP scheme instead. Example: - SMTP Cleartext: `smtp://foo:bar@my-mailserver:1234/?disable_starttls=true` - SMTP with StartTLS: `smtps://foo:bar@my-mailserver:1234/` -> `smtp://foo:bar@my-mailserver:1234/` - SMTP with implicit TLS: `smtps://foo:bar@my-mailserver:1234/?legacy_ssl=true` -> `smtps://foo:bar@my-mailserver:1234/We are extremely excited to share this next generation of Ory Kratos! The project is truly maturing and the community is getting larger by the hour. On this special occasion, we would like to bring to your attention that the [**Ory Summit is happening tomorrow and on Friday!**](https://events.hubilo.com/ory-summit/register?mtm_campaign=ory-summit-2021&mtm_kwd=banner-landingpage) You will hear gripping talks from the Ory Community and Ory maintainers! And the best part, tickets are free and we are covering multiple time zones! This release is truly the best version of Ory Kratos to date and we want to give you a tl;dr of the 345 commits and 1152 files changed, and what you can expect from this release: - Full multi-factor authentication with different enforcement policies (soft/hard MFA). - Support for WebAuthn (FIDO2 / U2F) two-factor authentication - from fingerprints to hardware tokens every FIDO2 device is supported! - Ability to fetch the initial OAuth2 Access and Refresh and OpenID Connect ID Tokens an identity receives when performing social sign up. Optionally, these tokens are stored encrypted in the database (XChaCha20Poly1305 or AES-GCM)! - Support for TOTP (Google Authenticator) two-factor verification/authentication. - Advanced two-factor recovery with lookup secrets. - [A complete reference implementation of the Ory Kratos end-user (self-service) facing UI in ReactJS & VercelJS](https://github.com/ory/kratos-react-nextjs-ui). - "Native" support for Single-Page App Single Sign-On. - Much improved single-page app and native app APIs for all self-service flows. - Support for PKBDF2 password hashing, which will help import user passwords from other systems in the future. - Bugfixes and improvements to the OpenAPI spec and auto-generated SDKs. - ARM Docker Images. - Greatly improved internal e2e test pipeline using Cypress 8.x. - Improved functional tests with cupaloy snapshot testing. - Documentation on different error codes and message identifiers to easier translate messages in your own UI. - Better form decoding and ability to mark required JSON Schema fields as required in the UI. - Bug fixes that could result in users ending up in irrecoverable UI states. - Better support for `return_to` across flows (e.g. OIDC) and in custom UIs. - SBOM Software Supply Chain scanning & reporting. - Docker Image vulnerability checking as part of the release pipeline. - Support sending emails via AWS SES SMTP. - A REST endpoint to invalidate all an identity's sessions. As you can see, much has happened and we are grateful for all the great interactions we have with you, every day! Let's take a look at some of the breaking changes. Even though much was added, little has changed in breaking ways! This is a testament that Ory Kratos' internals and APIs are becoming more stable! This release requires you to run SQL migrations. Please, as always, create a backup of your database first! The SDKs are now generated with tag v0alpha2 to reflect that some signatures have changed in a breaking fashion. Please update your imports from `v0alpha1` to `v0alpha2`. The SMTPS scheme used in courier config URL with cleartext/StartTLS/TLS SMTP connection types is now only supporting implicit TLS. For StartTLS and cleartext SMTP, please use the SMTP scheme instead. Example: - SMTP Cleartext: `smtp://foo:bar@my-mailserver:1234/?disable_starttls=true` - SMTP with StartTLS: `smtps://foo:bar@my-mailserver:1234/` -> `smtp://foo:bar@my-mailserver:1234/` - SMTP with implicit TLS: `smtps://foo:bar@my-mailserver:1234/?legacy_ssl=true` -> `smtps://foo:bar@my-mailserver:1234/` ## Breaking Changes The location of the homebrew tap has changed from `ory/ory/kratos` to `ory/tap/kratos`. To stay consistent with other query parameter's, the self-service login flow's `forced` key has been renamed to `refresh`. The SDKs are now generated with tag v0alpha2 to reflect that some signatures have changed in a breaking fashion. Please update your imports from `v0alpha1` to `v0alpha2`. To support 2FA on non-browser (e.g. native mobile) apps we have added the Ory Session Token as a possible parameter to both `initializeSelfServiceLoginFlowWithoutBrowser` and `submitSelfServiceLoginFlow`. Depending on the SDK generator, the order of the arguments may have changed. In JavaScript: ```patch - .submitSelfServiceLoginFlow(flow.id, payload) + .submitSelfServiceLoginFlow(flow.id, sessionToken, payload) + // or if the user has no session yet: + .submitSelfServiceLoginFlow(flow.id, undefined, payload) ``` To improve the overall API design we have changed the result of `POST /self-service/settings`. Instead of having flow be a key, the flow is now the response. The updated identity payload stays the same! ```patch { - "flow": { - "id": "flow-id-..." - ... - }, + "id": "flow-id-..." + ... "identity": { "id": "identity-id-..." } } ``` The SMTPS scheme used in courier config url with cleartext/StartTLS/TLS SMTP connection types is now only supporting implicit TLS. For StartTLS and cleartext SMTP, please use the smtp scheme instead. Example: - SMTP Cleartext: `smtp://foo:bar@my-mailserver:1234/?disable_starttls=true` - SMTP with StartTLS: `smtps://foo:bar@my-mailserver:1234/` -> `smtp://foo:bar@my-mailserver:1234/` - SMTP with implicit TLS: `smtps://foo:bar@my-mailserver:1234/?legacy_ssl=true` -> `smtps://foo:bar@my-mailserver:1234/` This patch changes the naming and number of prometheus metrics (see: https://github.com/ory/x/pull/379). In short: all metrics will have now `http_` prefix to conform to Prometheus best practices. ### Bug Fixes - Add error id ([1442784](https://github.com/ory/kratos/commit/1442784264d1f5032830a0646b853b925bb19c62)) - Add mfa e2e test scenarios and resolve found issues ([436992d](https://github.com/ory/kratos/commit/436992ddf2ace68b247c708fc955fccb95cf6fd2)) - Add middleware earlier [#1775](https://github.com/ory/kratos/issues/1775) ([#1776](https://github.com/ory/kratos/issues/1776)) ([b9d253e](https://github.com/ory/kratos/commit/b9d253ef05ff7cd616111a817d03a17e39f8f4a8)) - Allow refresh and aal upgrade at the same time ([2ec801f](https://github.com/ory/kratos/commit/2ec801f262cd8f6dcdf8121a20897257e3b74ad3)) - API client leaks stack trace with an error ([#1772](https://github.com/ory/kratos/issues/1772)) ([d3aff6d](https://github.com/ory/kratos/commit/d3aff6d3eb11942fbfd6f2de71f4399053075b62)), closes [#1771](https://github.com/ory/kratos/issues/1771) - Better const handling for internal context ([1e457e3](https://github.com/ory/kratos/commit/1e457e3b3dea9ea9a05c12740578af2d45902aba)) - Correct swagger path for /identities/:id/session endpoint ([#1756](https://github.com/ory/kratos/issues/1756)) ([d614f2a](https://github.com/ory/kratos/commit/d614f2a737eef90ad60a4bdedae248b74131ff35)) - Decoder regression in registration ([febf75a](https://github.com/ory/kratos/commit/febf75ae959a2b67c19fcd1705b591f22ff5314b)) - Deterministic clidoc dates ([e48d90a](https://github.com/ory/kratos/commit/e48d90ad5a178ab3317d89800526c516aad6e274)) - Disable totp per default ([7278589](https://github.com/ory/kratos/commit/7278589ff2460a13302650b5e3fae01d774f9684)) - Docs autogen should not use `time.Now` ([a830f5b](https://github.com/ory/kratos/commit/a830f5b3b535bc375e879c797626b6084b76776e)) - Ensure correct error propagation ([77ce709](https://github.com/ory/kratos/commit/77ce709d53d88f70c892ab0892c13e16f5b761a5)) - Ensure refresh issues a new session when the identity changes ([a10b385](https://github.com/ory/kratos/commit/a10b385510a0102ede5850f9be30b7deba810acf)) - Ensure return_to works for OIDC flows ([d615734](https://github.com/ory/kratos/commit/d615734c312db6f7fa48fb8c7b4090a80c9e5ce7)), closes [#1773](https://github.com/ory/kratos/issues/1773) - Explicit validation for return to in new flows ([284cf29](https://github.com/ory/kratos/commit/284cf29a6be82530b55c24a15c465ec9f1b6a210)) - Follow chrome webauthn best practice recommendation ([0a7c812](https://github.com/ory/kratos/commit/0a7c8128bb0b78f8dc236af06ca9be038b201829)) - Githup-app name in config ([#1822](https://github.com/ory/kratos/issues/1822)) ([1b50963](https://github.com/ory/kratos/commit/1b50963525ceaceea9afb8d1236d728de3107a8e)) - Handle return errors on the frontend and break early ([0e8d481](https://github.com/ory/kratos/commit/0e8d481cc220777aa56faf2e716da15537fa27fc)): Closes https://github.com/ory-corp/cloud/issues/1426 - Identity credential identifiers are now unique per method ([57fd99a](https://github.com/ory/kratos/commit/57fd99ac05d29fc0362f14e5910641944232d61e)) - Improve schema validation error tracing ([f793fe5](https://github.com/ory/kratos/commit/f793fe56182f3f195a57fe5f4b54f7fcf8402c81)) - Incorrect JSON response for browser flows ([1501f56](https://github.com/ory/kratos/commit/1501f5627ed12d2d149f1fcf49fcf326120e6b0b)) - Kill modd as well ([e5a98e5](https://github.com/ory/kratos/commit/e5a98e54ec68f122615dd902df9ebac788fdb579)) - **link:** Resolve incorrect response types when opening API recovery link in browser ([35ea8db](https://github.com/ory/kratos/commit/35ea8db300c2d3eeaf7d8f0e29c604ecc455cd2b)) - **login:** Properly handle refresh ([8dc7059](https://github.com/ory/kratos/commit/8dc7059222fa12dd0bca0183f42306b5169addb6)) - **lookup:** Ensure correct fields are set ([5ed4c55](https://github.com/ory/kratos/commit/5ed4c5572f9cbb35461e45dfc6b7c5eb4bce7434)) - **lookup:** Resolve reuse scenarios ([dbfe475](https://github.com/ory/kratos/commit/dbfe475ba5f0d2b9d4b0b67d0d8e7cb99e89ad5d)) - **lookup:** Set up codes correctly ([2f373f3](https://github.com/ory/kratos/commit/2f373f344326fbd5dbebf6233dbf5b56252b7e95)) - OIDC provider field in spec ([#1809](https://github.com/ory/kratos/issues/1809)) ([11b25de](https://github.com/ory/kratos/commit/11b25deb46b73c7d0ab95a77ff2ab60c032c1942)) - **oidc:** Ensure nested keys work on login ([71583c5](https://github.com/ory/kratos/commit/71583c57f1334bee1e5c9be1fae6a1b241ea3d6d)) - Omitempty for VerifiedAt and StateChangedAt ([#1736](https://github.com/ory/kratos/issues/1736)) ([bf2ec6e](https://github.com/ory/kratos/commit/bf2ec6e6ae8d656ea6dcac037dedd3603ad12915)): Closes https://github.com/ory/sdk/issues/95 - Only respect required modules for SDK ([4c5677f](https://github.com/ory/kratos/commit/4c5677f3ea48bd87e5d7a1f95e3807b7884a0b64)) - Panic when recovering deactivated user ([0a49f27](https://github.com/ory/kratos/commit/0a49f2714991a3f397dc5c721fe22d11846d3db5)), closes [#1794](https://github.com/ory/kratos/issues/1794) [#1826](https://github.com/ory/kratos/issues/1826) - Potentially resolve hanging postgres connection closing ([693a928](https://github.com/ory/kratos/commit/693a9286b02c2329dcfd358a038857901193b459)) - Properly encode aal error ([49b6288](https://github.com/ory/kratos/commit/49b6288c2345840a7517272e9616c2c20a254edb)) - Properly open recovery endpoints in browser if flow was initiated via API ([23c12e5](https://github.com/ory/kratos/commit/23c12e55d24591ca69c9178017355a9262fa35eb)) - Remove duplicate schema error ([4e69123](https://github.com/ory/kratos/commit/4e691238da3bf3ee8d9a92d4d9507b27fce20199)) - Remove initial_value again as it was not useful outside of booleans ([0cc984b](https://github.com/ory/kratos/commit/0cc984b85baff3db500fb656bd541cfa0396df98)) - Remove obsolete openapi patch ([11618ec](https://github.com/ory/kratos/commit/11618ecc6681a9108ee70a3e0d1ab3d21e33f9db)) - Remove unnecessary cmd reference ([351760e](https://github.com/ory/kratos/commit/351760ece01d421687179b8e3f6f48a720247a1d)) - Replace 302 with 303 ([2e2b0f8](https://github.com/ory/kratos/commit/2e2b0f840450c6d23f3e51e5885d0908685ef3f6)) - Resolve clidoc generation issue ([1aaaa03](https://github.com/ory/kratos/commit/1aaaa035f863852799575e1f65e9d9ed276a3160)) - Resolve merge issues ([1dc7497](https://github.com/ory/kratos/commit/1dc74976c785afca8079379cd5060116b5f3d831)) - Resolve openapi issues and regenerate clients ([f7d60c0](https://github.com/ory/kratos/commit/f7d60c02392d2ad664c73ee4ff6bb108a4cb04e2)) - Resolve swagger regression ([02b9d47](https://github.com/ory/kratos/commit/02b9d470df012ae9818a8516a5549aee83c0963d)) - Run format on ts files ([f55f6f6](https://github.com/ory/kratos/commit/f55f6f69bf0df88d001fda791b330bdcbf5d92b2)) - Slow CLI start-up time ([ae20c17](https://github.com/ory/kratos/commit/ae20c17777eb57363f811b57d782db88b2de91ae)): Found a deeply nested dependency which was importing `https://github.com/markbates/pkger`, causing unreasonable CPU consumption and significant delay at start up time. With this patch, start up time was reduced from almost 3s to ~0.01s. ``` $ time kratos kratos 2.55s user 2.46s system 508% cpu 0.986 total $ time ./kratos-patch ./kratos-patch 0.00s user 0.00s system 64% cpu 0.001 total ``` - **test:** OIDC storategy test ([#1836](https://github.com/ory/kratos/issues/1836)) ([b877dbe](https://github.com/ory/kratos/commit/b877dbecaf84e2d102bcceff4ad85c5b4efe18c5)) - **totp:** Reorder QR ([d096df7](https://github.com/ory/kratos/commit/d096df734ba8cf7dcfb872af03a19550d320c8b7)) - Try and reduce cookie flakyness ([e7ae8d6](https://github.com/ory/kratos/commit/e7ae8d63a16df69fd43afdf41691b9c1d3efe439)) - Typo ([8c4d8a2](https://github.com/ory/kratos/commit/8c4d8a2284f7a52a2dca7e7fd5e686756d410647)) - **ui:** Use correct type for anchor ([a6595e4](https://github.com/ory/kratos/commit/a6595e49c38a302f4a603dd46f5a0764680a24b1)) - Update schema config location ([539ae73](https://github.com/ory/kratos/commit/539ae7303158f14ca42165c12f9d3e8ef9dcdbdf)) - Use parallelism of 1 in go test ([8736334](https://github.com/ory/kratos/commit/8736334bf11fc9a742e2972aa97ee56c407c7c0c)) - **webauthn:** Support react-based webauth ([b6123b4](https://github.com/ory/kratos/commit/b6123b4840547b295be44272e76454462a0f60c4)) - X-session-token must not be mandatory ([05d73be](https://github.com/ory/kratos/commit/05d73beed26f1be31c6f2a62499c7c71d7d54bec)) ### Code Generation - Pin v0.8.0-alpha.1 release commit ([c2c902c](https://github.com/ory/kratos/commit/c2c902c1bd8d910843d747c25b99ee1bcc6f962d)) ### Code Refactoring - **courier:** Support SMTP schemes for implicit TLS, explicit StartTLS, and cleartext SMTP ([#1831](https://github.com/ory/kratos/issues/1831)) ([4cb082c](https://github.com/ory/kratos/commit/4cb082ce1e15ddd1d992a2def9e7d6410142cc02)), closes [#1770](https://github.com/ory/kratos/issues/1770) [#1769](https://github.com/ory/kratos/issues/1769) - Homogenize error messages ([421a319](https://github.com/ory/kratos/commit/421a3190d1d4f6f5d96ef8ad87c3a2a667b57a28)) - Improved prometheus metrics ([#1830](https://github.com/ory/kratos/issues/1830)) ([0be993b](https://github.com/ory/kratos/commit/0be993bebeb9e50d90806ad13f60bb8d72c3b2d3)), closes [#1735](https://github.com/ory/kratos/issues/1735): This will add new prometheus metrics for Kratos that are more useful for alerting and increase overall observability. - Login flow `forced` renamed to `refresh` ([92087e5](https://github.com/ory/kratos/commit/92087e5f00b4fcce1706442c9edf1b466f9a23c9)) - **login:** Rename forced -> refresh ([8d1e54b](https://github.com/ory/kratos/commit/8d1e54bd79cf617985602997f1121e168f58c389)) - **login:** Support 2FA for non-browser SDKs ([df4846d](https://github.com/ory/kratos/commit/df4846d3867599f49e58b6b4d59b338916f37cbf)) - Move expired error into top-level flow module ([01a2602](https://github.com/ory/kratos/commit/01a26025375f1d958a7e345c61fb6ba5e3403efe)) - Move homebrew tap to ory/tap ([0ee67c3](https://github.com/ory/kratos/commit/0ee67c388a1fea8aa9633cbf684e1f62e16d61cc)) - Move node identifiers to node package ([b0a86dc](https://github.com/ory/kratos/commit/b0a86dc6e5005017a9a0fa2120560f668ab2432f)) - Revert decision to return 422 errors and streamline 401/403 ([8aa5318](https://github.com/ory/kratos/commit/8aa53187f1e78d693463a47fcd9aedab30d1b55f)) - Sdk API is no v0alpha2 ([3f06738](https://github.com/ory/kratos/commit/3f067386e32ad3baeec48fd21dd51659a5725970)) - **session:** CreateAndIssueCookie is now UpsertAndIssueCookie ([a6d134d](https://github.com/ory/kratos/commit/a6d134de7710c7e92e51f735f13b7757eb7011e5)) - **session:** CreateSession is now UpsertSession ([3ec81a2](https://github.com/ory/kratos/commit/3ec81a2cc401ff18052abd2a9ba060e665f0baa2)) - **settings:** Change settings success response ([12f98f2](https://github.com/ory/kratos/commit/12f98f2884294669bbb7eab7e8ed73a5372386f6)) ### Documentation - Add 2fa credentials ([f7899a7](https://github.com/ory/kratos/commit/f7899a761aaf59d2cfddc2c330a805456cfca947)) - Add 2fa guide ([b4eed76](https://github.com/ory/kratos/commit/b4eed76305ecf1de3461525fd2ea748ec94da53c)) - Add a commandline example for the logout ([#1753](https://github.com/ory/kratos/issues/1753)) ([81ba264](https://github.com/ory/kratos/commit/81ba2647a66fca99b7ed2e56a67deec75ac06b89)) - Add admin ui guide ([ac88060](https://github.com/ory/kratos/commit/ac88060ed7390f0a34db637880c1660b8c45b352)) - Add advanced custom UI documentation ([5e3a2cd](https://github.com/ory/kratos/commit/5e3a2cdbedf0005c89db717d1136c56ab3304ede)) - Add image assets ([6bc93ca](https://github.com/ory/kratos/commit/6bc93ca79283bd993b0176dda11ad9d5860a5e4f)) - Add missing angle bracket ([#1799](https://github.com/ory/kratos/issues/1799)) ([4270140](https://github.com/ory/kratos/commit/427014052ef905c2003e2cd0133d57bf83819776)) - Add ory sessions as a concept ([626c0c9](https://github.com/ory/kratos/commit/626c0c90bd2d683048618452ba421e40be92f587)) - Add powershell to deps ([#1853](https://github.com/ory/kratos/issues/1853)) ([e945336](https://github.com/ory/kratos/commit/e94533690b658c4afba81e694a052579f0ffff42)), closes [#1848](https://github.com/ory/kratos/issues/1848) - **credentials:** Add AAL explanation ([c1f501e](https://github.com/ory/kratos/commit/c1f501e9ec3ba203fb252fbd56cb87843d667b17)) - Enhance error return values ([3799c24](https://github.com/ory/kratos/commit/3799c24fbc0397876df4f1c530e325bd1212d750)) - Fix invalid syntax ([#1819](https://github.com/ory/kratos/issues/1819)) ([8cd6428](https://github.com/ory/kratos/commit/8cd6428e40610fa40b9c59414beb3d5c614dddaa)) - Fix the flow links used for rendering ([#1752](https://github.com/ory/kratos/issues/1752)) ([131d2c2](https://github.com/ory/kratos/commit/131d2c284d4191ee979077937ea3b48fce772f3c)) - Fix the invalid links ([#1868](https://github.com/ory/kratos/issues/1868)) ([6d621ec](https://github.com/ory/kratos/commit/6d621ec89d1a7c37daf4622b06a0ad94f2d77b31)) - Remove obsolete file ([b7f9052](https://github.com/ory/kratos/commit/b7f905278edf4aed1e2984aa3d2d94a41368d6d8)) - Update generated docs ([72afb81](https://github.com/ory/kratos/commit/72afb81be8bfaa36236087ec7715bca1804aa62c)) - Update quickstart curl examples ([#1778](https://github.com/ory/kratos/issues/1778)) ([6c677c4](https://github.com/ory/kratos/commit/6c677c49df8fa8d48e7c0bbf91bbd18874f4c514)) - Use correct link ([f007919](https://github.com/ory/kratos/commit/f007919b7bd86c1d1b20b3625709e01b5f123302)), closes [#1793](https://github.com/ory/kratos/issues/1793) ### Features - Add `intended_for_someone_else` error code ([572a131](https://github.com/ory/kratos/commit/572a1315aec7d1103c8d4fb9c128644ea2af6d3b)) - Add aal fallback for existing sessions ([a5c7b11](https://github.com/ory/kratos/commit/a5c7b1143bca7029bf94fc42fe638534961a06bc)) - Add authenticators after set up ([035c276](https://github.com/ory/kratos/commit/035c276152a22a2c9c7159b1cf89dbe7724728dd)) - Add DeleteCredentialsType to identity struct including tests ([b12bf52](https://github.com/ory/kratos/commit/b12bf523e4213e49f545206c457a1739f493d385)) - Add e2e tests for react native 2fa ([a3ac253](https://github.com/ory/kratos/commit/a3ac253bdb9c42df6dce9288a2e7c2dada24d255)) - Add error ids for csrf-related errors ([dc2adbf](https://github.com/ory/kratos/commit/dc2adbf52f7ee845778ee5c3c943b9e10c41e181)) - Add error ids for redirect-related errors ([246a045](https://github.com/ory/kratos/commit/246a0453e65e70635331c95ff02ec9133ae81e46)) - Add error ids for session-related errors ([087d907](https://github.com/ory/kratos/commit/087d90731185b71cd88cc6451ce360d6c1dada34)) - Add explicit return_to to flow objects and API parameters ([50d04ea](https://github.com/ory/kratos/commit/50d04eaa455932a9a5cc31f812f66518e1d4ad3b)), closes [#1605](https://github.com/ory/kratos/issues/1605) [#1121](https://github.com/ory/kratos/issues/1121): This patch adds a `return_to` field to the flow objects which contains the original `?return_to=...` value. It uses the Flow's `request_url` for that purpose. - Add ids for user-facing errors for login, registration, settings ([787558b](https://github.com/ory/kratos/commit/787558b48fd7405ac61a48d3c18c7252ac1aaf19)): This patch adds a new field `id` to JSON error payloads. This helps tremendously in implementing better client-side (native / SPA) apps as the API now returns error IDs like `no_active_session`, `orbidden_return_to`, `no_verified_address` and more. UIs can use these IDs to decide what to do next in the application - for example redirecting to a particular endpoint or showing an error message. - Add initial value to bool checkboxes ([63dba73](https://github.com/ory/kratos/commit/63dba737376dbe2f15c5afb5df22c593328c6483)) - Add internal context to login and registration ([723e6ee](https://github.com/ory/kratos/commit/723e6eee731d34f85bc4346a1040f2f121662ae9)) - Add internal context to settings flow ([afb6895](https://github.com/ory/kratos/commit/afb6895daa8743edbf4fca957b2a156e676ef63a)) - Add lookup node to disable lookup ([d0836be](https://github.com/ory/kratos/commit/d0836beb53709c88eb9ed78df39e95a3204c7cec)): See https://github.com/ory/cloud/issues/12 - Add lookup to config ([14119b6](https://github.com/ory/kratos/commit/14119b623941b6f5e795ef0d369ee9e3adb73207)) - Add lookup to identity ([ead3833](https://github.com/ory/kratos/commit/ead3833e254b4939f2b86b34e954580960cc7ea1)) - Add lookup to migrations ([dac4f75](https://github.com/ory/kratos/commit/dac4f759a0b92c1eebca177c3c931fb3146e7dee)) - Add MFA enforcment option to whoami and settings ([554d725](https://github.com/ory/kratos/commit/554d72552702818c8f1fc45fd1daf9d93c0d2cad)) - Add mfa for non-browser ([4096fd3](https://github.com/ory/kratos/commit/4096fd3fbdb430fd325b38fe9102defa31dd1b6d)) - Add missing migrations ([ccc64d8](https://github.com/ory/kratos/commit/ccc64d87935c6b5ad506dce6a5f903d56541f864)) - Add option to disable recovery codes ([9d3daa6](https://github.com/ory/kratos/commit/9d3daa656a5361ef8a90fe3511f9c1a6e9015969)): Closes https://github.com/ory/cloud/issues/12 - Add ory cli config ([5b959be](https://github.com/ory/kratos/commit/5b959beaba4d03e143f7701c30bc30e25f2c51cc)) - Add schema patch for new initial_value field ([131e380](https://github.com/ory/kratos/commit/131e3803ff6d04af9ec668286c8e6fcf88467214)): The field sets a node input's initial value. This is primarily used for fields which are e.g. checkboxes or buttons (active/inactive). If this field is set on a button, it implies that clicking the button should trigger the "value" to be set. - Add script type and discriminator for attributes ([de0af95](https://github.com/ory/kratos/commit/de0af955904894d97997cf598686b6d33cd88bd4)): See https://github.com/ory/sdk/issues/72 - Add smtp headers config option ([#1747](https://github.com/ory/kratos/issues/1747)) ([7ffe0e9](https://github.com/ory/kratos/commit/7ffe0e9766e930615dbb6833e650b73a8975a544)), closes [#1725](https://github.com/ory/kratos/issues/1725) - Add support for onclick javascript in ui nodes ([7cc7efa](https://github.com/ory/kratos/commit/7cc7efa00ff0e8107f1369573bfdf766fcfc0e93)) - Add totp strategy for settings flow ([d1d6617](https://github.com/ory/kratos/commit/d1d6617013fbcc37eaf48cf19061b86955fc5d5e)): This patch allows adding a TOTP device in the settings, and also removing it when no longer needed. - Add webauthn identity credential ([f8b9582](https://github.com/ory/kratos/commit/f8b95828ea41d29c7f3577cc7772168135bc5514)) - Adding Dockle Container Linter ([#1852](https://github.com/ory/kratos/issues/1852)) ([3c0d519](https://github.com/ory/kratos/commit/3c0d519dd47657c6adca3d64bca8b3ed02cb7a8f)) - Adjust to new aal error handling ([b8956bc](https://github.com/ory/kratos/commit/b8956bc0fc8a45e88dd51f79608f9d6c34e2b6f3)) - API to return access, refresh, id tokens from social sign in ([#1818](https://github.com/ory/kratos/issues/1818)) ([198991a](https://github.com/ory/kratos/commit/198991a9ce25fbaccc927be3bd3f6b1593771bec)), closes [#1518](https://github.com/ory/kratos/issues/1518) [#397](https://github.com/ory/kratos/issues/397): This patch introduces the new `include_credential` query parameter to the `GET /identities` endpoint which allows administrators to receive the initial access, refresh, and ID tokens from Social Sign In (OpenID Connect / OAuth 2.0) flows. These tokens can be stored in an encrypted format (XChaCha20Poly1305 or AES-GCM) in the database if an appropriate encryption secret is set. To get started easily these values are not encrypted per default. For more information head [over to the docs](https://kratos/docs/guides/retrieve-social-sign-in-access-refresh-id-token). - Auto-generate list of messages ([cf46339](https://github.com/ory/kratos/commit/cf46339b9a07cd72b4d01e40c2df72e6c8104e9b)), closes [#1784](https://github.com/ory/kratos/issues/1784) - Endpoint to list all identity schemas ([#1703](https://github.com/ory/kratos/issues/1703)) ([aa23d5d](https://github.com/ory/kratos/commit/aa23d5d5af28d8a7789b4a0c7e97197c7758ad98)), closes [#1699](https://github.com/ory/kratos/issues/1699) - Generate sdks and update versions ([c9d22d9](https://github.com/ory/kratos/commit/c9d22d91f5fe49b5f2818160ade58bfd265f03e5)) - **hash:** PBKDF2 password hash verification ([#1774](https://github.com/ory/kratos/issues/1774)) ([33cc7e0](https://github.com/ory/kratos/commit/33cc7e02d9bcc24ae1de438102660cc89fd008d6)), closes [#1659](https://github.com/ory/kratos/issues/1659) - Identity schema validation on startup ([#1779](https://github.com/ory/kratos/issues/1779)) ([99db3f0](https://github.com/ory/kratos/commit/99db3f03afd4b2525cbce54133a1abd1d49d2886)), closes [#701](https://github.com/ory/kratos/issues/701) - **identity:** Add AAL constants ([882573d](https://github.com/ory/kratos/commit/882573df5621446e799b17ca0ab09d3934e44437)) - Implement AAL for login and sessions ([45467e0](https://github.com/ory/kratos/commit/45467e0caba7ed31e2ebde71a8b32ecd5f8db7c2)) - Implement endpoint for invalidating all sessions for a given identity ([#1740](https://github.com/ory/kratos/issues/1740)) ([dbd1689](https://github.com/ory/kratos/commit/dbd1689c11fd0a3d999ea09b553dd4a14a7a6972)), closes [#655](https://github.com/ory/kratos/issues/655): This PR introduces endpoint to destroy all sessions for a given identity which effectively logouts user from all devices/sessions. This is useful when for some security concern we want to make sure there are no "old" sessions active or other "staff" related actions (such as force logout after password change etc.). - Implement lookup code settings and login ([8f3ce7b](https://github.com/ory/kratos/commit/8f3ce7b33390fcae85e605193806364ca9d099c9)) - Improve detection of AAL errors and return 422 instead of 403 ([e2bfbea](https://github.com/ory/kratos/commit/e2bfbea1541aca983eb835d3da2b5fe70ac4b7a5)) - Improve labels for totp and lookup ([b92e00e](https://github.com/ory/kratos/commit/b92e00e345da1f8ab76750e3f0ae1301977bbae0)) - Improve session device annotations ([87907b8](https://github.com/ory/kratos/commit/87907b8d29dc9cd7140535e81ea62c2d7f8e41c3)) - In docker debug support with delve ([#1789](https://github.com/ory/kratos/issues/1789)) ([37325a1](https://github.com/ory/kratos/commit/37325a18d9430130d0062674433fa0d3f9a59eb3)) - Introduce cve scanning ([#1798](https://github.com/ory/kratos/issues/1798)) ([ade13ea](https://github.com/ory/kratos/commit/ade13ea082ee11e9c1005de3ccb3ae6b5f02bb49)) - **logout:** Add logout token to browser response ([#1758](https://github.com/ory/kratos/issues/1758)) ([d3f1177](https://github.com/ory/kratos/commit/d3f1177a9a82dc2c4f930f15c6ec87c3ec5a1d53)) - Mark recovery email address verified ([#1665](https://github.com/ory/kratos/issues/1665)) ([e3efc5d](https://github.com/ory/kratos/commit/e3efc5d0673106115a236e38b5d76d6672d64d20)), closes [#1662](https://github.com/ory/kratos/issues/1662) - Mark required fiels as required ([34cd5e8](https://github.com/ory/kratos/commit/34cd5e8e638be3d48ed8174112417bc36400e8cb)): Closes https://github.com/ory-corp/cloud/issues/1328 Closes https://github.com/ory/kratos/issues/400 Closes https://github.com/ory/kratos/issues/1058 See https://ory-community.slack.com/archives/C012RJ2MQ1H/p1631825476159000 - Natively support social sign in for single-page apps ([1a1a350](https://github.com/ory/kratos/commit/1a1a350a9f0df85195505690fc52086eddf78371)) - **persistence:** Add new columns for mfa ([6184fe3](https://github.com/ory/kratos/commit/6184fe385cf87b260117290089b06445e5b6b205)) - Potentially add arm64 docker support ([68112de](https://github.com/ory/kratos/commit/68112defb97db1c6f4b8bf65e2e522b22e27d280)) - Proper enum and type assertions for openapi ([c4d8516](https://github.com/ory/kratos/commit/c4d8516fb93c2127c6d0c28a914ed7b8f8646832)) - Publish webauthn as loadable script instead of eval ([2717c59](https://github.com/ory/kratos/commit/2717c5958ab3f088821fdf96fdf6d44d48fea310)) - Redirect on login if session aal is not matched ([8feff8d](https://github.com/ory/kratos/commit/8feff8daaf4ac744fab22627d9bdab45740570d5)) - Respect webauthn in session aal ([869b4a5](https://github.com/ory/kratos/commit/869b4a5a812b840196eaf1e591aeb685d7f0e904)) - **session:** Respect 2fa enforcement in whoami ([3a82c88](https://github.com/ory/kratos/commit/3a82c8806931a2b4cd05142a6dae8040a76658bc)) - Sign in with apple ([#1833](https://github.com/ory/kratos/issues/1833)) ([16ed123](https://github.com/ory/kratos/commit/16ed123adba06167f70eb952ae3877d4476f8c71)), closes [#1782](https://github.com/ory/kratos/issues/1782): Adds an adapter and configuration options for enabling Social Sign In with Apple. - Sort totp nodes ([5c9a494](https://github.com/ory/kratos/commit/5c9a49487f45af5b7edf069edf9c3d37ef293cd5)) - Stubable time in text package ([22e4ed1](https://github.com/ory/kratos/commit/22e4ed15e2eecb51b393762077872b19f6f2acd2)) - Support apple m1 ([54b4fb6](https://github.com/ory/kratos/commit/54b4fb698c6a087afef8821fa8300798e484ae18)) - Support setting the identity state via the admin API ([#1805](https://github.com/ory/kratos/issues/1805)) ([29c060b](https://github.com/ory/kratos/commit/29c060bd348733eeafee98d5f255c737a8cbcad0)), closes [#1767](https://github.com/ory/kratos/issues/1767) - Support strategy return to ui for settings ([74670bb](https://github.com/ory/kratos/commit/74670bb4b0cc45626537e5ac63283fd14f05dee1)) - Support webauthn for mfa ([e8f4d3c](https://github.com/ory/kratos/commit/e8f4d3cb899d44c777b094f2ae4d84ff68532bf9)) - **totp:** Add width and height to QR code ([a648ba3](https://github.com/ory/kratos/commit/a648ba3de9a0ba707ce39c37fa5d5e38c4da74d3)) - **totp:** Support account name setting from schema ([19a6bcc](https://github.com/ory/kratos/commit/19a6bcc9d8940acb2a5f0eb4a6cc7f28801a2f92)) - Treat lookup as aal2 in session ([3269028](https://github.com/ory/kratos/commit/3269028d46d0ef23de3f905c325d514f24db43b8)) - Use discriminators for ui node types in spec ([59e808e](https://github.com/ory/kratos/commit/59e808e8dc6339da59bbe08ebbcf7b840e3fdd50)) - Use initial_value in lookup strategy ([efe272f](https://github.com/ory/kratos/commit/efe272f06966edc4858602d94740b6ed36c12e57)) ### Reverts - 3745014 ([d493d10](https://github.com/ory/kratos/commit/d493d1049f90ca6ee7b85931e3652aa9fdeb0254)) ### Tests - Aal in login.NewFlow ([5986e38](https://github.com/ory/kratos/commit/5986e38e6ab9eec1761e4c723c807dc0ef2a3dfa)) - AcceptToRedirectOrJSON ([2ca153f](https://github.com/ory/kratos/commit/2ca153f027599c18583ce0ebacb5ed577b56ddf3)) - Add credentials test ([58b388c](https://github.com/ory/kratos/commit/58b388c70d5ff32822e8ac5f3a394e683273ac6a)) - Add expired test to login handler ([3bdb8ab](https://github.com/ory/kratos/commit/3bdb8abb558c0f8c4b33f712678f5da02d0ef4ee)) - Add identity change test to settings submit ([5eb090b](https://github.com/ory/kratos/commit/5eb090b2564192deb77e64dd74a07b96c381391d)) - Add initial spa e2e test ([20617f6](https://github.com/ory/kratos/commit/20617f628ac84981c3b47ce9e9ab193b8ff426d0)) - Add initial totp integration tests ([c9d456b](https://github.com/ory/kratos/commit/c9d456bf03cb33baf0745fe9a511f84b4c9427e3)) - Add login tests ([a71cadd](https://github.com/ory/kratos/commit/a71cadde91bdaf960caf30dcfa957a2646da86a2)) - Add migrations tests for new tables ([3c96ab0](https://github.com/ory/kratos/commit/3c96ab059af9bf6002b341c5db51d1b3ca5da655)) - Add react app to e2e tests ([1214eee](https://github.com/ory/kratos/commit/1214eeee24b06e6e72c55cfed2176860ecbf3c13)) - Add schema test for totp config ([c4f05ba](https://github.com/ory/kratos/commit/c4f05ba60af1d7ca31b4cf54097cbefa88085704)) - Add session amr test ([eedb60b](https://github.com/ory/kratos/commit/eedb60bec9bebfb0a4ffb67dd484d2e6b466e776)) - Add settings tests ([6959565](https://github.com/ory/kratos/commit/6959565212dc5e7296aad7f1365a944379dd5d6d)) - Add test for TOTPIssuer ([14731c4](https://github.com/ory/kratos/commit/14731c4e7809c2202c9298422c005358b7b26fc3)) - Add test for ui error page ([3977a9c](https://github.com/ory/kratos/commit/3977a9c4d6f98ef6d8f7f4c88d55b46579401ba8)) - Add TestEnsureInternalContext ([152bfc7](https://github.com/ory/kratos/commit/152bfc7294078081ca9f8fc6dd194db6d2e699ad)) - Add totp registry tests ([817e3ec](https://github.com/ory/kratos/commit/817e3ecb213454e4ce3f987ce8a8714301ee8165)) - Add totp settings tests ([c5a0d0f](https://github.com/ory/kratos/commit/c5a0d0f8435690786eaf719bb1376f7da15a6203)) - Add TOTP to profile ([7431e9f](https://github.com/ory/kratos/commit/7431e9fcf4e9c9853ec4d378221c7a3744b3b239)) - Add update session test ([47bd057](https://github.com/ory/kratos/commit/47bd057da0fbf849d643c27c6eb75ef09c5075fb)) - Additional checks for flow hydration ([a40d7fe](https://github.com/ory/kratos/commit/a40d7fe4340ff61c3fa9ac0a70dc5f7e4641a15e)) - Amr persistence ([b0b2d81](https://github.com/ory/kratos/commit/b0b2d8174ca46e066e8eb912a24d9e6efeea0ce8)) - Check if internal context is validated in store ([a23d851](https://github.com/ory/kratos/commit/a23d8518fc65f645cae9c196ff70df4efca67266)) - CheckAAL ([03b37e7](https://github.com/ory/kratos/commit/03b37e7675e369817d2bb226047ec9f26b18a456)) - Complete TOTP login integration tests ([6e503cf](https://github.com/ory/kratos/commit/6e503cff28428e707b3812cd2bf8e44ccc487b89)) - **e2e:** Add baseurl ([159b25f](https://github.com/ory/kratos/commit/159b25f7ab0ac659033d861868f472183b852167)) - **e2e:** Add checkboxes to schemas ([0c91f0c](https://github.com/ory/kratos/commit/0c91f0c89081726e7451d5411a6adeb631ae2edb)) - **e2e:** Add config for proxy to simplify cy.visit logic ([7d87985](https://github.com/ory/kratos/commit/7d8798560947227d64a35d2dd69623bc1a1ddc8f)) - **e2e:** Add mfa profile ([a60d157](https://github.com/ory/kratos/commit/a60d157bfeb79cb527bf73b3fc38e1ba5388cbed)) - **e2e:** Add modd to build ([48cd8ae](https://github.com/ory/kratos/commit/48cd8aeb851d02e2fd31e73e044befb45242e953)) - **e2e:** Add more helpers and ts defs ([21b35b0](https://github.com/ory/kratos/commit/21b35b025a21b1f6ab3ac8be79339f1734b3033a)) - **e2e:** Add more helpers for various flows and proxy settings ([755ac60](https://github.com/ory/kratos/commit/755ac60cb1a54cd188ab07d9448598d738c5e866)) - **e2e:** Add more routes to registry ([30423c9](https://github.com/ory/kratos/commit/30423c92ba27709e003e88e58072b78ef3e2aa04)) - **e2e:** Add more typings for cypress helpers ([60bd63f](https://github.com/ory/kratos/commit/60bd63f31d6b639af19048cc3d1e392b885213e0)) - **e2e:** Add plugin for using got ([8fafc40](https://github.com/ory/kratos/commit/8fafc40dff8a0d9d5d678b59ecf4c13755906a4f)) - **e2e:** Add proxy capabilities for react native app ([b5668df](https://github.com/ory/kratos/commit/b5668df755e186f12c0e543715bc2e16011583a6)) - **e2e:** Add recovery tests for SPA ([b6014ee](https://github.com/ory/kratos/commit/b6014eee8b507abf6e3b4324097b3015f722cbe3)) - **e2e:** Add spa as allowed redirect url ([2625d16](https://github.com/ory/kratos/commit/2625d1689d47fb1cdbe34708be27f2317cdc7bea)) - **e2e:** Add SPA tests for login and refactor tests to typescript ([d9a25df](https://github.com/ory/kratos/commit/d9a25df1ba34cbefd416dccfdb2f5fc93e0290b9)) - **e2e:** Add SPA tests for logout and refactor tests to typescript ([b0c6776](https://github.com/ory/kratos/commit/b0c67769e4afcdbc05d2c1966e38faa18404a5db)) - **e2e:** Add SPA tests for registration and refactor tests to typescript ([a61ed1e](https://github.com/ory/kratos/commit/a61ed1edb41df64f58e23f8c88894fb742fd275d)) - **e2e:** Add support functions and type definitions ([c82d68d](https://github.com/ory/kratos/commit/c82d68db36563b16623a63be9efaf6b25322f855)) - **e2e:** Clean up helper ([4806add](https://github.com/ory/kratos/commit/4806add17a5dd0ea8c8fded644a6c240b17861b3)) - **e2e:** Complete SPA tests for all mfa flows ([2196129](https://github.com/ory/kratos/commit/219612903bd4dce208e2074e4595980c1cb60711)) - **e2e:** Default and empty values and required fields ([72f2c5f](https://github.com/ory/kratos/commit/72f2c5fbd8227e19d62f26aeddfb1bd14d7c768b)) - **e2e:** Ensure advanced types work in forms also ([287269c](https://github.com/ory/kratos/commit/287269c9992390b52ff380b31eda3bb7ad205f09)) - **e2e:** Ensure correct app ([a9ff545](https://github.com/ory/kratos/commit/a9ff5457cb48a90668b62e54d0b08cb1e9108994)) - **e2e:** Finalize mobile tests ([acf5c3d](https://github.com/ory/kratos/commit/acf5c3d649e51edfd9e1e3755222d9c7161a92e7)) - **e2e:** Force port ([a49eda8](https://github.com/ory/kratos/commit/a49eda8e0405954d62058d8c1410a62f72bfb7ae)) - **e2e:** Homogenize profiles ([7798e19](https://github.com/ory/kratos/commit/7798e193aa3cce0347e5ca018e09685b6fda0ba2)) - **e2e:** Hot reload ory kratos on changes ([841da09](https://github.com/ory/kratos/commit/841da091689f9a3fceb5509490d7a2f4828b926f)) - **e2e:** Implement recovery tests for SPA ([3dea57f](https://github.com/ory/kratos/commit/3dea57ff986702b9a31621198794e1cc94e4881e)) - **e2e:** Implement required verification tests for SPA ([fb55f34](https://github.com/ory/kratos/commit/fb55f3475f25ab3aa6f7b1765ec5b9f13ef72b15)) - **e2e:** Improve stability for login tests ([43df22b](https://github.com/ory/kratos/commit/43df22bdd52305b2b5d98a0db1c09751bd3ebb4f)) - **e2e:** Improve stability for registration tests ([a1c59a3](https://github.com/ory/kratos/commit/a1c59a349cab3819e5f869dc89eba3c05100f1b8)) - **e2e:** Improve test reliability ([061a7e3](https://github.com/ory/kratos/commit/061a7e340c86b580abde02de3cb521dda7c23efb)) - **e2e:** Migrate email tests to new proxy set up ([54d8cd6](https://github.com/ory/kratos/commit/54d8cd65b8b19f7a643bf9d4060906b818fc91d6)) - **e2e:** Migrate settings tests to typescript and add SPA tests ([566336d](https://github.com/ory/kratos/commit/566336d910f0b3deb4675e1413bfd0182bde6a79)) - **e2e:** Move config to lower level and publish as package ([c21fa26](https://github.com/ory/kratos/commit/c21fa2688e560bb9c714d2078dbc9a72a1da125f)) - **e2e:** Move registration tests to new proxy set up ([eddeb85](https://github.com/ory/kratos/commit/eddeb8510ca4cb13d0644d7083d436778828d0bd)) - **e2e:** Port mobile test to typescript ([db42346](https://github.com/ory/kratos/commit/db4234694723b7dc965c9e2cf4ba792bad0374e9)) - **e2e:** Port remaining e2e tests to typescript ([5853d1a](https://github.com/ory/kratos/commit/5853d1a64b3f7b20af79cc6ebbc381de0d213139)) - **e2e:** Potentially resolve flaky login test ([e237d66](https://github.com/ory/kratos/commit/e237d66adbc3cce972d8e4689a88d02b9a925354)) - **e2e:** Potentially resolve webauthn startup issues ([eae6f5d](https://github.com/ory/kratos/commit/eae6f5d1e9dc08dc8f7152a9c441e029dd4351f3)) - **e2e:** Prototype typescript implementation ([2e869cf](https://github.com/ory/kratos/commit/2e869cff7b1cb87e15013a86b54fda16a01e0267)) - **e2e:** Recreate identities per flow ([1a560a3](https://github.com/ory/kratos/commit/1a560a37c13240d9ae16d34188a6221f589ebbbc)) - **e2e:** Reduce flaky tests ([cae86e7](https://github.com/ory/kratos/commit/cae86e7f6a4fcc9e1433b9c063efe3745273f2dc)) - **e2e:** Reduce test flakes in lookup codes ([bfea354](https://github.com/ory/kratos/commit/bfea354f45858e5be0a588840f6e8125819a244c)) - **e2e:** Refactor and add support for SPA app ([7609219](https://github.com/ory/kratos/commit/7609219448effde35844675533e71583babe1d14)) - **e2e:** Remove wait condition ([af10b03](https://github.com/ory/kratos/commit/af10b03ebca03cdb5654c116efbd3c23b47c7594)) - **e2e:** Resolve broken test ([c7cf134](https://github.com/ory/kratos/commit/c7cf134fbfbbb59b276aa00d02bbad3886f78dee)) - **e2e:** Resolve flaky test ([de7cc59](https://github.com/ory/kratos/commit/de7cc59f07a6b77e3bbf3d98a7b2104b60ce708c)) - **e2e:** Resolve flaky test issues ([1627745](https://github.com/ory/kratos/commit/162774567d44336c8999ee0c1362adb191855d0c)) - **e2e:** Resolve next not starting ([2a2a3cb](https://github.com/ory/kratos/commit/2a2a3cb016e820f651f3cf6cd33123672e5977cb)) - **e2e:** Resolve regression ([d62f0c0](https://github.com/ory/kratos/commit/d62f0c02315702f55b998d4c48d4ca8c6a41827f)) - **e2e:** Resolve regressions ([aaff34e](https://github.com/ory/kratos/commit/aaff34ed66165f787103292ac0a034a0cdaf1308)) - **e2e:** Resolve regressions ([af9aedc](https://github.com/ory/kratos/commit/af9aedc8d29678f480b1b6bad128aefbacd6a373)) - **e2e:** Revert proxy changes ([293d920](https://github.com/ory/kratos/commit/293d92084a7614ae0cd7d5326dc82a209a0841be)) - **e2e:** Stabilize e2e tests ([a5dca28](https://github.com/ory/kratos/commit/a5dca2839ef66217b0046262a7e1fc886276509f)) - **e2e:** Temporarily add totp to default profile ([8ffac9d](https://github.com/ory/kratos/commit/8ffac9d138656eb2322913992b350cea31ed7e87)) - **e2e:** Update e2e profiles to new proxy set up ([a3204cf](https://github.com/ory/kratos/commit/a3204cf9b85e274441c02592288a4f322481e894)) - **e2e:** Use 127.0.0.1 to prevent ipv6 issues ([6f4b534](https://github.com/ory/kratos/commit/6f4b5340d33b31a5e4582858b544beb9c82181c7)) - **e2e:** Wait for oidc to trigger ([9c67c49](https://github.com/ory/kratos/commit/9c67c49235a562430da7ae60426d60cfd6120fca)) - Enable cookie debug ([81c3064](https://github.com/ory/kratos/commit/81c3064d69f8a233b8e0b78e103f2a23ae63cb63)) - Ensure aal and amr is set on recovery ([5cbab54](https://github.com/ory/kratos/commit/5cbab54fe5780689f0b64700567ac4632eb04c0b)), closes [#1322](https://github.com/ory/kratos/issues/1322) - Ensure aal2 can not be used for oidc ([cbbcdd2](https://github.com/ory/kratos/commit/cbbcdd2e86c2d4da14c478637105eb8a36ae06c0)) - Ensure aal2 can not be used for password ([d9d39f0](https://github.com/ory/kratos/commit/d9d39f0bdda0725989a0a8261a449cf1a71afb6b)) - Ensure authenticated_at after all upgrade ([80408b4](https://github.com/ory/kratos/commit/80408b4c90229c61138411be8534fc577b8f0f33)) - Ensure redirect_url in password strategy ([9eafc10](https://github.com/ory/kratos/commit/9eafc10189ca88724fa6d75748299c2dd2c470b1)) - ErrStrategyAsksToReturnToUI behavior ([f739018](https://github.com/ory/kratos/commit/f7390184b02d526bb6e3ff496abc4522afc39d5a)) - Finalize webauthn tests ([97e59e6](https://github.com/ory/kratos/commit/97e59e61ee8be263199c3749e27dd81344777166)) - Fix regressions in the tests ([246c580](https://github.com/ory/kratos/commit/246c580222acd193eea784a6cbfd1e75181a484f)) - Fix tests in cmd/serve ([#1755](https://github.com/ory/kratos/issues/1755)) ([b704d08](https://github.com/ory/kratos/commit/b704d08382a9059157c2a649872e88943d66a99f)) - ID methods of node attributes ([ff9ff04](https://github.com/ory/kratos/commit/ff9ff048ddfa13ae73571064a36b33a867727392)) - Login form submission with AAL ([4d54fbb](https://github.com/ory/kratos/commit/4d54fbb37349126418274de8e21473c2ff81f785)) - **lookup:** Add secret_disable to snapshots ([68d6a87](https://github.com/ory/kratos/commit/68d6a876a4f1a0fd74789798397bd325a68d71d6)) - **lookup:** Ensure context is cleaned up after use ([8a210c4](https://github.com/ory/kratos/commit/8a210c41696d1865cce4c589a7cb3e52283fe24d)) - **lookup:** Refresh and reuse scenarios ([89736ed](https://github.com/ory/kratos/commit/89736ed9ba8667314313ca549a6377faddcc3d80)) - **migration:** Resolve mysql migration issue with empty array ([71a5649](https://github.com/ory/kratos/commit/71a5649a52036e29b351b6b4ee220ec7ce3aed05)) - Move to cupaloy for snapshots ([0cce70f](https://github.com/ory/kratos/commit/0cce70f47712da44d891c6d2890e818da6d9971b)) - Properly refresh mobile session ([c31915d](https://github.com/ory/kratos/commit/c31915de32e4b3db4af8ca8f3b5ecb0adf01a510)) - Registry regression ([25c88b5](https://github.com/ory/kratos/commit/25c88b55577b016aa77d2df3c595410633d0eefe)) - Remove todo items ([f60050e](https://github.com/ory/kratos/commit/f60050e0e30b1bf5441c95ada5777743719d65f1)) - Resolve flaky config test ([147c670](https://github.com/ory/kratos/commit/147c6704a9d38b5687eb8aba5661f24f99e577e3)) - Resolve flaky config test ([#1832](https://github.com/ory/kratos/issues/1832)) ([db98d01](https://github.com/ory/kratos/commit/db98d010639bfc387ef927c4f80ff6cd0ebc9588)) - Resolve flaky example tests ([#1817](https://github.com/ory/kratos/issues/1817)) ([0e700d8](https://github.com/ory/kratos/commit/0e700d89c0aaa99b9eec7ce070b7974373377f03)) - Resolve flaky tests ([2bd9100](https://github.com/ory/kratos/commit/2bd910037efd20ab1829784ee087c533e5e8b177)) - Resolve migratest regressions ([e9a1ed1](https://github.com/ory/kratos/commit/e9a1ed188a8f2556e1f60d1c171506dc0dd931d4)) - Resolve regressions ([1502ca1](https://github.com/ory/kratos/commit/1502ca1eb6c2e7ab698dc94675a50db63c326a41)) - Resolve regressions ([1a93b2f](https://github.com/ory/kratos/commit/1a93b2fba1fc41a6ba314253387af9770fd36f5a)) - Resolve regressions ([64850ed](https://github.com/ory/kratos/commit/64850ed3277185ebf68b50449721c903c01eab89)) - Resolve remaining regressions ([f02804c](https://github.com/ory/kratos/commit/f02804c567a532a30eaa228b0ba784b7f7fb0d9a)) - Resolve remaining regressions ([0224c22](https://github.com/ory/kratos/commit/0224c22ebda566c69363ae09dea9d42368c86f48)) - Resolve remaining regressions ([1fa2aa5](https://github.com/ory/kratos/commit/1fa2aa5b60d0b81e2035ae18c60d199b060a4c1f)) - Resolve time locality issues ([53b8b2a](https://github.com/ory/kratos/commit/53b8b2a22e5bad12dabf90c7bcbaf05b13a73a55)) - Restructure session struct tests ([50d3f66](https://github.com/ory/kratos/commit/50d3f66f82cb4e85a213fd86dc20bfadafefae23)) - Session AAL handling ([6fea3e5](https://github.com/ory/kratos/commit/6fea3e5aec6556697092c9a9d12295ed7e4d408b)) - Session activate ([c86fa03](https://github.com/ory/kratos/commit/c86fa03d3b2390403dcb14ef93307adc61ac7c79)) - **sql:** Fix incorrect UUID ([ea2894e](https://github.com/ory/kratos/commit/ea2894ed0f12de011fd5ce304dd614579ea5e96c)) - Temporarily enable lookup globally ([458f559](https://github.com/ory/kratos/commit/458f559ec816e64c6c9f53ecacdb4ae30fc9f8f7)) - **totp:** Ensure context is cleaned up after use ([1905883](https://github.com/ory/kratos/commit/19058830c0541f717360d3f599760b2a5cf47c4e)) - Upgrade cypress to 8.x ([c8a1dfc](https://github.com/ory/kratos/commit/c8a1dfcae3d42555b1215ad7eaa03a521bdcb1da)) - Use different return handler ([e489a43](https://github.com/ory/kratos/commit/e489a439e56dcd4218cf81284beaca0ef2ecd35e)) - Various aal combinations for newflow ([b095b99](https://github.com/ory/kratos/commit/b095b990224cbbd5ffa272b8f443b3345634d353)) - Webauth settings flow ([4c82772](https://github.com/ory/kratos/commit/4c82772ae28643ce69a5778c37f3c67644ef6f4c)) - Webauthn aal2 login ([60ace8b](https://github.com/ory/kratos/commit/60ace8b36c033ac4f9cd7e8cd929921e2e882946)) - Webauthn credentials ([c3e1184](https://github.com/ory/kratos/commit/c3e1184e719cd2041df8894edd4bd921bf2c3b00)) - Webauthn credentials counter ([f7701f6](https://github.com/ory/kratos/commit/f7701f629d5553e229546b00d3c345a8d74dd627)) - **webauthn:** Ensure context is cleaned up after use ([7a8055b](https://github.com/ory/kratos/commit/7a8055be357a64a1f4074fe28b249fbaf05cf519)) ### Unclassified - test(e2e) improve reliability ([763dd00](https://github.com/ory/kratos/commit/763dd0063f3166fad323b25a1b0e7bdf9850e519)) - Correct session godoc ([7108e65](https://github.com/ory/kratos/commit/7108e65447c37cc6f2937083a2a61442e0a43cb8)) # [0.7.6-alpha.1](https://github.com/ory/kratos/compare/v0.7.5-alpha.1...v0.7.6-alpha.1) (2021-09-12) Resolves further issues in the SDK and release pipeline. ### Code Generation - Pin v0.7.6-alpha.1 release commit ([8b0d1ee](https://github.com/ory/kratos/commit/8b0d1ee66f1ee2b9f37cd178ac2bcbd8980d6f1d)) # [0.7.5-alpha.1](https://github.com/ory/kratos/compare/v0.7.4-alpha.1...v0.7.5-alpha.1) (2021-09-11) Primarily resolves issues in the SDK pipeline. ### Code Generation - Pin v0.7.5-alpha.1 release commit ([3a741a5](https://github.com/ory/kratos/commit/3a741a5ed5cff78e0e060bc98f8526537e8719d7)) # [0.7.4-alpha.1](https://github.com/ory/kratos/compare/v0.7.3-alpha.1...v0.7.4-alpha.1) (2021-09-09) This release adds the GitHub-app provider, improves SQL instrumentation, resolves an expired flow bug, and resolves documentation issues. ### Bug Fixes - Corret sdk annotations for enums ([6152363](https://github.com/ory/kratos/commit/6152363cda20992a9b894e618c3a438f30808a97)) - Do not panic if cookiemanager returns a nil cookie ([6ea5678](https://github.com/ory/kratos/commit/6ea56785fa0354d8d9479a699304a4b933d6c294)), closes [#1695](https://github.com/ory/kratos/issues/1695) - Respect return_to in expired flows ([#1697](https://github.com/ory/kratos/issues/1697)) ([394a8de](https://github.com/ory/kratos/commit/394a8de9c0cdd33df91d56008eac12510ff14e07)), closes [#1251](https://github.com/ory/kratos/issues/1251) ### Code Generation - Pin v0.7.4-alpha.1 release commit ([67ff8a9](https://github.com/ory/kratos/commit/67ff8a947b5b339648aeb4c22aba89205c61382b)) ### Documentation - Add e2e quickstart ([2b749d3](https://github.com/ory/kratos/commit/2b749d39fcb0d320d193290966a558ee2c5734d1)) - Browser redirects ([#1700](https://github.com/ory/kratos/issues/1700)) ([a44089a](https://github.com/ory/kratos/commit/a44089a506f5ea9daa406fcb862ad707f569c2bb)) - Mark logout_url always available ([9021805](https://github.com/ory/kratos/commit/9021805c4399beb73f234726f8f5f3bfd312482c)) - Minor improvements ([#1707](https://github.com/ory/kratos/issues/1707)) ([79c132c](https://github.com/ory/kratos/commit/79c132c5a0737ea1632655d8aea0af63c4200d37)) ### Features - Making use of the updated instrumentedsql version ([#1723](https://github.com/ory/kratos/issues/1723)) ([9e6fbdd](https://github.com/ory/kratos/commit/9e6fbdd06a75d7207b4801d1148267b3a1a0a0c7)) - **oidc:** Github-app provider ([#1711](https://github.com/ory/kratos/issues/1711)) ([fb1fe8c](https://github.com/ory/kratos/commit/fb1fe8c468bb6f8275618b84c5fa157a314c345f)) ### Tests - **session:** Resolve incorrect assertion ([0531220](https://github.com/ory/kratos/commit/05312203ab12eec44e59dcd9210160f2781a69b4)) # [0.7.3-alpha.1](https://github.com/ory/kratos/compare/v0.7.1-alpha.1...v0.7.3-alpha.1) (2021-08-28) This patch resolves a regression issue with Facebook login, a memory leak issue introduced by an external dependency, adds a "requires verification" login hook, and improves performance for some endpoints. Also, Ory Kratos SDKs are now published in individual [GitHub repositories for every language](https://github.com/ory?q=kratos-client). ### Bug Fixes - Add new message when refresh parameter is true ([#1560](https://github.com/ory/kratos/issues/1560)) ([0525623](https://github.com/ory/kratos/commit/05256232bf85d68e068eece6c883f46a447ba5bd)), closes [#1117](https://github.com/ory/kratos/issues/1117) - Add session in spa registration if session cook is configured ([#1657](https://github.com/ory/kratos/issues/1657)) ([639a7dd](https://github.com/ory/kratos/commit/639a7dd52d43c57e9708ed3e7360c17d6efde6a5)), closes [#1604](https://github.com/ory/kratos/issues/1604) - **docs:** Ensure config reference is updated ([f6b3aa4](https://github.com/ory/kratos/commit/f6b3aa45b1f39ca5e9ee7ef4cd96de1970b2ed71)), closes [#1597](https://github.com/ory/kratos/issues/1597) - Facebook sign in regression ([#1689](https://github.com/ory/kratos/issues/1689)) ([85337bf](https://github.com/ory/kratos/commit/85337bf65af767d7296b14e8fd21bab5c64d23e2)), closes [#1687](https://github.com/ory/kratos/issues/1687) [#1686](https://github.com/ory/kratos/issues/1686) - Http context memory leak ([b21bd22](https://github.com/ory/kratos/commit/b21bd224059e8a42da9814237572a118297c5210)): Ory Kratos was using `gorilla/sessions` prior to version v1.2 which had a dependency on `gorilla/context`, a deprecated library with known memory management issues. Even though we used `gorilla/context`'s clean up middleware, it appears that `r.Context()` was not properly cleaned up, causing memory leaks. On average, the memory leak is pretty small, but depending on what gets added to `r.Context()` it could significantly increase the memory leak. By replacing `gorilla/sessions` with v1.2.1 we: 1. Increased the HTTP API throughput by an estimate of 4 times; 2. Brought average memory use back down to about 12MB; Closes https://github.com/ory-corp/cloud/issues/1292 - Outdated label ([#1681](https://github.com/ory/kratos/issues/1681)) ([149101e](https://github.com/ory/kratos/commit/149101ed145dae2b75e5150013efc478f5fd0cc3)) - Register argon2 CLI commands properly ([#1592](https://github.com/ory/kratos/issues/1592)) ([45c28d9](https://github.com/ory/kratos/commit/45c28d99064baf8051521a1078ac2b59bb3206ec)) - Remove session cookie on logout ([#1587](https://github.com/ory/kratos/issues/1587)) ([cdb30bb](https://github.com/ory/kratos/commit/cdb30bb65ac932a17e4924b4efc8952113452513)), closes [#1584](https://github.com/ory/kratos/issues/1584): Before, the logout endpoint would invalidate the session cookie, but not remove it. This was a regression introduced in 0.7.0. This patch resolves that issue. - **sdk:** Use proper annotation for genericError ([#1611](https://github.com/ory/kratos/issues/1611)) ([da214b2](https://github.com/ory/kratos/commit/da214b2933ae2a91d8c5bf6aa8eea613a2078b9d)), closes [#1609](https://github.com/ory/kratos/issues/1609) - Skip prompt on discord authorization by default ([#1594](https://github.com/ory/kratos/issues/1594)) ([a667255](https://github.com/ory/kratos/commit/a6672554b02378eb2dac7b1af99ea2915395867b)): When a value for prompt is not provided, Discord defaults to `prompt="consent"`. This change makes it so that if the request is not forced, prompt is explicitly set to "none". - Static parameter for warning message in config.baseURL(...) ([#1673](https://github.com/ory/kratos/issues/1673)) ([db54a1b](https://github.com/ory/kratos/commit/db54a1bd0c93d7a5845ee09d0a16cbc3b8f26a4a)), closes [#1672](https://github.com/ory/kratos/issues/1672) - Update csrf token cookie name ([#1601](https://github.com/ory/kratos/issues/1601)) ([64c90bf](https://github.com/ory/kratos/commit/64c90bf5e5cec6545a81f88ad5fabb29e9e80850)): See https://github.com/ory-corp/cloud/issues/1252 - Use eager preloading for list identites endpoint ([#1588](https://github.com/ory/kratos/issues/1588)) ([de5fb3e](https://github.com/ory/kratos/commit/de5fb3e52af9f2d0f1209eed217403a5d7d1ae2d)) ### Code Generation - Pin v0.7.3-alpha.1 release commit ([b5ad53e](https://github.com/ory/kratos/commit/b5ad53eca933438126eda3c6c647d99e05e37695)) ### Documentation - Change model to schema ([#1639](https://github.com/ory/kratos/issues/1639)) ([09c403e](https://github.com/ory/kratos/commit/09c403e55482e91a5bfe9a253e514b7a90826709)) - Fix func naming for Logout flow ([#1676](https://github.com/ory/kratos/issues/1676)) ([bbeb613](https://github.com/ory/kratos/commit/bbeb6132ba82e28057bc14bf35ea99b70f0c4118)): rename createSelfServiceLogoutUrlForBrowsers to createSelfServiceLogoutFlowUrlForBrowsers - Fix stub error example ([#1642](https://github.com/ory/kratos/issues/1642)) ([9bc2fd0](https://github.com/ory/kratos/commit/9bc2fd088ed9b3e7334713e63bae3c7bbcb922db)), closes [#1568](https://github.com/ory/kratos/issues/1568) - Fixes incorrect yaml identation ([#1641](https://github.com/ory/kratos/issues/1641)) ([6b58278](https://github.com/ory/kratos/commit/6b582784b49c1d103bbf7a6843cdf197fbd93931)) - Identity traits are visible to user ([#1621](https://github.com/ory/kratos/issues/1621)) ([641eba6](https://github.com/ory/kratos/commit/641eba675bdc583661565a6378776bfad26067c6)) - Make qickstart URLs consistent (playground vs. localhost) ([#1626](https://github.com/ory/kratos/issues/1626)) ([bae1847](https://github.com/ory/kratos/commit/bae1847eba0d925f28a010876e35e3c2093bc8c6)): Since the quick-start describes how to run Kratos locally the actual location of the redirect is `http://127.0.0.1:4433/self-service/login/browser`. - Update docker.md - Outdated information ([#1627](https://github.com/ory/kratos/issues/1627)) ([dc32720](https://github.com/ory/kratos/commit/dc32720de25f52b7deb3e32f7530c7827a6ce5df)), closes [#1619](https://github.com/ory/kratos/issues/1619): Kratos does not automatically use a config file that exists at `$HOME/.kratos.yaml`, or any other similar pattern. The documentation in the Docker Images section of the guides could lead developers to believe that the --config flag is unnecessary if they are binding the directory the configuration file is in to $HOME or using a custom docker image to provide the file. ### Features - Allow multiple webhook body sources ([#1606](https://github.com/ory/kratos/issues/1606)) ([51b1311](https://github.com/ory/kratos/commit/51b131177c9e0db018eced939fef43742c9e86cf)): This patch adds support for loading webhooks from the local filesystem, base64 encoded inline string, and remote (http/https) sources. Please note that support for relative/absolute paths without an URI scheme are deprecated and will eventually be removed. - Require verified address ([#1355](https://github.com/ory/kratos/issues/1355)) ([1cf61cd](https://github.com/ory/kratos/commit/1cf61cdeedbd8bf5b66310793249681ff976baab)), closes [#1328](https://github.com/ory/kratos/issues/1328) # [0.7.1-alpha.1](https://github.com/ory/kratos/compare/v0.7.0-alpha.1...v0.7.1-alpha.1) (2021-07-22) This release addresses regressions introduced in Ory Kratos v0.7.0 and resolves some bugs and documentation inconsistencies. ### Bug Fixes - Automatic tagging for node ui ([fe5056e](https://github.com/ory/kratos/commit/fe5056e11d1f8e4355cafa72ed1ff953077181cc)), closes [#1537](https://github.com/ory/kratos/issues/1537) - Bump kratos ui image for quickstart ([aedbb5a](https://github.com/ory/kratos/commit/aedbb5a259ea8ee63fb06c36fb1c7af78bb63ffc)), closes [#1537](https://github.com/ory/kratos/issues/1537) - Cleanup lint errors and add doc to x ([#1545](https://github.com/ory/kratos/issues/1545)) ([3cfd784](https://github.com/ory/kratos/commit/3cfd7845730685a4493c2b5d1974b79d873eea86)) - Correct meta schema ([8d4f3ff](https://github.com/ory/kratos/commit/8d4f3ff22d4ade6ae3f923c33303002e5f534cff)) - Do not reset link method ([#1573](https://github.com/ory/kratos/issues/1573)) ([835fb31](https://github.com/ory/kratos/commit/835fb3127bc10b1642b4a7573722e5dce63fedc7)) - Do not set csrf cookies on /sessions/whoami ([#1580](https://github.com/ory/kratos/issues/1580)) ([36bbd43](https://github.com/ory/kratos/commit/36bbd434114d120006d49785787a3c94c7f103f9)) - Export extensionschemas ([#1553](https://github.com/ory/kratos/issues/1553)) ([6af7638](https://github.com/ory/kratos/commit/6af76387caf37160ded75d83dc09ba0bc177a895)) - Generate CSRF token on validation creation ([#1549](https://github.com/ory/kratos/issues/1549)) ([6612c5f](https://github.com/ory/kratos/commit/6612c5f62e5cc242a808032def5714715ce49d11)), closes [#1547](https://github.com/ory/kratos/issues/1547) - Identity extension meta schema ([#1554](https://github.com/ory/kratos/issues/1554)) ([ba5ca64](https://github.com/ory/kratos/commit/ba5ca642d01917b43d49e009bf140ae13b4f1313)): Up until now the extension meta schema was only applied to top level keys. This fix now recursively checks the extension schema on any depth. - Remove domain alias config constraint ([#1542](https://github.com/ory/kratos/issues/1542)) ([c6145db](https://github.com/ory/kratos/commit/c6145dbfb278369c8e3ad6eae7e8574ed49ba193)) - Resolve wrong openapi types ([b07927c](https://github.com/ory/kratos/commit/b07927cd23cbfce23f3b0676303a2d0ca564143b)) - Update identity state openapi spec ([0217737](https://github.com/ory/kratos/commit/0217737f5a2860e299ccec4387a2cc83aaac1557)) - Use legacy ssl in quickstart config ([6c13c2b](https://github.com/ory/kratos/commit/6c13c2bedd45c10713907e24976658d4a4b88de6)), closes [#1569](https://github.com/ory/kratos/issues/1569) ### Code Generation - Pin v0.7.1-alpha.1 release commit ([4fe76af](https://github.com/ory/kratos/commit/4fe76af1302d45ddf4cf3c2c5949311c9cf1f8b8)) ### Documentation - Add instruction for creating user ([#1541](https://github.com/ory/kratos/issues/1541)) ([c2a1b6d](https://github.com/ory/kratos/commit/c2a1b6df95bcb5dfe2b238be5903f483b9e701b5)), closes [#1530](https://github.com/ory/kratos/issues/1530) - Clarify flags in schema which are not available in config file ([e5ea5fe](https://github.com/ory/kratos/commit/e5ea5fee31eb2f70dc7c33565f791da9e2e87cc2)), closes [#1514](https://github.com/ory/kratos/issues/1514) - Fix formatting of Email and Phone Verification Flow tab content ([#1536](https://github.com/ory/kratos/issues/1536)) ([0bfac67](https://github.com/ory/kratos/commit/0bfac67a06ef0d96ffd6a487c90edb44d3a40710)) - Fix typo ([#1543](https://github.com/ory/kratos/issues/1543)) ([b25bae7](https://github.com/ory/kratos/commit/b25bae7f2cdcbb60384808041744edd718a2a814)) - Fix typo ([#1544](https://github.com/ory/kratos/issues/1544)) ([547788d](https://github.com/ory/kratos/commit/547788de74794a1dcf43e5190cdfc9d2e1a2dc92)) - Update csrf pitfall flow section ([#1558](https://github.com/ory/kratos/issues/1558)) ([cc7ed4b](https://github.com/ory/kratos/commit/cc7ed4b5f65d2971a45d5d0ec6188908d070d915)), closes [#1557](https://github.com/ory/kratos/issues/1557) ### Tests - Longer wait time for e2e boot ([3a85a33](https://github.com/ory/kratos/commit/3a85a33ad8a8eec2ebf57d5a47937499141b6bc0)) # [0.7.0-alpha.1](https://github.com/ory/kratos/compare/v0.6.3-alpha.1...v0.7.0-alpha.1) (2021-07-13) About two months ago we released Ory Kratos v0.6. Today, we are excited to announce the next iteration of Ory Kratos v0.7! This release includes 215 commits from 24 contributors with over 770 files and more than 100.000 lines of code changed! Ory Kratos v0.7 brings massive developer experience improvements: - A reworked, tested, and standardized SDK based on OpenAPI 3.0.3 ([#1477](https://github.com/ory/kratos/pull/1477), [#1424](https://github.com/ory/kratos/issues/1424)); - Native support of Single-Page-Apps (ReactJS, AngularJS, ...) for all self-service flows ([#1367](https://github.com/ory/kratos/pull/1367)); - Sign in with Yandex, VK, Auth0, Slack; - An all-new, secure logout flow ([#1433](https://github.com/ory/kratos/pull/1433)); - Important security updates to the self-service GET APIs ([#1458](https://github.com/ory/kratos/pull/1458), [#1282](https://github.com/ory/kratos/issues/1282)); - Built-in support for TLS ([#1466](https://github.com/ory/kratos/pull/1466)); - Improved documentation and Go Module structure; - Resolving a case-sensitivity bug in self-service recovery and verification flows; - Improved performance for listing identities; - Support for Instant tracing ([#1429](https://github.com/ory/kratos/pull/1429)); - Improved control for SMTPS, supporting SSL and STARTTLS ([#1430](https://github.com/ory/kratos/pull/1430)); - Ability to run Ory Kratos in networks without outbound requests ([#1445](https://github.com/ory/kratos/pull/1445)); - Improved control over HTTP Cookie behavior ([#1531](https://github.com/ory/kratos/pull/1531)); - Several smaller user experience improvements and bug fixes; - Improved e2e test pipeline. In the next iteration of Ory Kratos, we will focus on providing a NextJS example application for the SPA integration as well as the long-awaited MFA flows! Please be aware that upgrading to Ory Kratos 0.7 requires you to apply SQL migrations. Make sure to back up your database before migration! For more details on breaking changes and patch notes, see below. ## Breaking Changes Prior to this change it was not possible to specify the verification/recovery link lifetime. Instead, it was bound to the flow expiry. This patch changes that and adds the ability to configure the lifespan of the link individually: ```patch selfservice: methods: link: enabled: true config: + # Defines how long a recovery link is valid for (default 1h) + lifespan: 15m ``` This is a breaking change because the link strategy no longer respects the recovery / verification flow expiry time and, unless set, will default to one hour. This change introduces a better SDK. As part of this change, several breaking changes with regards to the SDK have been introduced. We recommend reading this section carefully to understand the changes and how they might affect you. Before, the SDK was structured into tags `public` and `admin`. This stems from the fact that we have two ports in Ory Kratos - one administrative and one public port. While serves as a good overview when working with Ory Kratos, it does not express: - What module the API belongs to (e.g. self-service, identity, ...) - What maturity the API has (e.g. experimental, alpha, beta, ...) - What version the API has (e.g. v0alpha0, v1beta0, ...) This patch replaces the current `admin` and `public` tags with a versioned approach indicating the maturity of the API used. For example, `initializeSelfServiceSettingsForBrowsers` would no longer be under the `public` tag but instead under the `v0alpha1` tag: ```patch import { Configuration, - PublicApi + V0Alpha1 } from '@ory/kratos-client'; - const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public })); + const kratos = new V0Alpha1(new Configuration({ basePath: config.kratos.public })); ``` To avoid confusion when setting up the SDK, and potentially using the wrong endpoints in your codebase and ending up with strange 404 errors, Ory Kratos now redirects you to the correct port, given that `serve.(public|admin).base_url` are configured correctly. This is a significant improvement towards a more robust API experience! Further, all administrative functions require, in the Ory SaaS, authorization using e.g. an Ory Personal Access Token. In the open source, we do not know what developers use to protect their APIs. As such, we believe that it is ok to have admin and public functions under one common API and differentiate with an `admin` prefix. Therefore, the following patches should be made in your codebase: ```patch import { - AdminApi, + V0Alpha1, Configuration } from '@ory/kratos-client'; -const kratos = new AdminApi(new Configuration({ basePath: config.kratos.admin })); +const kratos = new V0Alpha1(new Configuration({ basePath: config.kratos.admin })); -kratos.createIdentity({ +kratos.adminCreateIdentity({ schema_id: 'default', traits: { /* ... */ } }) ``` Further, we have introduced a [style guide for writing SDKs annotations](https://www.ory.sh/docs/ecosystem/contributing#openapi-spec-and-go-swagger) governing how naming conventions should be chosen. We also streamlined how credentials are used. We now differentiate between: - Per-request credentials such as the Ory Session Token / Cookie ``` - public getSelfServiceRegistrationFlow(id: string, cookie?: string, options?: any) {} + public getSelfServiceSettingsFlow(id: string, xSessionToken?: string, cookie?: string, options?: any) {} ``` - Global credentials such as the Ory (SaaS) Personal Access Token. ```typescript const kratos = new V0Alpha0( new Configuration({ basePath: config.kratos.admin, accessToken: "some-token", }), ) kratosAdmin.adminCreateIdentity({ schema_id: "default", traits: { /* ... */ }, }) ``` We hope you enjoy the vastly improved experience! There are still many things that we want to iterate on. For full context, we recommend reading the proposal and discussion around these changes at [kratos#1424](https://github.com/ory/kratos/issues/1424). Additionally, the Self-Service Error endpoint was updated. First, the endpoint `/self-service/errors` is now located at the public port only with the admin port redirecting to it. Second, the parameter `?error` was renamed to `?id` for better SDK compatibility. Parameter `?error` is still working but will be deprecated at some point. Third, the response no longer contains an error array in `errors` but instead just a single error under `error`: ```patch { "id": "60208346-3a61-4880-96ae-0419cde8fca8", - "errors": [{ + "error": { "code": 404, "status": "Not Found", "reason": "foobar", "message": "The requested resource could not be found" - }], + }, "created_at": "2021-07-07T11:20:15.310506+02:00", "updated_at": "2021-07-07T11:20:15.310506+02:00" } ``` This patch introduces CSRF countermeasures for fetching all self-service flows. This ensures that users can not accidentally leak sensitive information when copy/pasting e.g. login URLs (see #1282). If a self-service flow for browsers is requested, the CSRF cookie must be included in the call, regardless if it is a client-side browser app or a server-side browser app calling. This **does not apply** for API-based flows. As part of this change, the following endpoints have been removed: - `GET /self-service/login/flows`; - `GET /self-service/registration/flows`; - `GET /self-service/verification/flows`; - `GET /self-service/recovery/flows`; - `GET /self-service/settings/flows`. Please ensure that your server-side applications use the public port (e.g. `GET /self-service/login/flows`) for fetching self-service flows going forward. If you use the SDKs, upgrading is easy by adding the `cookie` header when fetching the flows. This is only required when **using browser flows on the server side**. The following example illustrates a ExpressJS (NodeJS) server-side application fetching the self-service flows. ```patch app.get('some-route', (req: Request, res: Response) => { - kratos.getSelfServiceLoginFlow(flow).then((flow) => /* ... */ ) + kratos.getSelfServiceLoginFlow(flow, req.header('cookie')).then((flow) => /* ... */ ) - kratos.getSelfServiceRecoveryFlow(flow).then((flow) => /* ... */ ) + kratos.getSelfServiceRecoveryFlow(flow, req.header('cookie')).then((flow) => /* ... */ ) - kratos.getSelfServiceRegistrationFlow(flow).then((flow) => /* ... */ ) + kratos.getSelfServiceRegistrationFlow(flow, req.header('cookie')).then((flow) => /* ... */ ) - kratos.getSelfServiceVerificationFlow(flow).then((flow) => /* ... */ ) + kratos.getSelfServiceVerificationFlow(flow, req.header('cookie')).then((flow) => /* ... */ ) - kratos.getSelfServiceSettingsFlow(flow).then((flow) => /* ... */ ) + kratos.getSelfServiceSettingsFlow(flow, undefined, req.header('cookie')).then((flow) => /* ... */ ) }) ``` For concrete details, check out [the changes in the NodeJS app](https://github.com/ory/kratos-selfservice-ui-node/commit/e7fa292968111e06401fcfc9b1dd0e8e285a4d87). This patch refactors the logout functionality for browsers and APIs. It adds increased security and DoS-defenses to the logout flow. Previously, calling `GET /self-service/browser/flows/logout` would remove the session cookie and redirect the user to the logout endpoint. Now you have to make a call to `GET /self-service/logout/browser` which returns a JSON response including a `logout_url` URL to be used for logout. The call to `/self-service/logout/browser` must be made using AJAX with cookies enabled or by including the Ory Session Cookie in the `X-Session-Cookie` HTTP Header. You may also use the SDK method `createSelfServiceLogoutUrlForBrowsers` to do that. Additionally, the endpoint `DELETE /sessions` has been moved to `DELETE /self-service/logout/api`. Payloads and responses stay equal. The SDK method `revokeSession` has been renamed to `submitSelfServiceLogoutFlowWithoutBrowser`. We listened to your feedback and have improved the naming of the SDK method `initializeSelfServiceRecoveryForNativeApps` to better match what it does: `initializeSelfServiceRecoveryWithoutBrowser`. As in the previous release you may still use the old SDK if you do not want to deal with the SDK breaking changes for now. We listened to your feedback and have improved the naming of the SDK method `initializeSelfServiceVerificationForNativeApps` to better match what it does: `initializeSelfServiceVerificationWithoutBrowser`. As in the previous release you may still use the old SDK if you do not want to deal with the SDK breaking changes for now. We listened to your feedback and have improved the naming of the SDK method `initializeSelfServiceSettingsForNativeApps` to better match what it does: `initializeSelfServiceSettingsWithoutBrowser`. As in the previous release you may still use the old SDK if you do not want to deal with the SDK breaking changes for now. We listened to your feedback and have improved the naming of the SDK method `initializeSelfServiceregistrationForNativeApps` to better match what it does: `initializeSelfServiceregistrationWithoutBrowser`. As in the previous release you may still use the old SDK if you do not want to deal with the SDK breaking changes for now. We listened to your feedback and have improved the naming of the SDK method `initializeSelfServiceLoginForNativeApps` to better match what it does: `initializeSelfServiceLoginWithoutBrowser`. As in the previous release you may still use the old SDK if you do not want to deal with the SDK breaking changes for now. ### Bug Fixes - Add json detection to setting error subbranches ([fb83dcb](https://github.com/ory/kratos/commit/fb83dcb8ae7463079ddb33c04673cf4556f6058c)) - Add verification success message ([#1526](https://github.com/ory/kratos/issues/1526)) ([126698c](https://github.com/ory/kratos/commit/126698c0b531ca304bb323c825cbeb86b5814f31)), closes [#1450](https://github.com/ory/kratos/issues/1450) - Cache migration status ([5be2f14](https://github.com/ory/kratos/commit/5be2f149cd79ddfbe8496eccf5d5aacb6a9a0b8e)), closes [#1337](https://github.com/ory/kratos/issues/1337) - Change SMTP config validation from URI to a Regex pattern ([#1436](https://github.com/ory/kratos/issues/1436)) ([5ab1e8f](https://github.com/ory/kratos/commit/5ab1e8f17bcbc229fada2c584b2c1f576b819761)), closes [#1435](https://github.com/ory/kratos/issues/1435) - Check filesystem before fallback to bundled templates ([#1401](https://github.com/ory/kratos/issues/1401)) ([22d999e](https://github.com/ory/kratos/commit/22d999e78eb4f67d2f3ba07e62fd28ffb3331d6d)) - Continue button for oidc registration step ([2aad5ac](https://github.com/ory/kratos/commit/2aad5ac8f7055f39f4f434d26fbca74cdbe75337)), closes [#1422](https://github.com/ory/kratos/issues/1422) [#1320](https://github.com/ory/kratos/issues/1320): When signing up with an OIDC provider and the traits model is missing some fields, the submit button shows all OIDC options. Instead, it should show just one option called "Continue". - Deprecate sessionCookie ([#1428](https://github.com/ory/kratos/issues/1428)) ([eccad74](https://github.com/ory/kratos/commit/eccad741a1702181d4b207aad954a950906a808b)), closes [#1426](https://github.com/ory/kratos/issues/1426) - Do not cache incomplete migrations ([#1434](https://github.com/ory/kratos/issues/1434)) ([154c26f](https://github.com/ory/kratos/commit/154c26f6da4bb7040deabdc352c90cdae42c69fe)) - Do not run network migrations when booting ([12bbab9](https://github.com/ory/kratos/commit/12bbab9d3cf788998cd4a9be50ac8c7a9d2232bd)), closes [#1399](https://github.com/ory/kratos/issues/1399) - Format test files ([0468aa1](https://github.com/ory/kratos/commit/0468aa19ebfb0f68de5d9d1e59180d953f197cc0)) - Improve identity list performance ([f76886f](https://github.com/ory/kratos/commit/f76886fe7436f71fbef00081888a2f8d0106ba98)), closes [#1412](https://github.com/ory/kratos/issues/1412) - Incorrect openapi specification for verification submission ([#1431](https://github.com/ory/kratos/issues/1431)) ([ecb0a01](https://github.com/ory/kratos/commit/ecb0a01f61441aa97751943b5e9ddcc28f783d91)), closes [#1368](https://github.com/ory/kratos/issues/1368) - Link t docker guide ([953c6d6](https://github.com/ory/kratos/commit/953c6d60f6b6d82ac1406e84c2d87119e63dac48)) - Mark ui node message as optional ([#1365](https://github.com/ory/kratos/issues/1365)) ([7b8d59f](https://github.com/ory/kratos/commit/7b8d59f48ed14a6d0672238645d8675d4bf7fd77)), closes [#1361](https://github.com/ory/kratos/issues/1361) [#1362](https://github.com/ory/kratos/issues/1362) - Mark verified_at as omitempty ([77b258e](https://github.com/ory/kratos/commit/77b258e57a3d53fe437838a5e9c57805e9c970aa)): Closes https://github.com/ory/sdk/issues/46 - Panic if contextualizer is not set ([760035a](https://github.com/ory/kratos/commit/760035a6c5efa08561b93daff57ebb4655032b2a)) - Panic on error in issue session ([5fbd855](https://github.com/ory/kratos/commit/5fbd8557e1f907dd400bfcd26c187db16dc344ba)), closes [#1384](https://github.com/ory/kratos/issues/1384) - Prometheus metrics fix ([#1299](https://github.com/ory/kratos/issues/1299)) ([ac5d00d](https://github.com/ory/kratos/commit/ac5d00d472a87ab51e7c6834e2cb59f107fc3b3b)) - Recovery email case sensitive ([#1357](https://github.com/ory/kratos/issues/1357)) ([bce14c4](https://github.com/ory/kratos/commit/bce14c487450bd668859f362b98704644fa4c72a)), closes [#1329](https://github.com/ory/kratos/issues/1329) - Remove changelog ([7affb7a](https://github.com/ory/kratos/commit/7affb7a25bc84082e0ad8096e6c0e4b3933ac5f6)) - Remove obsolete ADD for corp module ([#1455](https://github.com/ory/kratos/issues/1455)) ([0fa3a53](https://github.com/ory/kratos/commit/0fa3a539fbe1ae498434b200c3b636de10d73a7c)) - Remove typing from node.attribute.value ([63a5e08](https://github.com/ory/kratos/commit/63a5e08afab76dafbfe13e6126e165af28492aad)): Closes https://github.com/ory/sdk/issues/75 Closes https://github.com/ory/sdk/issues/74 Closes https://github.com/ory/sdk/issues/72 - Rename client package for external consumption ([cba8b00](https://github.com/ory/kratos/commit/cba8b00c8b755cc0bdc7818bc9d7390ff3532ce1)) - Resolve build issues on release ([7c265a8](https://github.com/ory/kratos/commit/7c265a8b909dcc07ceeeda546a748ad28ab0c746)) - Resolve driver issues ([47b1c8d](https://github.com/ory/kratos/commit/47b1c8dce57a023e89a2b178bc8a033496ef4ff2)) - Resolve network regression ([8f96b1f](https://github.com/ory/kratos/commit/8f96b1fe4d0846a3ad97a45bc972ece04109289d)) - Resolve network regressions ([8fc52c0](https://github.com/ory/kratos/commit/8fc52c034ed9978c2a04cc66bccc9b795c9bbefa)) - Testhelper regressions ([bf3b04f](https://github.com/ory/kratos/commit/bf3b04fd2c7f9162073cb584d6fb0d59e868ecbf)) - Use correct url in submitSelfServiceVerificationFlow ([ab8a600](https://github.com/ory/kratos/commit/ab8a600080ac0d6a6235806b74c5b9e3dc1c2d60)) - Use local schema URL for sorting UI nodes ([#1449](https://github.com/ory/kratos/issues/1449)) ([a003885](https://github.com/ory/kratos/commit/a0038853f30cd7d139d42d1d4601c8cf49d03934)) - Use session cookie path settings for csrf cookie ([#1493](https://github.com/ory/kratos/issues/1493)) ([c6d08ed](https://github.com/ory/kratos/commit/c6d08edae32fd94877fb58355d3c711460c7d1a2)), closes [#1292](https://github.com/ory/kratos/issues/1292): This PR adds configuration option for CSRF cookies and improves the domain alias logic as well as adding tests for it. - Use STARTTLS for smtps connections ([#1430](https://github.com/ory/kratos/issues/1430)) ([c21bb80](https://github.com/ory/kratos/commit/c21bb80a749df7b224a8ac3f15fa62523a78d805)), closes [#781](https://github.com/ory/kratos/issues/781) - Version schema ([#1359](https://github.com/ory/kratos/issues/1359)) ([8c4bac7](https://github.com/ory/kratos/commit/8c4bac71674e45e440d916c6c947ed018a8ea29a)), closes [#1331](https://github.com/ory/kratos/issues/1331) [#1101](https://github.com/ory/kratos/issues/1101) [ory/hydra#2427](https://github.com/ory/hydra/issues/2427) ### Code Generation - Pin v0.7.0-alpha.1 release commit ([53a0e38](https://github.com/ory/kratos/commit/53a0e38c2b5d7003786a8386a9c4cf129acc06aa)) ### Code Refactoring - Corp package ([#1402](https://github.com/ory/kratos/issues/1402)) ([0202dc5](https://github.com/ory/kratos/commit/0202dc57aacc0d48e4c1ee4e68c91654451f63fa)) - Finalize SDK refactoring ([e772641](https://github.com/ory/kratos/commit/e772641f9bcfa462aa5111cf1329a479e3cdff99)), closes [#1424](https://github.com/ory/kratos/issues/1424) - Identity SDKs ([d8658dc](https://github.com/ory/kratos/commit/d8658dc887a76d82e3cf23386c03b5ebf7053189)), closes [#1477](https://github.com/ory/kratos/issues/1477) - Improve session sdk ([7207af4](https://github.com/ory/kratos/commit/7207af4cdf6c78dd3f0fd42b6727d7e320d252e6)) - Introduce DefaultContextualizer in corp package ([#1390](https://github.com/ory/kratos/issues/1390)) ([944d045](https://github.com/ory/kratos/commit/944d045aa7fc59eadfdd18951f0d4937b1ea79df)), closes [#1363](https://github.com/ory/kratos/issues/1363) - Move cleansql to separate package ([7c203dc](https://github.com/ory/kratos/commit/7c203dc8219afe07f180143f832158615b51f60a)) - Openapi.json -> api.json ([6df0de5](https://github.com/ory/kratos/commit/6df0de5d0b4c952576bf9e14c18d521934edd9bb)) - Self-service error APIs ([65c482f](https://github.com/ory/kratos/commit/65c482fba62c2782b03a3b840124eac062499266)) ### Documentation - Add docs for registration SPA flow ([84458f1](https://github.com/ory/kratos/commit/84458f1a9dfe8be6a97bddd832fcc508b60b8498)) - Add go sdk examples ([e948fad](https://github.com/ory/kratos/commit/e948faddce3a1f52df964c701f6ba2a28f5dfe03)) - Add kratos quickstart config notes ([#1490](https://github.com/ory/kratos/issues/1490)) ([2f8094c](https://github.com/ory/kratos/commit/2f8094c50eaf7e1cd964067172adcad407713764)) - Add replit instructions ([8ab8607](https://github.com/ory/kratos/commit/8ab8607dee433f6e708ade296a6c26d0a87d0aae)) - Add tested and running go sdk examples ([3b56bb5](https://github.com/ory/kratos/commit/3b56bb5fd37d0e7d4479967aa0b5721a68a267f2)) - Correct CII badge ([#1447](https://github.com/ory/kratos/issues/1447)) ([048aec3](https://github.com/ory/kratos/commit/048aec39295f0a3534df5e43e3cd7684d4fbd758)) - Fix broken link ([9eaf764](https://github.com/ory/kratos/commit/9eaf764b28f3ca1dae2816d4c0a985c4866c409b)) - Fix building from source ([#1473](https://github.com/ory/kratos/issues/1473)) ([af54d5b](https://github.com/ory/kratos/commit/af54d5bb9e36f90d272d293817f0d6d7eb2e79a8)) - Fix typo in "Sign in/up with ID & assword" ([#1383](https://github.com/ory/kratos/issues/1383)) ([f39739d](https://github.com/ory/kratos/commit/f39739d94e97f20b94630b957371d11294dc8300)) - Mark login endpoints as experimental ([6faf0f6](https://github.com/ory/kratos/commit/6faf0f65bb05bbafdee6b1274a719695fd5b4173)) - Refactor documentation and adopt changes for [#1477](https://github.com/ory/kratos/issues/1477) ([f5e96cd](https://github.com/ory/kratos/commit/f5e96cd5054e734c319ed32992357fcd73ac44a1)), closes [#1472](https://github.com/ory/kratos/issues/1472) - Remove changelog from docs folder ([5a7e3d8](https://github.com/ory/kratos/commit/5a7e3d83a5fb7f3e6945f37d42abca14d2982e72)) - Resolve build issues ([b51bb55](https://github.com/ory/kratos/commit/b51bb555d829ab020e593a764cbce4c5ba4885a2)) - Resolve typos and docs react issues ([2d640e4](https://github.com/ory/kratos/commit/2d640e4b9b556fd866c29c83564cb1c7702ab9ff)) - Update docs for all flows ([d29ea69](https://github.com/ory/kratos/commit/d29ea69f6bb908b529502030942b1ced52227372)) - Update documentation for plaintext templates ([#1369](https://github.com/ory/kratos/issues/1369)) ([419784d](https://github.com/ory/kratos/commit/419784dd0d4ddc338830ed0d77a7d99f8f440777)), closes [#1351](https://github.com/ory/kratos/issues/1351) - Update error documentation ([7d83609](https://github.com/ory/kratos/commit/7d8360973a3359bec321a60f4f3a4202ac7d2430)) - Update login flow documentation ([a27de91](https://github.com/ory/kratos/commit/a27de91e9e06f8501ae9cb70446ed0aae5a39f71)) - Update path ([f0384d9](https://github.com/ory/kratos/commit/f0384d9c11085230fd16290c524d22fac6002870)) - Update README.md Go instructions ([#1464](https://github.com/ory/kratos/issues/1464)) ([8db4b4a](https://github.com/ory/kratos/commit/8db4b4a966c5c418cf9d9169b66d7dacff256113)) - Update remaining self service documentation ([bcc6284](https://github.com/ory/kratos/commit/bcc62846297a67216e01e8c31d375d376c1b7cef)) - Update sdk use ([bcb8c06](https://github.com/ory/kratos/commit/bcb8c06ee324c639e548fc06315d9e952f470582)) - Update settings documentation ([258ceaf](https://github.com/ory/kratos/commit/258ceaf84e6ee15b8eee2f203f456f73e7d406d5)) - Use correct path ([#1333](https://github.com/ory/kratos/issues/1333)) ([e401135](https://github.com/ory/kratos/commit/e401135cf415d7e3e6a8ca463dd47e46fe399b33)) ### Features - Add examples for usage of go sdk ([870c2bd](https://github.com/ory/kratos/commit/870c2bd316a3e5b7ce9d526ebf369e41dbea2630)) - Add GetContextualizer ([ac32717](https://github.com/ory/kratos/commit/ac3271742c9c2b968b08dd2b35a5d120c5befcd9)) - Add helper for starting kratos e2e ([#1469](https://github.com/ory/kratos/issues/1469)) ([b9c7674](https://github.com/ory/kratos/commit/b9c7674c30df8200bcd7223c2fa6b058e833bb8a)) - Add instana as possible tracing provider ([#1429](https://github.com/ory/kratos/issues/1429)) ([abe48a9](https://github.com/ory/kratos/commit/abe48a97ee75567979a70f00dd73ff698efcc75d)), closes [#1385](https://github.com/ory/kratos/issues/1385) - Add redoc ([#1502](https://github.com/ory/kratos/issues/1502)) ([492266d](https://github.com/ory/kratos/commit/492266de9c9b7b775a7b21b5890361380d911da4)) - Add vk and yandex providers to oidc providers and documentation ([#1339](https://github.com/ory/kratos/issues/1339)) ([22a3ef9](https://github.com/ory/kratos/commit/22a3ef98181eb5922cc0f1c016d42ce46732d0a2)), closes [#1234](https://github.com/ory/kratos/issues/1234) - Anti-CSRF measures when fetching flows ([#1458](https://github.com/ory/kratos/issues/1458)) ([5171557](https://github.com/ory/kratos/commit/51715572ea08f654d1e97d760b9c3d3a9113aa3d)), closes [#1282](https://github.com/ory/kratos/issues/1282) - Configurable recovery/verification link lifetime ([f80d4e3](https://github.com/ory/kratos/commit/f80d4e3bf7df603b73589dbc6805c69d049921e0)) - Disable HaveIBeenPwned validation when HaveIBeenPwnedEnabled is set to false ([#1445](https://github.com/ory/kratos/issues/1445)) ([44002f4](https://github.com/ory/kratos/commit/44002f4fa93b40a6bb18f1e759bb416d082cec08)), closes [#316](https://github.com/ory/kratos/issues/316): This patch introduces an option to disable HaveIBeenPwned checks in environments where outbound network calls are disabled. - **identities:** Add a state to identities ([#1312](https://github.com/ory/kratos/issues/1312)) ([d22954e](https://github.com/ory/kratos/commit/d22954e2fdb7b2dd5206651b6dd5cf96185a33ba)), closes [#598](https://github.com/ory/kratos/issues/598) - Improve contextualization in serve/daemon ([f83cd35](https://github.com/ory/kratos/commit/f83cd355422fb4b422f703406473bda914d8419c)) - Include Credentials Metadata in admin api ([#1274](https://github.com/ory/kratos/issues/1274)) ([c8b6219](https://github.com/ory/kratos/commit/c8b62190fca53db4e1b3a4ddb5253fbd2fd46002)), closes [#820](https://github.com/ory/kratos/issues/820) - Include Credentials Metadata in admin api Missing changes in handler ([#1366](https://github.com/ory/kratos/issues/1366)) ([a71c220](https://github.com/ory/kratos/commit/a71c2208dedac45d32dab578e62a5e3105c8dee0)) - Natively support SPA for login flows ([6ff67af](https://github.com/ory/kratos/commit/6ff67afa8b0fc0a95cec44d3dda2cbc1987b51dd)), closes [#1138](https://github.com/ory/kratos/issues/1138) [#668](https://github.com/ory/kratos/issues/668): This patch adds the long-awaited capabilities for natively working with SPAs and AJAX requests. Previously, requests to the `/self-service/login/browser` endpoint would always end up in a redirect. Now, if the `Accept` header is set to `application/json`, the login flow will be returned as JSON instead. Accordingly, changes to the error and submission flow have been made to support `application/json` content types and SPA / AJAX requests. - Natively support SPA for recovery flows ([5461244](https://github.com/ory/kratos/commit/5461244943286081e13c304a3b38413b8ee6fdf2)): This patch adds the long-awaited capabilities for natively working with SPAs and AJAX requests. Previously, requests to the `/self-service/recovery/browser` endpoint would always end up in a redirect. Now, if the `Accept` header is set to `application/json`, the registration flow will be returned as JSON instead. Accordingly, changes to the error and submission flow have been made to support `application/json` content types and SPA / AJAX requests. - Natively support SPA for registration flows ([57d3c57](https://github.com/ory/kratos/commit/57d3c5786a88f0648e7fa57f181f060a057ec19f)), closes [#1138](https://github.com/ory/kratos/issues/1138) [#668](https://github.com/ory/kratos/issues/668): This patch adds the long-awaited capabilities for natively working with SPAs and AJAX requests. Previously, requests to the `/self-service/registration/browser` endpoint would always end up in a redirect. Now, if the `Accept` header is set to `application/json`, the registration flow will be returned as JSON instead. Accordingly, changes to the error and submission flow have been made to support `application/json` content types and SPA / AJAX requests. - Natively support SPA for settings flows ([ea4395e](https://github.com/ory/kratos/commit/ea4395ed25d5668e4ce365336cd7a5e13e0ba1cc)): This patch adds the long-awaited capabilities for natively working with SPAs and AJAX requests. Previously, requests to the `/self-service/settings/browser` endpoint would always end up in a redirect. Now, if the `Accept` header is set to `application/json`, the registration flow will be returned as JSON instead. Accordingly, changes to the error and submission flow have been made to support `application/json` content types and SPA / AJAX requests. - Natively support SPA for verification flows ([c151500](https://github.com/ory/kratos/commit/c1515009dcd1b5946a93733feedb01753de91c3d)): This patch adds the long-awaited capabilities for natively working with SPAs and AJAX requests. Previously, requests to the `/self-service/verification/browser` endpoint would always end up in a redirect. Now, if the `Accept` header is set to `application/json`, the registration flow will be returned as JSON instead. Accordingly, changes to the error and submission flow have been made to support `application/json` content types and SPA / AJAX requests. - Protect logout against CSRF ([#1433](https://github.com/ory/kratos/issues/1433)) ([1a7a74c](https://github.com/ory/kratos/commit/1a7a74c3fe425f139a87bb68fbc07f8862c00e58)), closes [#142](https://github.com/ory/kratos/issues/142) - Sign in with Auth0 ([#1352](https://github.com/ory/kratos/issues/1352)) ([f618a53](https://github.com/ory/kratos/commit/f618a53fb971ad16121aa8728cfec54253bb3f44)), closes [#609](https://github.com/ory/kratos/issues/609) - Support api in settings error ([23105db](https://github.com/ory/kratos/commit/23105dbb836d920b8766536b65de58932f53d6f6)) - Support reading session token from X-Session-Token HTTP header ([dcaefd9](https://github.com/ory/kratos/commit/dcaefd94a0b2cf819424f2e10b3bdae63b256726)) - Team id in slack oidc ([#1409](https://github.com/ory/kratos/issues/1409)) ([e4d021a](https://github.com/ory/kratos/commit/e4d021a037a6b44f8bd66372e9c260c640e87b9d)), closes [#1408](https://github.com/ory/kratos/issues/1408) - TLS support for public and admin endpoints ([#1466](https://github.com/ory/kratos/issues/1466)) ([7f44f81](https://github.com/ory/kratos/commit/7f44f819a5989a699e403e02c69541369573078f)), closes [#791](https://github.com/ory/kratos/issues/791) - Update openapi specs and regenerate ([cac507e](https://github.com/ory/kratos/commit/cac507eb5b1f39d003d72e57912dbbfe6f92deb1)) ### Tests - Add tests for cookie behavior of API and browser endpoints ([d1b1521](https://github.com/ory/kratos/commit/d1b15217867cfb92a615c793b26fad288f5e5742)) - **e2e:** Greatly improve test performance ([#1421](https://github.com/ory/kratos/issues/1421)) ([2ffad9e](https://github.com/ory/kratos/commit/2ffad9ee751471451e2151719a2e70d5f89437b0)): Instead of running the individual profiles as separate Cypress instances, we now use one singular instance which updates the Ory Kratos configuration depending on the test context. This ensures that hot-reloading is properly working while also signficantly reducing the amount of time spent on booting up the service dependencies. - **e2e:** Resolve flaky test issues related to timeouts and speed ([b083791](https://github.com/ory/kratos/commit/b083791858bc26a02250d7f5a4e8883cd7392a58)) - **e2e:** Resolve recovery regression ([72c47d6](https://github.com/ory/kratos/commit/72c47d65415efbb53d5d680bd9d78156d577b67f)) - **e2e:** Resolve test config regressions ([eb9c4f9](https://github.com/ory/kratos/commit/eb9c4f98f2e30ac420ed1e3f18a3f0d9ff23846e)) - Remove obsolete console.log ([3ecc869](https://github.com/ory/kratos/commit/3ecc869ebfef5c97334ae4334fb4af98ca9baf97)) - Resolve e2e regressions ([b0d3b82](https://github.com/ory/kratos/commit/b0d3b82f301942bebe3c0027c8b3160749f907af)) - Resolve migratest panic ([89d05ae](https://github.com/ory/kratos/commit/89d05ae0c376c4ea1f23708cccf95c9754a29c94)) - Resolve mobile regressions ([868e82e](https://github.com/ory/kratos/commit/868e82e3d7aec4cde80d7c1d0ce4601e40695f27)) - Resolve oidc regressions ([2403082](https://github.com/ory/kratos/commit/2403082701ac5d667706afd893a6d406496f67fa)) ### Unclassified - add CoC shield (#1439) ([826ed1a](https://github.com/ory/kratos/commit/826ed1a6deafdc2631a5c72f0bfacc91b06a3435)), closes [#1439](https://github.com/ory/kratos/issues/1439) - u ([b03549b](https://github.com/ory/kratos/commit/b03549b6340ec0bf4f9d741ce145ca90bbc09968)) - u ([318a31d](https://github.com/ory/kratos/commit/318a31d400b97653b4f377c67df4ae0afea189d9)) - Format ([eca7aff](https://github.com/ory/kratos/commit/eca7aff2be96c673dd6be5dc36ab1f4850cc44f0)) - Format ([5cc9fc3](https://github.com/ory/kratos/commit/5cc9fc3a6e91a96225d016d60c8da5cef647ac18)) - Format ([e525805](https://github.com/ory/kratos/commit/e525805246431075d26c3f47596ae93f6580d8ee)) - Format ([4a692ac](https://github.com/ory/kratos/commit/4a692acc7db160068ed7d81461b173bc957e4736)) - Format ([169c0cd](https://github.com/ory/kratos/commit/169c0cd8d424babef69a52ddf65e2b75ded09a46)) # [0.6.3-alpha.1](https://github.com/ory/kratos/compare/v0.6.2-alpha.1...v0.6.3-alpha.1) (2021-05-17) This release addresses some minor bugs and improves the SDK experience. Please be aware that the Ory Kratos SDK v0.6.3+ have breaking changes compared to Ory Kratos SDK v0.6.2. If you do not wish to update your code, you can keep using the Ory Kratos v0.6.2 SDK and upgrade to v0.6.3+ SDKs at a later stage, as only naming conventions have changed! ## Breaking Changes Unfortunately, some method signatures have changed in the SDKs. Below is a list of changed entries: - Error `genericError` was renamed to `jsonError` and now includes more information and better typing for errors; - The following functions have been renamed: - `initializeSelfServiceLoginViaAPIFlow` -> `initializeSelfServiceLoginForNativeApps` - `initializeSelfServiceLoginViaBrowserFlow` -> `initializeSelfServiceLoginForBrowsers` - `initializeSelfServiceRegistrationViaAPIFlow` -> `initializeSelfServiceRegistrationForNativeApps` - `initializeSelfServiceRegistrationViaBrowserFlow` -> `initializeSelfServiceRegistrationForBrowsers` - `initializeSelfServiceSettingsViaAPIFlow` -> `initializeSelfServiceSettingsForNativeApps` - `initializeSelfServiceSettingsViaBrowserFlow` -> `initializeSelfServiceSettingsForBrowsers` - `initializeSelfServiceRecoveryViaAPIFlow` -> `initializeSelfServiceRecoveryForNativeApps` - `initializeSelfServiceRecoveryViaBrowserFlow` -> `initializeSelfServiceRecoveryForBrowsers` - `initializeSelfServiceVerificationViaAPIFlow` -> `initializeSelfServiceVerificationForNativeApps` - `initializeSelfServiceVerificationViaBrowserFlow` -> `initializeSelfServiceVerificationForBrowsers` - Some type names have changed, for example `traits` -> `identityTraits`. ### Bug Fixes - Improve settings oas definition ([867abfc](https://github.com/ory/kratos/commit/867abfc813b08142786f71bfe28e373d4754c959)) - Properly handle CSRF for API flows in recovery and verification strategies ([461c829](https://github.com/ory/kratos/commit/461c829dc4d7f7b70620abee2263efba78ce463a)), closes [#1141](https://github.com/ory/kratos/issues/1141) - **session:** Use specific headers before bearer use ([82c0b54](https://github.com/ory/kratos/commit/82c0b545b29b30fcf3521d9621ec5c5f1a23dc96)) - Use correct api spec path ([5f41f87](https://github.com/ory/kratos/commit/5f41f87bea2919cdf4e9f55c6ad938c5bc08b619)) - Use correct openapi path for validation ([#1340](https://github.com/ory/kratos/issues/1340)) ([a0f5673](https://github.com/ory/kratos/commit/a0f5673d6aa4e60bab06ef699dce231f0bf4aeff)) ### Code Generation - Pin v0.6.3-alpha.1 release commit ([5edf952](https://github.com/ory/kratos/commit/5edf9524d812795ac5712e4a9541b34359234724)) ### Code Refactoring - Improve SDK experience ([71b8511](https://github.com/ory/kratos/commit/71b8511ae1f6f77b2996a01a55accc99d171cfaf)): This patch resolves UX issues in the auto-generated SDKs by using consistent naming and introducing a test suite for the Ory SaaS. # [0.6.2-alpha.1](https://github.com/ory/kratos/compare/v0.6.1-alpha.1...v0.6.2-alpha.1) (2021-05-14) Resolves an issue in the Go SDK. ### Code Generation - Pin v0.6.2-alpha.1 release commit ([99c1b1d](https://github.com/ory/kratos/commit/99c1b1d674df3bd8263f7cbf1ed2bdfae6281f69)) ### Documentation - Update link to example email template. ([#1326](https://github.com/ory/kratos/issues/1326)) ([28a1723](https://github.com/ory/kratos/commit/28a17234b557cabf17b592ee68041aec695f6d20)) # [0.6.1-alpha.1](https://github.com/ory/kratos/compare/v0.6.0-alpha.2...v0.6.1-alpha.1) (2021-05-11) This release primarily addresses issues in the SDK CI pipeline. ### Code Generation - Pin v0.6.1-alpha.1 release commit ([1df82da](https://github.com/ory/kratos/commit/1df82daaf3f9cfd3a470d7c9bf8d96abbd52b872)) ### Features - Allow changing password validation API DNS name ([#1009](https://github.com/ory/kratos/issues/1009)) ([ced85e8](https://github.com/ory/kratos/commit/ced85e8091b06d864cc55c9975f8b006f6be1ce4)) # [0.6.0-alpha.2](https://github.com/ory/kratos/compare/v0.6.0-alpha.1...v0.6.0-alpha.2) (2021-05-07) This release addresses issues with the SDK pipeline and also closes a bug related to email sending. ### Bug Fixes - Update node image ([eef307e](https://github.com/ory/kratos/commit/eef307e6bc33c9ec36ed9138f99c19f72c7be575)) ### Code Generation - Pin v0.6.0-alpha.2 release commit ([a3658ba](https://github.com/ory/kratos/commit/a3658badb848656b61d54b3ee35114972afc1f35)) ### Features - Fix unexpected emails when update profile ([#1300](https://github.com/ory/kratos/issues/1300)) ([7b24485](https://github.com/ory/kratos/commit/7b2448566f82e69d555997654ee410f9b4ff3939)), closes [#1221](https://github.com/ory/kratos/issues/1221) # [0.6.0-alpha.1](https://github.com/ory/kratos/compare/v0.5.5-alpha.1...v0.6.0-alpha.1) (2021-05-05) Today Ory Kratos v0.6 has been released! We are extremely happy with this release where we made many changes that pave the path for exciting future additions such as integrating 2FA more easily! We would like to thank the awesome community for the many contributions. Kratos v0.6 includes an insane amount of work spread over the last five months - 480 commits and over 4200 files changed. The team at Ory would like to thank all the amazing contributors that made this release possible! Here is a summary of the most important changes: - Ory Kratos now support highly customizable web hooks - contributed by [@dadrus](https://github.com/dadrus) and [@martinei](https://github.com/martinei); - Ory Kratos Courier can now be run as a standalone task using `kratos courier watch -c your/config.yaml`. To use the mail courier as a background task of the server run `kratos serve --watch-courier` - contributed by [@mattbonnell](https://github.com/mattbonnell); - Reworked migrations to ensure stable migrations in production systems - backward compatibility is ensured and tested; - Upgraded to Go 1.16 and removed all static file packers, greatly improving build time; - Refactored our SDK pipeline from Swagger 2.0 to OpenAPI Spec 3.0. Ory's SDKs are now properly typed and bugs can easily be addressed using a patch process. Due to this, we had to move away from go-swagger client generation for the Go SDK and replace it with openapi-generator. This, unfortunately, introduced breaking changes in the Go SDK APIs. If you have problems migrating, or have a tutorial on how to migrate, please share it with the community on GitHub! - Created reliable health and status checks by ensuring that e.g. migrations have completed; - Made resilient CLI client commands e.g. kratos identities list; - Better support for cookies in multi-domain setups called [domain aliasing](https://www.ory.sh/kratos/docs/guides/configuring-cookies); - A new, [dynamically generated FAQ](https://www.ory.sh/kratos/docs/next/faq); - Enhanced GitHub and Google claims parsing; - Faster and more resilient CI/CD pipeline; - Improvements for running Ory Kratos in secure Kubernetes environments; - Better Helm Charts for Ory Kratos; - Support for BCrypt hashing, which is now the default hashing implementation. Existing Argon2id hashes will be automatically translated to BCrypt hashes when the user signs in the next time. We recommend using Argon2id in use cases where password hashing is required to take at least 2 seconds. For regular web workloads (200ms) BCrypt is recommended - contributed by [@seremenko-wish](https://github.com/seremenko-wish); - The Argon2 memory configuration is now human readable: `hashers.argon2.memory: 131072` -> `hashers.argon2.memory: 131072B` (supports kb, mb, kib, mib, ...). - Add possibility to keep track of the return_to URLs for verification_flows after sign up using the new `after_verification_return_to` query parameter (e.g. `http://foo.com/registration?after_verification_return_to=verification_callback`) - contributed by [@mattbonnell](https://github.com/mattbonnell); - Emails are now populated at delivery time, offering more flexibility in terms of templating; - Emails contain a plaintext variant for email clients that do not display HTML emails - contributed by [@mattbonnell](https://github.com/mattbonnell); - Mitigation for password hash timing attacks by adding a random delay to login attempts where the user does not exist; - Resolving SDKs issues for whoami requests; - Simplified database schema for faster processing, significantly reducing the amount of data stored and latency as several JOINS have been removed; - Support for binding the HTTP server on UNIX sockets - contributed by [@sloonz](https://github.com/sloonz); There are even more contributions by [@NickUfer](https://github.com/NickUfer) and [harnash](https://github.com/harnash). In total, [33 people contributed to this release](https://github.com/ory/kratos/graphs/contributors?from=2020-12-09&to=2021-05-04&type=c)! Thank you all! _IMPORTANT:_ Please be aware that the database schema has changed significantly. Applying migrations might, depending on the size of your tables, take a long time. If your database does not support online schema migrations, you will experience downtimes. Please test the migration process before applying it to production! The probably biggest and most significant change is the refactoring of how self-service flows work and what their payloads look like. This took the most amount of time and introduces the biggest breaking changes in our APIs. We did this refactoring to support several flows planned for Ory Kratos 0.7: 1. Displaying QR codes (images) in login, registration, settings flows - necessary for TOTP 2FA; 2. Asking the login/registration/... UI to render JavaScript - necessary for CAPTCHA, WebAuthN, and more; 3. Refactoring the form submission API to use one endpoint per flow instead of one endpoint per flow per method. This allows us to process several registration/settings/login/... methods such as password + 2FA in one Go. [Check out how we migrated the NodeJS app](https://github.com/ory/kratos-selfservice-ui-node/commit/53ad90b6c82cde48994feebcc75d754ba74929ec) from the Ory Kratos 0.5 to Ory Kratos 0.6 SDK. Let's take a look into how these payloads have changed (the flows have identical configuration): **Ory Kratos v0.5** _Login_ ```json { "id": "ee6e1565-d3c3-4f3a-a6ff-0ba6b3a6481b", "type": "browser", "expires_at": "2020-09-13T10:49:54.8295242Z", "issued_at": "2020-09-13T10:39:54.8295242Z", "request_url": "http://127.0.0.1:4433/self-service/login/browser", "methods": { "password": { "method": "password", "config": { "action": "http://127.0.0.1:4433/self-service/login/methods/password?flow=ee6e1565-d3c3-4f3a-a6ff-0ba6b3a6481b", "method": "POST", "fields": [ { "name": "identifier", "type": "text", "required": true, "value": "" }, { "name": "password", "type": "password", "required": true }, { "name": "csrf_token", "type": "hidden", "required": true, "value": "lNrB8sW2fZY6xnnA91V7ISYrUVcJbmRCOoGHjsnsfI7MsIL5RTbuWFm5TRv1azQW+7IRCfnt2Ch6pC42/45sJQ==" } ] } } }, "forced": false } ``` _Registration_ ```json { "id": "2b1f8c5d-e830-4068-97b8-35f776df9217", "type": "browser", "expires_at": "2020-09-13T10:53:15.1774019Z", "issued_at": "2020-09-13T10:43:15.1774019Z", "request_url": "http://127.0.0.1:4433/self-service/registration/browser", "active": "password", "messages": null, "methods": { "password": { "method": "password", "config": { "action": "http://127.0.0.1:4433/self-service/registration/methods/password?flow=2b1f8c5d-e830-4068-97b8-35f776df9217", "method": "POST", "fields": [ { "name": "csrf_token", "type": "hidden", "required": true, "value": "1IlHWNjkAZxuYhO82WPgNTgujKsUSaW87j6og/20i2uM4wRTWGSSUg0dJ2fbXa8C5bfM9eTKGdauGwE7y9abwA==" }, { "name": "password", "type": "password", "required": true, "messages": [ { "id": 4000005, "text": "The password can not be used because the password has been found in at least 23597311 data breaches and must no longer be used..", "type": "error", "context": { "reason": "the password has been found in at least 23597311 data breaches and must no longer be used." } } ] }, { "name": "traits.email", "type": "text", "value": "foo@ory.sh" }, { "name": "traits.name.first", "type": "text", "value": "Ory" }, { "name": "traits.name.last", "type": "text", "value": "Corp" } ] } } } } ``` **Ory Kratos v0.6** _Login_ As you can see below, the input name `identifier` has changed to `password_identifier`. ```json { "id": "07016811-917d-4788-bb9c-fc297897af6c", "type": "browser", "expires_at": "2021-04-28T08:37:53.924337873Z", "issued_at": "2021-04-28T08:27:53.924337873Z", "request_url": "http://127.0.0.1:4433/self-service/login/browser", "ui": { "action": "http://127.0.0.1:4433/self-service/login?flow=07016811-917d-4788-bb9c-fc297897af6c", "method": "POST", "nodes": [ { "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "IuiHo8fajl6Nwi2CfR33bmC7ZI+geYY44oinK/npkS9gaeV6DlkzS0voYZuyGawsCruvlawFl/pY6/Ph6d9JVg==", "required": true, "disabled": false }, "messages": null, "meta": {} }, { "type": "input", "group": "password", "attributes": { "name": "password_identifier", "type": "text", "value": "", "required": true, "disabled": false }, "messages": null, "meta": { "label": { "id": 1070004, "text": "ID", "type": "info" } } }, { "type": "input", "group": "password", "attributes": { "name": "password", "type": "password", "required": true, "disabled": false }, "messages": null, "meta": { "label": { "id": 1070001, "text": "Password", "type": "info" } } }, { "type": "input", "group": "password", "attributes": { "name": "method", "type": "submit", "value": "password", "disabled": false }, "messages": null, "meta": { "label": { "id": 1010001, "text": "Sign in", "type": "info", "context": {} } } } ] }, "forced": false } ``` _Registration_ ```json { "id": "f0c0830a-f5b2-4c2d-a37f-2e70152a4f7c", "type": "browser", "expires_at": "2021-04-28T08:54:12.951178972Z", "issued_at": "2021-04-28T08:44:12.951178972Z", "request_url": "http://127.0.0.1:4433/self-service/registration/browser", "ui": { "action": "http://127.0.0.1:4433/self-service/registration?flow=f0c0830a-f5b2-4c2d-a37f-2e70152a4f7c", "method": "POST", "nodes": [ { "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "408SIAOvpKxW/WbcYfKue26MlLTMbON7T7JT1yhiSemhznD5yiwZuZDXKsWu9vU5BIxfrsAQ8rn10QcdOFSRkA==", "required": true, "disabled": false }, "messages": null, "meta": {} }, { "type": "input", "group": "password", "attributes": { "name": "traits.email", "type": "email", "disabled": false }, "messages": null, "meta": { "label": { "id": 1070002, "text": "E-Mail", "type": "info" } } }, { "type": "input", "group": "password", "attributes": { "name": "password", "type": "password", "required": true, "disabled": false }, "messages": null, "meta": { "label": { "id": 1070001, "text": "Password", "type": "info" } } }, { "type": "input", "group": "password", "attributes": { "name": "traits.name.first", "type": "text", "disabled": false }, "messages": null, "meta": { "label": { "id": 1070002, "text": "First Name", "type": "info" } } }, { "type": "input", "group": "password", "attributes": { "name": "traits.name.last", "type": "text", "disabled": false }, "messages": null, "meta": { "label": { "id": 1070002, "text": "Last Name", "type": "info" } } }, { "type": "input", "group": "password", "attributes": { "name": "method", "type": "submit", "value": "password", "disabled": false }, "messages": null, "meta": { "label": { "id": 1040001, "text": "Sign up", "type": "info", "context": {} } } } ] } } ``` These changes are analogous to settings, recovery, verification as well! We hope you enjoy these new features as much as we do, even if we were not able to deliver 2FA in time for 0.6! On the last note, Ory Platform, a SaaS is launching in May as early access. It includes Ory Kratos as a managed service and we plan on adding all the other Ory open source technology soon. In our view, Ory is a 10x improvement to the existing "IAM" ecosystem: 1. The major components of Ory Platform are and will remain Apache 2.0 licensed open source. We are _not changing our approach or commitment to open source_. The SaaS model allows us to keep commercialization and open source in harmony; 2. Affordable pricing - Ory does not charge on a per identity basis; 3. Supporting migrations from the Ory Platform (SaaS) to the open-source and vice versa; 4. Offering a planet-scale service with ultra-low latencies no matter where your users are; 5. The largest set of features and APIs of any Identity Product, including Identity and Credentials Management (Ory Kratos), Permissions and Access Control (Ory Keto), Zero-Trust Networking (Ory Oathkeeper), OAuth2, and OpenID Connect (Ory Hydra) plus integrations with Stripe, Mailchimp, Salesforce, and much more. 6. Data aggregation for threat mitigation, auditing, and other use cases (e.g. integration with Snowflake, AWS RedShift, GCP BigQuery, ...) 7. All the advantages of the open source projects - headless, fully customizable, strong security, built with a community; If you wish to become a part of the preview, please write a short email to [sales@ory.sh](mailto:sales@ory.sh). Early access adopters are also eligible for Ory Hypercare - helping you integrate with Ory fast and designing your security architecture following industry best practices. Thank you for being a part of our community! ## Breaking Changes BCrypt is now the default hashing alogrithm. If you wish to continue using Argon2id please set `hashers.algorithm` to `argon2`. This implies a significant breaking change in the verification flow payload. Please consult the new ui documentation. In essence, the login flow's `methods` key was replaced with a generic `ui` key which provides information for the UI that needs to be rendered. To apply this patch you must apply SQL migrations. These migrations will drop the flow method table implying that all verification flows that are ongoing will become invalid. We recommend purging the flow table manually as well after this migration has been applied, if you have users doing at least one self-service flow per minute. This implies a significant breaking change in the recovery flow payload. Please consult the new ui documentation. In essence, the login flow's `methods` key was replaced with a generic `ui` key which provides information for the UI that needs to be rendered. To apply this patch you must apply SQL migrations. These migrations will drop the flow method table implying that all recovery flows that are ongoing will become invalid. We recommend purging the flow table manually as well after this migration has been applied, if you have users doing at least one self-service flow per minute. This implies a significant breaking change in the settings flow payload. Please consult the new ui documentation. In essence, the login flow's `methods` key was replaced with a generic `ui` key which provides information for the UI that needs to be rendered. To apply this patch you must apply SQL migrations. These migrations will drop the flow method table implying that all settings flows that are ongoing will become invalid. We recommend purging the flow table manually as well after this migration has been applied, if you have users doing at least one self-service flow per minute. This implies a significant breaking change in the registration flow payload. Please consult the new ui documentation. In essence, the login flow's `methods` key was replaced with a generic `ui` key which provides information for the UI that needs to be rendered. To apply this patch you must apply SQL migrations. These migrations will drop the flow method table implying that all registration flows that are ongoing will become invalid. We recommend purging the flow table manually as well after this migration has been applied, if you have users doing at least one self-service flow per minute. This implies a significant breaking change in the login flow payload. Please consult the new ui documentation. In essence, the login flow's `methods` key was replaced with a generic `ui` key which provides information for the UI that needs to be rendered. To apply this patch you must apply SQL migrations. These migrations will drop the flow method table implying that all login flows that are ongoing will become invalid. We recommend purging the flow table manually as well after this migration has been applied, if you have users doing at least one self-service flow per minute. This change introduces a new feature: UI Nodes. Previously, all self-service flows (login, registration, ...) included form fields (e.g. `methods.password.config.fields`). However, these form fields lacked support for other types of UI elements such as links (for e.g. "Sign in with Google"), images (e.g. QR codes), javascript (e.g. WebAuthn), or text (e.g. recovery codes). With this patch, these new features have been introduced. Please be aware that this introduces significant breaking changes which you will need to adopt to in your UI. Please refer to the most recent documentation to see what has changed. Conceptionally, most things stayed the same - you do however need to update how you access and render the form fields. Please be also aware that this patch includes SQL migrations which **purge existing self-service forms** from the database. This means that users will need to re-start the login/registration/... flow after the SQL migrations have been applied! If you wish to keep these records, make a back up of your database prior! This change introduces a new feature: UI Nodes. Previously, all self-service flows (login, registration, ...) included form fields (e.g. `methods.password.config.fields`). However, these form fields lacked support for other types of UI elements such as links (for e.g. "Sign in with Google"), images (e.g. QR codes), javascript (e.g. WebAuthn), or text (e.g. recovery codes). With this patch, these new features have been introduced. Please be aware that this introduces significant breaking changes which you will need to adopt to in your UI. Please refer to the most recent documentation to see what has changed. Conceptionally, most things stayed the same - you do however need to update how you access and render the form fields. Please be also aware that this patch includes SQL migrations which **purge existing self-service forms** from the database. This means that users will need to re-start the login/registration/... flow after the SQL migrations have been applied! If you wish to keep these records, make a back up of your database prior! The configuration value for `hashers.argon2.memory` is now a string representation of the memory amount including the unit of measurement. To convert the value divide your current setting (KB) by 1024 to get a result in MB or 1048576 to get a result in GB. Example: `131072` would now become `128MB`. Co-authored-by: aeneasr <3372410+aeneasr@users.noreply.github.com> Co-authored-by: aeneasr Please run SQL migrations when applying this patch. The following configuration keys were updated: ```patch selfservice.methods.password.config.max_breaches ``` - `password.max_breaches` -> `selfservice.methods.password.config.max_breaches` - `password.ignore_network_errors` -> `selfservice.methods.password.config.ignore_network_errors` After battling with [spf13/viper](https://github.com/spf13/viper) for several years we finally found a viable alternative with [knadh/koanf](https://github.com/knadh/koanf). The complete internal configuration infrastructure has changed, with several highlights: 1. Configuration sourcing works from all sources (file, env, cli flags) with validation against the configuration schema, greatly improving developer experience when changing or updating configuration. 2. Configuration reloading has improved significantly and works flawlessly on Kubernetes. 3. Performance increased dramatically, completely removing the need for a cache layer between the configuration system and ORY Hydra. 4. It is now possible to load several config files using the `--config` flag. 5. Configuration values are now sent to the tracer (e.g. Jaeger) if tracing is enabled. Please be aware that ORY Kratos might complain about an invalid configuration, because the validation process has improved significantly. ### Bug Fixes - Add include stub go files ([6d725b1](https://github.com/ory/kratos/commit/6d725b1461a26d99c8b179be8ca219ba83ba0f17)) - Add index to migration status ([8c6ec27](https://github.com/ory/kratos/commit/8c6ec2741535c090aae16f02a744f56c15923e2b)) - Add node_modules to format tasks ([e5f6b36](https://github.com/ory/kratos/commit/e5f6b36caeff080905d15566cf55f8fe4905dbc0)) - Add titles to identity schema ([73c15d2](https://github.com/ory/kratos/commit/73c15d23840aa83d2c99c013cad52ad7df285f18)) - Adopt to new go-swagger changes ([5c45bd9](https://github.com/ory/kratos/commit/5c45bd9f354bfe19b8cbcd7eb4eaebf22c441f42)) - Allow absolute file URLs as config values ([#1069](https://github.com/ory/kratos/issues/1069)) ([4bb4f67](https://github.com/ory/kratos/commit/4bb4f679d1fe0a49edb0c0189bb7a2188d4f850d)) - Allow hashtag in ui urls ([#1040](https://github.com/ory/kratos/issues/1040)) ([7591f07](https://github.com/ory/kratos/commit/7591f07f7d48376a03e9eacfdb6f4a93fd26c0d5)) - Avoid unicode-escaping ampersand in recovery URL query string ([#1212](https://github.com/ory/kratos/issues/1212)) ([d172368](https://github.com/ory/kratos/commit/d17236870af490f043d87e220179b35c9eb2dd4e)) - Bcrypt regression in credentials counting ([23fc13b](https://github.com/ory/kratos/commit/23fc13ba778e0045ca30c00d673ebd6c2f2b7fb7)) - Broken make quickstart-dev task ([#980](https://github.com/ory/kratos/issues/980)) ([999828a](https://github.com/ory/kratos/commit/999828ae036f20bde6d12fe89851e1fde9bdaca6)), closes [#965](https://github.com/ory/kratos/issues/965) - Broken make sdk task ([#977](https://github.com/ory/kratos/issues/977)) ([5b01c7a](https://github.com/ory/kratos/commit/5b01c7a368c5bcfaa3af218d42f15288f51ab3e4)), closes [#950](https://github.com/ory/kratos/issues/950) - Call contextualized test helpers ([e1f3f78](https://github.com/ory/kratos/commit/e1f3f7835696b039409c9d05f63665aba7a179ae)) - **cmd:** Make HTTP calls resilient ([e8ed61f](https://github.com/ory/kratos/commit/e8ed61fc3e806453f78b8fa629e96ff7b320bf95)) - Code integer parsing bit size ([#1178](https://github.com/ory/kratos/issues/1178)) ([31e9632](https://github.com/ory/kratos/commit/31e9632bcd6ec3bdeabe862a4cce89021c6dd361)): In some cases we had a wrong bitsize of `64`, while the var was later cast to `int`. Replaced with a bitsize of `0`, which is the value to cast to `int`. - Contextualize identity persister ([f8640c0](https://github.com/ory/kratos/commit/f8640c04f0c5873c39c8af4652d16bfbd347b79e)) - Convert all identifiers to lower case on login ([#815](https://github.com/ory/kratos/issues/815)) ([d64b575](https://github.com/ory/kratos/commit/d64b5757c710c436d6789dbdb33ed04dc11cbdf9)), closes [#814](https://github.com/ory/kratos/issues/814) - Courier adress ([#1198](https://github.com/ory/kratos/issues/1198)) ([ebe4e64](https://github.com/ory/kratos/commit/ebe4e643150f7603a1e3a3cf6f909135097b3f49)), closes [#1194](https://github.com/ory/kratos/issues/1194) - Courier message dequeue race condition ([#1024](https://github.com/ory/kratos/issues/1024)) ([5396a82](https://github.com/ory/kratos/commit/5396a82c34eef5d42444b5c4371bd4f820fe3eb0)), closes [#652](https://github.com/ory/kratos/issues/652) [#732](https://github.com/ory/kratos/issues/732): Fixes the courier message dequeuing race condition by modifying `*sql.Persister.NextMessages(ctx context.Context, limit uint8)` to retrieve only messages with status `MessageStatusQueued` and update the status of the retrieved messages to `MessageStatusProcessing` within a transaction. On message send failure, the message's status is reset to `MessageStatusQueued`, so that the message can be dequeued in a subsequent `NextMessages` call. On message send success, the status is updated to `MessageStatusSent` (no change there). - Define credentials types as sql template and resolve crdb issue ([a2d6eeb](https://github.com/ory/kratos/commit/a2d6eeb2928c9750741237f559197fd80494310d)) - Dereference pointer types from new flow structures ([#1019](https://github.com/ory/kratos/issues/1019)) ([efedc92](https://github.com/ory/kratos/commit/efedc920e592bd6e963726e6b123ddc40df93a59)) - Do not include smtp in tracing ([#1268](https://github.com/ory/kratos/issues/1268)) ([bbfcbf9](https://github.com/ory/kratos/commit/bbfcbf9ce595d842a53a3ea21c286d5899eeb28f)) - Do not publish version at public endpoint ([3726ed4](https://github.com/ory/kratos/commit/3726ed4d145a949b25f5b5da5f58d4f448a2a90f)) - Do not reset registration method ([554bb0b](https://github.com/ory/kratos/commit/554bb0b4e62e4ac2a321fa4dbf89ffdf37b188df)) - Do not return system errors for missing identifiers ([1fcc855](https://github.com/ory/kratos/commit/1fcc8557bfee0f7ba562a635670b61dc9acb3530)), closes [#1286](https://github.com/ory/kratos/issues/1286) - Export mailhog dockertest runner ([1384148](https://github.com/ory/kratos/commit/138414873ad319c6c32c6cc64a73547540dffc74)) - Fix random delay norm distribution math ([#1131](https://github.com/ory/kratos/issues/1131)) ([bd9d28f](https://github.com/ory/kratos/commit/bd9d28fe354710957f4ebaf71d1fffeae3968364)) - Fork audit logger from root logger ([68a09e7](https://github.com/ory/kratos/commit/68a09e7f3dc3ded9a477bb309c68ac8c4e2c2836)) - Gitlab oidc flow ([#1159](https://github.com/ory/kratos/issues/1159)) ([0bb3eb6](https://github.com/ory/kratos/commit/0bb3eb6db1144a09f4ac356cc45e1644d862bb70)), closes [#1157](https://github.com/ory/kratos/issues/1157) - Give specific message instead of only 404 when method is disabled ([#1025](https://github.com/ory/kratos/issues/1025)) ([2f62041](https://github.com/ory/kratos/commit/2f62041a62588f5b3b062092c57053facb858e62)): Enabled strategies are not only used for handlers but also in other areas (e.g. populating the flow methods). So we should keep the logic to get enabled strategies and add new functions for getting all strategies. - **hashing:** Make bcrypt default hashing algorithm ([04abe77](https://github.com/ory/kratos/commit/04abe774ada1ef4bf318658fcf84c1d39a2a922d)) - Ignore unset domain aliases ([ada6997](https://github.com/ory/kratos/commit/ada6997ff3dc7e48fd098e40267db5f231a5201f)) - Improve cli error output ([43e9678](https://github.com/ory/kratos/commit/43e967887280b57639565dabd92a07f02fbddeb5)) - Improve error stack trace ([4351773](https://github.com/ory/kratos/commit/43517737109088eda3b1d7f5b42f78bd5eb701d2)) - Improve error tracing ([#1005](https://github.com/ory/kratos/issues/1005)) ([456fd25](https://github.com/ory/kratos/commit/456fd254485fc80b9ae02dfca672a9fea8ae0134)) - Improve test contextualization ([2f92a70](https://github.com/ory/kratos/commit/2f92a7066d72535d32146a98207996fda45e0b96)) - Initialize randomdelay with seeded source ([9896289](https://github.com/ory/kratos/commit/9896289216f10b808a8c78b86d9c27b8d74379de)) - Insert credentials type constants as part of migrations ([#865](https://github.com/ory/kratos/issues/865)) ([92b79b8](https://github.com/ory/kratos/commit/92b79b86762edddf2ad6529b98b3383b641148d5)), closes [#861](https://github.com/ory/kratos/issues/861) - Linking a connection may result in system error ([#990](https://github.com/ory/kratos/issues/990)) ([be02a70](https://github.com/ory/kratos/commit/be02a70c3cd60adbcc13559e1cb5dc01a8572da4)), closes [#694](https://github.com/ory/kratos/issues/694) - Marking whoami auhorization parameter as 'in header' ([#1244](https://github.com/ory/kratos/issues/1244)) ([62d8b85](https://github.com/ory/kratos/commit/62d8b85223a0535b07620b08d35c6c3f6b127642)), closes [#1215](https://github.com/ory/kratos/issues/1215) - Move schema loaders to correct file ([029781f](https://github.com/ory/kratos/commit/029781f69448e8abc85607a03b4bd2055158cf2c)) - Move to new transaction-safe migrations ([#1063](https://github.com/ory/kratos/issues/1063)) ([2588fb4](https://github.com/ory/kratos/commit/2588fb489d76939aeec2986d30fde9075b373831)): This patch introduces a new SQL transaction model for running SQL migrations. This fix is particularly targeted at CockroachDB which has limited support for mixing DDL and DML statements. Previously it could happen that migrations failure needed manual intervention. This has now been resolved. The new migration model is compatible with the old one and should work without a problem. - Pass down context to registry ([0879446](https://github.com/ory/kratos/commit/08794461ed95965a9e5460ded2b4c04ab0f5e2e8)) - Re-enable SDK generation ([1d5854d](https://github.com/ory/kratos/commit/1d5854d6298e3d21f85a8fa01d3004166c4b3f50)) - Record cypress runs ([db35d8f](https://github.com/ory/kratos/commit/db35d8ff6bb44dc9e9acf131cb0a14a7f4a7d160)) - Rehydrate settings form on successful submission ([3457e1a](https://github.com/ory/kratos/commit/3457e1a46f48ed79eabff76f8af08b82f12ecc89)), closes [#1305](https://github.com/ory/kratos/issues/1305) - Remove absolete 'make pack' from Dockerfile ([#1172](https://github.com/ory/kratos/issues/1172)) ([b8eb908](https://github.com/ory/kratos/commit/b8eb908529cc72a3147ad28e4eeee71850a8e431)) - Remove continuity cookies on errors ([85eea67](https://github.com/ory/kratos/commit/85eea6748be6ae8cdfc10cabaa6b677e4efd63eb)) - Remove include stubs ([1764e3a](https://github.com/ory/kratos/commit/1764e3a08a24db82dc391a77fdea09a91faffb5f)) - Remove obsolete clihelpers ([230fd13](https://github.com/ory/kratos/commit/230fd138d1bc7ec57647ea8eeca8e17baaacce0a)) - Remove record from bash script ([84a9315](https://github.com/ory/kratos/commit/84a9315a824cacd29d30b98b65725343af22732d)) - Remove stray non-ctx configs ([#1053](https://github.com/ory/kratos/issues/1053)) ([1fe137e](https://github.com/ory/kratos/commit/1fe137e0d6314bd0af47a29c00e2f72564e71cef)) - Remove trailing double-dot from error ([59581e3](https://github.com/ory/kratos/commit/59581e3fede0fd43028a5f064c350c3cc833b5b0)) - Remove unused sql migration ([1445d1d](https://github.com/ory/kratos/commit/1445d1d1b4b0b5e8ef3426a98ced9573063d8646)) - Remove unused var ([30a8cee](https://github.com/ory/kratos/commit/30a8cee22238d9f400e6d315a9bc99f710945f81)) - Remove verify hook ([98cfec6](https://github.com/ory/kratos/commit/98cfec6d72c2e7bf2db2e8dd6f8875e885923ba8)), closes [#1302](https://github.com/ory/kratos/issues/1302): The verify hook is automatically used when verification is enabled and has been removed as a configuration option. - Replace jwt module ([#1254](https://github.com/ory/kratos/issues/1254)) ([3803c8c](https://github.com/ory/kratos/commit/3803c8ce43e35c51a9c1d7ab55bc662c398cf0d8)), closes [#1250](https://github.com/ory/kratos/issues/1250) - Resolve build and release issues ([fb582aa](https://github.com/ory/kratos/commit/fb582aa06ad55ca3fd4e2b083e1e9bbb4ba7c715)) - Resolve clidoc issues ([599e9f7](https://github.com/ory/kratos/commit/599e9f773a743f811329cc57cea2748831105e58)) - Resolve compile issues ([63063c1](https://github.com/ory/kratos/commit/63063c15c17f4d3aca96b106275a3478a8ed717e)) - Resolve contextualized table issues ([5a4f0d9](https://github.com/ory/kratos/commit/5a4f0d92800df7fb5ca0df18203a6d73416814e1)) - Resolve crdb migration issue ([9f6edfd](https://github.com/ory/kratos/commit/9f6edfd1f544d5f85e5f5558a08672f40e928136)) - Resolve double hook invokation for registration ([032322c](https://github.com/ory/kratos/commit/032322c66fb6925d8f1473746cb4bfd800d60590)) - Resolve incorrect field types on oidc sign up completion ([f88b6ab](https://github.com/ory/kratos/commit/f88b6abe202605739092a8230fbdebaebcd4407a)) - Resolve lint issues ([0348825](https://github.com/ory/kratos/commit/03488250bcdbfda6ef6a536b4de6117fa8924dc8)) - Resolve lint issues ([75a995b](https://github.com/ory/kratos/commit/75a995b3f69778655611929b65ae22bd77c5370b)) - Resolve linting issues and disable nancy ([c8396f6](https://github.com/ory/kratos/commit/c8396f6007831240d83f77433876c5971a2191ef)) - Resolve mail queue issues ([b968bc4](https://github.com/ory/kratos/commit/b968bc4ed8962d421175adbcaa2dba6eaeea2245)) - Resolve merge regressions ([9862ac7](https://github.com/ory/kratos/commit/9862ac72e0877df4cf17c93e140c354e1ddbd0e7)) - Resolve oidc e2e regressions ([f28087a](https://github.com/ory/kratos/commit/f28087aaf133c116a81213f787dc6f2e982564c0)) - Resolve oidc regressions and e2e tests ([f5091fa](https://github.com/ory/kratos/commit/f5091fac161db0b1401b340a002278bc26891251)) - Resolve potential fsnotify leaks ([3159c0a](https://github.com/ory/kratos/commit/3159c0abe109ea4e3832770278c4e9bc4ca3b3e1)) - Resolve regressions and test failures ([8bae356](https://github.com/ory/kratos/commit/8bae3565ea5410b60c3e638a49f5454fac8e63d3)) - Resolve regressions in cookies and payloads ([9e34bf2](https://github.com/ory/kratos/commit/9e34bf2f6a2f3b007069a5415643c448798207a6)) - Resolve settings sudo regressions ([4b611f3](https://github.com/ory/kratos/commit/4b611f34755369eafcbafa2fc16da13ea3b82370)) - Resolve test regressions ([e3fb028](https://github.com/ory/kratos/commit/e3fb0281dd9be123271d11f2934cfb08fdc470b7)) - Resolve ui issues with nested form objects ([8e744b9](https://github.com/ory/kratos/commit/8e744b931954283cf5f5cbf3ebaca3fa94e035ed)) - Resolve update regression ([d0d661a](https://github.com/ory/kratos/commit/d0d661aaffcba8b039738b773c891ee6e8f6449e)) - Return delay instead of sleeping to improve tests ([27b977e](https://github.com/ory/kratos/commit/27b977ebbaa25b95caa7e3e4536a09ea0bfa61c3)) - Revert generator changes ([c18b97f](https://github.com/ory/kratos/commit/c18b97f333a638d4b4495678013c55faca4b04d0)) - Run correct error handler for registration hooks ([0d80447](https://github.com/ory/kratos/commit/0d80447102d5092e310ca728012f083147c0c5c9)) - Simplify data breaches password error reason ([#1136](https://github.com/ory/kratos/issues/1136)) ([33d29bf](https://github.com/ory/kratos/commit/33d29bf72af03aea77f1d318c19f5087a506719f)): This PR simplifies the error reason given when a password has appeared in data breaches to not include the actual number and rather just show "this password has appeared in data breaches and must not be used". - Support form and json formats in decoder ([d420fe6](https://github.com/ory/kratos/commit/d420fe6e8a491b20063d4bfeaa0a841058087d32)) - Update openapi definitions for signup ([eb0b69d](https://github.com/ory/kratos/commit/eb0b69d50ce834b170186a39bbc9cda4d3366c36)) - Update quickstart node image ([c19b2f4](https://github.com/ory/kratos/commit/c19b2f4c57307e27ce289d44eff34f5aec1341da)): See https://github.com/ory/kratos/discussions/1301 - Update to new goreleaser config ([4c2a1b7](https://github.com/ory/kratos/commit/4c2a1b7f5a0059a6e0c28779808ffb27e8910553)) - Update to new healthx ([6ec987a](https://github.com/ory/kratos/commit/6ec987ae81ef0c05f2c4d1eb836c40f9d15950b2)) - Use equalfold ([1c0e52e](https://github.com/ory/kratos/commit/1c0e52ec36ff95b53e3537c5ef457f1c818d7f6b)) - Use new TB interface ([d75a378](https://github.com/ory/kratos/commit/d75a378e700a206753f2cb17032315f2981960e7)) - Use numerical User ID instead of name to avoid k8s security warnings ([#1151](https://github.com/ory/kratos/issues/1151)) ([468a12e](https://github.com/ory/kratos/commit/468a12e56f22cfdf7bd05d68159cc735e75211b2)): Our docker image scanner does not allow running processes inside container using non-numeric User spec (to determine if we are trying to run docker image as root). - Use remote dependencies ([1e56457](https://github.com/ory/kratos/commit/1e56457d49e1cde69baa41e3111ca113aa49ee3c)) ### Code Generation - Pin v0.6.0-alpha.1 release commit ([507d13a](https://github.com/ory/kratos/commit/507d13a8ec9cd89c9933fc8814a8a99921da69fb)) ### Code Refactoring - Adapt new sdk in testhelpers ([6e15f6f](https://github.com/ory/kratos/commit/6e15f6f86c0f146e846a384ffd6eac78406178bc)) - Add nid everywhere ([407fd95](https://github.com/ory/kratos/commit/407fd95889f416f0d76d6f3f43644a6fafa13b44)) - Contextualize everything ([7ebc3a9](https://github.com/ory/kratos/commit/7ebc3a9a1a2cd85d28c5a9adf2c0c8c10cbd072e)): This patch contextualizes all configuration and DBAL models. - Do not use prefixed node names ([fc42ece](https://github.com/ory/kratos/commit/fc42ece24107dcb6e6a416cc54a2fb5de524fd94)) - Improve Argon2 tooling ([#961](https://github.com/ory/kratos/issues/961)) ([3151187](https://github.com/ory/kratos/commit/315118720419194be8baf5e5e64d7bf190179568)), closes [#955](https://github.com/ory/kratos/issues/955): This adds a load testing CLI that allows to adjust the hasher parameters under simulated load. - Move faker to exportable module ([09f8ae5](https://github.com/ory/kratos/commit/09f8ae5755c9978574e91676bf5df6a23a2feb78)) - Move migratest helpers to ory/x ([7eca67e](https://github.com/ory/kratos/commit/7eca67eb9ec3e4ab065af7221911a74ed16c7c48)) - Move password config to selfservice ([cd0e0eb](https://github.com/ory/kratos/commit/cd0e0ebb0de372ff31c982ef023fe1979addb05a)) - Move to go 1.16 embed ([43c4a13](https://github.com/ory/kratos/commit/43c4a13c25be4a3a23a1ffdbecfaa0f9eda1a11d)): This patch replaces packr and pkged with the Go 1.16 embed feature. - Remove password node attribute prefix ([e27fae4](https://github.com/ory/kratos/commit/e27fae4b0d7a91ff3964804963d4885178b80803)) - Remove profile node attribute prefix ([a3ff6f7](https://github.com/ory/kratos/commit/a3ff6f7eec45b1a9a1e7eb8569793fbc6a047d4f)) - Rename config structs and interfaces ([4a2f419](https://github.com/ory/kratos/commit/4a2f41977439354415118df3e37dd0cde8dac1aa)) - Rename form to container ([5da155a](https://github.com/ory/kratos/commit/5da155a07d3737cefabaf98c4ff650115f662480)) - Replace flow's forms with new ui node module ([647eb1e](https://github.com/ory/kratos/commit/647eb1e66850c67e539d0338cca6cb8ae476ee55)) - Replace flow's forms with new ui node module ([f74a5c2](https://github.com/ory/kratos/commit/f74a5c25af60936b59caee0866a21637a5c0ae6f)) - Replace login flow methods with ui container ([d4ca364](https://github.com/ory/kratos/commit/d4ca364fd8905cfb205ee047a9cb831064a6b9d0)) - Replace recovery flow methods with ui container ([cac0456](https://github.com/ory/kratos/commit/cac04562f2e4e77875275fcfd82c039d787607fb)) - Replace registration flow methods with ui container ([3f6388d](https://github.com/ory/kratos/commit/3f6388d03f91cfad17bd74ebca4d924b4b546668)) - Replace settings flow methods with ui container ([0efd17e](https://github.com/ory/kratos/commit/0efd17e76ba0a0cbd46916a7644b7bdf19bd4ab4)) - Replace verification flow methods with ui container ([dbf2668](https://github.com/ory/kratos/commit/dbf2668747922c93dd967961cd843354afbecfde)) - Replace viper with koanf config management ([5eb1bc0](https://github.com/ory/kratos/commit/5eb1bc0bff7c5d0f83c604484b8e845701112cad)) - Update RegisterFakes calls ([6268310](https://github.com/ory/kratos/commit/626831069ab4f971094ba0bc0b43ac9ff618d91d)) - Use underscore in webhook auth types ([26829d2](https://github.com/ory/kratos/commit/26829d21911cccd4a87c8693b6089af661c1bfe3)) ### Documentation - Add docker to docs main ([8ce8b78](https://github.com/ory/kratos/commit/8ce8b785e2246557253420ea97cf6b7d5ee75d58)) - Add docker to sidebar ([ed38c88](https://github.com/ory/kratos/commit/ed38c88bdbadcdcd2527a2b5270390251742bbe4)) - Add dotnet sdk ([#1183](https://github.com/ory/kratos/issues/1183)) ([32d874a](https://github.com/ory/kratos/commit/32d874a04bb384259aeb544a3fcd6b3a8b23acdd)) - Add faq sidebar ([#1105](https://github.com/ory/kratos/issues/1105)) ([10697aa](https://github.com/ory/kratos/commit/10697aa4ab5dc3e2ab90d1c037dfbe3492bf2bdf)) - Add log docs to schema config ([4967f11](https://github.com/ory/kratos/commit/4967f11d8df177ebdae855eb745e90d21ce38e9f)) - Add more HA docs ([cbb2e27](https://github.com/ory/kratos/commit/cbb2e27f8919a8991c4797a3f1c192ec364f0dd3)) - Add Rust and Dart SDKs ([6d96952](https://github.com/ory/kratos/commit/6d969528e13350ef099669510d3d37df1c007c82)): We now support for Rust and Dart SDKs! - Add SameSite help ([2df6729](https://github.com/ory/kratos/commit/2df6729b4acc70532024658e8874682de64b06b3)) - Add shell-session language ([d16db87](https://github.com/ory/kratos/commit/d16db87802ae2f230a02e4deed189f473588552c)) - Add ui node docs ([e48a07d](https://github.com/ory/kratos/commit/e48a07d03c19a0677d3a56f9e57294b358f24501)) - Adding double colons ([#1187](https://github.com/ory/kratos/issues/1187)) ([fc712f4](https://github.com/ory/kratos/commit/fc712f4530066c429242491c19d1534ffb267b0c)) - Bcrypt is default and add 72 char warning ([29ae53a](https://github.com/ory/kratos/commit/29ae53a96b4472ff549b34241894d72d439c8ea1)) - Better import identities examples ([#997](https://github.com/ory/kratos/issues/997)) ([2e2880a](https://github.com/ory/kratos/commit/2e2880ac057b5c98cd69481c4f6f36b564b5871d)) - Change forum to discussions readme ([#1220](https://github.com/ory/kratos/issues/1220)) ([ae39956](https://github.com/ory/kratos/commit/ae399561ea6ed89aaadd4128bc564254984520e8)) - Describe more about Kratos login/browser flow on quickstart doc ([#1047](https://github.com/ory/kratos/issues/1047)) ([fe725ad](https://github.com/ory/kratos/commit/fe725ad12b5aed5faa8f95bec24ed3aa82512de8)) - Docker file links ([#1182](https://github.com/ory/kratos/issues/1182)) ([4d9b6a3](https://github.com/ory/kratos/commit/4d9b6a3fd5de81310016a811126e40a263ecd27c)) - Document hash timing attack mitigation ([ec86993](https://github.com/ory/kratos/commit/ec869930a9c0e6f6f56c2614835894e0a6a3eaab)) - Explain how to use `after_verification_return_to` ([7e1546b](https://github.com/ory/kratos/commit/7e1546be1fd20baca10507d642d4f209eb88dcbc)) - FAQ improvements ([#1135](https://github.com/ory/kratos/issues/1135)) ([44d0bc9](https://github.com/ory/kratos/commit/44d0bc968a7c0ba5c0793b2349820fa8133bada3)) - FAQ item & minor changes ([#1174](https://github.com/ory/kratos/issues/1174)) ([11cf630](https://github.com/ory/kratos/commit/11cf630082b56c80d12f5915f8e34aa03a7e8c54)) - Fix broken link ([#1037](https://github.com/ory/kratos/issues/1037)) ([6b9aae8](https://github.com/ory/kratos/commit/6b9aae8af5aa3bd614c99b32e341fbd533caf116)) - Fix failing build ([0de328f](https://github.com/ory/kratos/commit/0de328ff0053605e6bded589a79d3ab938d55b31)) - Fix formatting ([#966](https://github.com/ory/kratos/issues/966)) ([687251a](https://github.com/ory/kratos/commit/687251a24e796322b43f8aed6b1fb3d7900e3271)) - Fix identity state bullets ([#1095](https://github.com/ory/kratos/issues/1095)) ([f476334](https://github.com/ory/kratos/commit/f476334c4693277656ad88e768f66b59cbcba126)) - Fix known/unknown email account recovery ([#1211](https://github.com/ory/kratos/issues/1211)) ([e208ca5](https://github.com/ory/kratos/commit/e208ca50ba4f03d5410c9644aaa3b04bdf1b8dbd)) - Fix link ([7f6d7f5](https://github.com/ory/kratos/commit/7f6d7f501d7118dfe6868c9d923fb5ecc5eded48)) - Fix link ([#1128](https://github.com/ory/kratos/issues/1128)) ([e7043e9](https://github.com/ory/kratos/commit/e7043e9b99260eaff2b48ca6f457af46a1521654)) - Fix link to blogpost ([#949](https://github.com/ory/kratos/issues/949)) ([4622e32](https://github.com/ory/kratos/commit/4622e3228fb12231222c7e6b602458111f35f727)), closes [#945](https://github.com/ory/kratos/issues/945) - Fix link to self-service flows overview ([#995](https://github.com/ory/kratos/issues/995)) ([2be8778](https://github.com/ory/kratos/commit/2be877847644a3df2645ac3be4bbd7704db30b17)) - Fix note block in third party login guide ([#920](https://github.com/ory/kratos/issues/920)) ([745cea0](https://github.com/ory/kratos/commit/745cea02d0e9940f689e668bbd814b29fd53bf37)): Allows the document to render properly - Fix npm links ([#991](https://github.com/ory/kratos/issues/991)) ([4ce4468](https://github.com/ory/kratos/commit/4ce4468132dde21c1692e3a834ad7780bee12b90)) - Fix self-service code flows labels ([#1253](https://github.com/ory/kratos/issues/1253)) ([f2ed424](https://github.com/ory/kratos/commit/f2ed424289cdd2a0edc1736888dd15be6df65f11)) - Fix typo in README ([#1122](https://github.com/ory/kratos/issues/1122)) ([e500707](https://github.com/ory/kratos/commit/e5007078c3cd597cea669827b96c7e6f205f2f32)) - Link to argon2 blogpost and add cross-references ([#1038](https://github.com/ory/kratos/issues/1038)) ([9ab7c3d](https://github.com/ory/kratos/commit/9ab7c3df59ecd94a74a7bf18af9c0ded5305e042)) - Make explicit the ID of the default schema ([#1173](https://github.com/ory/kratos/issues/1173)) ([cc6e9ff](https://github.com/ory/kratos/commit/cc6e9ffbac7118436d85078720cde2de98a68044)) - Minor cosmetics ([#1050](https://github.com/ory/kratos/issues/1050)) ([34db06f](https://github.com/ory/kratos/commit/34db06fd4f83d415c09109b06dfd3b82ce03705e)) - Minor improvements ([#1052](https://github.com/ory/kratos/issues/1052)) ([f0672b5](https://github.com/ory/kratos/commit/f0672b5cb8cca41fa914db21798d20f00a5699f9)) - ORY -> Ory ([ea30979](https://github.com/ory/kratos/commit/ea309797bf59f3da5c5cd184e45f2e585144be56)) - **prometheus:** Update codedoc ([47146ea](https://github.com/ory/kratos/commit/47146ea8ce169ee908aa4d33b59a01e9df4bae10)) - Reformat settings code samples ([cdbbf4d](https://github.com/ory/kratos/commit/cdbbf4df5fa3fa667a78d5cf682bc7fa36693e9d)) - Remove unnecessary and wrong docker pull commands ([#1203](https://github.com/ory/kratos/issues/1203)) ([2b0342a](https://github.com/ory/kratos/commit/2b0342ad7607d705bcebfafd5a78e4e09e57a940)) - Resolve duplication error ([a3d8284](https://github.com/ory/kratos/commit/a3d8284ab20ae76bccba361601b7290af20bdde6)) - Update build from source ([9b5754f](https://github.com/ory/kratos/commit/9b5754f36661f6de9c95f30c06f28164fe5be48b)), closes [#979](https://github.com/ory/kratos/issues/979) - Update email template docs ([1778cb9](https://github.com/ory/kratos/commit/1778cb9a293feb2c91c0b1921ab78a0395cdca98)), closes [#897](https://github.com/ory/kratos/issues/897) - Update identity-data-model links ([b5fd9a3](https://github.com/ory/kratos/commit/b5fd9a3a0821215f94da168c9c6f87dceba8c8f4)) - Update identity.ID field documentation ([4624f03](https://github.com/ory/kratos/commit/4624f03a5e9249a5449992a1f0b7ec80dc3499fd)): See https://github.com/ory/kratos/discussions/956 - Update kratos video link ([#1073](https://github.com/ory/kratos/issues/1073)) ([e86178f](https://github.com/ory/kratos/commit/e86178f4ee66e5053e0da2fab2c21ecb2e730ada)) - Update login code samples ([695a30f](https://github.com/ory/kratos/commit/695a30f6c80f277676bf04b4665efeb7ea4db618)) - Update login code samples ([ce6c755](https://github.com/ory/kratos/commit/ce6c75587bea80ef83855d764fed79a9d6c948d3)) - Update quickstart samples ([c3fcaba](https://github.com/ory/kratos/commit/c3fcaba65899d9d46a08ca8b60ec0c010f70b16c)) - Update recovery code samples ([d9fbb62](https://github.com/ory/kratos/commit/d9fbb62faff5144f587136935f15d24b6399f29c)) - Update registration code samples ([317810f](https://github.com/ory/kratos/commit/317810ffd8ba6faf87f2248263b6c82cf4e9ffd8)) - Update self-service code samples ([6415011](https://github.com/ory/kratos/commit/6415011ab83a19972c6f52467055fbdcef23a0cc)) - Update settings code samples ([bbd6266](https://github.com/ory/kratos/commit/bbd6266c22097fae195654957cbab589d04892c7)) - Update verification code samples ([4285dec](https://github.com/ory/kratos/commit/4285dec59a8fc31fa3416b594c765f5da9a9de1c)) - Use correct extension for identity-data-model ([acab3e8](https://github.com/ory/kratos/commit/acab3e8b489d9865e4bf0805895f0b7ae9e6f1b8)): See https://github.com/ory/kratos/pull/1197#issuecomment-819455322 ### Features - Add email template specification in doc ([#898](https://github.com/ory/kratos/issues/898)) ([4230d9e](https://github.com/ory/kratos/commit/4230d9e0fc35c651b0d2cbdbbf9e1f1c514743f8)) - Add error for when no login strategy was found ([6bae66c](https://github.com/ory/kratos/commit/6bae66cde362c4e2995c9d06a0d3ffee403feb74)) - Add facebook provider to oidc providers and documentation ([#1035](https://github.com/ory/kratos/issues/1035)) ([905bb03](https://github.com/ory/kratos/commit/905bb032520189212bd88f29641903945ae03608)), closes [#1034](https://github.com/ory/kratos/issues/1034) - Add FAQ to docs ([#1096](https://github.com/ory/kratos/issues/1096)) ([9c6b68c](https://github.com/ory/kratos/commit/9c6b68c454f472b26c34e1975b6a67b24b218f47)) - Add gh login to claims ([49deb2e](https://github.com/ory/kratos/commit/49deb2e166362a5d051bc08523ef44425f144bdd)) - Add login strategy text message ([7468c83](https://github.com/ory/kratos/commit/7468c835d4800c207035897fc9962860d8ab7803)) - Add more tests for multi domain args ([e99803b](https://github.com/ory/kratos/commit/e99803b62a847bcee52bcd87fa8088124b4deae2)) - Add Prometheus monitoring to Public APIs ([#1022](https://github.com/ory/kratos/issues/1022)) ([75a4f1a](https://github.com/ory/kratos/commit/75a4f1a5472ffd780fed43a7395a191ed495c6e9)) - Add random delay to login flow ([#1088](https://github.com/ory/kratos/issues/1088)) ([cb9894f](https://github.com/ory/kratos/commit/cb9894fefc694a4092215d3981e80f287021542f)), closes [#832](https://github.com/ory/kratos/issues/832) - Add return_url to verification flow ([#1149](https://github.com/ory/kratos/issues/1149)) ([bb99912](https://github.com/ory/kratos/commit/bb99912d823e9bcffa41edf50a01dcae40117fe6)), closes [#1123](https://github.com/ory/kratos/issues/1123) [#1133](https://github.com/ory/kratos/issues/1133) - Add sql migrations for new login flow ([e947edf](https://github.com/ory/kratos/commit/e947edf497b36bc576061c9ae38049e84ee48575)) - Add sql tracing ([3c4cc1c](https://github.com/ory/kratos/commit/3c4cc1cec170df14331288170a94ada770d3289f)) - Add tracing to config schema ([007dde4](https://github.com/ory/kratos/commit/007dde4482d11f22b8527c94b002da675152a872)) - Add transporter with host modification ([2c41b81](https://github.com/ory/kratos/commit/2c41b81be947f9972638d082105f0f5c83078b91)) - Add workaround template for go openapi ([5d72d10](https://github.com/ory/kratos/commit/5d72d10f6c6948c48c5701fe348084a668c8311a)) - Adds slack sogial login ([#974](https://github.com/ory/kratos/issues/974)) ([7c66053](https://github.com/ory/kratos/commit/7c66053390b3086fe7233625038a78431a61e507)), closes [#953](https://github.com/ory/kratos/issues/953) - Allow session cookie name configuration ([77ce316](https://github.com/ory/kratos/commit/77ce3162ba97cf5c516c26ef499d9fa892162f0a)), closes [#268](https://github.com/ory/kratos/issues/268) - Allow specifying sender name in smtp.from_address ([#1100](https://github.com/ory/kratos/issues/1100)) ([5904fe3](https://github.com/ory/kratos/commit/5904fe319f75f8138783434d568db6fc7c55b301)) - Bcrypt algorithm support ([#1169](https://github.com/ory/kratos/issues/1169)) ([b2612ee](https://github.com/ory/kratos/commit/b2612eefbad98d29482d364f670549f470d0a6f5)): This patch adds the ability to use BCrypt instead of Argon2id for password hashing. We recommend using BCrypt for web workloads where password hashing should take around 200ms. For workloads where login takes >= 2 seconds, we recommend to continue using Argon2id. To use bcrypt for password hashing, set your config as follows: ``` hashers: bcrypt: cost: 12 algorithm: bcrypt ``` Switching the hashing algorithm will not break existing passwords! Co-authored-by: Patrik - Check migrations in health check ([c6ef7ad](https://github.com/ory/kratos/commit/c6ef7ad16b70310c645550f7e41b3c8aff847de3)) - Configure domain alias as query param ([9d8563e](https://github.com/ory/kratos/commit/9d8563eeb3293c42cce440ad74f025b304cccbbe)) - Contextualize configuration ([d3d5327](https://github.com/ory/kratos/commit/d3d5327a3622318265a063be4782caa25e645a05)) - Contextualize health checks ([8145a1c](https://github.com/ory/kratos/commit/8145a1c9acaeab441e787118d40ccd448ea82fe4)) - Contextualize http client in cli calls ([3b3ef8f](https://github.com/ory/kratos/commit/3b3ef8f025d75b244d9285036e66f79af7d5ee35)) - Contextualize persitence testers ([6440373](https://github.com/ory/kratos/commit/64403736ad9f8b264567e1f8eed1af710cab6046)) - Courier foreground worker with "kratos courier watch" ([#1062](https://github.com/ory/kratos/issues/1062)) ([500b8ba](https://github.com/ory/kratos/commit/500b8bacd9fd541afd053f42fec66443cfebabda)), closes [#1033](https://github.com/ory/kratos/issues/1033) [#1024](https://github.com/ory/kratos/issues/1024): BREACKING CHANGES: This patch moves the courier watcher (responsible for sending mail) to its own foreground worker, which can be executed as a, for example, Kubernetes job. It is still possible to have the previous behaviour which would run the worker as a background task when running `kratos serve` by using the `--watch-courier` flag. To run the foreground worker, use `kratos courier watch -c your/config.yaml`. - **courier:** Allow sending individual messages ([cbb2c0b](https://github.com/ory/kratos/commit/cbb2c0bef63323a177589e9d2a809c84b4f1acdd)) - Do not enforce bcrypt 12 for dev envs ([bbf44d8](https://github.com/ory/kratos/commit/bbf44d887ae5cdb5975516149c74b3ba10896209)) - Email input validation ([#1287](https://github.com/ory/kratos/issues/1287)) ([cd56b73](https://github.com/ory/kratos/commit/cd56b73df363dd37485f07d31fef11fd4d9f40a6)), closes [#1285](https://github.com/ory/kratos/issues/1285) - Export and add config options ([4391fe5](https://github.com/ory/kratos/commit/4391fe572eb6a766afe9808396847ca5fdca07f5)) - Expose courier worker ([f50969e](https://github.com/ory/kratos/commit/f50969ecba757dea558e9e8b9dd142f5f564d53a)) - Expose crdb ui ([504d518](https://github.com/ory/kratos/commit/504d5181f5e391bb8d67768b314a0348ed252c8b)) - Global docs sidebar ([#1258](https://github.com/ory/kratos/issues/1258)) ([7108262](https://github.com/ory/kratos/commit/71082624e093b8c100e71ae59050f89b35ac20a2)) - Implement and test domain aliasing ([1516a54](https://github.com/ory/kratos/commit/1516a54657df485627251de4e7019bc16353c956)): This patch adds a feature called domain aliasing. For more information, head over to http://ory.sh/docs/kratos/next/guides/multi-domain-cookies - Improve oas spec and fix mobile tests ([4ead2c8](https://github.com/ory/kratos/commit/4ead2c826a2f1a307e327b9736dd8ac99ef52743)) - Improve sorting of ui fields ([797b49d](https://github.com/ory/kratos/commit/797b49d0175280f85f568014cf3083e9bc42d354)): See https://github.com/ory/kratos/discussions/1196 - Include schema ([348a493](https://github.com/ory/kratos/commit/348a493c9e5381830b76e57cad803a308e6ce53a)) - Make cli commands consumable in Ory Cloud ([#926](https://github.com/ory/kratos/issues/926)) ([fed790b](https://github.com/ory/kratos/commit/fed790b0f71f028f6d92e8ebceee188dbdb20770)) - Migrate to openapi v3 ([595224b](https://github.com/ory/kratos/commit/595224b1efd5a225702ef236a87f08180a7118b8)) - **oidc:** Support google hd claim ([#1097](https://github.com/ory/kratos/issues/1097)) ([1f20a5c](https://github.com/ory/kratos/commit/1f20a5ceba7682719112d24a3b18bf046fb2ac22)) - Populate email templates at delivery time, add plaintext defaults ([#1155](https://github.com/ory/kratos/issues/1155)) ([7749c7a](https://github.com/ory/kratos/commit/7749c7a75a4386c1fd53db57626355467b698c2f)), closes [#1065](https://github.com/ory/kratos/issues/1065) - **schema:** Add totp errors ([a61f881](https://github.com/ory/kratos/commit/a61f8814101401dbb422967e37b6c6c1ae85d113)) - Sort and label nodes with easy to use defaults ([cbec27c](https://github.com/ory/kratos/commit/cbec27c957a733411e4c1d511ed5854855b7236e)): Ory Kratos takes a guess based on best practices for - ordering UI nodes (e.g. email, password, submit button) - grouping UI nodes (e.g. keep password and oidc nodes together) - labeling UI nodes (e.g. "Sign in with GitHub") - using the "title" attribute from the identity schema to label trait fields This greatly simplifies front-end code on your end and makes it even easier to integrate with Ory Kratos! If you want a custom experience with e.g. translations or other things you can always adjust this in your UI integration! - Support base64 inline schemas ([815a248](https://github.com/ory/kratos/commit/815a24890a118f4128ac083241a93d8df27042f7)) - Support contextual csrf cookies ([957ef38](https://github.com/ory/kratos/commit/957ef38b69fc6ab071b91262736e6c191be3a4b8)) - Support domain aliasing in session cookie ([0681c12](https://github.com/ory/kratos/commit/0681c123f2d856ca27caee645dadc9e6e3731d2c)) - Support label in oidc config ([a99cdcd](https://github.com/ory/kratos/commit/a99cdcddaa0c4bd7b679884b232c2ef8f2dcd978)) - Support retryable CRDB transactions ([f0c21d7](https://github.com/ory/kratos/commit/f0c21d7e0a6ed85818d0e9025a451cb8cbdee086)) - Unix sockets support ([#1255](https://github.com/ory/kratos/issues/1255)) ([ad010de](https://github.com/ory/kratos/commit/ad010de240ddd9219f0cfb2ca3fbb180d2d3a697)) - Web hooks support (recovery) ([#1289](https://github.com/ory/kratos/issues/1289)) ([3e181fe](https://github.com/ory/kratos/commit/3e181fe3d7750a715ab31eb8347fbb4bdb89d6e6)), closes [#271](https://github.com/ory/kratos/issues/271): feat: web hooks for self-service flows This feature adds the ability to define web-hooks using a mixture of configuration and JsonNet. This allows integration with services like Mailchimp, Stripe, CRMs, and all other APIs that support REST requests. Additional to these new changes it is now possible to define hooks for verification and recovery as well! For more information, head over to the [hooks documentation](https://www.ory.sh/kratos/docs/self-service/hooks). ### Tests - Add case to ensure correct behavior when verifying a different email address ([#999](https://github.com/ory/kratos/issues/999)) ([f95a117](https://github.com/ory/kratos/commit/f95a117677c9c59436ad10aa8951fe875c39a64f)), closes [#998](https://github.com/ory/kratos/issues/998) - Add oasis test case ([f80691b](https://github.com/ory/kratos/commit/f80691b9dd77566857c4284e2639cc94d5b8c333)) - Bump poll interval ([b3dc925](https://github.com/ory/kratos/commit/b3dc925a5d43557293745ee81c0ffb3db37b6342)) - Bump video quality ([b7f8d04](https://github.com/ory/kratos/commit/b7f8d042646037e1589ae2d03602bd63a5cec2fe)) - Bump wait times ([b2e43f8](https://github.com/ory/kratos/commit/b2e43f8b0b64784f60e5f57d9a0f5d2928c2b891)) - Clean up hydra env before restart ([cf49414](https://github.com/ory/kratos/commit/cf494149e6a46b15e3b174185e1e87cfcd6f9f7a)) - **e2e:** Significantly reduce wait and idle times ([f525fc5](https://github.com/ory/kratos/commit/f525fc53afec6f5232ce507fe25ddec1b9069196)) - Longer wait times ([4bec9ef](https://github.com/ory/kratos/commit/4bec9ef50f14f22342a311f09ba1b59cde47befc)) - Reliable migration tests on crdb ([2e3764b](https://github.com/ory/kratos/commit/2e3764ba66c156d810de66fba2b0e142dced6f4d)) - Remove old noop test ([16dca3f](https://github.com/ory/kratos/commit/16dca3f78b2021c09ec83e81ab6d2e68c42ca081)) - Resolve compile issues ([c1b5ba4](https://github.com/ory/kratos/commit/c1b5ba42171ec522579df9dfaff27b5b74a1566a)) - Resolve flaky tests ([cb670a8](https://github.com/ory/kratos/commit/cb670a854cbb09b8437bfed7e4a6908ff6dcfd27)) - Resolve json parser test regression ([a1b9b9a](https://github.com/ory/kratos/commit/a1b9b9a95d58583dc7ecf6d2a501da52f84dd6bb)) - Resolve login integration regressions ([388b5b2](https://github.com/ory/kratos/commit/388b5b27d6dee7770e5f37d6d83c532044a4e984)) - Resolve migration regression ([2051a71](https://github.com/ory/kratos/commit/2051a716cb4b8cf334dd65f2ccddb31e5fbed545)) - Resolve more json parser test regressions ([ff791c4](https://github.com/ory/kratos/commit/ff791c41a1d9ce25af4e883469d3f8c0ef9eb302)) - Resolve more regressions ([c5a23af](https://github.com/ory/kratos/commit/c5a23af81427480088651833d904e3403a969fab)) - Resolve order regression ([40a849c](https://github.com/ory/kratos/commit/40a849ca35f4700185322e9ac4f6a4b70132851c)) - Resolve regression ([e2b0ad3](https://github.com/ory/kratos/commit/e2b0ad3c1845da80f078b11b327b9a0376cbb7c5)) - Resolve regression ([f0c9e5f](https://github.com/ory/kratos/commit/f0c9e5ff105d76d6bc9478c98522b2440c7181df)) - Resolve regressions ([4b9da3c](https://github.com/ory/kratos/commit/4b9da3c9d98d40f7b71a56c51543fc115974630d)) - Resolve stub regressions ([82650cf](https://github.com/ory/kratos/commit/82650cf1843f6bfde015f556f4452a7b6fd52b11)) - Resolve test migrations ([de0b65d](https://github.com/ory/kratos/commit/de0b65d96daef0e31c12b3b6915f283a8e71244b)) - Resolve test regression issues ([ccf9fed](https://github.com/ory/kratos/commit/ccf9feddade11f9fcaaf1c37dd3efeb2c4df6649)) - Speed up tests ([a16737c](https://github.com/ory/kratos/commit/a16737cccc36a14444711660f1737913ffd7ba01)) - Update schema tests for webhooks ([d1ddfa8](https://github.com/ory/kratos/commit/d1ddfa80742728b28dc5710ca5b6e7282a2dec55)) - Update test description ([55fb37f](https://github.com/ory/kratos/commit/55fb37f62fc3ab7c0d5324ed31ef3e7f66a73aa2)) - Use bcrypt cost 4 to reduce CI times ([cabe97d](https://github.com/ory/kratos/commit/cabe97d0656858fd1ee0442b40881417e91294f3)) - Use fast bcrypt for e2e ([d90cf13](https://github.com/ory/kratos/commit/d90cf13230632e76eb74965c0945573b4f2e98ff)) ### Unclassified - fix: resolve clidoc issues (#976) ([346bc73](https://github.com/ory/kratos/commit/346bc73921655d52861b8803eb3351c4205657ee)), closes [#976](https://github.com/ory/kratos/issues/976) [#951](https://github.com/ory/kratos/issues/951) - :bug: fix ory home directory path (#897) ([2fca2be](https://github.com/ory/kratos/commit/2fca2bedaa907691bef324c11545e007b51d4881)), closes [#897](https://github.com/ory/kratos/issues/897) - Fix typo in config schema ([16337f1](https://github.com/ory/kratos/commit/16337f13e4388a715c8109c29cf198c82a848a16)) - Format ([e4b7e79](https://github.com/ory/kratos/commit/e4b7e79f4ee91dadfcd008a5b3e318b6bfedad10)) - Format ([193d266](https://github.com/ory/kratos/commit/193d2668ae0955a1346390057539a8b796d17afd)) - Format ([1ebfbde](https://github.com/ory/kratos/commit/1ebfbdea75f27c8eeafa7d3aff45de133ea340bb)) - Format ([ba1eeef](https://github.com/ory/kratos/commit/ba1eeef4f232c4ab59343a2ca3c7cf0eb6dfd110)) - Format ([ada5dbb](https://github.com/ory/kratos/commit/ada5dbb58c45502b8275850a3bc0876debc66888)) - Format ([17a0bf5](https://github.com/ory/kratos/commit/17a0bf5872b33eac615afc675c7d92d7c7441b2e)) - Initial documentation tests via Text-Runner ([#567](https://github.com/ory/kratos/issues/567)) ([c30eb26](https://github.com/ory/kratos/commit/c30eb26f76ab70a6098c0b40c9a04726d36d72f2)) # [0.5.5-alpha.1](https://github.com/ory/kratos/compare/v0.5.4-alpha.1...v0.5.5-alpha.1) (2020-12-09) The ORY Community is proud to present you the next iteration of ORY Kratos. In this release, we focused on improving production stability! ### Bug Fixes - CSRF token is required when using the Revoke Session API endpoint ([#839](https://github.com/ory/kratos/issues/839)) ([d3218a0](https://github.com/ory/kratos/commit/d3218a0f23de7293b0a4a966ad21369a92b68b1a)), closes [#838](https://github.com/ory/kratos/issues/838) - Incorrect home path ([#848](https://github.com/ory/kratos/issues/848)) ([5265af0](https://github.com/ory/kratos/commit/5265af00c92fe505819300caddfcc64004d45c65)) - Make password policy configurable ([#888](https://github.com/ory/kratos/issues/888)) ([7a00483](https://github.com/ory/kratos/commit/7a00483908bb623efdf281e76005c4485ea6b1ab)), closes [#450](https://github.com/ory/kratos/issues/450) [#316](https://github.com/ory/kratos/issues/316): Allows configuring password breach thresholds and optionally enforces checks against the HIBP API. - Remove obsolete types ([#887](https://github.com/ory/kratos/issues/887)) ([b8bac7a](https://github.com/ory/kratos/commit/b8bac7aa56c16cd98f76a95a5e0d01fb1bbde6b7)), closes [#716](https://github.com/ory/kratos/issues/716) - Set samesite attribute to lax if in dev mode ([#824](https://github.com/ory/kratos/issues/824)) ([91d6698](https://github.com/ory/kratos/commit/91d6698e4ce05ee59bb72fc84b54af9d1d204b41)), closes [#821](https://github.com/ory/kratos/issues/821) - Use working cache-control header for cdn/proxies/cache ([#869](https://github.com/ory/kratos/issues/869)) ([d8e3d40](https://github.com/ory/kratos/commit/d8e3d40001ffdc64da2288f3cffd53cf3bfdf781)), closes [#601](https://github.com/ory/kratos/issues/601) ### Code Generation - Pin v0.5.5-alpha.1 release commit ([83aedcb](https://github.com/ory/kratos/commit/83aedcb885acb96c5deb39fff675d5f0528af32d)) ### Documentation - Add contributing to sidebar ([#866](https://github.com/ory/kratos/issues/866)) ([44f33f9](https://github.com/ory/kratos/commit/44f33f97d43f2a3c553a65ebb2986e0731c0e5f2)): The same change as in https://github.com/ory/hydra/pull/2209 - Add newsletter to config ([1735ca2](https://github.com/ory/kratos/commit/1735ca2ced104971de4e97524d0a23d57ba045f2)) - Add recovery flow ([#868](https://github.com/ory/kratos/issues/868)) ([d95cfe9](https://github.com/ory/kratos/commit/d95cfe9759d3ffc08c24048a064c0c800abdf4b4)), closes [#864](https://github.com/ory/kratos/issues/864): Added a short section for the recovery flow on managing-user-identities. - Fix account recovery click instruction ([#870](https://github.com/ory/kratos/issues/870)) ([383de9e](https://github.com/ory/kratos/commit/383de9ecf6f6504dbb9c20fb4cb984e934f0751e)) - Fix broken link ([#893](https://github.com/ory/kratos/issues/893)) ([dec38a2](https://github.com/ory/kratos/commit/dec38a28964aaa13827d356e5bfa12c2a6d1400e)), closes [#835](https://github.com/ory/kratos/issues/835) - Fix oidc config example structure ([#845](https://github.com/ory/kratos/issues/845)) ([c102a68](https://github.com/ory/kratos/commit/c102a6844db29f994b67d23bb04e64ee71376264)) - Fix redirect ([#802](https://github.com/ory/kratos/issues/802)) ([b868782](https://github.com/ory/kratos/commit/b86878229f343e6b11521596b04040f892d1e2c3)) - Fix typo ([#847](https://github.com/ory/kratos/issues/847)) ([9b3da9f](https://github.com/ory/kratos/commit/9b3da9f0fe2ce71743115844d8c91a1dc9c4cbae)) - Fix typo ([#881](https://github.com/ory/kratos/issues/881)) ([3078293](https://github.com/ory/kratos/commit/3078293717a2ce21c4b939de4c2c4886c75303b5)) - Fix typo MKFA to MFA ([#826](https://github.com/ory/kratos/issues/826)) ([a5613d0](https://github.com/ory/kratos/commit/a5613d08aa21f90f4d192e5663ba4977b3de16c3)) - Remove workaround note ([#886](https://github.com/ory/kratos/issues/886)) ([05409bc](https://github.com/ory/kratos/commit/05409bc13f527398e3de01f29437e5d4353ef8d4)), closes [#718](https://github.com/ory/kratos/issues/718) - Swagger specs for selfservice settings browser flow ([#825](https://github.com/ory/kratos/issues/825)) ([28d50f4](https://github.com/ory/kratos/commit/28d50f45ab14d561609be7047cac13902394b547)) - Update oidc provider with json conf support ([#833](https://github.com/ory/kratos/issues/833)) ([670eb37](https://github.com/ory/kratos/commit/670eb37d19674f33a36402cd9a88d61ca7327751)) ### Features - Add return_to parameter to logout flow ([#823](https://github.com/ory/kratos/issues/823)) ([1c146dd](https://github.com/ory/kratos/commit/1c146dd21d616a56f510019abadd37402782bb39)), closes [#702](https://github.com/ory/kratos/issues/702) - Add selinux compatible quickstart config ([#889](https://github.com/ory/kratos/issues/889)) ([0f87948](https://github.com/ory/kratos/commit/0f879481df209ed96b778799adcc2a9424449b37)), closes [#831](https://github.com/ory/kratos/issues/831) ### Tests - Ensure registration runs only once ([#872](https://github.com/ory/kratos/issues/872)) ([5ffc036](https://github.com/ory/kratos/commit/5ffc036ac82f36ad6ef499e217971275a35fc23a)) ### Unclassified - docs: fix link and typo in Configuring Cookies (#883) ([c51ed6b](https://github.com/ory/kratos/commit/c51ed6b789d2e3a8fe4e93565c3bded37d298f98)), closes [#883](https://github.com/ory/kratos/issues/883) # [0.5.4-alpha.1](https://github.com/ory/kratos/compare/v0.5.3-alpha.1...v0.5.4-alpha.1) (2020-11-11) This release introduces the new CLI command `kratos hashers argon2 calibrate 500ms`. This command will choose the best parameterization for Argon2. Check out the [Choose Argon2 Parameters for Secure Password Hashing and Login](https://www.ory.sh/choose-recommended-argon2-parameters-password-hashing/) blog article for more insights! ### Bug Fixes - Case in settings handler method ([#798](https://github.com/ory/kratos/issues/798)) ([83eb4e0](https://github.com/ory/kratos/commit/83eb4e0021621014d2b543e57a01401381f07fe4)) - Force brew install statement ([#796](https://github.com/ory/kratos/issues/796)) ([ad542ad](https://github.com/ory/kratos/commit/ad542ad5919205ac26a757145474e5a46f3937ec)): Closes https://github.com/ory/homebrew-kratos/issues/1 ### Code Generation - Pin v0.5.4-alpha.1 release commit ([b02926c](https://github.com/ory/kratos/commit/b02926c42aee2748bc37ce2600596bd0c2537a0d)) ### Code Refactoring - Move pkger and ioutil helpers to ory/x ([60a0fc4](https://github.com/ory/kratos/commit/60a0fc449d90ead6065ca00926536a989d8b2a2b)) ### Documentation - Fix another broken link ([15bae9f](https://github.com/ory/kratos/commit/15bae9f893c2e2910167326d987455246c110001)) - Fix broken links ([#795](https://github.com/ory/kratos/issues/795)) ([0ab0e7e](https://github.com/ory/kratos/commit/0ab0e7eca8e95d6c26d028c177cbbd1f06b68871)), closes [#793](https://github.com/ory/kratos/issues/793) - Fix broken relative link ([#812](https://github.com/ory/kratos/issues/812)) ([b32b173](https://github.com/ory/kratos/commit/b32b173fe30b7c5c43700abfa4ddb3409a33556b)) - Fix links ([#800](https://github.com/ory/kratos/issues/800)) ([5fcc272](https://github.com/ory/kratos/commit/5fcc272e625de9e583b2ec24d5679895a6d24c1b)) - Fix oidc config examples ([#799](https://github.com/ory/kratos/issues/799)) ([8a4f480](https://github.com/ory/kratos/commit/8a4f480121995d9899668f037382086fcdd2da4c)) - Fix self-service recovery flow typo ([#807](https://github.com/ory/kratos/issues/807)) ([800110d](https://github.com/ory/kratos/commit/800110d87c9df70a5ec79b58d9fcb9ae39ff76b9)) - Remove duplicate words & fix spelling ([#810](https://github.com/ory/kratos/issues/810)) ([4e1b966](https://github.com/ory/kratos/commit/4e1b96667d9f08dbafeb2f5ce144ca43309de8e0)) - Remove leftover category from reference sidebar ([#813](https://github.com/ory/kratos/issues/813)) ([94fde51](https://github.com/ory/kratos/commit/94fde5101d00b9e1f7228e9d122ef0a8e4719355)) - Use correct links ([#797](https://github.com/ory/kratos/issues/797)) ([a4de293](https://github.com/ory/kratos/commit/a4de29399e4f1b5d0a33acc85478f2d38579a174)) ### Features - Add helper for choosing argon2 parameters ([#803](https://github.com/ory/kratos/issues/803)) ([ca5a69b](https://github.com/ory/kratos/commit/ca5a69b798635d0e5361fd5b0cc369b035dca738)), closes [#723](https://github.com/ory/kratos/issues/723) [#572](https://github.com/ory/kratos/issues/572) [#647](https://github.com/ory/kratos/issues/647): This patch adds the new command "hashers argon2 calibrate" which allows one to pick the desired hashing time for password hashing and then chooses the optimal parameters for the hardware the command is running on: ``` $ kratos hashers argon2 calibrate 500ms Increasing memory to get over 500ms: took 2.846592732s in try 0 took 6.006488824s in try 1 took 4.42657975s with 4.00GB of memory [...] Decreasing iterations to get under 500ms: took 484.257775ms in try 0 took 488.784192ms in try 1 took 486.534204ms with 3 iterations Settled on 3 iterations. { "memory": 1048576, "iterations": 3, "parallelism": 32, "salt_length": 16, "key_length": 32 } ``` # [0.5.3-alpha.1](https://github.com/ory/kratos/compare/v0.5.2-alpha.1...v0.5.3-alpha.1) (2020-10-27) This release improves the developer and user experience around CSRF counter-measures. It should now be possible to use the self-service API flows without having to explicitly disable cookie features in your SDKs and integrations. Additionally, another issue in the CGO pipeline was resolved which finally allows running ORY Kratos without CGO if the target database is not SQLite. Further improvements to default config values have been made and a full end-to-end test suite for the exemplary [kratos-selfservice-ui-react-native](kratos-selfservice-ui-react-native) app. The app is now available in the iTunes store as well - just search for "ORY Profile App"! ### Bug Fixes - Add "x-session-token" to default allowed headers ([3c912e4](https://github.com/ory/kratos/commit/3c912e4c7d46fd45c00cabb68ed7770bd44f7d07)) - Do not set cookies on api endpoints ([2f67c28](https://github.com/ory/kratos/commit/2f67c28718856ea03ea2effa89b28a8c4b3b8ae0)) - Do not set csrf cookies on potential api endpoints ([4d97a95](https://github.com/ory/kratos/commit/4d97a95d084ea99f5aca158609e197acd256cdd7)) - Ignore unsupported migration dialects ([12bb8d1](https://github.com/ory/kratos/commit/12bb8d14ae1edef18591996411be67d5693e5101)), closes [#778](https://github.com/ory/kratos/issues/778): Skips sqlite3 migrations when support is lacking. - Improve semver regex ([584c0b5](https://github.com/ory/kratos/commit/584c0b5043e85e88ac2648cf699d60fed3e775a9)) - Properly set nosurf context even when ignored ([0dcb774](https://github.com/ory/kratos/commit/0dcb774157bcbfd41a5d9df3914c31162226da75)) - Update cypress ([ba8b172](https://github.com/ory/kratos/commit/ba8b1729477233f79d099e5d7b397430ac1c6ace)) - Use correct regex for version replacement ([ce870ab](https://github.com/ory/kratos/commit/ce870ababdf089344a9428d3a405e18504a3c906)), closes [#787](https://github.com/ory/kratos/issues/787) ### Code Generation - Pin v0.5.3-alpha.1 release commit ([64dc91a](https://github.com/ory/kratos/commit/64dc91af54cdf3eba158a50690240cdc8f7cb43b)) ### Documentation - Fix docosaurus admonitions ([#788](https://github.com/ory/kratos/issues/788)) ([281a7c9](https://github.com/ory/kratos/commit/281a7c9289570d4bee33447655281b610cbe7e52)) - Pin download script version ([e4137a6](https://github.com/ory/kratos/commit/e4137a6a41d68b1480af2075bda8c5f46c42cd22)) - Remove trailing garbage from quickstart ([#787](https://github.com/ory/kratos/issues/787)) ([7e70924](https://github.com/ory/kratos/commit/7e709242ada28b7781c6ace272f60f9d1b9d5b2f)) ### Features - Improve makefile install process and update deps ([d1eb37f](https://github.com/ory/kratos/commit/d1eb37f5d9d0f16e7864b5f8f08a44ba80853fa5)) ### Tests - Add e2e tests for mobile ([d481d51](https://github.com/ory/kratos/commit/d481d51f5f4de96cbbc7c347f5dbff381b44462d)) - Add option to disable csrf protection in apis ([a0077f1](https://github.com/ory/kratos/commit/a0077f12adf94ff428b502b69bbb0eaafd05be66)) - Bump wait time ([7a719e1](https://github.com/ory/kratos/commit/7a719e17c5641f4df47314f6f0ac2cf73dddc8bb)) - Install expo-cli globally ([db21cfa](https://github.com/ory/kratos/commit/db21cfa1c589a2dab829a4c8eaf1db15d14d965e)) - Install expo-cli in cci config with sudo ([d255f46](https://github.com/ory/kratos/commit/d255f462402f2d2c2278dcba1a139d0064343b22)) - Log wait-on output ([62b5ba9](https://github.com/ory/kratos/commit/62b5ba92d56e9f6b98adb8fb9c4daff03be08f2e)) - Output web server address ([cb41ca7](https://github.com/ory/kratos/commit/cb41ca78367b1943d230fa9ac116fcf3cf69b1c1)) - Resolve csrf test issues in settings ([ef8ba7d](https://github.com/ory/kratos/commit/ef8ba7dc93d6ba84f22b7aa65d00797e33b520a3)) - Resolve test panic ([6f6461f](https://github.com/ory/kratos/commit/6f6461fe3690576015ded9146c065a1e5d950be1)) - Revert delay increase and improve install scripts ([1eafcaa](https://github.com/ory/kratos/commit/1eafcaa86be194e412b0470a759bff6afc6c21af)) # [0.5.2-alpha.1](https://github.com/ory/kratos/compare/v0.5.1-alpha.1...v0.5.2-alpha.1) (2020-10-22) This release addresses bugs and user experience issues. ### Bug Fixes - Add debug quickstart yml ([#780](https://github.com/ory/kratos/issues/780)) ([16e6b4d](https://github.com/ory/kratos/commit/16e6b4d76d297182ea9a1f5dc6367570f02f7b42)) - Gracefully handle double slashes in URLs ([aeb9414](https://github.com/ory/kratos/commit/aeb941477910b5ab54429a6aab7a3e1e388c48c5)), closes [#779](https://github.com/ory/kratos/issues/779) - Merge gobuffalo CGO fix ([fea2e77](https://github.com/ory/kratos/commit/fea2e77ca0f9b20185c7a7704854fdcf29b7ab33)) - Remove obsolete recovery_token and add link to schema ([acf6ac4](https://github.com/ory/kratos/commit/acf6ac4e11c755e56c7d40728088257de367f7ff)) - Return correct error in login csrf ([dd9cab0](https://github.com/ory/kratos/commit/dd9cab0e02400c88e89877f755f03c6179013123)), closes [#785](https://github.com/ory/kratos/issues/785) - Use correct assert package ([76be5b0](https://github.com/ory/kratos/commit/76be5b0a5d94c251f5f07eee9f700ec11b341e2e)) ### Code Generation - Pin v0.5.2-alpha.1 release commit ([79fcd8a](https://github.com/ory/kratos/commit/79fcd8a6949886f847f7be0c9ba2aba7554ab204)) ### Documentation - Small improvements to discord oidc provider guide ([#783](https://github.com/ory/kratos/issues/783)) ([6a3c453](https://github.com/ory/kratos/commit/6a3c45330885eb95015fa7ee9b58a72c38132499)) ### Tests - Add tests for csrf behavior ([48993e2](https://github.com/ory/kratos/commit/48993e2c496fb8af7e7b9e2752ba7078a134a75a)), closes [#785](https://github.com/ory/kratos/issues/785) - Mark link as enabled in e2e test ([c214b81](https://github.com/ory/kratos/commit/c214b81a7026b06aaca062b2aa77951d01b0e237)) - Resolve schema test regression ([bb7af1b](https://github.com/ory/kratos/commit/bb7af1b759d6c812755956ef872bcbd31b9c50be)) # [0.5.1-alpha.1](https://github.com/ory/kratos/compare/v0.5.0-alpha.1...v0.5.1-alpha.1) (2020-10-20) This release resolves an issue where ORY Kratos Docker Images without CGO and SQLite support would fail to boot even when SQLite was not used as a data source. ### Bug Fixes - Do not require sqlite without build tag ([2ee787b](https://github.com/ory/kratos/commit/2ee787bc1e97bdc11d0c92d55664d59e777f7ed1)) - Use extra dc config file for quickstart-dev ([72c03f9](https://github.com/ory/kratos/commit/72c03f9bcb91d30d5ff6b94030f2cbb6144fbf8d)) ### Code Generation - Pin v0.5.1-alpha.1 release commit ([b85b36b](https://github.com/ory/kratos/commit/b85b36b967d91c13b6d70ed668f17d3474eafae7)) ### Documentation - Fix spelling mistake ([14e7f65](https://github.com/ory/kratos/commit/14e7f6535e69f4bee2e3ca611a8d1a36bfd5f8f8)) - Fix spelling mistake ([#772](https://github.com/ory/kratos/issues/772)) ([bf401a2](https://github.com/ory/kratos/commit/bf401a26ee4422a8ea1b52f642885b0d8bac1272)) - Improve schemas ([#773](https://github.com/ory/kratos/issues/773)) ([e614859](https://github.com/ory/kratos/commit/e6148590577e1688d58534b8559d3bc602f9c2e7)) ### Features - Auto-update docker and git tags on release ([08084a9](https://github.com/ory/kratos/commit/08084a987501939544da1a1c7ee102819e2480ce)) - Use fixed versions for docker-compose ([e73c4ce](https://github.com/ory/kratos/commit/e73c4ce6f328376ad310b8f6d5c391ea06573003)) ### Tests - Increase waittime ([5e911d6](https://github.com/ory/kratos/commit/5e911d687247e4878bdcf82e5b008617f0bbdf4e)) - Reduce flakes by increasing wait time for expiry test ([cddf29e](https://github.com/ory/kratos/commit/cddf29e7dc5304c497d5ba7c1e6a2d63c9b6c137)) ### Unclassified - Format ([8be02c8](https://github.com/ory/kratos/commit/8be02c8938769dfcd7c9b7ed5e72e4ded3b1924b)) # [0.5.0-alpha.1](https://github.com/ory/kratos/compare/v0.4.6-alpha.1...v0.5.0-alpha.1) (2020-10-15) The ORY team and community is very proud to present the next ORY Kratos iteration! ORY Kratos is now capable of handling native (iOS, Android, Windows, macOS, ...) login, registration, settings, recovery, and verification flows. As a goodie on top, we released a reference React Native application which you can find on [GitHub](http://github.com/ory/kratos-selfservice-ui-react-native). We co-released our reference React Native application which acts as a reference on implementing these flows: ![Registration](http://ory.sh/images/newsletter/kratos-0.5.0/registration-screen.png) ![Welcome](http://ory.sh/images/newsletter/kratos-0.5.0/welcome-screen.png) ![Settings](http://ory.sh/images/newsletter/kratos-0.5.0/settings-screen.png) In total, almost 1200 files were changed in about 480 commits. While you can find a list of all changes in the changelist below, these are the changes we are most proud of: - We renamed login, registration, ... requests to "flows" consistently across the code base, APIs, and data storage. We now: - Initiate a login, registration, ... flow; - Fetch a login, registration, ... flow; and - Complete a login, registration, ... flow using a login flow method such as "Log in with username and password". - All self-service flows are now capable of handling API-based requests that do not originate from Browser such as Chrome. This is set groundwork for handling native flows (see above)! - The self service documentation has been refactored and simplified. We added code samples, screenshots, payloads, and curl commands to make things easier and clearer to understand. Video guides have also been added to help you and the community get things done faster! - Documentation for rotating important secrets such as the cookie and session secrets was added. - The need for reverse proxies was removed by adding the ability to change the ORY Kratos Session Cookie domain and path! The [kratos-selfservice-ui-node](https://github.com/ory/kratos-selfservice-ui-node) reference implementation no longer requires HTTP Request piping which greatly simplifies the network layout and codebase! - The ORY Kratos CLI is now capable of managing identities with an interface that works almost like the Docker CLI we all love! - Admins are now able to initiate account recovery for identities. - Email verification and account recovery were refactored. It is now possible to add additional strategies (e.g. recovery codes) in the future, greatly increasing the feature set and security capabilities of future ORY Kratos versions! - Lookup to Have I Been Pwnd is no longer a hard requirement, allowing registration processes to complete when the service is unavailable or the network is slow. - We contributed several issues and features in upstream projects such as justinas/nosurf, gobuffalo/pop, and many more! - The build pipeline has been upgraded to support cross-compilation of CGO with Go 1.15+. - Fetching flows no longer requires CSRF cookies to be set, improving developer experience while not compromising on security! - ORY Kratos now has ORY Kratos Session Cookies (set in the HTTP Cookie header) and ORY Kratos Session Tokens (set as a HTTP Bearer Authorization token or the `X-Session-Token` HTTP Header). Additionally tons of bugs were fixed, tests added, documentation improved, and much more. Please note that several things have changed in a breaking fashion. You can find details for the individual breaking changes in the changelog below. We would like to thank all community members who contributed towards this release (in no particular order): - https://github.com/kevgo - https://github.com/NickUfer - https://github.com/drwatsno - https://github.com/alsuren - https://github.com/wezzle - https://github.com/sherbang - https://github.com/perryao - https://github.com/jikunchong - https://github.com/err0r500 - https://github.com/debrutal - https://github.com/c0depwn - https://github.com/aschepis - https://github.com/jakhog Have fun exploring the new release, we hope you like it! If you haven't already, join the [ORY Community Slack](http://slack.ory.sh) where we hold weekly community hangouts via video chat and answer your questions, exchange ideas, and present new developments! ## Breaking Changes The "common" keyword has been removed from the Swagger 2.0 spec which deprecates the `common` module / package / class (depending on the generated SDK). Please use `public` or `admin` instead! Additionally, the SDK for TypeScript now uses the `fetch` API which allows the SDK to be used in both client-side as well as server-side contexts. Please note that several methods and parameters in the generated TypeScript SDK have changed. Please check the TypeScript results to see what needs to be changed! This patch changes the OpenID Connect and OAuth2 ("Sign in with Google, Facebook, ...") Callback URL from `http(s):///self-service/browser/flows/strategies/oidc/` to `http(s):///self-service/methods/oidc/`. To apply this patch, you need to update these URLs at the OAuth2 Client configuration pages of the individual OpenID Conenct providers (e.g. GitHub, Google). Configuration key `selfservice.strategies` was renamed to `selfservice.methods`. This patch significantly changes how email verification works. The Verification Flow no longer uses its own system but now re-uses the API and Browser flows and flow methods established in other components such as login, recovery, registration. Due to the many changes these patch notes does not cover how to upgrade this particular flow. We instead want to kindly ask you to check out the updated documentation for this flow at: https://www.ory.sh/kratos/docs/self-service/flows/verify-email-account-activation This patch changes the SQL schema and thus requires running the SQL Migration command (e.g. `... migrate sql`). Never apply SQL migrations without backing up your database prior. Configuration items `selfservice.flows..request_lifespan` have been renamed to `selfservice.flows..lifespan` to match the new flow semantics. Wording has changed from "Self-Service Recovery Request" to "Self-Service Recovery Flow" to follow community feedback and practice already applied in the documentation. Additionally, fetching a recovery flow over the public API no longer requires Anti-CSRF cookies to be sent. This patch renames several important recovery flow endpoints: - `/self-service/browser/flows/recovery` is now `/self-service/recovery/browser` without functional changes. - `/self-service/browser/flows/requests/recovery?request=abcd` is now `/self-service/recovery/flows?id=abcd` and no longer needs anti-CSRF cookies to be available. Additionally, the URL for completing the password and oidc recovery method has been moved. Given that this endpoint is typically not manually called, you can probably ignore this change: - `/self-service/browser/flows/recovery/link?request=abcd` is now `/self-service/recovery/methods/link?flow=abcd` without functional changes. The Recovery UI Endpoint no longer receives a `?request=abcde` query parameter but instead a `?flow=abcde` query parameter. Functionality did not change however. As part of this change SDK methods have been renamed: ``` const kratos = new CommonApi(config.kratos.public) // ... - kratos.completeSelfServiceBrowserRecoveryLinkStrategyFlow(req.query.request) + kratos.completeSelfServiceRecoveryFlowWithLinkMethod(req.query.flow) ``` This patch requires you to run SQL migrations. Wording has changed from "Self-Service Settings Request" to "Self-Service Settings Flow" to follow community feedback and practice already applied in the documentation. This patch renames several important settings flow endpoints: - `/self-service/browser/flows/settings` is now `/self-service/settings/browser` without functional changes. - `/self-service/browser/flows/requests/settings?request=abcd` is now `/self-service/settings/flows?id=abcd` and no longer needs anti-CSRF cookies to be available. Additionally, the URL for completing the password, profile, and oidc settings method has been moved. Given that this endpoint is typically not manually called, you can probably ignore this change: - `/self-service/browser/flows/login/strategies/password?request=abcd` is now `/self-service/login/methods/password?flow=abcd` without functional changes. - `/self-service/browser/flows/strategies/oidc?request=abcd` is now `/self-service/methods/oidc?flow=abcd` without functional changes. - `/self-service/browser/flows/settings/strategies/profile?request=abcd` is now `/self-service/settings/methods/profile?flow=abcd` without functional changes. The Settings UI Endpoint no longer receives a `?request=abcde` query parameter but instead a `?flow=abcde` query parameter. Functionality did not change however. As part of this change SDK methods have been renamed: ``` const kratos = new CommonApi(config.kratos.public) // ... - kratos.getSelfServiceBrowserSettingsRequest(req.query.request) + kratos.getSelfServiceSettingsFlow(req.query.flow) // You will most likely not be using this: const kratos = new PublicApi(config.kratos.public) - kratos.completeSelfServiceBrowserSettingsPasswordStrategyFlow //... - kratos.completeSelfServiceSettingsFlowWithPasswordMethod //.. - kratos.completeSelfServiceBrowserSettingsProfileStrategyFlow //... - kratos.completeSelfServiceSettingsFlowWithProfileMethod //.. ``` This patch requires you to run SQL migrations. This patch makes the reverse proxy functionality required in prior versions of the self-service UI example obsolete. All examples work now with a simple set up and documentation has been added to assist in subdomain scenarios. The session field `sid` has been renamed to `id` to stay consistent with other APIs which also use `id` terminology to clarify identifiers. The payload of, for example, `/session/whoami` has changed as follows: ```patch { - "sid": "abcde", + "id": "abcde", "expires_at": "..." "identity": { // .. } } ``` Wording has changed from "Self-Service Registration Request" to "Self-Service Registration Flow" to follow community feedback and practice already applied in the documentation. Additionally, fetching a login flow over the public API no longer requires Anti-CSRF cookies to be sent. This patch renames several important registration flow endpoints: - `/self-service/browser/flows/registration` is now `/self-service/registration/browser` without behavioral change. - `/self-service/browser/flows/requests/registration?request=abcd` is now `/self-service/registration/flows?id=abcd` and no longer needs anti-CSRF cookies to be available. Additionally, the URL for completing the password registration method has been moved. Given that this endpoint is typically not manually called, you can probably ignore this change: - `/self-service/browser/flows/registration/strategies/password?request=abcd` is now `/self-service/registration/methods/password?flow=abcd` without functional changes. - `/self-service/browser/flows/strategies/oidc?request=abcd` is now `/self-service/methods/oidc?flow=abcd` without functional changes. The Registration UI Endpoint no longer receives a `?request=abcde` query parameter but instead a `?flow=abcde` query parameter. Functionality did not change however. As part of this change SDK methods have been renamed: ``` const kratos = new CommonApi(config.kratos.public) // ... - kratos.getSelfServiceBrowserRegistrationRequest(req.query.request) + kratos.getSelfServiceRegistrationFlow(req.query.flow) ``` This patch requires you to run SQL migrations. Existing login sessions will no longer be valid because the session cookie data model changed. If you apply this patch, your users will need to sign in again. Wording has changed from "Self-Service Login Request" to "Self-Service Login Flow" to follow community feedback and practice already applied in the documentation. Additionally, fetching a login flow over the public API no longer requires Anti-CSRF cookies to be sent. This patch renames several important login flow endpoints: - `/self-service/browser/flows/login` is now `/self-service/login/browser` without functional changes. - `/self-service/browser/flows/requests/login?request=abcd` is now `/self-service/login/flows?id=abcd` and no longer needs anti-CSRF cookies to be available. Additionally, the URL for completing the password and oidc login method has been moved. Given that this endpoint is typically not manually called, you can probably ignore this change: - `/self-service/browser/flows/login/strategies/password?request=abcd` is now `/self-service/login/methods/password?flow=abcd` without functional changes. - `/self-service/browser/flows/strategies/oidc?request=abcd` is now `/self-service/methods/oidc?flow=abcd` without functional changes. The Login UI Endpoint no longer receives a `?request=abcde` query parameter but instead a `?flow=abcde` query parameter. Functionality did not change however. As part of this change SDK methods have been renamed: ``` const kratos = new CommonApi(config.kratos.public) // ... - kratos.getSelfServiceBrowserLoginRequest(req.query.request) + kratos.getSelfServiceLoginFlow(req.query.flow) ``` This patch requires you to run SQL migrations. Configuraiton value `session.cookie_same_site` has moved to `session.cookie.same_site`. There was no functional change. ### Bug Fixes - Add missing 'recovery' path in oathkeeper access-rules.yml ([#763](https://github.com/ory/kratos/issues/763)) ([f180dba](https://github.com/ory/kratos/commit/f180dba2207638e83e4a23ebc213cddaecb5677f)) - Add missing error handling ([43c1446](https://github.com/ory/kratos/commit/43c14464efa7b736695e2144b031daf6fca87703)) - Add ory-prettier-styles to main repo ([#744](https://github.com/ory/kratos/issues/744)) ([aeaddbc](https://github.com/ory/kratos/commit/aeaddbcb27f89d61b076bdd9ad1739fb1da2ffd9)) - Add remote help description ([f66bbe1](https://github.com/ory/kratos/commit/f66bbe18cfad1e8725ecbcf6e2843b34c3d5119f)) - Add serve help description ([2eb072b](https://github.com/ory/kratos/commit/2eb072b71e5602895d4232e197bfd76180fcdcd7)) - Allow using json with form layout in password registration ([bd2225c](https://github.com/ory/kratos/commit/bd2225c0fff3e0363716d2096346d59046838bb7)) - Annotate whoami endpoint with cookie and token ([a8a781c](https://github.com/ory/kratos/commit/a8a781c00847c74c65558b55e882e12c1e69d8c8)) - Bump datadog version to fix build failure ([4dfd322](https://github.com/ory/kratos/commit/4dfd322290313ec8467ebe8b385b56004b2417bd)) - Change KRATOS_ADMIN_ENDPOINT to KRATOS_ADMIN_URL ([763fdc5](https://github.com/ory/kratos/commit/763fdc56d19d12fa2b83eed2757fbf178d9288b1)) - Clarify fetch use ([8eb2e6f](https://github.com/ory/kratos/commit/8eb2e6f222788a9a579774772696c77987f3cf97)) - Complete verification by redirecting to UI with success ([f0ecf51](https://github.com/ory/kratos/commit/f0ecf5144970f666643aa7c00a3f4ca73f4ab047)) - Correct cookie domain on logout ([#646](https://github.com/ory/kratos/issues/646)) ([6d77e04](https://github.com/ory/kratos/commit/6d77e043ce3bec0864b8abdee371a101f68e4335)), closes [#645](https://github.com/ory/kratos/issues/645) - Correct help message for import ([a5f46d2](https://github.com/ory/kratos/commit/a5f46d260b43d15f8e77b04cb36c589e103468bf)) - Correct password and profile swagger annotations ([668c184](https://github.com/ory/kratos/commit/668c1847c4c4236ca28f9dcd5147b523a2f60832)) - Correct password registration method api spec ([08dd582](https://github.com/ory/kratos/commit/08dd582195cdb6a891d2428ba5d02cd956555e48)) - Correct PHONY spelling ([#739](https://github.com/ory/kratos/issues/739)) ([e3d3617](https://github.com/ory/kratos/commit/e3d3617b8d82812b0ad67cc1cb02ff86c2c0c66c)) - Cover more test cases for persister ([37d2e08](https://github.com/ory/kratos/commit/37d2e0839b88792733387f26abb98c51bd1e1395)) - Create decoder only once ([34dc43b](https://github.com/ory/kratos/commit/34dc43b0c75303f88d2c304225c027faf5366c1f)) - Deprecate packr2 dependency in makefile ([be9a84d](https://github.com/ory/kratos/commit/be9a84dcffbccd5f0e073a38264cf11a404d3b66)), closes [#711](https://github.com/ory/kratos/issues/711) [#750](https://github.com/ory/kratos/issues/750) - Do not propagate parent validation error ([bf6093d](https://github.com/ory/kratos/commit/bf6093d442d9779b4df051031565d020ef628ded)) - Don't resend verification emails once verified ([#583](https://github.com/ory/kratos/issues/583)) ([a4d9969](https://github.com/ory/kratos/commit/a4d99694525e65b58d49197c96324b27fb8c31c2)), closes [#578](https://github.com/ory/kratos/issues/578) - Enforce endpoint to be set ([171ac18](https://github.com/ory/kratos/commit/171ac18d73eaa0822b45f544a9034d6734400f31)) - Escape jsx characters in api documentation ([0946094](https://github.com/ory/kratos/commit/09460948a24918b2a84804cafa86cf88189af919)) - Exit with code 1 on unimplemented CLI commands ([66943d7](https://github.com/ory/kratos/commit/66943d7e5b47fc477a378d8a7cf2b2009ccfceb3)) - Explicitly ignore fprint return values ([f50e582](https://github.com/ory/kratos/commit/f50e5823f4ee047fdc3e276b80b4fb08c9128d99)) - Explicitly ignore fprintf results ([a83dc50](https://github.com/ory/kratos/commit/a83dc509970b3be46d832743481357f336fecc35)) - Fallback to default return url if logout after url is not defined ([#594](https://github.com/ory/kratos/issues/594)) ([7edd367](https://github.com/ory/kratos/commit/7edd367dc64a01dbe252ca0ab8cf4d3926a35014)) - Favor packr2 over pkger ([ac18a45](https://github.com/ory/kratos/commit/ac18a45ea55929c34ca20953e3baa197363483bc)): See https://github.com/markbates/pkger/issues/117 - Find and replace "request" references ([41fb673](https://github.com/ory/kratos/commit/41fb673e38779cb27d4400f70458617eb7e5b93c)) - Force exe buildmode for windows CGO ([e017bb5](https://github.com/ory/kratos/commit/e017bb579cd29ad1a634cd552e2601295ff9c104)) - Html form parse regression issue ([6b07cbb](https://github.com/ory/kratos/commit/6b07cbb657702d36423d1fa66fe8a149222c8772)) - Ignore x/net false positives ([7044b95](https://github.com/ory/kratos/commit/7044b95f6188c4ffbfff42c666dee6ebaba055c8)) - Improve debugging output for login hook and restructure files ([dabac40](https://github.com/ory/kratos/commit/dabac40f82407f72071780840f468d0b5b389777)) - Improve debugging output for registration hook and restructure files ([ec11775](https://github.com/ory/kratos/commit/ec117754f5dd41e5a3a43b3807c05796396ced55)) - Improve expired error responses ([124a92e](https://github.com/ory/kratos/commit/124a92ee98d62abeb695e1e271ee2536a69d6047)) - Improve hook tests ([55ba485](https://github.com/ory/kratos/commit/55ba48530a890fdd55ed7da380940f2791148f26)) - Improve makefile dependency building ([8e1d69a](https://github.com/ory/kratos/commit/8e1d69a024414196b39eb3d419f4850cd547e3b5)) - Improve pagination when listing identities ([c60bf44](https://github.com/ory/kratos/commit/c60bf440b9c85b4f2e871237e3d7725571151efe)) - Improve post login hook log and audit messages ([ddd5d5a](https://github.com/ory/kratos/commit/ddd5d5a253d01d2b7b74239a1c7c701759084140)) - Improve post registration hook log and audit messages ([2495629](https://github.com/ory/kratos/commit/24956296dd91cf6f5b110a17f65f9f60d8a7aa78)) - Improve registration hook tests ([8163152](https://github.com/ory/kratos/commit/8163152a4d9595b1ea73d2887205e7ba80b016f9)) - Improve session max-age behavior ([65189fe](https://github.com/ory/kratos/commit/65189fe4a2f84f832240cd67366400e44bb7f09a)), closes [#42](https://github.com/ory/kratos/issues/42) - Keep HTML form type on registration error ([#698](https://github.com/ory/kratos/issues/698)) ([6c9e756](https://github.com/ory/kratos/commit/6c9e7564efffe1452004d4eda42e1b9ec9feac6b)), closes [#670](https://github.com/ory/kratos/issues/670) - Lowercase emails on login ([244b4dd](https://github.com/ory/kratos/commit/244b4dd825b9a2448cc61465cef81bd9dcb051db)) - Mark flow methods' fields as required ([#708](https://github.com/ory/kratos/issues/708)) ([834c607](https://github.com/ory/kratos/commit/834c60738ca7bb26e982ff73134b7b0e85a72076)) - Merge public and admin login flow fetch handlers ([48c4906](https://github.com/ory/kratos/commit/48c4906a606396d889e057a03dc83b619220db54)) - Missing write in registration error handler ([3b2af53](https://github.com/ory/kratos/commit/3b2af5397048d63099eace092bf2e50e84a4c610)) - Properly annotate swagger password parameters ([2ef57c4](https://github.com/ory/kratos/commit/2ef57c4323eb2623f4115bee0e44ee27dd1648a9)) - Properly fetch identity for session ([7be4086](https://github.com/ory/kratos/commit/7be4086045fddfacc38813ca3dd7fbcc7039391f)) - Recursive loop on network errors in password validator ([#589](https://github.com/ory/kratos/issues/589)) ([b4d5a42](https://github.com/ory/kratos/commit/b4d5a42346510e40222b8eb59b455b585f0a05cf)), closes [#316](https://github.com/ory/kratos/issues/316): The old code no error when ignoreNetworkErrors was set to true, but did not set a hash result which caused an infinite loop. - Remove incorrect security specs ([4c3d46d](https://github.com/ory/kratos/commit/4c3d46dac20363202f0ccd043e1c9d6bf97fb1f8)) - Remove obsolete tests ([f102f95](https://github.com/ory/kratos/commit/f102f95f420c8a03520602880d096616069c9233)): The test is no longer valid as CSRF checks now happen after checking for login sessions in settings flows. - Remove redirector from code base ([6689ecf](https://github.com/ory/kratos/commit/6689ecf110b11ba15ec39af822906c2b4b17369e)) - Remove stray debug statements ([a8e1ec4](https://github.com/ory/kratos/commit/a8e1ec42cda6ebc664e9434bb5ba7e4dd7c21b4c)) - Rename import to put ([8003e0f](https://github.com/ory/kratos/commit/8003e0f42a5d1b77e326d1dba0a70fcd44c704c0)) - Rename quickstart config files and path ([#671](https://github.com/ory/kratos/issues/671)) ([be8b9e5](https://github.com/ory/kratos/commit/be8b9e5f1ca70b1aa06b77bb2ca35644d8cd3c00)) - Rename quickstart schema file name ([e943c90](https://github.com/ory/kratos/commit/e943c9018a495b39b72ae463fd4727b1798d5ba2)) - Rename recovery models and generate SDKs ([d764435](https://github.com/ory/kratos/commit/d7644359c39732e0b25f43e122d05c1566fb837b)) - Resolve and test for missing data when updating flows ([045ecab](https://github.com/ory/kratos/commit/045ecab11ec185ca688a10de75e506fe413afa26)) - Resolve broken csrf tests ([6befe2e](https://github.com/ory/kratos/commit/6befe2ec08c01c6c9fb397ba119ecebdcecf7db3)) - Resolve broken docs links ([56f4a39](https://github.com/ory/kratos/commit/56f4a397a715b6c0428ae63baa0d2e4bc936f737)) - Resolve broken migrations and bump fizz ([1ed9c70](https://github.com/ory/kratos/commit/1ed9c700b946a090bce9587a57eeb9ac64f04c59)) - Resolve broken OIDC tests and disallow API flows ([9986d8f](https://github.com/ory/kratos/commit/9986d8f818934bd5e073f59bf7a73c6b7a74b6e2)) - Resolve cookie issues ([6e2b6d2](https://github.com/ory/kratos/commit/6e2b6d2f0ce2fb6df7d3e26d6cc8e755e6593a81)) - Resolve e2e headless test failures ([82d506e](https://github.com/ory/kratos/commit/82d506e9d35bbbe4c1578f72e5bcf380ebc97142)) - Resolve e2e test failures ([2627db2](https://github.com/ory/kratos/commit/2627db26089e8f8e4c18782ff59b4cb2068b276f)) - Resolve failing test cases ([f8647b4](https://github.com/ory/kratos/commit/f8647b4c637b4aee29d68df2336fd216306ec78c)) - Resolve flaky passwort setting tests ([#582](https://github.com/ory/kratos/issues/582)) ([c42d936](https://github.com/ory/kratos/commit/c42d936ef51d2ffb48b491b99988d048442e3b8b)), closes [#581](https://github.com/ory/kratos/issues/581) [#577](https://github.com/ory/kratos/issues/577) - Resolve handler testing issue ([4f6bafd](https://github.com/ory/kratos/commit/4f6bafdc84ba4d878c68700dc243cd3cfe8fe530)) - Resolve identity admin api issues ([#586](https://github.com/ory/kratos/issues/586)) ([feef8a7](https://github.com/ory/kratos/commit/feef8a7d4454c1b343c34a96fa4dadd56149b0cd)), closes [#435](https://github.com/ory/kratos/issues/435) [#500](https://github.com/ory/kratos/issues/500): This patch resolves several issues that occurred when creating or updating identities using the Admin API. Now, all hooks are running properly and updating privileged properties no longer causes errors. - Resolve interface type issues ([064b305](https://github.com/ory/kratos/commit/064b305ab31dc003ccb5992eb1ed2804f85085b9)) - Resolve logout csrf issues ([#761](https://github.com/ory/kratos/issues/761)) ([74c0aac](https://github.com/ory/kratos/commit/74c0aac3b94446c3824ae52b04b6f69395938b81)) - Resolve migratest failures ([e2f34d3](https://github.com/ory/kratos/commit/e2f34d3f411bac042079d7f5425063ef117fae77)) - Resolve migratest ordering failing tests ([dffecc0](https://github.com/ory/kratos/commit/dffecc0e80810ffae57870fd313ee0103ad3f60c)) - Resolve migration issues ([b545e15](https://github.com/ory/kratos/commit/b545e15eeaa3e6e1f4a8fe0f8e1890012ac62c94)) - Resolve panic on `serve` ([ae34155](https://github.com/ory/kratos/commit/ae341555e7b2b622cf58d09d3eb6a78d833dfdcc)) - Resolve panic when DSN="memory" ([#574](https://github.com/ory/kratos/issues/574)) ([05e55f3](https://github.com/ory/kratos/commit/05e55f3584e20ae5d39cfda6e542d4da40d718e4)): Executing the migration logic in registry.go cause a panic as the registry is not initalized at that point. Therefore we decided to move the handling to driver_default.go, after the registry has been initialized. - Resolve pkger issues ([294066c](https://github.com/ory/kratos/commit/294066c41be1d508681caa435afda4858a37b7f1)) - Resolve remaining testing issues ([af40d93](https://github.com/ory/kratos/commit/af40d933b2f663adb6a537b32546b43ba13ae237)) - Resolve SQL persistence tester issues ([4952df4](https://github.com/ory/kratos/commit/4952df43e0aba067c06cdedb1fc2c2d9a2a81a40)) - Resolve swagger issues and regenerate SDK ([be4c7e4](https://github.com/ory/kratos/commit/be4c7e4ea72d2ad7cec67b1d6709858d5a1b3d61)) - Resolve template loading issue ([145fb20](https://github.com/ory/kratos/commit/145fb204d9a8ca189480f9f2221527ccc62980a0)) - Resolve test issues introduced by new csrf protection ([625ef5e](https://github.com/ory/kratos/commit/625ef5e4781700449af0c4e4f1f6cb8aa1787764)) - Resolve verification sql errors ([784da53](https://github.com/ory/kratos/commit/784da53ddefe59aea90254be40ae63e919b4b419)) - Resolves a bug that prevents sessions from expiring ([#612](https://github.com/ory/kratos/issues/612)) ([86b281a](https://github.com/ory/kratos/commit/86b281a46b676d80c8f70bfc42c91d988997c21c)), closes [#611](https://github.com/ory/kratos/issues/611) - Revert disabling `swagger flatten` during sdk generation ([98c7915](https://github.com/ory/kratos/commit/98c7915cc493ad99c959244eef68b70bc9baa971)) - Set correct path for kratos in oathkeeper set up ([414259f](https://github.com/ory/kratos/commit/414259f9383f30b762051c712763d484f5358075)) - Set quickstart logging to trace ([d3e9192](https://github.com/ory/kratos/commit/d3e919249ae59b449367511d3cc8adef839f31c9)) - Support browser flows only in redirector ([cab5280](https://github.com/ory/kratos/commit/cab5280859b0fc7fc7fec2b2ec9945f457910b20)) - Swagger models ([1b5f9ab](https://github.com/ory/kratos/commit/1b5f9abd5d82251ab93a05d4ff26b4c48c8151ca)): The `swagger:parameters ` definitions for `updateIdentity` and `createIdentity` where defined two times with the same ID. They had some old definition swagger used. The `internal/httpclient` should now work again as expected. - Tell tls what the smtps server name is ([#634](https://github.com/ory/kratos/issues/634)) ([b724038](https://github.com/ory/kratos/commit/b724038a67e84ca71b146bf4b9b044be2dc8c0b4)) - Type ([e264c69](https://github.com/ory/kratos/commit/e264c69a07e569429b5e835b1e15c318eff23339)) - Update cli documentation examples ([216ea7f](https://github.com/ory/kratos/commit/216ea7f926798ff03d211447200919f9ef3c8b39)) - Update contrib samples ([79d24b4](https://github.com/ory/kratos/commit/79d24b4472017a75854cce4a45b4c762e5390a67)) - Update crdb quickstart version ([249a6ba](https://github.com/ory/kratos/commit/249a6bae32ccaa6cf002eaab921388e8cb10e58f)) - Update import description ([aef1e1a](https://github.com/ory/kratos/commit/aef1e1acf757637590fe19644952a44d1994ba18)) - Update quickstart kratos config ([e3246e5](https://github.com/ory/kratos/commit/e3246e5d56b95750529239663bab03168789cc09)) - Update recovery token field and column names ([42abfa1](https://github.com/ory/kratos/commit/42abfa1dea2a6291c5b723baf25f35a66f2af835)) - Update status help description ([b147831](https://github.com/ory/kratos/commit/b1478316d2f601843133fd33d75c3b047384f283)) - Update swagger names and fix broken tests ([85b7fb1](https://github.com/ory/kratos/commit/85b7fb1d466bc4dcee97ad75cc92b8bea8e44d9f)) - Update version help description ([8bf4a79](https://github.com/ory/kratos/commit/8bf4a79064a93cb53ef8aee3433b24602bc9f30a)) - Use and test for csrf tokens and prevent api misuse ([a4e3bc5](https://github.com/ory/kratos/commit/a4e3bc55e43ba42582a33551c1cc2e83ecd865fa)) - Use correct HTTP method for password login ([4f4fcee](https://github.com/ory/kratos/commit/4f4fcee8931ab4998e974106b8d88e0c61736e3f)) - Use correct log message ([53c384a](https://github.com/ory/kratos/commit/53c384a542a583259a75315b2602cf4fb41a0ef0)) - Use correct redirection for registration ([8d47113](https://github.com/ory/kratos/commit/8d47113a5f7c0c25dc5f92c683b560763cfd47c9)) - Use correct security annotation ([c9bebe0](https://github.com/ory/kratos/commit/c9bebe00452a73d1c831831e5a95cb4ed8de37b9)) - Use correct swagger tags and regenerate ([df99d8c](https://github.com/ory/kratos/commit/df99d8cbe6e0f2f6a5da872f66db557b2a5e9f70)) - Use helpers to create flow ([aba8610](https://github.com/ory/kratos/commit/aba861097d2c67ce9ebff85df59fce8018862516)) - Use nosurf fork to address VerifyToken bug ([cd84e51](https://github.com/ory/kratos/commit/cd84e51b7b1861ca9bd2312a4dfc5e84afd890cf)) - Use params per_page and page for pagination ([5dfb6e3](https://github.com/ory/kratos/commit/5dfb6e32c44420ed49d652733b9099a41c9347f2)) - Use proper pwd in makefile ([52e22c3](https://github.com/ory/kratos/commit/52e22c3b5c0130afd3e235aba9847389369f435e)) - Use public instead of common sdk ([dcb4a36](https://github.com/ory/kratos/commit/dcb4a36f9fb3c25ace9a252b7e05f7ab71d2e21f)) - Use relative threshold to judge longest common substring in password policy ([#585](https://github.com/ory/kratos/issues/585)) ([3e9f8cc](https://github.com/ory/kratos/commit/3e9f8cce4b058b05d69c73fff514f3b8e46c2be3)), closes [#581](https://github.com/ory/kratos/issues/581) - Whoami returns 401 not 403 ([3b3b78c](https://github.com/ory/kratos/commit/3b3b78c04bbbbb7b7fb05635d96b4f7c7fa7776f)), closes [#729](https://github.com/ory/kratos/issues/729) ### Code Generation - Pin v0.5.0-alpha.1 release commit ([557d37d](https://github.com/ory/kratos/commit/557d37d1139adb14a25abe40d0174d47d4e18fee)) ### Code Refactoring - Add flow methods to verification ([00ee828](https://github.com/ory/kratos/commit/00ee828842bd4bc6f917ba2446b1374d28b62000)): Completely refactors the verification flow to support other methods. The original email verification flow now moved to the "link" method also used for recovery. Additionally, several upstream bugs in gobuffalo/pop and gobuffalo/fizz have been addressed, patched, and merged which improves support for SQLite and CockroachDB migrations: - https://github.com/gobuffalo/fizz/pull/97 - https://github.com/gobuffalo/fizz/pull/96 - Add method and rename request to flow ([006bf56](https://github.com/ory/kratos/commit/006bf56671d8162cdb5bcce630c027b67935263d)) - Change oidc callback URL ([36d9380](https://github.com/ory/kratos/commit/36d9380b2123d27219c908b51ad97574ee11bc57)) - Complete login flow refactoring ([ad2b3db](https://github.com/ory/kratos/commit/ad2b3db4493085b80889cbc0dce9562288ec6896)) - Dry up login.NewFlow ([f261c44](https://github.com/ory/kratos/commit/f261c442dbe74e3b9887193b74e36fe70306f9d8)) - Improve CSRF infrastructure ([7e367e7](https://github.com/ory/kratos/commit/7e367e7f45481147d5c231d0ea8cbb30b738226f)) - Improve login test reuse ([b4184e5](https://github.com/ory/kratos/commit/b4184e5f1525a9918bc795f2353b186141ce5399)) - Improve NewFlowExpiredError ([1caefac](https://github.com/ory/kratos/commit/1caefac6e0e82aa2b12458ef16d7f5af24014bf9)) - Improve registration tests with testhelpers ([9bf4530](https://github.com/ory/kratos/commit/9bf45303be908449b78c68c7382eab5cfc5c40fa)) - Improve selfservice method tests ([df4d06d](https://github.com/ory/kratos/commit/df4d06d553852cdb8b914810c19bdd0fcc845c9c)) - Improve settings helper functions ([fda17ca](https://github.com/ory/kratos/commit/fda17ca5ea7824c4bf5010218cace7d5fbc7ad5b)) - Move samesite config to cookie parent-key ([753eb86](https://github.com/ory/kratos/commit/753eb86c904c4af9e7d91e46ff4c836dcce35807)) - Moved clihelpers to ory/x ([#756](https://github.com/ory/kratos/issues/756)) ([6ccffa8](https://github.com/ory/kratos/commit/6ccffa8a1cc5b9fd33435187720257bb66323546)): Contributes to https://github.com/ory/hydra/issues/2124. - Profile settings method is now API-able ([c5f361f](https://github.com/ory/kratos/commit/c5f361ff418336cfcaa452eded4bd61132808b16)) - Remove common keyword from API spec ([6619562](https://github.com/ory/kratos/commit/6619562667ef0e363d14c57cfbcd15c16f292853)) - Remove need for reverse proxy in selfservice-ui ([beb4c32](https://github.com/ory/kratos/commit/beb4c3284e552fe51c3a8cebb20a8c2bfc07cdf8)), closes [#661](https://github.com/ory/kratos/issues/661) - Rename `session.sid` to `session.id` ([809fe73](https://github.com/ory/kratos/commit/809fe7334e4a308405c1f03ada1dbef6ed33c01a)) - Rename login request to login flow ([9369d1b](https://github.com/ory/kratos/commit/9369d1bb637fc80b5d5980140693d5bcac0c76bb)), closes [#635](https://github.com/ory/kratos/issues/635): As part of this change, fetching a login flow over the public API no longer requires Anti-CSRF cookies to be sent. - Rename LoginRequestErrorHandler to LoginFlowErrorHandler ([66ae029](https://github.com/ory/kratos/commit/66ae029f49aecdfba5fa6905cfccfcdad992dd5a)) - Rename package recoverytoken to link ([f87fb54](https://github.com/ory/kratos/commit/f87fb549f6d8a10ba5adffddeb2fe12060d520ab)) - Rename recovery request to flow internally ([16c5618](https://github.com/ory/kratos/commit/16c5618644e78cf1081f966e01b570a36eea709b)) - Rename recovery request to recovery flow ([b0f433d](https://github.com/ory/kratos/commit/b0f433d4cb65d79acba789394d828663e873a833)), closes [#635](https://github.com/ory/kratos/issues/635): As part of this change, fetching a login flow over the public API no longer requires Anti-CSRF cookies to be sent. - Rename registration request to flow ([8437ebc](https://github.com/ory/kratos/commit/8437ebcf4deb2844562ec701af3bbbb2a9b5dea4)) - Rename registration request to registration flow ([0470956](https://github.com/ory/kratos/commit/0470956128d03921d8554c43af2c5a0003abe82f)), closes [#635](https://github.com/ory/kratos/issues/635): As part of this change, fetching a registration flow over the public API no longer requires Anti-CSRF cookies to be sent. - Rename request_lifespan to lifespan ([#677](https://github.com/ory/kratos/issues/677)) ([3c8d5e0](https://github.com/ory/kratos/commit/3c8d5e02b04686a1e0bfbd28caa0bc536e3414e4)), closes [#666](https://github.com/ory/kratos/issues/666) - Rename strategies to methods ([8985189](https://github.com/ory/kratos/commit/89851896d563518909bc2b47a7ff91683eec4958)): This patch renames `strategies` such as "Username/Email & Password" to methods. - Rename verify to verificaiton ([#597](https://github.com/ory/kratos/issues/597)) ([0ecd69a](https://github.com/ory/kratos/commit/0ecd69a60f741fc334c9b060b6aeaafc39e048b1)) - Replace all occurrences of login request to flow ([1b3c491](https://github.com/ory/kratos/commit/1b3c49174a7a2eff51dd531f3a49afc15c31c536)) - Replace all registration request occurrences with registration flow ([308ef47](https://github.com/ory/kratos/commit/308ef47846c9ab4f18a598ef6ef78514fad77c42)) - Replace packr2 with pkger fork ([4e2acae](https://github.com/ory/kratos/commit/4e2acae7c4fc17880cf88ef05cf7cca5f20f5be3)) - Restructure login package ([c99e2a2](https://github.com/ory/kratos/commit/c99e2a2f23c3c2aabaae55de67e40ab7fb2dd307)) - Use session token as cookie identifier ([60fd9c2](https://github.com/ory/kratos/commit/60fd9c2efa881fcdd769a8967abe73c05a198868)) ### Documentation - Add administrative user management guide ([b97e0c6](https://github.com/ory/kratos/commit/b97e0c69bb1115bdec88b218e8cdda34f137d798)) - Add code samples to session checking ([eba8eda](https://github.com/ory/kratos/commit/eba8eda70423aa802eace278889a5e8d2e0bc513)) - Add configuring introduction ([#630](https://github.com/ory/kratos/issues/630)) ([b8cfb35](https://github.com/ory/kratos/commit/b8cfb351c2dca783e355f39d25ce17b65fef7dd4)) - Add descriptions to cobra commands ([607b76d](https://github.com/ory/kratos/commit/607b76d109d1fa519235fe9d6af78c8315b9c4fc)) - Add documentation for configuring cookies ([e3dbc8a](https://github.com/ory/kratos/commit/e3dbc8acc055f6e2d78bc959be7356f9a66ac90f)), closes [#516](https://github.com/ory/kratos/issues/516) - Add domain, subdomain, multi-domain cookie guides ([3eb1e59](https://github.com/ory/kratos/commit/3eb1e5987df56993c792684a6a2bc11f5eb570b8)), closes [#661](https://github.com/ory/kratos/issues/661) - Add github video tutorial ([#622](https://github.com/ory/kratos/issues/622)) ([0c4222c](https://github.com/ory/kratos/commit/0c4222c0d12df4e971fd7e5099006484e0bcb317)) - Add guide for cors ([a8ae759](https://github.com/ory/kratos/commit/a8ae759565d94ebd9d0f758b7eb6efbddf486372)) - Add guide for cors ([91fd278](https://github.com/ory/kratos/commit/91fd278d1a6720576998b115dedb882b90915561)) - Add guide for dealing with login sessions ([4e2718c](https://github.com/ory/kratos/commit/4e2718c779031c0e3b877e9df1747ccb2371927b)) - Add identity state ([fb4aedb](https://github.com/ory/kratos/commit/fb4aedb9a95367e25080491b54aab11de491d819)) - Add login session to navbar ([b212d64](https://github.com/ory/kratos/commit/b212d6484e40c9f2cce10f2ba4aaf4e2a72f03a1)) - Add milestones to sidebar ([aae13ec](https://github.com/ory/kratos/commit/aae13ec141a2c315aff1a53aa005bb9465efcdc0)) - Add missing GitLab provider to the list of supported OIDC providers ([#766](https://github.com/ory/kratos/issues/766)) ([a43ed33](https://github.com/ory/kratos/commit/a43ed335262fd542f349224aef918af5263c384d)) - Add missing TOC entries ([#748](https://github.com/ory/kratos/issues/748)) ([bd7edfb](https://github.com/ory/kratos/commit/bd7edfbebd19f01af337c34293ebc2865f2b077d)) - Add pagination docs ([7fe0901](https://github.com/ory/kratos/commit/7fe0901ee5d0e829e110bd0c4fdecb24bfc27768)) - Add secret key rotation guide ([3d6e21a](https://github.com/ory/kratos/commit/3d6e21af2f726944468299c326600a8ab0e4e885)) - Add sequence diagrams for browser/api flows ([590d767](https://github.com/ory/kratos/commit/590d767352b9253b7550eaba56fea99400399cd7)) - Add session hook to ssi guide ([#623](https://github.com/ory/kratos/issues/623)) ([1bbed39](https://github.com/ory/kratos/commit/1bbed390ffedd811afdb5fcfe69047554419d8ce)) - Add terminology section ([29b81a7](https://github.com/ory/kratos/commit/29b81a78fcf880cd6d9d3b2cbb03f955b701ffbd)) - Add theme helpers and decouple mermaid ([7c3eb32](https://github.com/ory/kratos/commit/7c3eb32df5d9287845258bf25d6719733f6c4227)) - Add video to OIDC guide ([#619](https://github.com/ory/kratos/issues/619)) ([f286980](https://github.com/ory/kratos/commit/f286980c29ce8460ba550e5d74b8dee23602e920)) - Added sidebar cli label ([5d24a29](https://github.com/ory/kratos/commit/5d24a2998b412159295feca40421b8b11cf02274)): `clidoc.Generate` expects to find an entry under `sidebar.json/Reference` that contains the substring "CLI" in it's label. Because that was missing, a new entry was appended on every regeneration of the file. - Added sidebar item ([#639](https://github.com/ory/kratos/issues/639)) ([8574761](https://github.com/ory/kratos/commit/857476112d12b8ab79ef49054452a950ff81bc23)): Added Kratos Video Tutorial Transcripts document to sidebar. - Added transcript ([#627](https://github.com/ory/kratos/issues/627)) ([cec7f1f](https://github.com/ory/kratos/commit/cec7f1fc4955b02d21d772e748ec791f31bad24e)): Added Login with Github Transcript - Adds twitch oidc provider guide ([#760](https://github.com/ory/kratos/issues/760)) ([339e622](https://github.com/ory/kratos/commit/339e62202170bf21d469d1a2bfe6b053a78c374d)) - Bring oidc docs up to date ([7d0e470](https://github.com/ory/kratos/commit/7d0e47058cd6dca1763f01e45ed46cee49321240)) - Changed transcript location ([#642](https://github.com/ory/kratos/issues/642)) ([c52764d](https://github.com/ory/kratos/commit/c52764d4394181b24dffbf8301418530ba5dbcc2)): Changed the location so it is in the right place. - Clarify 302 redirect on expired login flows ([ca31b53](https://github.com/ory/kratos/commit/ca31b53837e8eb2b811bf384da3724fdf61b423b)) - Clarify api flow use ([a38b4a1](https://github.com/ory/kratos/commit/a38b4a1684cfbc385ca21005c91a47e57df5a35d)) - Clarify feature-set ([2266ae7](https://github.com/ory/kratos/commit/2266ae7ea92207cdc4fcb58ef1384e287a5b34dc)) - Clarify kratos config snippet ([e7732f3](https://github.com/ory/kratos/commit/e7732f3283d82a1678076cd2463ef5ff33dd30ea)) - Clean up docs and correct samples ([8627ec5](https://github.com/ory/kratos/commit/8627ec58edb15118e0c4ce2cfcef7a5573482c5a)) - Complete registration documentation ([b3af02b](https://github.com/ory/kratos/commit/b3af02b0ea4cbf16ea282b7ce5f5057d99044ac3)) - Consistent formatting of badges ([#745](https://github.com/ory/kratos/issues/745)) ([b391a03](https://github.com/ory/kratos/commit/b391a036f3b49cd6c1915444c9f26dead4855a7c)) - Correct settings and verification redir ([30e25e7](https://github.com/ory/kratos/commit/30e25e7287a2579da99a6a6dc2f890e7e06fcc81)) - Docker image documentation ([#573](https://github.com/ory/kratos/issues/573)) ([bfe032e](https://github.com/ory/kratos/commit/bfe032e2b6bfd8b9415d466011bdd7e36efa4146)) - Document APi flows in self-service overview ([71ed0bd](https://github.com/ory/kratos/commit/71ed0bd2027d61c2e5cebf6b031fe66469bdf97e)) - Document how to check for login sessions ([9ad73b8](https://github.com/ory/kratos/commit/9ad73b8dab06c6796933448cb93ae4e55d9f2c51)) - Explain high-level API and browser flows ([fe3ee0a](https://github.com/ory/kratos/commit/fe3ee0a0c8681a99dc6b61b90cff547c6a7fc6d2)), closes [hi#level](https://github.com/hi/issues/level) - Fix logout url ([#593](https://github.com/ory/kratos/issues/593)) ([f0971d4](https://github.com/ory/kratos/commit/f0971d44a911caed8a6071358fa6b7ebc0fcf145)) - Fix sidebar missing comment ([d90123a](https://github.com/ory/kratos/commit/d90123ae31edbae6a39a1f039cc9362f9acdfdcb)) - Fix typo ([c2f94da](https://github.com/ory/kratos/commit/c2f94daa4143a70c13426ccd5366ec891182e4d0)) - Fix typo on index page ([#656](https://github.com/ory/kratos/issues/656)) ([907add5](https://github.com/ory/kratos/commit/907add5edb526adb4de57d35da16929ac08041e1)) - Fix url of admin-api /recovery/link ([#650](https://github.com/ory/kratos/issues/650)) ([e68c7cb](https://github.com/ory/kratos/commit/e68c7cbdc2191565570d0ee6812318ac9ad3421d)) - Fixed link ([c2aebbd](https://github.com/ory/kratos/commit/c2aebbd898f38388d849954938d56212c88d280f)) - Fixed link ([#629](https://github.com/ory/kratos/issues/629)) ([ad1276f](https://github.com/ory/kratos/commit/ad1276f2b2cf3cbbecba4dee1d6d433999286946)) - Fixed typos/readability ([#620](https://github.com/ory/kratos/issues/620)) ([7fd3ce0](https://github.com/ory/kratos/commit/7fd3ce0d8c52346ba3504ce5777321937baf8d1e)): Fixed a few typos, and moved some sentences around to improve readability. - Fixed typos/readability ([#621](https://github.com/ory/kratos/issues/621)) ([c4fc75f](https://github.com/ory/kratos/commit/c4fc75f7dca59fa8f31d068f57179f49bf798b6a)) - Import mermaid ([#696](https://github.com/ory/kratos/issues/696)) ([6f75004](https://github.com/ory/kratos/commit/6f750047d41add6bd2d30adb1c654181c9636d2d)) - Improve charts and examples in self-service overview ([312c91d](https://github.com/ory/kratos/commit/312c91de3ae3c086f836ec3928735d787ad40dde)) - Improve documentation and add tests ([3dde956](https://github.com/ory/kratos/commit/3dde956e09d1f3f6411046b12f8684d8760f9b91)) - Improve long messages and render cli documentation ([e5fc02f](https://github.com/ory/kratos/commit/e5fc02ff22836e074a1dfca043d4b4b8ad64c747)) - Make assumptions neutral in concepts overview ([e89d980](https://github.com/ory/kratos/commit/e89d98099bd3fc5c8361f9015e44668494211152)) - Move development section ([2e6f643](https://github.com/ory/kratos/commit/2e6f6430f88105efd5618482043809c6d643216b)) - Move hooks ([c02b588](https://github.com/ory/kratos/commit/c02b58867ee2c0a386b2b741375ec8cd76122461)) - Move to json sidebar ([504af3b](https://github.com/ory/kratos/commit/504af3b89d728eb11bf42f4a2037c78b3b7cb788)) - Password login and registration methods for API clients ([5a44356](https://github.com/ory/kratos/commit/5a4435643ae3463df85458f22f87730c11af10ab)) - Prettify all files ([#743](https://github.com/ory/kratos/issues/743)) ([d9d1bfd](https://github.com/ory/kratos/commit/d9d1bfdff70ad835629a2dba00579925fcb3094d)) - Quickstart next steps ([#676](https://github.com/ory/kratos/issues/676)) ([ee9dd0d](https://github.com/ory/kratos/commit/ee9dd0d58a4146a0e131f6a7b74943bb39d26c0b)): Added a section outlining some easy config changes, that users can apply to the quickstart to test out different scenarios and configurations. - Refactor login and registration documentation ([c660a04](https://github.com/ory/kratos/commit/c660a04ed6a70aefca18896662331fcc5d1919cf)) - Refactor settings and recovery documentation ([11ca9f7](https://github.com/ory/kratos/commit/11ca9f7d1b858dcda3a96e1e1d2607ba64f7fbbe)) - Refactor verification docs ([70f2789](https://github.com/ory/kratos/commit/70f2789363773fccc4bd8691597ff588ac6892c6)) - Regenerate clidocs with up-to-date binary ([e53289c](https://github.com/ory/kratos/commit/e53289c8e9f34a02ec66ec7ee03e2269a4a13c42)) - Remove `make tools` task ([ec6e664](https://github.com/ory/kratos/commit/ec6e6641234191d4eb39e1ad17bc7fcc03c2a0b5)), closes [#711](https://github.com/ory/kratos/issues/711) [#750](https://github.com/ory/kratos/issues/750): This task does not exist any more and the dependency building is much smarter now. - Remove contraction ([#747](https://github.com/ory/kratos/issues/747)) ([cd4f21d](https://github.com/ory/kratos/commit/cd4f21dbfa2b3824468146677f542fbab2417c42)) - Remove duplicate word ([b84e659](https://github.com/ory/kratos/commit/b84e659af29aa1b129f33ccf5ca9e0d54353c019)) - Remove duplicate word ([#700](https://github.com/ory/kratos/issues/700)) ([a12100e](https://github.com/ory/kratos/commit/a12100e7644b535c4bd3073e03c48229bb81e7b2)) - Remove react native guide for now ([daa5f2e](https://github.com/ory/kratos/commit/daa5f2e3de3fe8380a91f594e034afcadc6e6ba5)) - Rename self service and add admin section ([639c424](https://github.com/ory/kratos/commit/639c424d3bde0557f7edd7edc489a476f1aa60b3)) - Replace ampersand ([#749](https://github.com/ory/kratos/issues/749)) ([8337b80](https://github.com/ory/kratos/commit/8337b80a13e8cf0cb2848241c93bb151420ac6a4)) - Resolve regression issues ([0470fd7](https://github.com/ory/kratos/commit/0470fd734fb30170033e10758d99cf5711c80eb1)) - Resolve typo in message IDs ([562cfc4](https://github.com/ory/kratos/commit/562cfc4392ba1c9c1fb8854ea0ac85bd44d0fac9)) - Resolve typo in message IDs ([#607](https://github.com/ory/kratos/issues/607)) ([f7688f0](https://github.com/ory/kratos/commit/f7688f0ab07b579a375ce4cc25361b360e82dd88)) - Update cli docs ([085efca](https://github.com/ory/kratos/commit/085efcae895b3aa3c76c819dca0f080ea79d57cd)) - Update link to mfa issue ([d03a706](https://github.com/ory/kratos/commit/d03a706307be21b83d18601223fb0d1430459a29)) - Update links ([a06fd88](https://github.com/ory/kratos/commit/a06fd88b0dcb747808ffea450bf1ac74dd941769)) - Update MFA link to issue ([#690](https://github.com/ory/kratos/issues/690)) ([7a744ad](https://github.com/ory/kratos/commit/7a744ad7b62540dd5789aee8532c1f97ddcab32d)): MFA issue was pushed to a later milestone. Update the documentation to point to the issue instead of the milestone. - Update repository templates ([f422485](https://github.com/ory/kratos/commit/f4224852ceeb054405251b21895efa493e1abc9c)) - Update repository templates ([#678](https://github.com/ory/kratos/issues/678)) ([bdb6875](https://github.com/ory/kratos/commit/bdb6875e55aed454cda061969e1dd4f712e09bb5)) - Update sidebar ([ea15c20](https://github.com/ory/kratos/commit/ea15c2093fc66e4cfc0a66aabf7dfad6965777dc)) - Update ts examples ([65cb46e](https://github.com/ory/kratos/commit/65cb46e57595b920bd6544f9a9a4f7b886462be0)) - Use correct id for multi-domain-cookies ([b49288a](https://github.com/ory/kratos/commit/b49288a351647c91a3c7d4a62537146d4a9f1bd0)) - Use correct path in 0.4 docs ([9fcaac4](https://github.com/ory/kratos/commit/9fcaac4048e05500d0456eb3cd9cd11cc123e370)), closes [#588](https://github.com/ory/kratos/issues/588) - Use NYT Capitalization for all Swagger headlines ([#675](https://github.com/ory/kratos/issues/675)) ([6c96429](https://github.com/ory/kratos/commit/6c9642959dab8cf042ad227711609d5726328394)), closes [#664](https://github.com/ory/kratos/issues/664) ### Features - Add ability to configure session cookie domain/path ([faeb332](https://github.com/ory/kratos/commit/faeb3328dab343c6ef3974065ba0c5c590a8817e)), closes [#516](https://github.com/ory/kratos/issues/516) - Add and improve settings testhelpers ([10a43fc](https://github.com/ory/kratos/commit/10a43fc518bd5c764712b549e6d35bf7159d757a)) - Add bearer helper ([ec6ca20](https://github.com/ory/kratos/commit/ec6ca20279d839dc10e7e3bc80e0442a630e586b)) - Add config version schema ([#608](https://github.com/ory/kratos/issues/608)) ([d218662](https://github.com/ory/kratos/commit/d218662388ef4fb7ea3bfee7b29c5cc8d34f1c8c)), closes [#590](https://github.com/ory/kratos/issues/590) - Add discord oidc provider ([#767](https://github.com/ory/kratos/issues/767)) ([487296d](https://github.com/ory/kratos/commit/487296dd39d2e59d61b63f00f3d61fea9b8aed8c)) - Add enum to form field type ([96028d8](https://github.com/ory/kratos/commit/96028d8c80414cdcea177150ba6e986d0ecb29c6)) - Add flow type to login ([ce9133b](https://github.com/ory/kratos/commit/ce9133b0ff6d03738a5d27cf9c6a213496d75772)) - Add HTTP request flow validator ([1a6e847](https://github.com/ory/kratos/commit/1a6e84774b65ee7be9294baaaff77192cec8f0f2)) - Add new prometheus metrics endpoint [#672](https://github.com/ory/kratos/issues/672) ([#673](https://github.com/ory/kratos/issues/673)) ([0f5c436](https://github.com/ory/kratos/commit/0f5c436ce6e4aa78ca52ae63e58812e6703a1ab7)): Adds endpoint `/metrics` for prometheus metrics collection to the Admin API Endpoint. - Add nocache helpers ([54dcc4d](https://github.com/ory/kratos/commit/54dcc4da2ff22bdb17e53dd6eac1c0bd54a20390)) - Add pagination tests ([e3aa81b](https://github.com/ory/kratos/commit/e3aa81b7da55108f43ea6e16c817c97e2f8a1d50)) - Add session token security definition ([d36c26f](https://github.com/ory/kratos/commit/d36c26f2edd66ddbd8338de4901957a9b9b7342e)): Adds the new Session Token as a Swagger security definition to allow setting the session token as a Bearer token when calling `/sessions/whoami`. - Add stub errors to errorx ([5d452bb](https://github.com/ory/kratos/commit/5d452bb582e6a9e3b893424ec135d0cbdf875659)), closes [#610](https://github.com/ory/kratos/issues/610) - Add test helper for fetching settings requests ([3646383](https://github.com/ory/kratos/commit/36463838d81d8b108aa9ded8c1ec6bc8f48f2267)) - Add tests and helpers to test recovery/verifiable addresses ([#579](https://github.com/ory/kratos/issues/579)) ([29979e6](https://github.com/ory/kratos/commit/29979e6c4934b71c7fb158cfa5b85e97be3ea8fc)), closes [#576](https://github.com/ory/kratos/issues/576) - Add tests to cover auth ([c9d3a15](https://github.com/ory/kratos/commit/c9d3a1525cc74976d16b483e0ab5c48909b84022)) - Add texts for settings ([795548c](https://github.com/ory/kratos/commit/795548c25507c34c7fc37ce1c1a8ecc076c34ef4)) - Add the already declared (and settable) tracer as a middleware ([#614](https://github.com/ory/kratos/issues/614)) ([e24fffe](https://github.com/ory/kratos/commit/e24fffe3f13c353e3c07214c1e056a849533a9f6)) - Add token to session ([08c8c78](https://github.com/ory/kratos/commit/08c8c7837dbf799e6ba01d1820812c9e792d7850)) - Add type to all flows in SQL ([5515776](https://github.com/ory/kratos/commit/551577659f6a416ff6ef032c35af224b517df413)) - Allow import/validation of arrays ([d11ac32](https://github.com/ory/kratos/commit/d11ac32db6ddc0dce73067ffe7d4d0a734a3f991)) - Bump cli and migration render tasks ([6dcb42a](https://github.com/ory/kratos/commit/6dcb42a487476371a545b72f7ee7e820b815bbee)) - Finalize tests for registration flow refactor ([8e52c3a](https://github.com/ory/kratos/commit/8e52c3a99bd39b3429ff476340b5df49e0a85707)) - Finish off client cli ([36d60c7](https://github.com/ory/kratos/commit/36d60c7e7bc38d83726b4b4a3061ba6353dd1978)) - Implement administrative account recovery ([f5f9c43](https://github.com/ory/kratos/commit/f5f9c43e10dd3a9547e87776164d2d4a171f35ce)) - Implement API flow for recovery link method ([d65bf66](https://github.com/ory/kratos/commit/d65bf66781bdd2fae73e75c0ba39287b1575c45a)) - Implement API-based tests for password method settings flows ([60664aa](https://github.com/ory/kratos/commit/60664aaf05dbd6b228f420688d0171e5789246be)) - Implement max-age for session cookie ([2e642ff](https://github.com/ory/kratos/commit/2e642ff13c59a7e23babe9209c1a114ef0163bad)), closes [#326](https://github.com/ory/kratos/issues/326) - Implement tests and anti-csrf for API settings flows ([8b8b6e5](https://github.com/ory/kratos/commit/8b8b6e5367e05f49950b851ea6834a9f18e896e7)) - Implement tests for new migrations ([e08ece9](https://github.com/ory/kratos/commit/e08ece9bb1c8c52580c15cf9152b4203821a0a0e)) - Improve test readability for password method ([a896d9b](https://github.com/ory/kratos/commit/a896d9b55596d2925941a6b6a91b8a6e4ef2caa1)) - Log successful hook execution ([f6026cf](https://github.com/ory/kratos/commit/f6026cfb0418767d99d18cd50529c2b71b21d775)) - Log successful hook execution ([1e7d044](https://github.com/ory/kratos/commit/1e7d044603b204632d2ec73c2e54db896992300b)) - Make login error handle JSON aware ([88f581f](https://github.com/ory/kratos/commit/88f581ff40a183cb96b5fb6d1ba398c58a9792d1)) - Make password settings method API-able ([0cf6027](https://github.com/ory/kratos/commit/0cf60274f87f098d5eb57531f5071cd407b65f4d)) - Make public cors configurable ([863a0d4](https://github.com/ory/kratos/commit/863a0d4f4696b05209b16f2e0c3daa9e8f4c1945)), closes [#712](https://github.com/ory/kratos/issues/712) - Oidc provider claims config option ([#753](https://github.com/ory/kratos/issues/753)) ([bf94a40](https://github.com/ory/kratos/commit/bf94a40acd52128303c0b878ddb92d56abc4ceaf)), closes [#735](https://github.com/ory/kratos/issues/735) - Reply with cache-control: 0 for browser-facing APIs ([1a45b53](https://github.com/ory/kratos/commit/1a45b5341e0ab4580208bfb6a505859d1e5d2faf)), closes [#360](https://github.com/ory/kratos/issues/360) - Schemas are now static assets ([1776d58](https://github.com/ory/kratos/commit/1776d58278c42094b2c703e269a5901a96617051)) - Support and document api flow in session issuer hook ([91f3cc7](https://github.com/ory/kratos/commit/91f3cc7a559b1ea1279216f8dc81abd8e6f73776)) - Support application/json in registration ([3476b97](https://github.com/ory/kratos/commit/3476b978fdaee90358cc5505e20a0526f812a460)), closes [#44](https://github.com/ory/kratos/issues/44) - Support custom session token header ([56bec76](https://github.com/ory/kratos/commit/56bec760fd1b94428ba296395a11358664d9e830)): The `/sessions/whoami` endpoint now accepts the ORY Kratos Session Token in the `X-Session-Token` HTTP header. - Support GitLab OIDC Provider ([#519](https://github.com/ory/kratos/issues/519)) ([8580d96](https://github.com/ory/kratos/commit/8580d96b7e345cc85a646f2945c3931f831afebf)), closes [#518](https://github.com/ory/kratos/issues/518) - Support json payloads for login and password ([354e8b2](https://github.com/ory/kratos/commit/354e8b2cd63ee8feb1fd8a4ed8b033490155d90c)) - Support JSON payloads in password login flow ([dd32c23](https://github.com/ory/kratos/commit/dd32c23121da42e7eb3294fc8cb940fb7982723b)) - Support session token bearer auth and lifecycle ([c12600a](https://github.com/ory/kratos/commit/c12600a7243b541a91631169ec09d618a45c72dc)): This patch adds support for issuing, validating, and revoking session tokens. Session tokens carry a reference to a session, and are equal to session cookies but can be used on environments which do not support cookies (e.g. React Native) by sending them in the Bearer Authorization. - Update migration tests ([fb28173](https://github.com/ory/kratos/commit/fb28173afa46ee828a3090981f394043c075f1ec)) - Use uri-reference for ui_url etc. to allow relative urls ([#617](https://github.com/ory/kratos/issues/617)) ([2dba450](https://github.com/ory/kratos/commit/2dba4503266436a615f4c1c18e07aa36ec713498)) - Write request -> flow rename migrations ([d7189a9](https://github.com/ory/kratos/commit/d7189a99c9d3e0ce33b4cc9846e6b2530ddfe5ec)) ### Tests - Add handler update tests ([aea1fb8](https://github.com/ory/kratos/commit/aea1fb807a16acd8406b94a72c3b39be8c3e1280)), closes [#325](https://github.com/ory/kratos/issues/325) - Add init browser flow tests ([f477ece](https://github.com/ory/kratos/commit/f477ecebc73741b638cd62ef8aa2adb8b7adb8f2)) - Add test for no-cache on public router ([b8aa63b](https://github.com/ory/kratos/commit/b8aa63b7ebd269a87578e8a5c6b2df27e18f9efa)) - Add test for registration request ([79ed63c](https://github.com/ory/kratos/commit/79ed63cb4536499712796dab52999bcb73fe8466)) - Add tests for registration flows ([4772f71](https://github.com/ory/kratos/commit/4772f710f66d1ee36b52eca120d617a354f72413)) - Complete test suite for API-based auth ([fb9d62f](https://github.com/ory/kratos/commit/fb9d62f658165aa80bd117e1f827bbcc7c635150)) - Implement API login password tests ([8bfd5f2](https://github.com/ory/kratos/commit/8bfd5f294ff03280bcf01c5066acefe767eabc73)) - Implement API registration password tests ([db178b7](https://github.com/ory/kratos/commit/db178b73b097820c8dcd8760eec041a6fd0740aa)) - Replace e2e-memory with unit test ([52bd839](https://github.com/ory/kratos/commit/52bd839ea9fe8de1aac4663b9dc0a88ae18a5765)), closes [#580](https://github.com/ory/kratos/issues/580) - Resolve broken decoder tests ([07add1b](https://github.com/ory/kratos/commit/07add1b3e4f46e4aff52174ce43d6970f60cf3ee)) - Use correct hook in test ([421320c](https://github.com/ory/kratos/commit/421320ca4ad5b346c6dfb6ef0a9d14d7cf23fded)) ### Unclassified - u ([e207a6a](https://github.com/ory/kratos/commit/e207a6adb98f639413accce383633d7e74ca4db9)) - As part of this change, fetching a settings flow over the public API no longer requires Anti-CSRF cookies to be sent. ([31d560e](https://github.com/ory/kratos/commit/31d560e47d55b087519355081cbca20b2a49da4e)), closes [#635](https://github.com/ory/kratos/issues/635) - Create labels.json ([68b1f6f](https://github.com/ory/kratos/commit/68b1f6f5a35c66cc71f74f1473796fa16a852366)) - Add codedoc to identifier hint block ([6fe840f](https://github.com/ory/kratos/commit/6fe840f9c7a27ed97593e01936913e2239fd9446)) - Format ([e61a51d](https://github.com/ory/kratos/commit/e61a51dd6e2d5e003165a0b7906a9c86ebbc87d9)) - Format ([1e5b738](https://github.com/ory/kratos/commit/1e5b738f0765ec110c3ee70d7fc90fad0d1c89ac)) - Format code ([c3b5ff5](https://github.com/ory/kratos/commit/c3b5ff5d3bc3a1e72f48498fbed60bae9f159617)) # [0.4.6-alpha.1](https://github.com/ory/kratos/compare/v0.4.5-alpha.1...v0.4.6-alpha.1) (2020-07-13) Resolves build and install issues and includes a few bugfixes. ### Bug Fixes - Use proper binary name in dockerfile ([d36bbb0](https://github.com/ory/kratos/commit/d36bbb0875177ccd68747f4a17e59c981a7a6464)) ### Code Generation - Pin v0.4.6-alpha.1 release commit ([ad90e77](https://github.com/ory/kratos/commit/ad90e772cf59a33b213bc0fb782959a1685d9741)): Bumps from v0.4.4-alpha.1 # [0.4.5-alpha.1](https://github.com/ory/kratos/compare/v0.4.4-alpha.1...v0.4.5-alpha.1) (2020-07-13) Resolves build and install issues and includes a few bugfixes. ### Bug Fixes - Ensure default_browser_return_url for flows is configured in after ([#570](https://github.com/ory/kratos/issues/570)) ([cf9753c](https://github.com/ory/kratos/commit/cf9753c690c67e6401be52d2c1ce69f168aae6e8)), closes [#569](https://github.com/ory/kratos/issues/569) - Require selfservice.default_browser_return_url to be set in config ([#571](https://github.com/ory/kratos/issues/571)) ([af2af7d](https://github.com/ory/kratos/commit/af2af7d35ba8b10dcd6d7636b044b0f7761a719d)) ### Code Generation - Pin v0.4.5-alpha.1 release commit ([3ea7fd3](https://github.com/ory/kratos/commit/3ea7fd3e7fd2c0b4aef638aa30e2b5b05c1bad26)): Bumps from v0.4.4-alpha.1 # [0.4.4-alpha.1](https://github.com/ory/kratos/compare/v0.4.3-alpha.1...v0.4.4-alpha.1) (2020-07-10) The purpose of this release is to resolve issues with install scripts, homebrew, and scoop. ### Bug Fixes - Detection of SQLite memory mode ([#564](https://github.com/ory/kratos/issues/564)) ([605cd57](https://github.com/ory/kratos/commit/605cd579895f3b765d398074cfdb37fa3eae0c4e)) - Improve goreleaser config ([0f8a0d8](https://github.com/ory/kratos/commit/0f8a0d8afa6489383800d3eff1b7b1da01fbef08)) ### Code Generation - Pin v0.4.4-alpha.1 release commit ([154d543](https://github.com/ory/kratos/commit/154d543eef29ab67be8637a96d8d06620974094f)) ### Documentation - Add description for subkeys of serve ([#562](https://github.com/ory/kratos/issues/562)) ([deae005](https://github.com/ory/kratos/commit/deae005a259747872f678d355b49cca21904e565)) - Add section about password expiry ([19c2414](https://github.com/ory/kratos/commit/19c2414c3defe79fe6e80e50dd0e85026ecd60e6)) - Specify the use of secrets ([#565](https://github.com/ory/kratos/issues/565)) ([7680450](https://github.com/ory/kratos/commit/7680450cfa44049759b27ec09d5bebc236b19a29)) - Update upgrade guide ([a40b1ec](https://github.com/ory/kratos/commit/a40b1ec18e7801f2862aad4e37becb7ce8f99c37)) # [0.4.3-alpha.1](https://github.com/ory/kratos/compare/v0.4.2-alpha.1...v0.4.3-alpha.1) (2020-07-08) We are very happy to announce the 0.4 release of ORY Kratos with 163 commits and 817 changed files with 52,681 additions and 9,876 deletions. There have been many improvements and bugfixes merged. The biggest changes are: 1. Account recovery ("reset password") has been implemented. 2. Documentation has been improved with easier to understand examples - currently only for account recovery so let us know what you think! 3. The configuration has been simplified a lot. It is now much easier to enable account recovery and email verification. This is a breaking change - please read the breaking changes section with care! 4. The Identity Traits JSON Schema has been renamed to the Identity JSON Schema. This is a breaking change - please read the breaking changes section with care! 5. `prompt=login` has been renamed to `refresh=true`. This is a breaking change - please read the breaking changes section with care! 6. We have reworked how (error) messages are returned. They now include an ID and all the parameters required for translating and customizing UI messages. This is a breaking change - please read the breaking changes section with care! 7. Instead of keeping track of `update_successful` with booleans, flows (e.g. the settings flow) that have more than one state now include a state machine. This is a breaking change - please read the breaking changes section with care! 8. Tons of tests have been added. 9. We have reworked and fully tested the migration pipeline to prevent breaking schema changes in future versions. 10. ORY Kratos now supports login with Azure AD and the Microsoft Identity Platform. Before upgrading, please make a backup of your database and read the section "Breaking Changes" with care! ### Bug Fixes - Resolve goreleaser build issues ([223571b](https://github.com/ory/kratos/commit/223571bca15f507067d20bedb104923331f88e59)) - Update install.sh script ([883d99b](https://github.com/ory/kratos/commit/883d99ba42de084018a32eaa094b5ae1a8ad4fc2)) ### Code Generation - Pin v0.4.3-alpha.1 release commit ([a3a34b1](https://github.com/ory/kratos/commit/a3a34b1e43b2d010ed85e098cd7cea31127df311)): Bumps from v0.4.0-alpha.1 # [0.4.2-alpha.1](https://github.com/ory/kratos/compare/v0.4.0-alpha.1...v0.4.2-alpha.1) (2020-07-08) We are very happy to announce the 0.4 release of ORY Kratos with 153 commits and 760 changed files with 36,223 additions and 9,754 deletions. There have been many improvements and bugfixes merged. The biggest changes are: 1. Account recovery ("reset password") has been implemented. 2. Documentation has been improved with easier to understand examples - currently only for account recovery so let us know what you think! 3. The configuration has been simplified a lot. It is now much easier to enable account recovery and email verification. This is a breaking change - please read the breaking changes section with care! 4. The Identity Traits JSON Schema has been renamed to the Identity JSON Schema. This is a breaking change - please read the breaking changes section with care! 5. `prompt=login` has been renamed to `refresh=true`. This is a breaking change - please read the breaking changes section with care! 6. We have reworked how (error) messages are returned. They now include an ID and all the parameters required for translating and customizing UI messages. This is a breaking change - please read the breaking changes section with care! 7. Instead of keeping track of `update_successful` with booleans, flows (e.g. the settings flow) that have more than one state now include a state machine. This is a breaking change - please read the breaking changes section with care! 8. Tons of tests have been added. 9. We have reworked and fully tested the migration pipeline to prevent breaking schema changes in future versions. 10. ORY Kratos now supports login with Azure AD and the Microsoft Identity Platform. Before upgrading, please make a backup of your database and read the section "Breaking Changes" with care! ### Bug Fixes - Ignore pkged generated files ([1d385e4](https://github.com/ory/kratos/commit/1d385e4d1a004405099242c3003006d1713a24c6)) ### Code Generation - Pin v0.4.2-alpha.1 release commit ([20024cb](https://github.com/ory/kratos/commit/20024cbbb44b4f556004ef752a7f37e70a070e6a)): Bumps from v0.4.0-alpha.1 # [0.4.0-alpha.1](https://github.com/ory/kratos/compare/v0.3.0-alpha.1...v0.4.0-alpha.1) (2020-07-08) We are very happy to announce the 0.4 release of ORY Kratos with 153 commits and 760 changed files with 36,223 additions and 9,754 deletions. There have been many improvements and bugfixes merged. The biggest changes are: 1. Account recovery ("reset password") has been implemented. 2. Documentation has been improved with easier to understand examples - currently only for account recovery so let us know what you think! 3. The configuration has been simplified a lot. It is now much easier to enable account recovery and email verification. This is a breaking change - please read the breaking changes section with care! 4. The Identity Traits JSON Schema has been renamed to the Identity JSON Schema. This is a breaking change - please read the breaking changes section with care! 5. `prompt=login` has been renamed to `refresh=true`. This is a breaking change - please read the breaking changes section with care! 6. We have reworked how (error) messages are returned. They now include an ID and all the parameters required for translating and customizing UI messages. This is a breaking change - please read the breaking changes section with care! 7. Instead of keeping track of `update_successful` with booleans, flows (e.g. the settings flow) that have more than one state now include a state machine. This is a breaking change - please read the breaking changes section with care! 8. Tons of tests have been added. 9. We have reworked and fully tested the migration pipeline to prevent breaking schema changes in future versions. 10. ORY Kratos now supports login with Azure AD and the Microsoft Identity Platform. Before upgrading, please make a backup of your database and read the section "Breaking Changes" with care! This release requires running SQL migrations when upgrading! ## Breaking Changes This patch renames the Identity Traits JSON Schema to Identity JSON Schema. The identity payload has changed from ``` { - "traits_schema_url": "...", - "traits_schema_id": "...", + "schema_url": "...", + "schema_id": "...", } ``` Additionally, it is now expected that your Identity JSON Schema includes a "traits" key at the root level. **Before (example)** ``` { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } }, "required": [ "email" ], "additionalProperties": false } ``` **After (example)** ``` { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } }, "required": [ "email" ], "additionalProperties": false } } } ``` You also need to remove the `traits` key from your ORY Kratos config like this: ``` identity: - traits: - default_schema_url: http://test.kratos.ory.sh/default-identity.schema.json - schemas: - - id: other - url: http://test.kratos.ory.sh/other-identity.schema.json + default_schema_url: http://test.kratos.ory.sh/default-identity.schema.json + schemas: + - id: other + url: http://test.kratos.ory.sh/other-identity.schema.json ``` Do not forget to also update environment variables for the Identity JSON Schema as well if set. To address these refactorings, the configuration had to be changed and with breaking changes as keys have moved or have been removed. Hook configuration has also changed. It is no longer required to include hooks such as `verification` to get verification working. Instead, verification is enabled globally (`selfservice.flows.verification.enabled`). Also, the `redirect` hook has been removed as it lead to confusion because there are already default redirect URLs configurable. You will find more information in the details below. **Session Management** ```diff -ttl: - session: 1h -security: - session: - cookie: - same_site: Lax +session: + lifespan: 1h + cookie_same_site: Lax ``` **Secrets** ```diff -secrets: - session: - - secret-to-encrypt-session-cookies - - old-session-cookie-secret-that-has-been-rotated +secrets: + default: + # This secret is used as default and will also be used for encrypting e.g. cookies when a dedicated cookie secret (as shown below) is not defined. + - default-secret-to-encrypt-stuff + cookie: + - secret-to-encrypt-session-cookies + - old-session-cookie-secret-that-has-been-rotated ``` **URLs** The Base URL configuration has moved to `serve.public` and `serve.admin`. They are also no longer required and fall back to defaults based on the machine's hostname, port configuration, and other settings: ```diff -urls: - self: - public: https://kratos.my-website.com/ - admin: https://admin.kratos.cluster.localnet/ +serve: + public: + base_url: https://kratos.my-website.com/ + admin: + base_url: https://admin.kratos.cluster.localnet/ ``` The UI URLs have moved from `urls` to their respective self-service flows: ```diff -urls: - login_ui: http://127.0.0.1:4455/auth/login - registration_ui: http://127.0.0.1:4455/auth/registration - settings_ui: http://127.0.0.1:4455/settings - verify_ui: http://127.0.0.1:4455/verify - error_ui: http://127.0.0.1:4455/error +selfservice: + flows: + login: + ui_url: http://127.0.0.1:4455/auth/login + registration: + ui_url: http://127.0.0.1:4455/auth/registration + settings: + ui_url: http://127.0.0.1:4455/settings + # please note that `verify` has changed to `verification`! + verification: + ui_url: http://127.0.0.1:4455/verify + error: + ui_url: http://127.0.0.1:4455/error ``` The default redirect URL as well as whitelisted redirect URLs have also changed their location: ```diff -urls: - default_return_to: https://self-service/dashboard - whitelisted_return_to_urls: - - https://self-service/some-other-url - - https://example.org/another-url +selfservice: + default_browser_return_url: https://self-service/dashboard # Please note that the `to` has been removed (`whitelisted_return_to_urls` -> `whitelisted_return_urls`) + whitelisted_return_urls: + - https://self-service/some-other-url + - https://example.org/another-url ``` **Self-Service Login** `selfservice.login` has moved to `selfservice.flow.login`: ```diff selfservice: - login: + flows: + login: ``` On top of this change, a few keys under `login` have changed as well: ```diff selfservice flows: login: + ui_url: http://127.0.0.1:4455/auth/login request_lifespan: 99m - before: - hooks: - - hook: redirect - config: - default_redirect_url: http://test.kratos.ory.sh:4000/ - allow_user_defined_redirect: false + # The before hooks have been removed because there were no good use cases for them. If + # this is a problem for you feel free to open an issue! after: - default_return_to: https://self-service/login/return_to + default_browser_return_url: https://self-service/login/return_to password: - default_return_to: https://self-service/login/password/return_to + default_browser_return_url: https://self-service/login/password/return_to hooks: - hook: revoke_active_sessions oidc: - default_return_to: https://self-service/login/podc/return_to + default_browser_return_url: https://self-service/login/podc/return_to hooks: - hook: revoke_active_sessions ``` **Self-Service Registration** `selfservice.registration` has moved from to `selfservice.flow.registration`: ```diff selfservice: - registration: + flows: + registration: ``` On top of this change, a few keys under `registration` have changed as well: ```diff selfservice flows: registration: + ui_url: http://127.0.0.1:4455/auth/registration request_lifespan: 99m - before: - hooks: - - hook: redirect - config: - default_redirect_url: http://test.kratos.ory.sh:4000/ - allow_user_defined_redirect: false + # The before hooks have been removed because there were no good use cases for them. If + # this is a problem for you feel free to open an issue! after: - default_return_to: https://self-service/registration/return_to + default_browser_return_url: https://self-service/registration/return_to password: - default_return_to: https://self-service/registration/password/return_to + default_browser_return_url: https://self-service/registration/password/return_to hooks: - hook: revoke_active_sessions + # The verify hook is now executed automatically when verification is turned on. - - hook: verify + # The redirect hook was confusing as it aborts the registration flow and does not solve redirection on + # success. It has thus been removed. - - hook: redirect oidc: - default_return_to: https://self-service/registration/podc/return_to + default_browser_return_url: https://self-service/registration/podc/return_to hooks: - hook: revoke_active_sessions + # The verify hook is now executed automatically when verification is turned on. - - hook: verify + # The redirect hook was confusing as it aborts the registration flow and does not solve redirection on + # success. It has thus been removed. - - hook: redirect ``` **Self-Service Settings** `selfservice.settings` has moved from to `selfservice.flow.settings`: ```diff selfservice: - settings: + flows: + settings: ``` On top of this change, a few keys under `settings` have changed as well: ```diff selfservice flows: settings: + ui_url: http://127.0.0.1:4455/settings request_lifespan: 99m privileged_session_max_age: 99m - default_return_to: https://self-service/settings/return_to after: + default_browser_return_url: https://self-service/settings/return_to + # The profile/password after hooks have been removed as verification is now executed automatically + # when turned on. - password: - hooks: - - hook: verify - profile: - hooks: - - hook: verify ``` **Self-Service Verification** `selfservice.verify` has moved from to `selfservice.flow.verification`: ```diff selfservice: - verify: + flows: + verification: ``` Instead of configuring verification with hooks and other components, it can now be enabled in a central place. If enabled, a SMTP server must be configured in the `courier` section. You are still required to mark a field as verifiable in your Identity JSON Schema. ```diff selfservice: flows: verification: + enabled: true # defaults to true + ui_url: http://127.0.0.1:4455/recovery request_lifespan: 1m - default_return_to: https://self-service/verification/return_to after: + default_browser_return_url: https://self-service/verification/return_to ``` Replaces the `update_successful` field of the settings request with a field called `state` which can be either `show_form` or `success`. Flows, request methods, form fields have had a key errors to show e.g. validation errors such as ("not an email address", "incorrect username/password", and so on. The `errors` key is now called `messages`. Each message now has a `type` which can be `error` or `info`, an `id` which can be used to translate messages, a `text` (which was previously errors[*].message). This affects all login, request, settings, and recovery flows and methods. To refresh a login session it is now required to append `refresh=true` instead of `prompt=login` as the second has implications for revoking an existing issue and might be confusing when used in combination with OpenID Connect. - Applying this patch requires running SQL Migrations. - The field `identity.addresses` has moved to `identity.verifiable_addresses`. - Configuration key `selfservice.verification.link_lifespan` has been merged with `selfservice.verification.request_lifespan`. ### Bug Fixes - Account recovery can't use recovery token ([#526](https://github.com/ory/kratos/issues/526)) ([379f24e](https://github.com/ory/kratos/commit/379f24e96e50a3e5c71b53a11195bdd84a8dc957)), closes [#525](https://github.com/ory/kratos/issues/525) - Add and document recovery to quickstart ([c229c54](https://github.com/ory/kratos/commit/c229c54603bdc3efb863fd76b64096ae599d1aac)) - Add pkger to docker builds ([d3ef5a0](https://github.com/ory/kratos/commit/d3ef5a0fe90f430999d0d94cb2f55acc8d628212)) - Allow linking oidc credentials without existing oidc connection ([#548](https://github.com/ory/kratos/issues/548)) ([39c1234](https://github.com/ory/kratos/commit/39c1234f8ff3f6c7b0923053c8a317677d6cb667)), closes [#532](https://github.com/ory/kratos/issues/532) - Bump pop version ([#558](https://github.com/ory/kratos/issues/558)) ([9e46cea](https://github.com/ory/kratos/commit/9e46ceabec8d5c1995321b62cbba9ac3900de446)), closes [#556](https://github.com/ory/kratos/issues/556) - Clear error messages after updating settings successfully ([#421](https://github.com/ory/kratos/issues/421)) ([7eec388](https://github.com/ory/kratos/commit/7eec38829449237cffe345d8bec67578764559be)), closes [#420](https://github.com/ory/kratos/issues/420) - Do not send debug on session/whoami ([16d3670](https://github.com/ory/kratos/commit/16d3670070bf46170c4540203e8380ad81bfb4c3)), closes [#483](https://github.com/ory/kratos/issues/483) - Document login refresh parameter in swagger ([#482](https://github.com/ory/kratos/issues/482)) ([6b94993](https://github.com/ory/kratos/commit/6b949936725a6100a31851a5d879c877c2c76cbf)) - Embedded video link properly ([#514](https://github.com/ory/kratos/issues/514)) ([962bbc6](https://github.com/ory/kratos/commit/962bbc6e4af0797c190418b812f6298372dabdde)) - Embedded video link properly ([#515](https://github.com/ory/kratos/issues/515)) ([821ca93](https://github.com/ory/kratos/commit/821ca93838a360551378e336e9ce10cfe13369ec)) - Enable recovery for quickstart ([0ccc651](https://github.com/ory/kratos/commit/0ccc651f809b1e39dd6c41b88f1a10c67451eae2)) - Improve grammar of similar password error ([#471](https://github.com/ory/kratos/issues/471)) ([39873bf](https://github.com/ory/kratos/commit/39873bfad89a654fe12e101b54e9b0c2f95714ec)) - Improvements to Dockerfiles ([#552](https://github.com/ory/kratos/issues/552)) ([6023877](https://github.com/ory/kratos/commit/6023877184efeadd6ec27a050a6969b6d0dd6caa)): - expose ory home as volume to simplify passing in own config file - declare Kratos default ports in Dockerfile - Initialize verification request with correct state ([3264ecf](https://github.com/ory/kratos/commit/3264ecfbb8f7b34d9dbb22237df8d9f591ac09f3)), closes [#543](https://github.com/ory/kratos/issues/543) - Re-add all databases to persister ([#527](https://github.com/ory/kratos/issues/527)) ([b04d178](https://github.com/ory/kratos/commit/b04d17815b5a28b5fe73a6a94ce1d907a63115e1)) - Re-add redirect targets for quickstart ([3c48ad2](https://github.com/ory/kratos/commit/3c48ad26961560d6e10a627a64052e316d9ffdc7)) - Reduce docker bloat by ignoring docs and others ([ecc555b](https://github.com/ory/kratos/commit/ecc555b5ad0fa888a8d5ba39cc09094fd251e655)) - Resolve broken redirect in verify flow ([a9ca8fd](https://github.com/ory/kratos/commit/a9ca8fd793347ed8e4404a4bd29e330a3f1ef684)), closes [#436](https://github.com/ory/kratos/issues/436) - Respect multiple secrets and fix used flag ([#526](https://github.com/ory/kratos/issues/526)) ([b16c2b8](https://github.com/ory/kratos/commit/b16c2b80edfc78afca0c72fa8da7d73b51b3075a)), closes [#525](https://github.com/ory/kratos/issues/525) - Respect self-service enabled flag ([#470](https://github.com/ory/kratos/issues/470)) ([b198faf](https://github.com/ory/kratos/commit/b198fafce9d96fbb644300243e6a757242fbbd06)), closes [#417](https://github.com/ory/kratos/issues/417): Respects the `enabled` flag for self-service strategies. Also a new testhelper function was needed, to defer route registration (because whether strategies are enabled or not is determined only once: at route registration) - Typo accent -> account ([984d978](https://github.com/ory/kratos/commit/984d978cf44763d916a9329742d046e00f21577b)) - Use correct brew replacements ([fd269b1](https://github.com/ory/kratos/commit/fd269b1afa784becac7ee79cd7a6f9d2bbe39121)), closes [#423](https://github.com/ory/kratos/issues/423) - Write migration tests ([#499](https://github.com/ory/kratos/issues/499)) ([d32413a](https://github.com/ory/kratos/commit/d32413a1fcd0ce1a82d2529f18b5d4334a490a2a)), closes [#481](https://github.com/ory/kratos/issues/481) ### Code Generation - Pin v0.4.0-alpha.1 release commit ([e8690c4](https://github.com/ory/kratos/commit/e8690c4037ba5d80aa2459625be553c5bc2d2152)) ### Code Refactoring - Improve and simplify configuration ([#536](https://github.com/ory/kratos/issues/536)) ([8e7f9f5](https://github.com/ory/kratos/commit/8e7f9f5ec3ac6f5675584974e8d189247b539634)), closes [#432](https://github.com/ory/kratos/issues/432) - Move schema packing to pkger ([173f9d2](https://github.com/ory/kratos/commit/173f9d2b09d597376490b5d4588f7c0a4f525857)) - Move verify fallback to verification ([1ce6469](https://github.com/ory/kratos/commit/1ce64695ec61c3a31e00875069d2847be502744b)) - Rename identity traits schema to identity schema ([#557](https://github.com/ory/kratos/issues/557)) ([949e743](https://github.com/ory/kratos/commit/949e743ef9ddbc6e711f0174593f59f4fa3a1171)), closes [#531](https://github.com/ory/kratos/issues/531) - Rename prompt=login to refresh=true ([#478](https://github.com/ory/kratos/issues/478)) ([c04346e](https://github.com/ory/kratos/commit/c04346e0f01aa7ce5627c0b7135032b225e7faf9)), closes [#477](https://github.com/ory/kratos/issues/477) - Replace settings update_successful with state ([#488](https://github.com/ory/kratos/issues/488)) ([ca3b3f4](https://github.com/ory/kratos/commit/ca3b3f4dbdcd75ceb13c9a1b2c8dc991aba7c7e4)), closes [#449](https://github.com/ory/kratos/issues/449) - Text errors to text messages ([#476](https://github.com/ory/kratos/issues/476)) ([8106951](https://github.com/ory/kratos/commit/81069514e5ef1d851f76d44bb45d6a896d4985a6)), closes [#428](https://github.com/ory/kratos/issues/428): This patch implements a better way to deal with text messages by giving them a unique ID, a context, and a default message. ### Documentation - Add azure to next docs ([e1dd3fa](https://github.com/ory/kratos/commit/e1dd3fad30a07be6f105201a8478642e9792df46)) - Add fixme note for viper workaround ([7e3eef6](https://github.com/ory/kratos/commit/7e3eef6d36dcbb1a06ce0a20e2de0874a7dc5d38)): See https://github.com/ory/x/issues/169 - Add guide for setting up account recovery ([bbf3762](https://github.com/ory/kratos/commit/bbf37620d5b47fd18cb754c8ed43856652ee33c0)) - Add guide for setting up email verification ([1435cbc](https://github.com/ory/kratos/commit/1435cbcea5d45c9cde1a0eb7e5ebb66ce65c4b82)) - Add guide for SSO via Google ([#424](https://github.com/ory/kratos/issues/424)) ([5c45b16](https://github.com/ory/kratos/commit/5c45b1653791cc3ab5d4e4694da98da7543e816d)) - Add new guides to sidebar ([24c5cbc](https://github.com/ory/kratos/commit/24c5cbc129ad185ec02883c3451d7e573409b865)) - Added video tutorials to guides ([#513](https://github.com/ory/kratos/issues/513)) ([956731d](https://github.com/ory/kratos/commit/956731d562f33f2849197b2e692a4f20b18279f9)) - Added youtube manual ([#490](https://github.com/ory/kratos/issues/490)) ([ec232f7](https://github.com/ory/kratos/commit/ec232f72d7204b2cdf946874d51f7473a10a76a4)) - Connecting Kratos to AzureAD ([#433](https://github.com/ory/kratos/issues/433)) ([7660bcd](https://github.com/ory/kratos/commit/7660bcd2ba90d83c4ab0683a2f011e6841b2c810)) - Correct claims.email in github guide ([#422](https://github.com/ory/kratos/issues/422)) ([052a622](https://github.com/ory/kratos/commit/052a622de79d34e32ccab9c7da12a1275c7be51b)): There is no email_primary in claims, and the selfservice strategy is currently using claims.email. - Correct claims.email in github guide ([#422](https://github.com/ory/kratos/issues/422)) ([58f7e15](https://github.com/ory/kratos/commit/58f7e15093d2461d4322fe68adb0723ae244bed9)): There is no email_primary in claims, and the selfservice strategy is currently using claims.email. - Correct link in user-settings ([d13317d](https://github.com/ory/kratos/commit/d13317d9bf71db775067a7c17f4c98cdbf1cc7e5)) - Correct SDK use in quickstart ([#480](https://github.com/ory/kratos/issues/480)) ([dfdf975](https://github.com/ory/kratos/commit/dfdf9751d9333994a49537d82a15b780ebd8bc76)), closes [#430](https://github.com/ory/kratos/issues/430) - Correct stray dot ([e820f41](https://github.com/ory/kratos/commit/e820f41e63aff1a85094a9e14dfd968353ae6b1b)) - Correct user settings render form ([197e246](https://github.com/ory/kratos/commit/197e24603fc67707131e54e52e1bfb52011ca839)) - Delete old redirect homepage ([b6d9244](https://github.com/ory/kratos/commit/b6d9244b5d683f5baf27e9af5970596261a4fd20)) - Document new account recovery feature ([2252a86](https://github.com/ory/kratos/commit/2252a8676e573b9ade85814acc40b212dcfd48c1)), closes [#436](https://github.com/ory/kratos/issues/436) - Document refresh=true for login ([#479](https://github.com/ory/kratos/issues/479)) ([2ab5ead](https://github.com/ory/kratos/commit/2ab5ead77517ab5b750835195ab6673e219da71a)), closes [#464](https://github.com/ory/kratos/issues/464) - Embedded quickstart video ([#491](https://github.com/ory/kratos/issues/491)) ([ee80346](https://github.com/ory/kratos/commit/ee80346a30ebc2c7b06292e58bd3578e002e242a)) - Fix broken link ([d20816e](https://github.com/ory/kratos/commit/d20816e5335abb8bcde5c6d68b17eaabae5d01b0)) - Fix broken link ([aa9d3e6](https://github.com/ory/kratos/commit/aa9d3e6347375170a84ba53b2a9050c9544e7e2a)) - Fix broken link ([#506](https://github.com/ory/kratos/issues/506)) ([dac8dfd](https://github.com/ory/kratos/commit/dac8dfd970255f8e79e7fc7811f563e6903f6fc9)): The rest api is no longer under sdk but under reference. - Fix broken link ([#554](https://github.com/ory/kratos/issues/554)) ([e80d691](https://github.com/ory/kratos/commit/e80d691e256326aacfa89b391583e0494d8a6872)) - Fix code sample comment ([781a76b](https://github.com/ory/kratos/commit/781a76bb6de20767d6150b1fcb5236f4f376edd7)) - Fix copy paste errors in code docs ([e456a4e](https://github.com/ory/kratos/commit/e456a4e435265eade7026fd899c4bc7d2b28a5c9)) - Fix iframe syntax ([#520](https://github.com/ory/kratos/issues/520)) ([0cb36ca](https://github.com/ory/kratos/commit/0cb36ca9d8459dc8027358190e6e8aa8764bffe4)) - Fix typo ([#535](https://github.com/ory/kratos/issues/535)) ([c57d270](https://github.com/ory/kratos/commit/c57d270758a97315c874df3fae867b0031300501)) - Fix typo in base docs ([#503](https://github.com/ory/kratos/issues/503)) ([6668048](https://github.com/ory/kratos/commit/666804812d707b1d50ea160877bdb3878ddfe6b0)) - Fix typo in oauth sign in documentation ([#504](https://github.com/ory/kratos/issues/504)) ([886e24d](https://github.com/ory/kratos/commit/886e24d93a5eb233062b8c7d562c8208f7a4f48f)) - Fix typos ([81903a5](https://github.com/ory/kratos/commit/81903a5137d87588531391623b92afde70abc3ea)) - Fix typos ([#489](https://github.com/ory/kratos/issues/489)) ([57a7bc8](https://github.com/ory/kratos/commit/57a7bc89961612fea0255202d3dd6a535921ef3c)) - Fix ui url keys everywhere ([b75debb](https://github.com/ory/kratos/commit/b75debb0ee4f87dd9910b30bd76d8c6ad382fb38)) - Fix username example by renaming property and removing format ([#508](https://github.com/ory/kratos/issues/508)) ([4573426](https://github.com/ory/kratos/commit/45734260bcead3087aadcaaf3033cc1e89bc1844)) - Fix wording in settings flow graph ([e2a0084](https://github.com/ory/kratos/commit/e2a00842cb5bd3cfbddd0e5117c7f3f968e9f2df)) - Fixed broken link ([#452](https://github.com/ory/kratos/issues/452)) ([d1ddbd1](https://github.com/ory/kratos/commit/d1ddbd1ee465a7d3e29815fcfd9c75b5decbb5f9)) - Fixed broken link ([#455](https://github.com/ory/kratos/issues/455)) ([4f3d179](https://github.com/ory/kratos/commit/4f3d17906f3fa2aea3a0b0505047da6aa54938e4)) - Fixed broken link ([#456](https://github.com/ory/kratos/issues/456)) ([4b43e99](https://github.com/ory/kratos/commit/4b43e993df62d2bf54fa39624651f081eb75bbb0)) - Fixed broken link ([#460](https://github.com/ory/kratos/issues/460)) ([7da304c](https://github.com/ory/kratos/commit/7da304caf0de93442f047872cdd30d7fc316218e)) - Fixed broken link ([#461](https://github.com/ory/kratos/issues/461)) ([c248e4e](https://github.com/ory/kratos/commit/c248e4e2a48a409b53ed02644abfc27e3cebeb11)) - Fixed broken link ([#462](https://github.com/ory/kratos/issues/462)) ([ceacac3](https://github.com/ory/kratos/commit/ceacac30eda7d94cb24403c1fb988d4dd5fcd21f)) - Fixed broken links ([#451](https://github.com/ory/kratos/issues/451)) ([193a781](https://github.com/ory/kratos/commit/193a781576031818006d6e2b72418293cf94dda1)): Fixed a few broken links, .md in the url was the problem. - Fixed broken links ([#453](https://github.com/ory/kratos/issues/453)) ([59d00eb](https://github.com/ory/kratos/commit/59d00ebb87564cc9ff9c5ae12bcd7d25fb0b26c9)) - Fixed broken links ([#457](https://github.com/ory/kratos/issues/457)) ([00ec00d](https://github.com/ory/kratos/commit/00ec00d09ca5318c75832caff5e7a97d640ac083)) - Fixed broken links ([#458](https://github.com/ory/kratos/issues/458)) ([f960887](https://github.com/ory/kratos/commit/f9608876e30dbdd7c67ee70dcf5d9a1985b80f0f)) - Fixed broken links ([#459](https://github.com/ory/kratos/issues/459)) ([2749596](https://github.com/ory/kratos/commit/27495964c7cd34e9bf914b19c83157e484c9cde4)) - Fixed broken markdown ([#474](https://github.com/ory/kratos/issues/474)) ([22d5be1](https://github.com/ory/kratos/commit/22d5be16f91ed9df206310c6f04d843cd79328ca)) - Format guides ([407c70f](https://github.com/ory/kratos/commit/407c70f23d815380d98ee9252f263e07c1f0f4a9)) - Improve grammar and wording ([#448](https://github.com/ory/kratos/issues/448)) ([a19adf3](https://github.com/ory/kratos/commit/a19adf30426ff8df03a3eb725ae0101ebb6c4ab1)) - Improve grammar, clarify sections, update images ([#419](https://github.com/ory/kratos/issues/419)) ([79019d1](https://github.com/ory/kratos/commit/79019d1246b1517b3297996a207a3d2f517fab01)) - Make whitelisted_return_to_urls examples an array ([#426](https://github.com/ory/kratos/issues/426)) ([7ed5605](https://github.com/ory/kratos/commit/7ed56057f533f23ca18cab5a2614429554e877e2)), closes [#425](https://github.com/ory/kratos/issues/425) - Minor fixes ([#467](https://github.com/ory/kratos/issues/467)) ([8d15307](https://github.com/ory/kratos/commit/8d153079ee44f0765993640500bbe746dc0a34aa)) - Move security questions to own document ([2b77fba](https://github.com/ory/kratos/commit/2b77fba79b724dcd68ff0cd739cd65517aea4325)) - Properly annotate forms disabled field ([#486](https://github.com/ory/kratos/issues/486)) ([be1acb3](https://github.com/ory/kratos/commit/be1acb3d161412d18599c970364f0c91fa6ebffb)): See https://github.com/ory/kratos/pull/467#discussion_r434764266 - Remove rogue slash and fix closing tag ([#521](https://github.com/ory/kratos/issues/521)) ([3fd1076](https://github.com/ory/kratos/commit/3fd1076929eeecffb7e8aa8e906970774283daeb)) - Rename redirect page to browser-redirect-flow-completion ([ae77d48](https://github.com/ory/kratos/commit/ae77d48a3435069556382b9403cb1ad45a9d7c07)) - Replace mailhog references with mailslurper ([#509](https://github.com/ory/kratos/issues/509)) ([d0e5a0f](https://github.com/ory/kratos/commit/d0e5a0fa64e2d46437fb2abd17dc306bdec34a91)) - Run format ([2b3f299](https://github.com/ory/kratos/commit/2b3f29913be844498a02b9869789c2b2d4aaacf8)) - Typo correction in credentials.md ([#551](https://github.com/ory/kratos/issues/551)) ([3b7e104](https://github.com/ory/kratos/commit/3b7e104c2bcba52326f89761c9e3da14b4f06d08)) - Typos and stale links ([29fb466](https://github.com/ory/kratos/commit/29fb466d9881b6574ee697d7e25e45785f07114b)) - Typos and stale links ([#510](https://github.com/ory/kratos/issues/510)) ([7557ab8](https://github.com/ory/kratos/commit/7557ab85ddf8501935d70e2558682dff2024897b)) - Update repository templates ([4c89834](https://github.com/ory/kratos/commit/4c89834ce59195c5b59da5bc5b41db7ed03bf1c4)) - Use central banner repo for README ([d1e8a82](https://github.com/ory/kratos/commit/d1e8a8272cd536b6e12326778258bfbe0b7e8af7)) - Use shorthand closing tag for Mermaid ([f9f2dbc](https://github.com/ory/kratos/commit/f9f2dbc063f82a852b540013ddff81501f7c1222)) ### Features - Add support for Multitenant Azure AD as an OIDC provider ([#434](https://github.com/ory/kratos/issues/434)) ([a8f1179](https://github.com/ory/kratos/commit/a8f117985217c753cfca52905e43b640e89a6bd1)) - Add tests for defaults ([a16fc51](https://github.com/ory/kratos/commit/a16fc5121b36353cf2e684190eda976a1ea53a8f)) - Add User ID to a header when calling whoami ([#530](https://github.com/ory/kratos/issues/530)) ([183b4d0](https://github.com/ory/kratos/commit/183b4d075a9ff50c1f9f53d108a48789e49a5138)) - Implement account recovery ([#428](https://github.com/ory/kratos/issues/428)) ([e169a3e](https://github.com/ory/kratos/commit/e169a3e4079b1ef3a18564e0723baf81c44c38ec)), closes [#37](https://github.com/ory/kratos/issues/37): This patch implements the account recovery with endpoints such as "Init Account Recovery", a new config value `urls.recovery_ui` and so on. A new identity field has been added `identity.recovery_addresses` containing all recovery addresses. Additionally, some refactoring was made to DRY code and make naming consistent. As part of dependency upgrades, structured logging has also improved and an audit trail prototype has been added (currently streams to stderr only). ### Unclassified - docs:fixed broken link (#454) ([22720c6](https://github.com/ory/kratos/commit/22720c6c5e3d31acc175980223183e2336b3751d)), closes [#454](https://github.com/ory/kratos/issues/454) - Allow kratos to talk to databases in docker-compose quickstart ([#522](https://github.com/ory/kratos/issues/522)) ([8bf9a1a](https://github.com/ory/kratos/commit/8bf9a1ac4162c677a455c2f02de658bd5d146905)): All of the databases must exist on the same docker network to allow the main kratos applications to communicate with them. - Fixed typo ([#472](https://github.com/ory/kratos/issues/472)) ([31263b6](https://github.com/ory/kratos/commit/31263b68ab8d81d264e0fa375a915f8f82d70bb3)) # [0.3.0-alpha.1](https://github.com/ory/kratos/compare/v0.2.1-alpha.1...v0.3.0-alpha.1) (2020-05-15) This release finalizes the OpenID Connect and OAuth2 login, registration, and settings strategy with JsonNet data transformation! From now on, "Sign in with Google, Github, ..." is officially supported! It's also possible to link and unlink these connections using the Self-Service Settings Flow! The documentation has been updated to reflect those changes and includes guides to setting up "Sign in with GitHub" in under 5 Minutes! Please be aware that existing OpenID Connect connections will stop working. Check out the "Breaking Changes" section for more info! Want to learn more? Check [out the docs](https://www.ory.sh/kratos/docs/concepts/credentials/openid-connect-oidc-oauth2)! We also changed the config validation output, making it easier than ever to find bugs in your config: ``` % kratos --config invalid-config.yml serve INFO[0001] Config file loaded successfully. path=invalid-config.yml ERRO[0001] The provided configuration is invalid and could not be loaded. Check the output below to understand why. config_file=invalid-config.yml dsn: ^-- one or more required properties are missing urls.whitelisted_return_to_urls: https://selfservice.office.example.com ^-- expected array, but got string FATA[0001] The services failed to start because the configuration is invalid. Check the output above for more details. ``` This release concludes over 50 commits and 16.000 lines of code changed. ## Breaking Changes If you upgrade and have existing Social Sign In connections, it will no longer be possible to use them to sign in. Because the oidc strategy was undocumented and not officially released we do not provide an upgrade guide. If you run into this issue on a production system you may need to use SQL to change the config of those identities. If this is a real issue for you that you're unable to solve, please create an issue on GitHub. This is a breaking change as previous OIDC configurations will not work. Please consult the newly written documentation on OpenID Connect to learn how to use OIDC in your login and registration flows. Since the OIDC feature was not publicly broadcasted yet we have chosen not to provide an upgrade path. If you have issues, please reach out on the forums or slack. ### Bug Fixes - Access rules of oathkeeper for quick start ([#390](https://github.com/ory/kratos/issues/390)) ([5ed6d05](https://github.com/ory/kratos/commit/5ed6d05b3e13027e4e7ffef1ff10ab2fb948093d)), closes [#389](https://github.com/ory/kratos/issues/389): To access `/` as dashboard - Active field should not be required ([#401](https://github.com/ory/kratos/issues/401)) ([aed2a5c](https://github.com/ory/kratos/commit/aed2a5c3c8e39132df53ae8f0eecfb7924296796)), closes [ory/sdk#14](https://github.com/ory/sdk/issues/14) - Adopt jsonnet in e2e oidc tests ([5e518fb](https://github.com/ory/kratos/commit/5e518fb2de678e27fcc0e4fff020a4d575f1c109)) - Detect postgres unique constraint ([3a777af](https://github.com/ory/kratos/commit/3a777af00244066a42751005d832e4058ddad8d2)) - Fix oidc strategy jsonnet test ([f6c48bf](https://github.com/ory/kratos/commit/f6c48bf2c64cea1f111e5777de22878e0be5f03c)) - Improve config validation error message ([#414](https://github.com/ory/kratos/issues/414)) ([d1e6896](https://github.com/ory/kratos/commit/d1e6896b3870cad49217ee78f6024a8a5c416f46)), closes [#413](https://github.com/ory/kratos/issues/413) - Reset request id after parse ([9550205](https://github.com/ory/kratos/commit/9550205a35364473e0f620ef2b2a7eac223dbfff)) - Resolve flaky swagger generation ([#416](https://github.com/ory/kratos/issues/416)) ([ac4acfc](https://github.com/ory/kratos/commit/ac4acfcd7f4e686b5d5c01136158fdf1687329ac)) - Resolve regression issues and bugs ([e6d5369](https://github.com/ory/kratos/commit/e6d53693e146ec6e0d9de2ea366323721af3d8fb)) - Return correct error on id mismatch ([5915f28](https://github.com/ory/kratos/commit/5915f2882d2a481ea357d50b0058093ba3ddb51b)) - Test and implement mapper_url for jsonnet ([40ac3dc](https://github.com/ory/kratos/commit/40ac3dc7b5828ac775055fed3c0bd9ff393e5d86)) - Transaction usage in the identity persister ([#404](https://github.com/ory/kratos/issues/404)) ([7f5072d](https://github.com/ory/kratos/commit/7f5072dc2d4fbf1f48cdf4d199ce4e89683a87b1)) ### Chores - Pin v0.3.0-alpha.1 release commit ([43b693a](https://github.com/ory/kratos/commit/43b693a449bf7cd219eb6901acf36725ace1c41c)) ### Code Refactoring - Adopt new request parser ([ad16cc9](https://github.com/ory/kratos/commit/ad16cc917c8067eb1c4b89ef8192287be1c912c8)) - Dry config and oidc tests ([3e98756](https://github.com/ory/kratos/commit/3e9875612ea895f9b565d34f4d5b0f80d136868f)) - Improve oidc flows and payloads and add e2e tests ([#381](https://github.com/ory/kratos/issues/381)) ([f9a5079](https://github.com/ory/kratos/commit/f9a50790637a848897ba275373bc538728e09f3d)), closes [#387](https://github.com/ory/kratos/issues/387): This patch improves the OpenID Connect login and registration user experience by simplifying the network flows and introduces e2e tests using ORY Hydra. - Move cypress files to test/e2e ([df8e627](https://github.com/ory/kratos/commit/df8e627d81d69682e01ec5670c7088ba564df578)) - Moved scanner json to ory/x ([#412](https://github.com/ory/kratos/issues/412)) ([8a0967d](https://github.com/ory/kratos/commit/8a0967daef4329981b01e6c2b8bb55a8105b4829)) - Partition files and change creds structure ([4f1eb94](https://github.com/ory/kratos/commit/4f1eb946fe1e74e537fc2166fc000180a11c2048)): This patch changes the data model of the OpenID Connect strategy. Instead of using an array of providers as the base config item (e.g. `{"type":"oidc","config":[{"provider":"google","subject":"..."}]}`) the credentials config is now an object with a `providers` key: `{"type":"oidc","config":{"providers":[{"provider":"google","subject":"..."}]}}`. This change allows introduction of future changes to the schema without breaking compatibility. - Replace oidc jsonschema with jsonnet ([2b45e79](https://github.com/ory/kratos/commit/2b45e7953787ad46a6937fe44cb24b6c786eb223)), closes [#380](https://github.com/ory/kratos/issues/380): This patch replaces the previous methodology of merging OIDC data which used JSON Schema with Extensions and JSON Path in favor of a much easier to use approach with JSONNet. - **settings:** Use common request parser ([ad6c402](https://github.com/ory/kratos/commit/ad6c4026e5fd15924dc906cdc9cb6c9de2fc4daa)) ### Documentation - Document account enumeration defenses for oidc ([266329c](https://github.com/ory/kratos/commit/266329cd2969627c823418c1267360193e6342df)), closes [#32](https://github.com/ory/kratos/issues/32) - Document new oidc jsonnet mapper ([#392](https://github.com/ory/kratos/issues/392)) ([088b30f](https://github.com/ory/kratos/commit/088b30feb6845863e6651489e0c963cde7e10516)) - Document oidc strategy ([#415](https://github.com/ory/kratos/issues/415)) ([9f079f4](https://github.com/ory/kratos/commit/9f079f4f77e54f7be67ac59e13e8ec2696522637)), closes [#409](https://github.com/ory/kratos/issues/409) [#124](https://github.com/ory/kratos/issues/124) [#32](https://github.com/ory/kratos/issues/32) - Explain that form data is merged with oidc data ([#394](https://github.com/ory/kratos/issues/394)) ([b0dbec4](https://github.com/ory/kratos/commit/b0dbec403c96af41346b6b14fc74b7010e7f8e8a)), closes [#127](https://github.com/ory/kratos/issues/127) - Fix links in README ([efb6102](https://github.com/ory/kratos/commit/efb610239ac2ae828db26ee84c4c5a83c54c0a6a)), closes [#403](https://github.com/ory/kratos/issues/403) - Improve social sign in guide ([#393](https://github.com/ory/kratos/issues/393)) ([647ced3](https://github.com/ory/kratos/commit/647ced3084d203e9954ca037afea34316f2080d8)), closes [#49](https://github.com/ory/kratos/issues/49): This patch changes the social sign in guide to represent more use cases such as Google and Facebook. Additionally, the example has been updated to work with Jsonnet. This patch also documents limitations around merging user data from GitHub. - Improve the identity data model page ([#410](https://github.com/ory/kratos/issues/410)) ([2915b8f](https://github.com/ory/kratos/commit/2915b8faf3530fe7b9d252094c3aeb9fdbe9dd08)) - Include redirect doc in nav ([5aaebff](https://github.com/ory/kratos/commit/5aaebffd8c03e613ec60735536b6ef38d4da39e3)), closes [#406](https://github.com/ory/kratos/issues/406) - Prepare v0.3.0-alpha.1 ([d6a6f43](https://github.com/ory/kratos/commit/d6a6f432f375018a2dc79d6b60de18455057c25a)) - Ui should show only active form sections ([#395](https://github.com/ory/kratos/issues/395)) ([4db674d](https://github.com/ory/kratos/commit/4db674de14bc50e782321c7bd88ac8077db2bf75)) - Update github templates ([#408](https://github.com/ory/kratos/issues/408)) ([6e646b0](https://github.com/ory/kratos/commit/6e646b033e0d43499bf37579a2f04b726af0e3f7)) ### Features - Add format and lint for JSONNet files ([0a1b244](https://github.com/ory/kratos/commit/0a1b244a6fd2f714a12d101071b3c0f82b4da584)): This patch adds two commands `kratos jsonnet format` and `kratos jsonnet lint` that help with formatting and linting JSONNet code. - Implement oidc settings e2e tests ([919925c](https://github.com/ory/kratos/commit/919925c87be561064300c3981b5a230c6cada4f7)) - Introduce leaklog for debugging oidc map payloads ([238d7a4](https://github.com/ory/kratos/commit/238d7a493566bcc28f08b1b2bf6463f95b100254)) - Write tests and fix bugs for oidc settings ([575a61f](https://github.com/ory/kratos/commit/575a61f58a887fefa6b2917761c06304c94c9892)) ### Unclassified - Format code ([bc7557a](https://github.com/ory/kratos/commit/bc7557a4247ede1fdb4141f2670532aec7cbd456)) # [0.2.1-alpha.1](https://github.com/ory/kratos/compare/v0.2.0-alpha.2...v0.2.1-alpha.1) (2020-05-05) Resolves a bug in the kratos-selfservice-ui-node application. ### Chores - Pin v0.2.1-alpha.1 release commit ([16463ea](https://github.com/ory/kratos/commit/16463ead91a009f33373150d10095aa3857b38f4)) ### Documentation - Fix quickstart hero sections ([7c6c439](https://github.com/ory/kratos/commit/7c6c4397bccd2b505fc04cc8d3b0944ceca18982)) - Fix typo in upgrade guide ([a1b1d7c](https://github.com/ory/kratos/commit/a1b1d7c9cbe5fad3b1112a16eced4f3064cfdda0)) # [0.2.0-alpha.2](https://github.com/ory/kratos/compare/v0.1.1-alpha.1...v0.2.0-alpha.2) (2020-05-04) This is a heavy release with over hundreds of commits and files changed! Let's take a look at some of the highlights! **ORY Oathkeeper now optional** Using ORY Oathkeeper to protect your API is now optional. The basic quickstart now uses a much simpler set up. Go [check it out](https://www.ory.sh/kratos/docs/quickstart) now! **PostgreSQL, MySQL, CockroachDB support now tested and official!** All three databases now pass acceptance tests and are thus officially supported! **Self-Service Profile Flow** The self-service profile flow has been refactored into a more generic flow allowing users to make modifications to their traits and credentials. Check out the [docs to learn more](https://www.ory.sh/kratos/docs/self-service/flows/user-settings-profile-management) about the flow and it's features. Please keep in mind that the flow's APIs have changed. We recommend re-reading the docs! **Managing Privileged Profile Fields** Flows such as changing ones profile or primary email address should not be possible unless the login session is fresh. This prevents your colleague or evil friend to take over your account while you make yourself a coffee. ORY Kratos now supports this by redirecting the user to the login screen if changes to sensitive fields are made. The changes will only be applied after successful reauthentication. **Changes to Hooks** This patch focuses on refactoring how self-service flows terminate and changes how hooks behave and when they are executed. Before this patch, it was not clear whether hooks run before or after an identity is persisted. This caused problems with multiple writes on the HTTP ResponseWriter and other bugs. This patch removes certain hooks from after login, registration, and profile flows. Per default, these flows now respond with an appropriate payload ( redirect for browsers, JSON for API clients) and deprecate the `redirect` hook. This patch includes documentation which explains how these hooks work now. Additionally, the documentation was updated. Especially the sections about hooks have been refactored. The login and user registration docs have been updated to reflect the latest changes as well. BREAKING CHANGE: Please remove the `redirect` hook from both login, registration, and settings after configuration. Please remove the `session` hook from your login after configuration. Hooks have moved down a level and are now configured at `selfservice...hooks` instead of `selfservice...hooks`. Hooks are now identified by `hook:` instead of `job:`. Please rename those sections accordingly. We recommend re-reading the [Hooks Documentation](https://www.ory.sh/kratos/docs/self-service/hooks/index). **Changing Passwords** It's now possible to change your password using the Self-Service Settings Flow! Lean more about this flow [here](https://www.ory.sh/kratos/docs/self-service/flows/user-settings-profile-management) **End-To-End Tests** We added tons of end-to-end and integration tests to find and fix pesky bugs. ## Breaking Changes Please remove the `redirect` hook from both login, registration, and settings after configuration. Please remove the `session` hook from your login after configuration. Hooks have moved down a level and are now configured at `selfservice...hooks` instead of `selfservice...hooks`. Hooks are now identified by `hook:` instead of `job:`. Please rename those sections accordingly. Several profile-related URLs have and payloads been updated. Please consult the most recent documentation. The payloads of the Profile Management Request API that previously were set in `{ "methods": { "traits": { ... } }}` have now moved to `{ "methods": { "profile": { ... } }}`. This patch introduces a refactor that is needed for the profile management API to be capable of handling (password, oidc, ...) credential changes as well. To implement this, the payloads of the Profile Management Request API that previously were set in `{"form": {...} }` have now moved to `{"methods": { "traits": { ... } }}`. In the future, as more credential updates are handled, there will be additional keys in the forms key `{"methods": { "traits": { ... }, "password": { ... } }}`. ### Bug Fixes - Allow setting new password in profile flow ([3b5fd5c](https://github.com/ory/kratos/commit/3b5fd5ca8c09b2344c0262547f2b387bda362362)) - Automatically append multiStatements parameter to mySQL URI ([#374](https://github.com/ory/kratos/issues/374)) ([39f77bb](https://github.com/ory/kratos/commit/39f77bb29637db048b15c097d869d8828b0d292b)) - **config:** Rename config key stmp to smtp ([#278](https://github.com/ory/kratos/issues/278)) ([ef95811](https://github.com/ory/kratos/commit/ef95811bb891afe3a0ef3b19514f13a56a32ea3b)) - Create pop connection without parsed connection options ([#366](https://github.com/ory/kratos/issues/366)) ([10b6481](https://github.com/ory/kratos/commit/10b6481774aaff42b70b9c6af3ed776ac8f7734c)) - Declare proper vars for setting version ([#383](https://github.com/ory/kratos/issues/383)) ([2fc7556](https://github.com/ory/kratos/commit/2fc7556b70b11e519162326ded0ba2638b6d32df)) - Decouple quickstart scenarios ([#336](https://github.com/ory/kratos/issues/336)) ([17363b3](https://github.com/ory/kratos/commit/17363b312deff8b92fc1b0d158dc70670d5938e5)), closes [#262](https://github.com/ory/kratos/issues/262): Creates several docker compose examples which include various scenarios of the quickstart. The regular quickstart guide now works without ORY Oathkeeper and uses the standalone mode of the example app instead. Additionally, the Makefile was improved and now automatically pulls required dependencies in the appropriate version. - **docker:** Throw away build artifacts ([481ec1b](https://github.com/ory/kratos/commit/481ec1ba14480ced39516f6e0c47a40b6a44a631)) - Document Schema API and serve over admin endpoint ([#299](https://github.com/ory/kratos/issues/299)) ([4be417c](https://github.com/ory/kratos/commit/4be417c0ee18622247a15d2803f7f436cfe3c229)), closes [#287](https://github.com/ory/kratos/issues/287) - Exempt whomai from csrf protection ([#329](https://github.com/ory/kratos/issues/329)) ([31d4065](https://github.com/ory/kratos/commit/31d4065c2b0cbd6c8d2b0031ce8f6f157ff967cf)) - Fix swagger annotation ([#331](https://github.com/ory/kratos/issues/331)) ([5c5c78f](https://github.com/ory/kratos/commit/5c5c78f404a11d5df25cb68584b826b685bf5385)): Closes https://github.com/ory/sdk/issues/10 - Move to ory sqa service ([#309](https://github.com/ory/kratos/issues/309)) ([7c244e0](https://github.com/ory/kratos/commit/7c244e0a28a010e56e07d061132dad7a0309ea75)) - Properly annotate error API ([a6f1300](https://github.com/ory/kratos/commit/a6f1300951010e7c862c410e93653f7c02c2e79f)) - Remove unused returnTo ([e64e5b0](https://github.com/ory/kratos/commit/e64e5b0cecceedda29a525f683cbf6070a9ef1eb)) - Resolve docker build permission issues ([f3612e8](https://github.com/ory/kratos/commit/f3612e8f82018bae17c9146d273fe7e82ceb033d)) - Resolve failing test issues ([2e968e5](https://github.com/ory/kratos/commit/2e968e52d3ae3396a3f2e212c0dab22677b4b5fd)) - Resolve linux install script archive naming ([#302](https://github.com/ory/kratos/issues/302)) ([c98b8aa](https://github.com/ory/kratos/commit/c98b8aa4cd3ab881b904e9dc4cdcb6383a8ad09b)) - Resolve NULL value for seen_at ([#259](https://github.com/ory/kratos/issues/259)) ([a7d1e86](https://github.com/ory/kratos/commit/a7d1e86844a9cdd0c58353e1f1e4340dac4260b3)), closes [#244](https://github.com/ory/kratos/issues/244): Previously, errorx tests were not executed which caused several bugs. - Resolve password continuity issues ([56a44fa](https://github.com/ory/kratos/commit/56a44fa33d325eea9fddec4269e34e632310f77b)) - Revert use host volume mount for sqlite ([#272](https://github.com/ory/kratos/issues/272)) ([#285](https://github.com/ory/kratos/issues/285)) ([a7477ab](https://github.com/ory/kratos/commit/a7477ab1db0d986f96e754946607d05888de4c97)): This reverts commit 230ab2d83f4d187f410e267c6d68554e82514948. - Self-service error query parameter name ([#308](https://github.com/ory/kratos/issues/308)) ([be257f5](https://github.com/ory/kratos/commit/be257f5448abaa48e25735a088757f3fd6dc6d22)): The query parameter for the self-service errors endpoint was named `id` in the API docs, whereas it is the `error` param that is used by the handler. - **session:** Regenerate CSRF Token on principal change ([#290](https://github.com/ory/kratos/issues/290)) ([1527ef4](https://github.com/ory/kratos/commit/1527ef4209b937e2175b60d56efd019f17b33b04)), closes [#217](https://github.com/ory/kratos/issues/217) - **session:** Whoami endpoint now supports all HTTP methods ([#283](https://github.com/ory/kratos/issues/283)) ([4bf645b](https://github.com/ory/kratos/commit/4bf645b66c7a128182ff55e52fdad7f53d752ce7)), closes [#270](https://github.com/ory/kratos/issues/270) - Show log in ui only when unauthenticated or forced ([df77310](https://github.com/ory/kratos/commit/df77310ffbe7cfc90fa3bc5dad0450e79c34ebef)), closes [#323](https://github.com/ory/kratos/issues/323) - **sql:** Rename migrations with same version ([#280](https://github.com/ory/kratos/issues/280)) ([07e46b9](https://github.com/ory/kratos/commit/07e46b9c9e57940bec904d744ffdd272d610a77b)), closes [#279](https://github.com/ory/kratos/issues/279) - **swagger:** Move nolint,deadcode instructions to own file ([#293](https://github.com/ory/kratos/issues/293)) ([1935510](https://github.com/ory/kratos/commit/1935510ad9b0f387eb3b2e690e31c5313a06883e)): Closes https://github.com/ory/docs/pull/279 - Use host volume mount for sqlite ([#272](https://github.com/ory/kratos/issues/272)) ([230ab2d](https://github.com/ory/kratos/commit/230ab2d83f4d187f410e267c6d68554e82514948)) - Use resilient client for HIBP lookup ([#288](https://github.com/ory/kratos/issues/288)) ([735b435](https://github.com/ory/kratos/commit/735b43508392c6966a57907c20caa7cf9df4fc4d)), closes [#261](https://github.com/ory/kratos/issues/261) - Use semver-regex replacer func ([d5c9a47](https://github.com/ory/kratos/commit/d5c9a47800fc2a55b96c7b9330f68b0a2db328cb)) - Use sqlite tag on make install ([2c82784](https://github.com/ory/kratos/commit/2c82784cd69e0468a72354f6898945032d826306)) - Verified_at field should not be required ([#353](https://github.com/ory/kratos/issues/353)) ([15d5e26](https://github.com/ory/kratos/commit/15d5e268d2ec397f0647d2407d86404c4ee8bfa3)): Closes https://github.com/ory/sdk/issues/11 ### Chores - Pin v0.2.0-alpha.2 release commit ([ab91689](https://github.com/ory/kratos/commit/ab916894b761b18c53e4ed1fd0e42d9f5aa0817c)) ### Code Refactoring - Move docs to this repository ([#317](https://github.com/ory/kratos/issues/317)) ([aa0d726](https://github.com/ory/kratos/commit/aa0d72639ecae3b0649761e6ee881a59b2f3e94e)) - Prepare profile management payloads for credentials ([44493f3](https://github.com/ory/kratos/commit/44493f3ddbb449981576ec317ac45530ca3be14d)) - Rename traits method to profile ([4f1e033](https://github.com/ory/kratos/commit/4f1e0339ecc1efbdfa3d3680ad64b7683e90e447)) - Rework hooks and self-service flow completion ([#349](https://github.com/ory/kratos/issues/349)) ([a7c7fef](https://github.com/ory/kratos/commit/a7c7fef758e843393b0dc1e60bee11b88b8c9b4a)), closes [#348](https://github.com/ory/kratos/issues/348) [#347](https://github.com/ory/kratos/issues/347) [#179](https://github.com/ory/kratos/issues/179) [#51](https://github.com/ory/kratos/issues/51) [#50](https://github.com/ory/kratos/issues/50) [#31](https://github.com/ory/kratos/issues/31): This patch focuses on refactoring how self-service flows terminate and changes how hooks behave and when they are executed. Before this patch, it was not clear whether hooks run before or after an identity is persisted. This caused problems with multiple writes on the HTTP ResponseWriter and other bugs. This patch removes certain hooks from after login, registration, and profile flows. Per default, these flows now respond with an appropriate payload ( redirect for browsers, JSON for API clients) and deprecate the `redirect` hook. This patch includes documentation which explains how these hooks work now. Additionally, the documentation was updated. Especially the sections about hooks have been refactored. The login and user registration docs have been updated to reflect the latest changes as well. Also, some other minor, cosmetic, changes to the documentation have been made. ### Documentation - Add banner kratos ([8a9dfbb](https://github.com/ory/kratos/commit/8a9dfbbd54bac14778cc84ec13326eb1ef80f5b3)) - Add csrf and cookie debug section ([#342](https://github.com/ory/kratos/issues/342)) ([cac2948](https://github.com/ory/kratos/commit/cac2948685ed2a3c3edbc8eb4696bbfb8523dfeb)), closes [#341](https://github.com/ory/kratos/issues/341) - Add database connection documentation ([#332](https://github.com/ory/kratos/issues/332)) ([4f9e8b0](https://github.com/ory/kratos/commit/4f9e8b00bacda3612db3f48b81fabd562075470a)) - Add HA docs ([2e5c591](https://github.com/ory/kratos/commit/2e5c59158915d1ccbb90363e23f73a09c227b6f7)) - Add hook changes to upgrade guide ([55b5fe0](https://github.com/ory/kratos/commit/55b5fe00c0472f5f6f7408eee76bf9a39318db7e)) - Add info to oidc ([#382](https://github.com/ory/kratos/issues/382)) ([6eeeb5d](https://github.com/ory/kratos/commit/6eeeb5dbe98d2f31fd922d60a35d9d8f81d0b2a8)) - Add more examples to config schema ([#372](https://github.com/ory/kratos/issues/372)) ([ed2ccb9](https://github.com/ory/kratos/commit/ed2ccb935fdcfcb11999996cd582726bba096435)), closes [#345](https://github.com/ory/kratos/issues/345) - Add quickstart notes for docker debugging ([74f082a](https://github.com/ory/kratos/commit/74f082a407ee73741453ff6a394f47790e79b667)) - Add settings docs and improve flows ([#375](https://github.com/ory/kratos/issues/375)) ([478cd9c](https://github.com/ory/kratos/commit/478cd9c5b5755030307d1f11e9bcbd4e171ee0d6)), closes [#345](https://github.com/ory/kratos/issues/345) - **concepts:** Fix typo ([a49184c](https://github.com/ory/kratos/commit/a49184c30d9c2ccff5a2d41d3aff61b24e7d2ea9)): Closes https://github.com/ory/docs/pull/296 - **concepts:** Properly close code tag ([1c841c2](https://github.com/ory/kratos/commit/1c841c213bdbc79a6aa41e8450444d8d6c1f0284)) - Declare api frontmatter properly ([df7591f](https://github.com/ory/kratos/commit/df7591f7b70c94cfe62042a598eceb36b6a4f29a)) - Document 0.2.0 high-level changes ([9be1064](https://github.com/ory/kratos/commit/9be1064500dd86489b79e1abd9cbf1268b97853a)), closes [hi#level](https://github.com/hi/issues/level) - Document multi-tenant set up ([891594d](https://github.com/ory/kratos/commit/891594df488e42ce30a81465f10f2936d152cb55)), closes [#370](https://github.com/ory/kratos/issues/370) - Fix broken images in quickstart ([52aa4cf](https://github.com/ory/kratos/commit/52aa4cf0b6967108fa58f58b6b151e6f6118bcc9)) - Fix broken link ([bf7843c](https://github.com/ory/kratos/commit/bf7843cd96795a894488a0910529c847cf7eee19)), closes [#327](https://github.com/ory/kratos/issues/327) - Fix broken link ([c2adc73](https://github.com/ory/kratos/commit/c2adc734a73758d858d50d8738dc2a556110f26c)), closes [#327](https://github.com/ory/kratos/issues/327) - Fix broken mermaid links ([f24fc1b](https://github.com/ory/kratos/commit/f24fc1bbba234d71098298bcddbba236ac4297f3)) - Fix spelling in quickstart ([#356](https://github.com/ory/kratos/issues/356)) ([3ce6b4a](https://github.com/ory/kratos/commit/3ce6b4a1b0722a96bcbae79b7261616f20741494)) - Improve changelog ([#384](https://github.com/ory/kratos/issues/384)) ([a973ca7](https://github.com/ory/kratos/commit/a973ca7719cd820bb196ec5732c85418528be1d0)) - Improve profile section and restructure nav ([#373](https://github.com/ory/kratos/issues/373)) ([3cc0979](https://github.com/ory/kratos/commit/3cc097934edc81d4c6d853594eed5e68e9e48445)), closes [#345](https://github.com/ory/kratos/issues/345) - Regenerate and update changelog ([7d4ed98](https://github.com/ory/kratos/commit/7d4ed9873f25b14b59f727002fb08a8b8a4e91a6)) - Regenerate and update changelog ([175b626](https://github.com/ory/kratos/commit/175b626f74b4471e068bd79259c6d479fd6c1a7d)) - Regenerate and update changelog ([e60e2df](https://github.com/ory/kratos/commit/e60e2df5d5cc4c1ef8a6a7f13487d4ebbf54741e)) - Regenerate and update changelog ([41eeb75](https://github.com/ory/kratos/commit/41eeb7587fad864f64c4179ac20847f902c438b3)) - Regenerate and update changelog ([468105a](https://github.com/ory/kratos/commit/468105a6080b861f1e02db3a404f2bac7f2f5eb6)) - Regenerate and update changelog ([8414520](https://github.com/ory/kratos/commit/8414520c995cb2405ed051952357d37ca8111f25)) - Regenerate and update changelog ([85d5866](https://github.com/ory/kratos/commit/85d5866df403b3cfa5566cef5cb983714b395505)) - Regenerate and update changelog ([e8d2d10](https://github.com/ory/kratos/commit/e8d2d1019bbc05fbe4eeaaee7a8eb1e8f2d18cf9)) - Regenerate and update changelog ([4c58b6d](https://github.com/ory/kratos/commit/4c58b6de4a3a39b1e94516abd1ea8ed7b09c1fe4)) - Regenerate and update changelog ([a726eb2](https://github.com/ory/kratos/commit/a726eb202a070038148612f98f12e5d22170d1ec)) - Regenerate and update changelog ([87b47ba](https://github.com/ory/kratos/commit/87b47baa9cdc0175c58ccbb20e67b458ce6a445f)) - Regenerate and update changelog ([537d496](https://github.com/ory/kratos/commit/537d496d2043a17c68f31a8744c39bc76f76314c)) - Regenerate and update changelog ([00e6af9](https://github.com/ory/kratos/commit/00e6af96060ec38059c449ac5e8b3c1df5bb8c95)) - Regenerate and update changelog ([48a2eca](https://github.com/ory/kratos/commit/48a2eca2dcd274ca73d55132efca4a6dae63efdf)) - Regenerate and update changelog ([8a71948](https://github.com/ory/kratos/commit/8a719481b54957681aa21eff5415229f3e5d4bff)) - Regenerate and update changelog ([ad3d510](https://github.com/ory/kratos/commit/ad3d5101dad3c8a2725083c63f155638905b6e8c)) - Regenerate and update changelog ([48bcc70](https://github.com/ory/kratos/commit/48bcc704ed22d8c78620aa3a5f8ecb5b41937759)) - Regenerate and update changelog ([816a55c](https://github.com/ory/kratos/commit/816a55c81a27b53d5bd823392751853b68d3f607)) - Regenerate and update changelog ([4ed74d2](https://github.com/ory/kratos/commit/4ed74d25c45f6e439377329d42cd7ae0acf9d0f1)) - Regenerate and update changelog ([367927e](https://github.com/ory/kratos/commit/367927e716e7c1c6898151a5f14876fb30070dd3)) - Regenerate and update changelog ([38f4019](https://github.com/ory/kratos/commit/38f40190f54264808c7a2716555876d05cdf560f)) - Typo in README.md ([#265](https://github.com/ory/kratos/issues/265)) ([9f865a2](https://github.com/ory/kratos/commit/9f865a2ebace801414b2de17fe2f627d91f23474)) - Update banner url ([292c986](https://github.com/ory/kratos/commit/292c986729d83187f7e77365e11ef74a6f3cadf6)) - Update forum and chat links ([3039191](https://github.com/ory/kratos/commit/30391919d7ea58609dd3cd37db2709495e7abc76)) - Update github templates ([#338](https://github.com/ory/kratos/issues/338)) ([57dbc77](https://github.com/ory/kratos/commit/57dbc77b548383522ca428e899dfde461334216c)) - Update github templates ([#343](https://github.com/ory/kratos/issues/343)) ([eb13dc1](https://github.com/ory/kratos/commit/eb13dc1285cb16515d1c63b99cc389147508a31e)) - Update github templates ([#350](https://github.com/ory/kratos/issues/350)) ([faf2f30](https://github.com/ory/kratos/commit/faf2f305aea1826e3d5f0b2614313920ac2b585b)) - Update github templates ([#351](https://github.com/ory/kratos/issues/351)) ([20ff289](https://github.com/ory/kratos/commit/20ff2890004745231073cd4fd6ef1b37521cde72)) - Update linux install guide ([3b8e549](https://github.com/ory/kratos/commit/3b8e5493a01357f8c442a8a2dc9437712498452c)) - Update linux install guide ([#354](https://github.com/ory/kratos/issues/354)) ([ec49cae](https://github.com/ory/kratos/commit/ec49caec6ddea2c800db0779005bac6da73903e1)) - Update self service reg docs ([#367](https://github.com/ory/kratos/issues/367)) ([4cf0323](https://github.com/ory/kratos/commit/4cf0323095990c5ec25283a01561cb9b8833f9ef)): The old links pointed at `/auth/browser/(login|registration)` which seems to be outdated now. From the ui node code: https://github.com/ory/kratos-selfservice-ui-node/blob/489c76d1b0474ee55ef56804b28f54d8718747ba/src/routes/auth.ts#L28 and the api documentation for kratos https://www.ory.sh/kratos/docs/reference/api#get-the-request-context-of-browser-based-login-user-flows, these seem to be incorrect. The actual url hit is `/self-service/browser/flows/requests/(login|registration)`. This commit updates those links This blob was previously one large inline string, which personally made the docs a bit hard to read. This formats it into an (arguably) easier to parse code block - Update user-settings-profile-management.md ([#322](https://github.com/ory/kratos/issues/322)) ([45dc3a5](https://github.com/ory/kratos/commit/45dc3a56c15ae442890313a7dbc784b75644248a)) - Updates issue and pull request templates ([#298](https://github.com/ory/kratos/issues/298)) ([1be738d](https://github.com/ory/kratos/commit/1be738d3f8e9bbc6dae31ffad5d990657a66761c)) - Updates issue and pull request templates ([#313](https://github.com/ory/kratos/issues/313)) ([299063c](https://github.com/ory/kratos/commit/299063caf2fdde40713bae4c36abb3b6fac7271d)) - Updates issue and pull request templates ([#314](https://github.com/ory/kratos/issues/314)) ([d5ae452](https://github.com/ory/kratos/commit/d5ae452a8ce5f641a40e510e82441d4eb8137218)) - Updates issue and pull request templates ([#315](https://github.com/ory/kratos/issues/315)) ([8b68db1](https://github.com/ory/kratos/commit/8b68db140a7fc1c0eaa9318c1759ea9d8d0c27df)) - Use git checkout in quickstart ([#339](https://github.com/ory/kratos/issues/339)) ([2d2562b](https://github.com/ory/kratos/commit/2d2562b587a69a2891ff29d927cb001e15d75b5d)), closes [#335](https://github.com/ory/kratos/issues/335) ### Features - Add `dsn: memory` shorthand ([#284](https://github.com/ory/kratos/issues/284)) ([e66a030](https://github.com/ory/kratos/commit/e66a030f7d67dec639121fb23dfc7f1444474c6b)), closes [#228](https://github.com/ory/kratos/issues/228) - Add and test id hint in reauth flow ([2298f01](https://github.com/ory/kratos/commit/2298f0140e77da870c842daa8eaca274e5d64254)), closes [#323](https://github.com/ory/kratos/issues/323) - Add cypress e2e tests ([#334](https://github.com/ory/kratos/issues/334)) ([abc0e91](https://github.com/ory/kratos/commit/abc0e91e278f7938b264598ac0c60d18c5a9e8a0)) - Allow configuring same-site for session cookies ([#303](https://github.com/ory/kratos/issues/303)) ([2eb2054](https://github.com/ory/kratos/commit/2eb2054a94281aefa9a0818110d168cc9c052094)), closes [#257](https://github.com/ory/kratos/issues/257): It is now possible to set SameSite for the session cookie via the key `security.session.cookie.same_site`. - **continuity:** Implement request continuity ([135e047](https://github.com/ory/kratos/commit/135e04750b1855ab0db812517c61e292a770ba94)), closes [#304](https://github.com/ory/kratos/issues/304) [#311](https://github.com/ory/kratos/issues/311): This patch adds a module which is capable of aborting a request, waiting for another option to complete, and then resuming the request again. This feature makes use of a temporary cookie which keeps track of the request state. This feature is required for several workflows that update privileged fields such as passwords, 2fa recovery codes, email addresses. refactor: rename profile to settings flow Renames selfservice/profile to settings. The settings flow includes a strategy for managing profile information - Enable CockroachDB integration ([#260](https://github.com/ory/kratos/issues/260)) ([adc5153](https://github.com/ory/kratos/commit/adc5153410fb4d9f99702d7c73a78aeec8c1e9f1)), closes [#132](https://github.com/ory/kratos/issues/132) [#155](https://github.com/ory/kratos/issues/155) - Enable continuity management for settings module ([009d755](https://github.com/ory/kratos/commit/009d7558f525168fecf86168de2906088662535e)) - Enable updating auth related traits ([#266](https://github.com/ory/kratos/issues/266)) ([65b88ba](https://github.com/ory/kratos/commit/65b88ba52fb9e6da3c1a65f734352519303327a6)), closes [#243](https://github.com/ory/kratos/issues/243) - Implement password profile management flow ([a31839a](https://github.com/ory/kratos/commit/a31839a5c33c80500c900fb50d1dd499ab1161a1)), closes [#243](https://github.com/ory/kratos/issues/243) - Introduce fallbacks for required configs ([#376](https://github.com/ory/kratos/issues/376)) ([b3bcb25](https://github.com/ory/kratos/commit/b3bcb25be6b417647ece2b3dda26d691f8e8d685)), closes [#369](https://github.com/ory/kratos/issues/369) [#352](https://github.com/ory/kratos/issues/352) - **login:** Forced reauthentication ([#248](https://github.com/ory/kratos/issues/248)) ([344fc9c](https://github.com/ory/kratos/commit/344fc9cddccff958f13249b999a835d3e46a7771)), closes [#243](https://github.com/ory/kratos/issues/243) - Return 410 when selfservice requests expire ([#289](https://github.com/ory/kratos/issues/289)) ([b414607](https://github.com/ory/kratos/commit/b4146076148d9ff079e9d433f0a90f5bc938650c)), closes [#235](https://github.com/ory/kratos/issues/235) - Send verification emails on profile update ([#333](https://github.com/ory/kratos/issues/333)) ([1cacc80](https://github.com/ory/kratos/commit/1cacc80c54f92b380ef3752591970cc4dd97085e)), closes [#267](https://github.com/ory/kratos/issues/267) ### Unclassified - u ([0b6fa48](https://github.com/ory/kratos/commit/0b6fa48e90fa0c50b9c26bae034eb1662c855d69)) - u ([03fa4f0](https://github.com/ory/kratos/commit/03fa4f05363aa1f38fe45730317375ce380cfa31)) - u ([a3dfd9d](https://github.com/ory/kratos/commit/a3dfd9d15e1f7287558b85c3a4f23d02444b0bf4)) - u ([616aa0f](https://github.com/ory/kratos/commit/616aa0f0cf3d662b48fcaa02715e02e854e05581)) - fix:add graceful shutdown to courier handler (#296) ([235d784](https://github.com/ory/kratos/commit/235d784b7f8bf38859d15d68c37b089fc9371195)), closes [#296](https://github.com/ory/kratos/issues/296) [#295](https://github.com/ory/kratos/issues/295): Courier would not stop with the provided Background handler. This changes the methods of Courier so that the graceful package can be used in the same way as the http endpoints can be used. - fix(sql) change courier body to text field (#276) ([ed5268d](https://github.com/ory/kratos/commit/ed5268d539b2a28f5367e8ba2e2e6bd3a605ce5b)), closes [#276](https://github.com/ory/kratos/issues/276) [#269](https://github.com/ory/kratos/issues/269) - Make format ([b85e5af](https://github.com/ory/kratos/commit/b85e5af2e29f9ca3bc3341ba4f2b1b338b441398)) # [0.1.1-alpha.1](https://github.com/ory/kratos/compare/v0.1.0-alpha.6...v0.1.1-alpha.1) (2020-02-18) docs: Regenerate and update changelog ### Bug Fixes - Add verify return to address ([#252](https://github.com/ory/kratos/issues/252)) ([64ab9e5](https://github.com/ory/kratos/commit/64ab9e510e6b65f9dd16fdfaadfd24785dab0c93)) - Clean up docker quickstart ([#255](https://github.com/ory/kratos/issues/255)) ([7f0996b](https://github.com/ory/kratos/commit/7f0996b99646e57136f20c04a77a6f682eecdd9c)) - Resolve several verification problems ([#253](https://github.com/ory/kratos/issues/253)) ([30d4632](https://github.com/ory/kratos/commit/30d46326373cf038b600ee07db3e95ce6d94ab12)) - Update verify URLs ([#258](https://github.com/ory/kratos/issues/258)) ([5d4f909](https://github.com/ory/kratos/commit/5d4f9099b5c61ff9572ad23a3eb9c0e0025d92da)) ### Code Refactoring - Support context-based SQL transactions ([#254](https://github.com/ory/kratos/issues/254)) ([6ace1ee](https://github.com/ory/kratos/commit/6ace1ee2070c35b0da3e36dcd5417ff70a4ff9cb)) ### Documentation - Regenerate and update changelog ([a125822](https://github.com/ory/kratos/commit/a1258221a1fef82cc525be7b1042e91e2d20b1eb)) - Regenerate and update changelog ([b3a8220](https://github.com/ory/kratos/commit/b3a822035509ec2c9fb04037b2088ce6df8191da)) - Regenerate and update changelog ([a141b30](https://github.com/ory/kratos/commit/a141b309a1fc22bc45d70a090869fdee198a065e)) - Regenerate and update changelog ([7e12e20](https://github.com/ory/kratos/commit/7e12e20be0fa61a2f41a416a3edcd2b522165196)) - Regenerate and update changelog ([3c1c67b](https://github.com/ory/kratos/commit/3c1c67b31a54dd8d5fceac9449d305db82ff8844)) - Regenerate and update changelog ([ee07937](https://github.com/ory/kratos/commit/ee07937d5e797f0217c86946da42d0070ca7c250)) # [0.1.0-alpha.6](https://github.com/ory/kratos/compare/v0.1.0-alpha.5...v0.1.0-alpha.6) (2020-02-16) feat: Add verification to quickstart (#251) ### Bug Fixes - Adapt quickstart to verify changes ([#247](https://github.com/ory/kratos/issues/247)) ([24eceb7](https://github.com/ory/kratos/commit/24eceb7147cef1081ac1ad969713ca1bc36229cb)) - Gracefully handle selfservice request expiry ([#242](https://github.com/ory/kratos/issues/242)) ([4421e6b](https://github.com/ory/kratos/commit/4421e6bde494fbe9672251cf813a39e3031bf3fd)), closes [#233](https://github.com/ory/kratos/issues/233) - Set AuthenticatedAt in session issuer hook ([#246](https://github.com/ory/kratos/issues/246)) ([29c83fa](https://github.com/ory/kratos/commit/29c83fa986c612fb17e13fe9415f7836062159d2)), closes [#224](https://github.com/ory/kratos/issues/224) - **swagger:** Sanitize before validate ([c72f140](https://github.com/ory/kratos/commit/c72f140083e94f3a47ee2398c56d188e6d4edcb4)) - **swagger:** Use correct annotations for request methods ([#237](https://github.com/ory/kratos/issues/237)) ([8473c85](https://github.com/ory/kratos/commit/8473c85d8282b27375b53babbbc79046d407b3fb)), closes [#234](https://github.com/ory/kratos/issues/234) ### Code Refactoring - Move to ory/jsonschema/v3 everywhere ([#229](https://github.com/ory/kratos/issues/229)) ([61f5c1d](https://github.com/ory/kratos/commit/61f5c1d3d896841b08deb08c42ba896118e3fc71)), closes [#225](https://github.com/ory/kratos/issues/225) ### Documentation - Regenerate and update changelog ([922cf0f](https://github.com/ory/kratos/commit/922cf0f3d7ec8860d13aff3b88849a71fb59e2c9)) - Regenerate and update changelog ([e097c23](https://github.com/ory/kratos/commit/e097c23d8b4902a9013f3a8fa9a397033a92fb88)) - Regenerate and update changelog ([2d1685f](https://github.com/ory/kratos/commit/2d1685f4f4235e9293b1ab79e67050042787c6e9)) - Regenerate and update changelog ([f8964e9](https://github.com/ory/kratos/commit/f8964e9e5c442f75ba501ce7cfcb18916b781dc1)) - Regenerate and update changelog ([92b8001](https://github.com/ory/kratos/commit/92b80013c98e9556138eff04aa24dc696b8d6128)) - Regenerate and update changelog ([d7083ab](https://github.com/ory/kratos/commit/d7083ab9fb8e8172707cae3ac4a8a183f0c25903)) - Regenerate and update changelog ([c4547dc](https://github.com/ory/kratos/commit/c4547dc53ecf167b63e5d7d3b6764535bd86fa5a)) - Regenerate and update changelog ([d8d8bba](https://github.com/ory/kratos/commit/d8d8bbae055e2220023a45b832d2435984191029)) - Regenerate and update changelog ([b012ed9](https://github.com/ory/kratos/commit/b012ed9ce1f4fd0ece2e3463e952711b4380f4a4)) ### Features - Add disabled flag to identifier form fields ([#238](https://github.com/ory/kratos/issues/238)) ([a2178bd](https://github.com/ory/kratos/commit/a2178bdbbe20798a3e1e3fb5ed7b44afc187c640)), closes [#227](https://github.com/ory/kratos/issues/227) - Add verification to quickstart ([#251](https://github.com/ory/kratos/issues/251)) ([172dc87](https://github.com/ory/kratos/commit/172dc87d22f925668c21da1b3b581156e01d45a4)) - Implement email verification ([#245](https://github.com/ory/kratos/issues/245)) ([eed00f4](https://github.com/ory/kratos/commit/eed00f4b328c173057455980ce0e1aad909c278f)), closes [#27](https://github.com/ory/kratos/issues/27) - Improve password validation strategy ([#231](https://github.com/ory/kratos/issues/231)) ([256fad3](https://github.com/ory/kratos/commit/256fad37164c81cc44c35e77b99911996722a86a)) # [0.1.0-alpha.5](https://github.com/ory/kratos/compare/v0.1.0-alpha.4...v0.1.0-alpha.5) (2020-02-06) docs: Regenerate and update changelog ### Documentation - Regenerate and update changelog ([e87e9c9](https://github.com/ory/kratos/commit/e87e9c9ec9cf55351439ab16a778f3ea303ec646)) - Regenerate and update changelog ([d6f0794](https://github.com/ory/kratos/commit/d6f0794d53b6e7d6d9e3bc63a77d402e43a29bed)) - Regenerate and update changelog ([eb7326c](https://github.com/ory/kratos/commit/eb7326c98c2d5e87a8ac3cd9f2efb43f2552164a)) ### Features - Redirect to new auth session on expired auth sessions ([#230](https://github.com/ory/kratos/issues/230)) ([b477ecd](https://github.com/ory/kratos/commit/b477ecd47de33a9a45159a298ac288c4ad5a0b55)), closes [#96](https://github.com/ory/kratos/issues/96) # [0.1.0-alpha.4](https://github.com/ory/kratos/compare/v0.1.0-alpha.3...v0.1.0-alpha.4) (2020-02-06) ci: Bump ory/sdk to 0.1.22 ### Continuous Integration - Bump ory/sdk to 0.1.22 ([c0d0edf](https://github.com/ory/kratos/commit/c0d0edf1f369ecaeb28d1337930b16222b97337f)) ### Documentation - Regenerate and update changelog ([f02afb3](https://github.com/ory/kratos/commit/f02afb3fed310f7fe9c5e6f7df34dfc9738018ad)) # [0.1.0-alpha.3](https://github.com/ory/kratos/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) (2020-02-06) ci: Bump ory/sdk orb ### Continuous Integration - Bump ory/sdk orb ([65b2ca0](https://github.com/ory/kratos/commit/65b2ca0b8a1da8249aa4b4cb439b1d63aecaf8e0)) # [0.1.0-alpha.2](https://github.com/ory/kratos/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) (2020-02-03) docs: Regenerate and update changelog ### Bug Fixes - Add paths to sqa middleware ([#216](https://github.com/ory/kratos/issues/216)) ([130c9c2](https://github.com/ory/kratos/commit/130c9c242e1434074d9fa4970b60ccb9b4f2ff47)) - **daemon:** Register error routes on admin port ([#226](https://github.com/ory/kratos/issues/226)) ([decd8d8](https://github.com/ory/kratos/commit/decd8d8ef8dac3674938b564962238195ffaf017)) - Set csrf token on public endpoints ([d0b15ae](https://github.com/ory/kratos/commit/d0b15aeca991a94771715a6eabd4a956be41ceda)) ### Documentation - Introduce upgrade guide ([736a3b1](https://github.com/ory/kratos/commit/736a3b19bfe35cc699dea508b4bdb56b3302ba7e)) - Prepare ecosystem automation ([7013b6c](https://github.com/ory/kratos/commit/7013b6c9a856e05f6ad385eb8ce36c5faf342f5a)) - Regenerate and update changelog ([f39b942](https://github.com/ory/kratos/commit/f39b9422d79d3e69304f013c85f3850337ca1730)) - Regenerate and update changelog ([c121601](https://github.com/ory/kratos/commit/c121601b5c741c846d9c478b01aabb9907d81b95)) - Regenerate and update changelog ([a947d55](https://github.com/ory/kratos/commit/a947d554ba2be94f334568a4e77a501742ca95af)) - Regenerate and update changelog ([8ba2044](https://github.com/ory/kratos/commit/8ba2044ebb369ea741f99c65163f650c607e6c07)) - Regenerate and update changelog ([9c023e1](https://github.com/ory/kratos/commit/9c023e1a9288f156c79ea78b3a979d0fefab8825)) - Regenerate and update changelog ([1e855a9](https://github.com/ory/kratos/commit/1e855a9e0ebd232ba2b07dc4a8bb79b84cd548e6)) - Regenerate and update changelog ([01ce3a8](https://github.com/ory/kratos/commit/01ce3a891edd84174694111637dd44fe65e48b37)) - Updates issue and pull request templates ([#222](https://github.com/ory/kratos/issues/222)) ([4daae88](https://github.com/ory/kratos/commit/4daae88af527018e9ee4e1e9717a07dffab427fe)) ### Features - Override semantic config ([#220](https://github.com/ory/kratos/issues/220)) ([9b4214b](https://github.com/ory/kratos/commit/9b4214bf5eac81a92513e04dc5f862b93df86935)) ### Unclassified - Update CHANGELOG [ci skip] ([ce9390c](https://github.com/ory/kratos/commit/ce9390c27f61966b7ed23244400215c2218bbc0b)) - refactor!: Improve user-facing error APIs (#219) ([7d4054f](https://github.com/ory/kratos/commit/7d4054f4363da7bc0e943e7abfbd0c804eb7f0c1)), closes [#219](https://github.com/ory/kratos/issues/219) [#204](https://github.com/ory/kratos/issues/204): This patch refactors user-facing error APIs: - The `/errors` endpoint moved to `/self-service/errors` - The endpoint is now available at both the Admin and Public API. The Public API requires CSRF Token match or a 403 error will be returned. - The Public API endpoint no longer returns 404 errors but 403 instead. - The response payload changed. What was `[{"code": ...}]` is now `{"id": "...", "errors": [{"code": ...}]}` This patch requires running `kratos migrate sql` as a new column (`csrf_token`) has been added to the user-facing error store. - Update CHANGELOG [ci skip] ([c368a11](https://github.com/ory/kratos/commit/c368a11523a9bcb30a830d65c11e4f6d27417a78)) # [0.1.0-alpha.1](https://github.com/ory/kratos/compare/v0.0.3-alpha.15...v0.1.0-alpha.1) (2020-01-31) docs: Updates issue and pull request templates (#215) Signed-off-by: aeneasr ### Documentation - Updates issue and pull request templates ([#215](https://github.com/ory/kratos/issues/215)) ([10c45f2](https://github.com/ory/kratos/commit/10c45f23e11abba1ca82095548769cd923a6a6a6)) # [0.0.3-alpha.15](https://github.com/ory/kratos/compare/v0.0.3-alpha.14...v0.0.3-alpha.15) (2020-01-31) Update permissions in SQLite Dockerfile ### Unclassified - Update permissions in SQLite Dockerfile ([1266e53](https://github.com/ory/kratos/commit/1266e533ac9a1f6ec375980cadce9755998f9fe6)) # [0.0.3-alpha.14](https://github.com/ory/kratos/compare/v0.0.3-alpha.13...v0.0.3-alpha.14) (2020-01-31) Update README.md ### Unclassified - Update README.md ([db8d65b](https://github.com/ory/kratos/commit/db8d65bf136223df546aa27f1ecff03d01159624)) # [0.0.3-alpha.13](https://github.com/ory/kratos/compare/v0.0.3-alpha.12...v0.0.3-alpha.13) (2020-01-31) Allow mounting SQLite in /home/ory/sqlite (#212) ### Unclassified - Allow mounting SQLite in /home/ory/sqlite (#212) ([2fe8c0f](https://github.com/ory/kratos/commit/2fe8c0f752e870028d68e8593a46c0902f673a65)), closes [#212](https://github.com/ory/kratos/issues/212) # [0.0.3-alpha.11](https://github.com/ory/kratos/compare/v0.0.3-alpha.10...v0.0.3-alpha.11) (2020-01-31) Clean up cmd and resolve packr2 issues (#211) This patch addresses issues with the build pipeline caused by an invalid import. Profiling was also added. ### Unclassified - Clean up cmd and resolve packr2 issues (#211) ([2e43ec0](https://github.com/ory/kratos/commit/2e43ec09e9d6aa572c4351bfef4c59dfc43f2343)), closes [#211](https://github.com/ory/kratos/issues/211): This patch addresses issues with the build pipeline caused by an invalid import. Profiling was also added. - Improve field types (#209) ([aeefa93](https://github.com/ory/kratos/commit/aeefa93bf0427685f6ffadad5abfaa1fc26ce074)), closes [#209](https://github.com/ory/kratos/issues/209) - Update CHANGELOG [ci skip] ([fc32207](https://github.com/ory/kratos/commit/fc32207482861b8f989cb1d6fe5d96bf34c54e4c)) # [0.0.3-alpha.10](https://github.com/ory/kratos/compare/v0.0.3-alpha.9...v0.0.3-alpha.10) (2020-01-31) Update README ### Unclassified - Update README ([35a310d](https://github.com/ory/kratos/commit/35a310d6de52fa74ad8728b1df67f88ce900aa61)) - Update CHANGELOG [ci skip] ([3c98745](https://github.com/ory/kratos/commit/3c987455a44b9e12e31619ba9f447e8a5feafc38)) - Update CHANGELOG [ci skip] ([c1c01df](https://github.com/ory/kratos/commit/c1c01df3a04fc7988bf847e3f31680112f5a642d)) # [0.0.3-alpha.7](https://github.com/ory/kratos/compare/v0.0.3-alpha.5...v0.0.3-alpha.7) (2020-01-30) Use correct project root in Dockerfile ### Unclassified - Use correct project root in Dockerfile ([3528758](https://github.com/ory/kratos/commit/352875878c74d15b522336b518df339c8ad48e49)) - Update CHANGELOG [ci skip] ([e78bbbe](https://github.com/ory/kratos/commit/e78bbbecbd9515c02e447efc3208599bf27ef85c)) # [0.0.3-alpha.5](https://github.com/ory/kratos/compare/v0.0.3-alpha.4...v0.0.3-alpha.5) (2020-01-30) ci: Resolve final docker build issues (#210) ### Continuous Integration - Resolve final docker build issues ([#210](https://github.com/ory/kratos/issues/210)) ([d703a1e](https://github.com/ory/kratos/commit/d703a1e328808df6761a9da5866a3f4df4c7923e)) ### Unclassified - Update CHANGELOG [ci skip] ([ebb1744](https://github.com/ory/kratos/commit/ebb1744d68b8a416774477182b1e2b2cd8bdfc43)) - Add libmusl to binary output ([e9b8445](https://github.com/ory/kratos/commit/e9b8445f2fc8e9e571ec0b8480cc70fe3251db9e)) # [0.0.3-alpha.4](https://github.com/ory/kratos/compare/v0.0.3-alpha.3...v0.0.3-alpha.4) (2020-01-30) Update CHANGELOG [ci skip] ### Unclassified - Update CHANGELOG [ci skip] ([018c229](https://github.com/ory/kratos/commit/018c229c4cff62e47c1154ca29ab9c70766a43e5)) - Add and use ory docker user ([cccbe09](https://github.com/ory/kratos/commit/cccbe09cc6e2ad72847206d46afe3e0bf7f79ab5)) - Update CHANGELOG [ci skip] ([0e436e5](https://github.com/ory/kratos/commit/0e436e57f79692c4c6e0a0c25f48a41654afcda1)) - Update goreleaser changelog filters ([7e5af97](https://github.com/ory/kratos/commit/7e5af97fded9f56a3cc9d1d92a7726e7b613b586)) - Update CHANGELOG [ci skip] ([4387503](https://github.com/ory/kratos/commit/438750326c5d6ad1569802c82806e831f43e785e)) # [0.0.3-alpha.2](https://github.com/ory/kratos/compare/v0.0.3-alpha.1...v0.0.3-alpha.2) (2020-01-30) Resolve goreleaser build issues (#208) ### Unclassified - Resolve goreleaser build issues (#208) ([d59a08a](https://github.com/ory/kratos/commit/d59a08a0ef680a984352d7f5068626cc1958185a)), closes [#208](https://github.com/ory/kratos/issues/208) # [0.0.3-alpha.1](https://github.com/ory/kratos/compare/v0.0.1-alpha.9...v0.0.3-alpha.1) (2020-01-30) Update CHANGELOG [ci skip] ### Unclassified - Update CHANGELOG [ci skip] ([49e09ea](https://github.com/ory/kratos/commit/49e09eaaab1fc681f9330e12ce6e5483c62ee9e3)) - Take form field orders from JSON Schema (#205) ([a880f0d](https://github.com/ory/kratos/commit/a880f0ddb52fb4366acf8fbd80aabaa9843445a9)), closes [#205](https://github.com/ory/kratos/issues/205) [#176](https://github.com/ory/kratos/issues/176) - Update CHANGELOG [ci skip] ([ff52bbb](https://github.com/ory/kratos/commit/ff52bbb264542b48658679bf5563b0f3b7ad73c7)) - Adapt quickstart docker compose config (#207) ([e532583](https://github.com/ory/kratos/commit/e532583b35a22cb39bbab0101bf86c0bf01b1088)), closes [#207](https://github.com/ory/kratos/issues/207) - Update CHANGELOG [ci skip] ([7f4800b](https://github.com/ory/kratos/commit/7f4800b07556e688ba0cd551438876b3bf23ace5)) - Update CHANGELOG [ci skip] ([1b2c3f6](https://github.com/ory/kratos/commit/1b2c3f645e64848e7fba6656aa730c7e346ed75d)) - Rework public and admin fetch strategy (#203) ([99aa169](https://github.com/ory/kratos/commit/99aa1693e758f706f264c2439594e2be37ae9bc6)), closes [#203](https://github.com/ory/kratos/issues/203) [#122](https://github.com/ory/kratos/issues/122) - Update CHANGELOG [ci skip] ([1cea427](https://github.com/ory/kratos/commit/1cea42780a95d4ebf5520e1c1803fb13ef596d52)) - ss/profile: Use request ID as query param everywhere (#202) ([ed32b14](https://github.com/ory/kratos/commit/ed32b14f8ea972cf549480f29cbf1b95d010789c)), closes [#202](https://github.com/ory/kratos/issues/202) [#190](https://github.com/ory/kratos/issues/190) - Update CHANGELOG [ci skip] ([a392027](https://github.com/ory/kratos/commit/a3920278129399ce576c5336c2e50dd015b8f2f8)) - Update HTTP routes for a consistent API naming (#199) ([9ed4bda](https://github.com/ory/kratos/commit/9ed4bda9f0b0d45e8ac0de0c42b78f717f3d92f3)), closes [#199](https://github.com/ory/kratos/issues/199) [#195](https://github.com/ory/kratos/issues/195) # [0.0.1-alpha.9](https://github.com/ory/kratos/compare/v0.0.1-alpha.11...v0.0.1-alpha.9) (2020-01-29) ci: Bump goreleaser orb ### Continuous Integration - Bump goreleaser orb ([29cd754](https://github.com/ory/kratos/commit/29cd754d33ec2f800730bd007f17fc0ce53a51eb)) # [0.0.2-alpha.1](https://github.com/ory/kratos/compare/v0.0.1-alpha.8...v0.0.2-alpha.1) (2020-01-29) Use correct build archive for homebrew ### Unclassified - Use correct build archive for homebrew ([74ac29f](https://github.com/ory/kratos/commit/74ac29f43f2937cad9065ad3c03cf3cf909cff42)) # [0.0.1-alpha.6](https://github.com/ory/kratos/compare/v0.0.1-alpha.5...v0.0.1-alpha.6) (2020-01-29) ci: Bump goreleaser orb ### Continuous Integration - Bump goreleaser orb ([018c94c](https://github.com/ory/kratos/commit/018c94ccc9e833f28f827fd10d607a7a1c954ac5)) # [0.0.1-alpha.5](https://github.com/ory/kratos/compare/v0.0.1-alpha.3...v0.0.1-alpha.5) (2020-01-29) ci: Bump goreleaser dependency ### Continuous Integration - Bump goreleaser dependency ([ec49bfb](https://github.com/ory/kratos/commit/ec49bfb4b636a72e51d3a68521ba047f97d4c5e6)) ### Unclassified - Resolve build issues with CGO (#196) ([298f4ea](https://github.com/ory/kratos/commit/298f4ea85b3e7405929f481b756efe8c5c133479)), closes [#196](https://github.com/ory/kratos/issues/196) - ss/password: Make form fields an array (#197) ([6cb0058](https://github.com/ory/kratos/commit/6cb005860755ff897ad847f09af50bc911bbc7f0)), closes [#197](https://github.com/ory/kratos/issues/197) [#186](https://github.com/ory/kratos/issues/186) # [0.0.1-alpha.3](https://github.com/ory/kratos/compare/ab6f24a85276bdd8687f2fc06390c1279892b005...v0.0.1-alpha.3) (2020-01-28) ci: Only compile goarmv7 ### Continuous Integration - Only compile goarmv7 ([d8e7ec7](https://github.com/ory/kratos/commit/d8e7ec788d1b43bcbbe221becde3432fdbf28e9b)) ### Documentation - Present ORY Hive to the world ([#107](https://github.com/ory/kratos/issues/107)) ([7883589](https://github.com/ory/kratos/commit/78835897664a5ab5564751fc9f04172f7d20d572)) - Updates issue and pull request templates ([0441dff](https://github.com/ory/kratos/commit/0441dffe0c439cc54214bf9ee8f4a4bd25206999)) - Updates issue and pull request templates ([#174](https://github.com/ory/kratos/issues/174)) ([ad405e9](https://github.com/ory/kratos/commit/ad405e9037e2db2910a012f414556fea672e732a)) - Updates issue and pull request templates ([#39](https://github.com/ory/kratos/issues/39)) ([daf5aa8](https://github.com/ory/kratos/commit/daf5aa89c717de6176ee25119d2e751ae2ef6558)) - Updates issue and pull request templates ([#40](https://github.com/ory/kratos/issues/40)) ([f5907f3](https://github.com/ory/kratos/commit/f5907f3f248e05511b19ff6dc15bf6f60f8b62da)) - Updates issue and pull request templates ([#59](https://github.com/ory/kratos/issues/59)) ([8c5612c](https://github.com/ory/kratos/commit/8c5612c080e5b7531028b778b86cc4cde2abd516)) - Updates issue and pull request templates ([#7](https://github.com/ory/kratos/issues/7)) ([a1220ba](https://github.com/ory/kratos/commit/a1220ba1e950498a6e9594266dc730c9a8731b49)) - Updates issue and pull request templates ([#8](https://github.com/ory/kratos/issues/8)) ([c56798a](https://github.com/ory/kratos/commit/c56798ab29e72ed308fff840e3b1b98ead19aea6)) ### Unclassified - Remove redundant return statement ([7c2989f](https://github.com/ory/kratos/commit/7c2989f52c090bb9900380b4ec74e04d9c37a441)) - ss/oidc: Remove obsolete request field from form (#193) ([59671ba](https://github.com/ory/kratos/commit/59671badb63009e2440b14868b622adc75cf882f)), closes [#193](https://github.com/ory/kratos/issues/193) [#180](https://github.com/ory/kratos/issues/180) - strategy/oidc: Allow multiple OIDC Connections (#191) ([8984831](https://github.com/ory/kratos/commit/898483137ff9dc47d65750cd94a973f2e5bee770)), closes [#191](https://github.com/ory/kratos/issues/191) [#114](https://github.com/ory/kratos/issues/114) - Improve Docker Compose Quickstart (#187) ([9459072](https://github.com/ory/kratos/commit/945907297ded4b18e1bd0e7c9824a975ac7395c6)), closes [#187](https://github.com/ory/kratos/issues/187) [#188](https://github.com/ory/kratos/issues/188) - selfservice/password: Remove request field and ensure method is set (#183) ([e035adc](https://github.com/ory/kratos/commit/e035adc233198e9b5c9a6e08d442fb5fb3290816)), closes [#183](https://github.com/ory/kratos/issues/183) - Add tests and fixtures for the config JSON Schema (#171) ([ede9c0e](https://github.com/ory/kratos/commit/ede9c0e9c45ee91e60587311dc18a0a04ff62295)), closes [#171](https://github.com/ory/kratos/issues/171) - Add example values for config JSON Schema ([12ba728](https://github.com/ory/kratos/commit/12ba7283bf879cd7682d3017c3b3f12e49029d6b)) - Replace `url` with `uri` format in config JSON Schema ([68eddef](https://github.com/ory/kratos/commit/68eddef0cf179bf61abb999d84d2af19c3703c80)) - Replace number with integer in config JSON Schema (#177) ([9eff6fd](https://github.com/ory/kratos/commit/9eff6fd09720b11acae089ebfcaf37288bc031b0)), closes [#177](https://github.com/ory/kratos/issues/177) - Improve `--dev` flag (#167) ([9b61ee1](https://github.com/ory/kratos/commit/9b61ee10bbb4710d6694addfa60c04313855516f)), closes [#167](https://github.com/ory/kratos/issues/167) [#162](https://github.com/ory/kratos/issues/162) - Add goreleaser orb task (#170) ([5df0def](https://github.com/ory/kratos/commit/5df0defefc95ced289a9c59a4f5deb3c67446e75)), closes [#170](https://github.com/ory/kratos/issues/170) - Add changelog generation task (#169) ([edd937c](https://github.com/ory/kratos/commit/edd937c21b7e37b2f2e926f0fe62c2e7d4a7d608)), closes [#169](https://github.com/ory/kratos/issues/169) - Adopt new SDK pipeline (#168) ([21d9b6d](https://github.com/ory/kratos/commit/21d9b6d27adbfe8504fb46ac95952e7cea239085)), closes [#168](https://github.com/ory/kratos/issues/168) - Add docker-compose quickstart (#153) ([e096190](https://github.com/ory/kratos/commit/e096190e778f22573e30f35e85b7cf147caf851b)), closes [#153](https://github.com/ory/kratos/issues/153) - Update README (#160) ([533775b](https://github.com/ory/kratos/commit/533775ba78a2c1758c47ed093da6acc18ab951c2)), closes [#160](https://github.com/ory/kratos/issues/160) - Separate post register/login hooks (#150) ([f4b7812](https://github.com/ory/kratos/commit/f4b78122d9cbe4dcc05b4fd52d94a2d9f1b16eb2)), closes [#150](https://github.com/ory/kratos/issues/150) [#149](https://github.com/ory/kratos/issues/149) - Update README badges ([4f7838e](https://github.com/ory/kratos/commit/4f7838e69181c5a10e27cde1e241779e4e724909)) - Bump go-acc and resolve test issues (#154) ([15b1b63](https://github.com/ory/kratos/commit/15b1b630c5363e0e1afbed53285b3f39098c0792)), closes [#154](https://github.com/ory/kratos/issues/154) [#152](https://github.com/ory/kratos/issues/152) [#151](https://github.com/ory/kratos/issues/151): Due to a bug in `go-acc`, tests would not run if `-tags sqlite` was supplied as a go tool argument to `go-acc`. This patch resolves that issue and also includes several test patches from previous community PRs and some internal test issues. - Add ORY Kratos banner to README (#145) ([23b824f](https://github.com/ory/kratos/commit/23b824f7f99efbc23787508c03506e73a3240a2a)), closes [#145](https://github.com/ory/kratos/issues/145) - Replace DBAL layer with gobuffalo/pop (#130) ([21d08b8](https://github.com/ory/kratos/commit/21d08b84560230d8a063a418a74efcf53c146872)), closes [#130](https://github.com/ory/kratos/issues/130): This is a major refactoring of the internal DBAL. After a successful proof of concept and evaluation of gobuffalo/pop, we believe this to be the best DBAL for Go at the moment. It abstracts a lot of boilerplate code away. As with all sophisticated DBALs, pop too has its quirks. There are several issues that have been discovered during testing and adoption: https://github.com/gobuffalo/pop/issues/136 https://github.com/gobuffalo/pop/issues/476 https://github.com/gobuffalo/pop/issues/473 https://github.com/gobuffalo/pop/issues/469 https://github.com/gobuffalo/pop/issues/466 However, the upside of moving much of the hard database/sql plumbing into another library cleans up the code base significantly and reduces complexity. As part of this change, the "ephermal" DBAL ("in memory") will be removed and sqlite will be used instead. This further reduces complexity of the code base and code-duplication. To support sqlite, CGO is required, which means that we need to run tests with `go test -tags sqlite` on a machine that has g++ installed. This also means that we need a Docker Image with `alpine` as opposed to pure `scratch`. While this is certainly a downside, the upside of less maintenance and "free" support for SQLite, PostgreSQL, MySQL, and CockroachDB simply outweighs any downsides that come with CGO. - Replace local deps with remote ones ([8605e45](https://github.com/ory/kratos/commit/8605e454cf538e047c5a9c3479372892d6b3f483)) - ss/profile: Improve success and error flows ([9e0015a](https://github.com/ory/kratos/commit/9e0015acec7f8d927498e48366b377e22ec768b7)), closes [#112](https://github.com/ory/kratos/issues/112): This patch completes the profile management flow by implementing proper error and success states and adding several data integrity tests. - Rebrand ORY Hive to ORY Kratos (#111) ([ceda7fb](https://github.com/ory/kratos/commit/ceda7fb3472b081f0c6066aa1f282d4ec1787f7b)), closes [#111](https://github.com/ory/kratos/issues/111) - Fix broken tests and ci linter issues (#104) ([69760fe](https://github.com/ory/kratos/commit/69760fe9fecb2f302dd5c1821185ea990f4e411c)), closes [#104](https://github.com/ory/kratos/issues/104) - Update to Go modules 1.13 ([1da4d75](https://github.com/ory/kratos/commit/1da4d757bc2434f97c588e395305066edce9ef0d)) - Resolve minor configuration issues and response errors (#85) ([a44913b](https://github.com/ory/kratos/commit/a44913b26b515333576def6b882861ff2c8d4aff)), closes [#85](https://github.com/ory/kratos/issues/85) - Clean up dead files (#84) ([e0c96ef](https://github.com/ory/kratos/commit/e0c96effbee2521b12eeedc851b67fa3a1ae41c8)), closes [#84](https://github.com/ory/kratos/issues/84) - Add health endpoints (#83) ([0e936f7](https://github.com/ory/kratos/commit/0e936f7047bb9eacae0c5107360ce752a23d8282)), closes [#83](https://github.com/ory/kratos/issues/83) [#82](https://github.com/ory/kratos/issues/82) - Update Dockerfile and related build tools (#80) ([d20c701](https://github.com/ory/kratos/commit/d20c701433cea916d3df4863846cf09743150966)), closes [#80](https://github.com/ory/kratos/issues/80) - Implement SQL Database adapter (#79) ([86d07c4](https://github.com/ory/kratos/commit/86d07c4a9e3b3e6607e73f4d54b4e7b9f0382e59)), closes [#79](https://github.com/ory/kratos/issues/79) [#69](https://github.com/ory/kratos/issues/69) - Prevent duplicate signups (#76) ([4c88968](https://github.com/ory/kratos/commit/4c88968a6853396755f61db2673a0cb2201868f7)), closes [#76](https://github.com/ory/kratos/issues/76) [#46](https://github.com/ory/kratos/issues/46) - Contributing 08 10 19 00 52 45 (#74) ([43b511f](https://github.com/ory/kratos/commit/43b511f1a43be114ac04b377434b22ec8afe465b)), closes [#74](https://github.com/ory/kratos/issues/74) - Echo form values from oidc signup ([98b1da5](https://github.com/ory/kratos/commit/98b1da5f59d5dcde4416b74ea323af3e29fefa75)), closes [#71](https://github.com/ory/kratos/issues/71) - Properly decode values in error handler ([5eb9088](https://github.com/ory/kratos/commit/5eb9088efb291256d65fadbd5a803369cc96bdd2)), closes [#71](https://github.com/ory/kratos/issues/71) - Force path and domain on CSRF cookie (#70) ([a80d8b0](https://github.com/ory/kratos/commit/a80d8b0e0bb16fce530559826de29fd6b9836873)), closes [#70](https://github.com/ory/kratos/issues/70) [#68](https://github.com/ory/kratos/issues/68) - Require no session when accessing login or sign up (#67) ([c0e0da1](https://github.com/ory/kratos/commit/c0e0da1b38ebadaa33eb5b59dc566731b3320b70)), closes [#67](https://github.com/ory/kratos/issues/67) [#63](https://github.com/ory/kratos/issues/63) - Add tests for selfservice ErrorHandler (#62) ([4bb9e70](https://github.com/ory/kratos/commit/4bb9e7086ee57c4eb1a73fea436c7b2dec0257b7)), closes [#62](https://github.com/ory/kratos/issues/62) - Enable Circle CI (#57) ([6fb0afd](https://github.com/ory/kratos/commit/6fb0afd30e3755026b6ffca0cc80f2fe00267681)), closes [#57](https://github.com/ory/kratos/issues/57) [#53](https://github.com/ory/kratos/issues/53) - OIDC provider selfservice data enrichment (#56) ([936970a](https://github.com/ory/kratos/commit/936970a9abaadeab5c191ff52218bf4f65af2220)), closes [#56](https://github.com/ory/kratos/issues/56) [#23](https://github.com/ory/kratos/issues/23) [#55](https://github.com/ory/kratos/issues/55) - Remove local jsonschema module override ([cd2a5d8](https://github.com/ory/kratos/commit/cd2a5d8c74b21b122f5d5437702d8c74fb1cb726)) - Implement identity management, login, and registration (#22) ([bf3395e](https://github.com/ory/kratos/commit/bf3395ea34ecf85303034f3e941a049c8cbd6229)), closes [#22](https://github.com/ory/kratos/issues/22) - Revert incorrect license changes ([fb9740b](https://github.com/ory/kratos/commit/fb9740b37a94dbdde1a8f4433fb7e5a8b4dac295)) - Create FUNDING.yml ([3c67ac8](https://github.com/ory/kratos/commit/3c67ac83f58c5b03dc3935d279083268b8a85e0d)) - Initial commit ([ab6f24a](https://github.com/ory/kratos/commit/ab6f24a85276bdd8687f2fc06390c1279892b005)) - Add ability to define multiple schemas and serve them over HTTP ([#164](https://github.com/ory/kratos/issues/164)) ([c65119c](https://github.com/ory/kratos/commit/c65119c24378dabd306e5a49f89c28c0367f7c2e)), closes [#86](https://github.com/ory/kratos/issues/86): All identity traits schemas have to be configured using a human readable ID and the corresponding URL. This PR enables multiple schemas to be used next to the default schema. It also adds the kratos.public/schemas/:id endpoint that mirrors all schemas. - Add helper for requiring authentication ([3888fbd](https://github.com/ory/kratos/commit/3888fbdc239b7a06c7fca34d08de7d55af69a48c)) - Add helpers for go-swagger ([165a660](https://github.com/ory/kratos/commit/165a660f277588ed572d7843354c207f72f1678d)): See https://github.com/go-swagger/go-swagger/issues/2119 - Add profile management and refactor internals ([3ec9263](https://github.com/ory/kratos/commit/3ec9263f597a5949d0de6d10073cc626cfcfcca4)), closes [#112](https://github.com/ory/kratos/issues/112) - Add session destroyer hook ([#148](https://github.com/ory/kratos/issues/148)) ([d17f002](https://github.com/ory/kratos/commit/d17f002cdfe1f11ebb6bcbb17f6976aa329eab4a)), closes [#139](https://github.com/ory/kratos/issues/139): This patch adds a hook that destroys all active session by the identity which is being logged in. This can be useful in scenarios where only one session should be active at any given time. - Add SQL adapter ([#100](https://github.com/ory/kratos/issues/100)) ([9e7f998](https://github.com/ory/kratos/commit/9e7f99871e3f09e7ae9ec1c38c8b8cf94d076f45)), closes [#92](https://github.com/ory/kratos/issues/92) - Explicitly whitelist form parser keys ([#105](https://github.com/ory/kratos/issues/105)) ([28b056e](https://github.com/ory/kratos/commit/28b056e5bbfec645262914c52f0386d70c787a32)), closes [#98](https://github.com/ory/kratos/issues/98): Previously the form parser would try to detect the field type by asserting types for the whole form. That caused passwords containing only numbers to fail to unmarshal into a string value. This patch resolves that issue by introducing a prefix option to the BodyParser - Fix broken import ([308aa13](https://github.com/ory/kratos/commit/308aa1334dd43bc4bebade4e70e9c81c83fe8806)) - Handle securecookie errors appropriately ([#101](https://github.com/ory/kratos/issues/101)) ([75bf6fe](https://github.com/ory/kratos/commit/75bf6fe3f79d025f2aaa79d06db39c26430dc3fc)), closes [#97](https://github.com/ory/kratos/issues/97): Previously, IsNotAuthenticated would not handle securecookie errors appropriately. This has been resolved. - Implement CRUD for identities ([#60](https://github.com/ory/kratos/issues/60)) ([58a3c24](https://github.com/ory/kratos/commit/58a3c240fca66e1195bf310024a2f8473826bce6)), closes [#58](https://github.com/ory/kratos/issues/58) - Implement message templates and SMTP delivery ([#146](https://github.com/ory/kratos/issues/146)) ([dc674bf](https://github.com/ory/kratos/commit/dc674bfa7d1fa9ee94b014d09866bbdc0a97c321)), closes [#99](https://github.com/ory/kratos/issues/99): This patch adds a message templates (with override capabilities) and SMTP delivery. Integration tests using MailHog test fault resilience and e2e email delivery. This system is designed to be extended for SMS and other use cases. - Improve migration command ([#94](https://github.com/ory/kratos/issues/94)) ([2b631de](https://github.com/ory/kratos/commit/2b631de6d621dcebac5318f6dd628646fec7712f)) - Inject Identity Traits JSON Schema ([3a4c5ad](https://github.com/ory/kratos/commit/3a4c5ad35f885c7d38ffcf1d5836fb485f122fe9)), closes [#189](https://github.com/ory/kratos/issues/189) - Mark active field as nullable ([#89](https://github.com/ory/kratos/issues/89)) ([292702d](https://github.com/ory/kratos/commit/292702d9e031e43c63e0ecb59354557139499e87)) - Move package to selfservice ([063b767](https://github.com/ory/kratos/commit/063b7679af76333fc546e94e92b197079e5bdb30)): Because this module is primarily used in selfservice scenarios, it has been moved to the selfservice parent. - Omit request header from login/registration request ([#106](https://github.com/ory/kratos/issues/106)) ([9b07587](https://github.com/ory/kratos/commit/9b07587f2de2b270c5c326e37b2b6b3dbbfa8595)), closes [#95](https://github.com/ory/kratos/issues/95): When fetching a login and registration request, the HTTP Request Headers must not be included in the response, as they contain irrelevant information for the API caller. - Properly handle empty credentials config in sql ([#93](https://github.com/ory/kratos/issues/93)) ([b79c5d1](https://github.com/ory/kratos/commit/b79c5d1d5216e994f986ce739285cb1a89523df5)) - Re-introduce migration plans to CLI command ([#192](https://github.com/ory/kratos/issues/192)) ([bb32cd3](https://github.com/ory/kratos/commit/bb32cd3cad3cd0bd6f3166de0166701e1f676ac6)), closes [#131](https://github.com/ory/kratos/issues/131) - Reset CSRF token on principal change ([#64](https://github.com/ory/kratos/issues/64)) ([9c889ab](https://github.com/ory/kratos/commit/9c889ab4f6c846812a4290545fef7d8106da35f0)), closes [#38](https://github.com/ory/kratos/issues/38): Add tests for logout. - Resolve wrong column reference in sql ([#90](https://github.com/ory/kratos/issues/90)) ([0c0eb87](https://github.com/ory/kratos/commit/0c0eb87cd341bd3e73eb9adb303054b38c103ba9)): Reference ic.method instead of ici.method. Added regression tests against this particular issue. - Update keyword from kratos to ory.sh/kratos ([f45cbe0](https://github.com/ory/kratos/commit/f45cbe0339db8d129522314f3099e6944e4a6ea3)), closes [#115](https://github.com/ory/kratos/issues/115) - Update sdk generation method ([24aa3d7](https://github.com/ory/kratos/commit/24aa3d73354d5a28f05999a09e7bbbe51a44d44e)) - Update to ory/x 0.0.80 ([#110](https://github.com/ory/kratos/issues/110)) ([64de2f8](https://github.com/ory/kratos/commit/64de2f86540bf8715a1703d773fa95011603a854)): Removes the need for BindEnv() - Use JSON Schema to type assert form body ([#116](https://github.com/ory/kratos/issues/116)) ([1944c7c](https://github.com/ory/kratos/commit/1944c7c6e82b5b6a3b9d47db94c8f8f45248feb7)), closes [#109](https://github.com/ory/kratos/issues/109) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Open Source Community Support Ory Open source software is collaborative and based on contributions by developers in the Ory community. There is no obligation from Ory to help with individual problems. If Ory open source software is used in production in a for-profit company or enterprise environment, we mandate a paid support contract where Ory is obligated under their service level agreements (SLAs) to offer a defined level of availability and responsibility. For more information about paid support please contact us at sales@ory.com. ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [office@ory.com](mailto:office@ory.com). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][mozilla coc]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][faq]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [mozilla coc]: https://github.com/mozilla/diversity [faq]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: CONTRIBUTING.md ================================================ # Contribute to Ory Kratos - [Introduction](#introduction) - [FAQ](#faq) - [How can I contribute?](#how-can-i-contribute) - [Communication](#communication) - [Contribute examples or community projects](#contribute-examples-or-community-projects) - [Contribute code](#contribute-code) - [Contribute documentation](#contribute-documentation) - [Disclosing vulnerabilities](#disclosing-vulnerabilities) - [Code style](#code-style) - [Working with forks](#working-with-forks) - [Conduct](#conduct) ## Introduction _Please note_: We take Ory Kratos's security and our users' trust very seriously. If you believe you have found a security issue in Ory Kratos, please disclose it by contacting us at security@ory.com. There are many ways in which you can contribute. The goal of this document is to provide a high-level overview of how you can get involved in Ory. As a potential contributor, your changes and ideas are welcome at any hour of the day or night, on weekdays, weekends, and holidays. Please do not ever hesitate to ask a question or send a pull request. If you are unsure, just ask or submit the issue or pull request anyways. You won't be yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contributions and don't want a wall of rules to get in the way of that. That said, if you want to ensure that a pull request is likely to be merged, talk to us! You can find out our thoughts and ensure that your contribution won't clash with Ory Kratos's direction. A great way to do this is via [Ory Kratos Discussions](https://github.com/ory/kratos/discussions) or the [Ory Chat](https://www.ory.com/chat). ## FAQ - I am new to the community. Where can I find the [Ory Community Code of Conduct?](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md) - I have a question. Where can I get [answers to questions regarding Ory Kratos?](#communication) - I would like to contribute but I am not sure how. Are there [easy ways to contribute?](#how-can-i-contribute) [Or good first issues?](https://github.com/search?l=&o=desc&q=label%3A%22help+wanted%22+label%3A%22good+first+issue%22+is%3Aopen+user%3Aory+user%3Aory-corp&s=updated&type=Issues) - I want to talk to other Ory Kratos users. [How can I become a part of the community?](#communication) - I would like to know what I am agreeing to when I contribute to Ory Kratos. Does Ory have [a Contributors License Agreement?](https://cla-assistant.io/ory/kratos) - I would like updates about new versions of Ory Kratos. [How are new releases announced?](https://www.ory.com/l/sign-up-newsletter) ## How can I contribute? If you want to start to contribute code right away, take a look at the [list of good first issues](https://github.com/ory/kratos/labels/good%20first%20issue). There are many other ways you can contribute. Here are a few things you can do to help out: - **Give us a star.** It may not seem like much, but it really makes a difference. This is something that everyone can do to help out Ory Kratos. Github stars help the project gain visibility and stand out. - **Join the community.** Sometimes helping people can be as easy as listening to their problems and offering a different perspective. Join our Slack, have a look at discussions in the forum and take part in community events. More info on this in [Communication](#communication). - **Answer discussions.** At all times, there are several unanswered discussions on GitHub. You can see an [overview here](https://github.com/discussions?discussions_q=is%3Aunanswered+org%3Aory+sort%3Aupdated-desc). If you think you know an answer or can provide some information that might help, please share it! Bonus: You get GitHub achievements for answered discussions. - **Help with open issues.** We have a lot of open issues for Ory Kratos and some of them may lack necessary information, some are duplicates of older issues. You can help out by guiding people through the process of filling out the issue template, asking for clarifying information or pointing them to existing issues that match their description of the problem. - **Review documentation changes.** Most documentation just needs a review for proper spelling and grammar. If you think a document can be improved in any way, feel free to hit the `edit` button at the top of the page. More info on contributing to the documentation [here](#contribute-documentation). - **Help with tests.** Pull requests may lack proper tests or test plans. These are needed for the change to be implemented safely. ## Communication We use [Slack](https://www.ory.com/chat). You are welcome to drop in and ask questions, discuss bugs and feature requests, talk to other users of Ory, etc. Check out [Ory Kratos Discussions](https://github.com/ory/kratos/discussions). This is a great place for in-depth discussions and lots of code examples, logs and similar data. You can also join our community calls if you want to speak to the Ory team directly or ask some questions. You can find more info and participate in [Slack](https://www.ory.com/chat) in the #community-call channel. If you want to receive regular notifications about updates to Ory Kratos, consider joining the mailing list. We will _only_ send you vital information on the projects that you are interested in. Also, [follow us on Twitter](https://twitter.com/orycorp). ## Contribute examples or community projects One of the most impactful ways to contribute is by adding code examples or other Ory-related code. You can find an overview of community code in the [awesome-ory](https://github.com/ory/awesome-ory) repository. _If you would like to contribute a new example, we would love to hear from you!_ Please [open a pull request at awesome-ory](https://github.com/ory/awesome-ory/) to add your example or Ory-related project to the awesome-ory README. ## Contribute code **All code contributions require prior discussion and agreement with maintainers before opening a pull request.** This applies to bug fixes, new features, and refactoring. Before writing code: 1. Open a [Discussion](https://github.com/ory/kratos/discussions/new/choose) or [Issue](https://github.com/ory/kratos/issues/new/choose) describing the problem and your proposed solution. 2. Wait for maintainer feedback and explicit agreement on the implementation approach. 3. Only then begin writing code. Pull requests without prior discussion may be closed without review. This policy exists to protect your time and maintainer time. "Drive-by" PRs, even well-intentioned ones, often conflict with the project roadmap, duplicate ongoing work, or introduce architectural inconsistencies. A quick conversation can help avoid these problems. All contributions are made via pull requests. To make a pull request, you will need a GitHub account; if you are unclear on this process, see GitHub's documentation on [forking](https://help.github.com/articles/fork-a-repo) and [pull requests](https://help.github.com/articles/using-pull-requests). Pull requests should be targeted at the `master` branch. Before creating a pull request, go through this checklist: 1. Create a feature branch off of `master` so that changes do not get mixed up. 1. [Rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) your local changes against the `master` branch. 1. Run the full project test suite with the `go test -tags sqlite ./...` (or equivalent) command and confirm that it passes. 1. Run `make format` 1. Add a descriptive prefix to commits. This ensures a uniform commit history and helps structure the changelog. Please refer to this [Convential Commits configuration](https://github.com/ory/kratos/blob/master/.github/workflows/conventional_commits.yml) for the list of accepted prefixes. You can read more about the Conventional Commit specification [at their site](https://www.conventionalcommits.org/en/v1.0.0/). If a pull request is not ready to be reviewed yet [it should be marked as a "Draft"](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request). Before your contributions can be reviewed you need to sign our [Contributor License Agreement](https://cla-assistant.io/ory/kratos). This agreement defines the terms under which your code is contributed to Ory. More specifically it declares that you have the right to, and actually do, grant us the rights to use your contribution. You can see the Apache 2.0 license under which our projects are published [here](https://github.com/ory/meta/blob/master/LICENSE). When pull requests fail the automated testing stages (for example unit or E2E tests), authors are expected to update their pull requests to address the failures until the tests pass. Pull requests eligible for review 1. follow the repository's code formatting conventions; 2. include tests that prove that the change works as intended and does not add regressions; 3. document the changes in the code and/or the project's documentation; 4. pass the CI pipeline; 5. have signed our [Contributor License Agreement](https://cla-assistant.io/ory/kratos); 6. include a proper git commit message following the [Conventional Commit Specification](https://www.conventionalcommits.org/en/v1.0.0/). If all of these items are checked, the pull request is ready to be reviewed and you should change the status to "Ready for review" and [request review from a maintainer](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). Reviewers will approve the pull request once they are satisfied with the patch. ### AI-assisted contributions AI can be a valuable aid for writing code, documentation, and tests. However, to maintain the quality and reliability of Ory Kratos, please follow these guidelines: - When submitting a pull request or issue that involved AI assistance, mention the tools you used and the extent of their involvement. This helps reviewers understand the context of your contribution. - Pull requests created with AI assistance should address an existing, accepted issue. If you have an idea for a new feature or improvement, please open a discussion or issue first to ensure alignment with the project's direction before investing time in implementation. - You must use AI responsibly when writing code. All contributions must be tested and verified before submission. Unreviewed AI-generated code will not be accepted, and repeated submissions of this nature may result in restricted contribution privileges. - When using AI to help draft issues, discussions, or documentation, review and edit the output before submitting. AI tends to be verbose. Trim unnecessary content and ensure your submission is clear and focused. Contributors must use AI responsibly. These guidelines exist to ensure that every contribution meets the high standards our community expects, while still embracing the productivity benefits that AI tools can provide. ## Contribute documentation Please provide documentation when changing, removing, or adding features. All Ory Documentation resides in the [Ory documentation repository](https://github.com/ory/docs/). For further instructions please head over to the Ory Documentation [README.md](https://github.com/ory/docs/blob/master/README.md). ## Disclosing vulnerabilities Please disclose vulnerabilities exclusively to [security@ory.com](mailto:security@ory.com). Do not use GitHub issues. ## Code style Please run `make format` to format all source code following the Ory standard. ### Working with forks ```bash # First you clone the original repository git clone git@github.com:ory/ory/kratos.git # Next you add a git remote that is your fork: git remote add fork git@github.com:/ory/kratos.git # Next you fetch the latest changes from origin for master: git fetch origin git checkout master git pull --rebase # Next you create a new feature branch off of master: git checkout my-feature-branch # Now you do your work and commit your changes: git add -A git commit -a -m "fix: this is the subject line" -m "This is the body line. Closes #123" # And the last step is pushing this to your fork git push -u fork my-feature-branch ``` Now go to the project's GitHub Pull Request page and click "New pull request" ## Conduct Whether you are a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. [Ory Community Code of Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md) We welcome discussion about creating a welcoming, safe, and productive environment for the community. If you have any questions, feedback, or concerns [please let us know](https://www.ory.com/chat). ================================================ FILE: DEVELOP.md ================================================ # Development This document explains how to develop Ory Kratos, run tests, and work with the tooling around it. ## Upgrading and changelog Check [releases tab](https://github.com/ory/kratos/releases) for updates and changelogs when using the open source license. ## Command line documentation To see available commands and flags, run: ```bash kratos -h # or kratos help ``` ## Contribution guidelines We encourage all contributions. Before opening a pull request, read the [contribution guidelines](./CONTRIBUTING.md). ## Prerequisites You need Go 1.16+ and, for the test suites: * Docker and Docker Compose * `make` * Node.js and npm You can develop Ory Kratos on Windows, but most guides assume a Unix shell such as `bash` or `zsh`. ## Install from source To install Kratos from source: ```make make install ``` ## Formatting code Format all code using: ```make make format ``` The continuous integration pipeline checks code formatting. ## Running tests There are three types of tests: * Short tests that do not require a SQL database * Regular tests that require PostgreSQL, MySQL, and CockroachDB * End to end tests that use real databases and a test browser ### Short tests Short tests run quickly and use SQLite. Run all short tests: ```bash go test -short -tags sqlite ./... ``` Run short tests in a specific module: ```bash cd client go test -short -tags sqlite . ``` ### Regular tests Regular tests require a database setup. The test suite can start databases using [ory/dockertest](https://github.com/ory/dockertest). In practice, it is usually easier and faster to use the Makefile targets. Run the full test suite: ```make make test ``` > Note: `make test` recreates the databases every time. This can be slow if you > are iterating frequently on a specific test. If you want to reuse databases across test runs, initialize them once: ```bash make test-resetdb export TEST_DATABASE_MYSQL='mysql://root:secret@(127.0.0.1:3444)/mysql?parseTime=true' export TEST_DATABASE_POSTGRESQL='postgres://postgres:secret@127.0.0.1:3445/kratos?sslmode=disable' export TEST_DATABASE_COCKROACHDB='cockroach://root@127.0.0.1:3446/defaultdb?sslmode=disable' ``` Then you can run Go tests directly as often as needed: ```bash go test -tags sqlite ./... # or in a module: cd client go test -tags sqlite . ``` ### Updating test fixtures Some tests use snapshot fixtures. Update snapshots for short tests: ```bash make test-update-snapshots ``` Update all snapshots: ```bash UPDATE_SNAPSHOTS=true go test -p 4 -tags sqlite ./... ``` You can run this from the repository root or from subdirectories. ### End-to-end tests End to end tests are implemented with [Cypress](https://www.cypress.io). > On ARM based Macs you may need to install Rosetta 2 to run Cypress. > See the Cypress documentation: > [https://www.cypress.io/blog/2021/01/20/running-cypress-on-the-apple-m1-silicon-arm-architecture-using-rosetta-2/](https://www.cypress.io/blog/2021/01/20/running-cypress-on-the-apple-m1-silicon-arm-architecture-using-rosetta-2/) To install Rosetta 2: ```bash softwareupdate --install-rosetta --agree-to-license ``` Run e2e tests in development mode: ```bash ./test/e2e/run.sh --dev sqlite ``` Run all e2e tests with databases: ```make make test-e2e ``` For more options: ```bash ./test/e2e/run.sh ``` #### Run a single test Add `.only` to the test you want to run, for example: ```ts it.only('invalid remote recovery email template', () => { // ... }) ``` #### Run a subset of tests To run a subset of e2e tests: 1. Edit `cypress.json` in `test/e2e/`. 2. Add the `testFiles` option and point it to the specs you want, for example: ```json "testFiles": ["profiles/network/*"] ``` 3. Start the tests again using the run script or Makefile. ## Build Docker image To build a development Docker image: ```make make docker ``` ## Preview API documentation To work on and preview the generated API documentation: 1. Update the SDK including the OpenAPI specification: ```make make sdk ``` 2. Run the preview server for API documentation: ```make make docs/api ``` 3. Run the preview server for Swagger documentation: ```make make docs/swagger ``` ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ SHELL=/usr/bin/env bash -o pipefail # EXECUTABLES = docker-compose docker node npm go # K := $(foreach exec,$(EXECUTABLES),\ # $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH"))) export PATH := .bin:${PATH} export PWD := $(shell pwd) export BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") export VCS_REF := $(shell git rev-parse HEAD) export QUICKSTART_OPTIONS ?= export IMAGE_TAG := $(if $(IMAGE_TAG),$(IMAGE_TAG),latest) .bin/clidoc: echo "deprecated usage, use docs/cli instead" go build -o .bin/clidoc ./cmd/clidoc/. .PHONY: docs/cli docs/cli: go run ./cmd/clidoc/. . .PHONY: docs/api docs/api: npx @redocly/openapi-cli preview-docs spec/api.json .PHONY: docs/swagger docs/swagger: npx @redocly/openapi-cli preview-docs spec/swagger.json .bin/golangci-lint: Makefile curl --retry 7 --retry-connrefused -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b .bin v2.10.1 .bin/hydra: Makefile bash <(curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/meta/master/install.sh) -d -b .bin hydra v2.2.0 .bin/ory: Makefile curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/meta/master/install.sh | bash -s -- -b .bin ory v0.2.2 touch -a -m .bin/ory .bin/buf: Makefile curl -sSL \ "https://github.com/bufbuild/buf/releases/download/v1.39.0/buf-$(shell uname -s)-$(shell uname -m).tar.gz" | \ tar -xvzf - -C ".bin/" --strip-components=2 buf/bin/buf buf/bin/protoc-gen-buf-breaking buf/bin/protoc-gen-buf-lint touch -a -m .bin/buf .PHONY: lint lint: .bin/golangci-lint .bin/buf .bin/golangci-lint run -v --timeout 10m ./... .bin/buf lint .PHONY: mocks mocks: go tool mockgen -mock_names Manager=MockLoginExecutorDependencies -package pkg -destination pkg/hook_login_executor_dependencies.go github.com/ory/kratos/selfservice loginExecutorDependencies .PHONY: proto proto: gen/oidc/v1/state.pb.go gen/oidc/v1/state.pb.go: proto/oidc/v1/state.proto buf.yaml buf.gen.yaml .bin/buf .bin/buf generate go tool goimports -w gen/ .PHONY: install install: go install -tags sqlite . .PHONY: test-resetdb test-resetdb: script/testenv.sh .PHONY: test test: docker pull oryd/hydra:v2.2.0 go test -p 1 -tags sqlite -count=1 -failfast ./... test-short: go test -tags sqlite -count=1 -failfast -short ./... .PHONY: test-coverage test-coverage: go test -coverprofile=coverage.out -failfast -timeout=20m -tags sqlite ./... .PHONY: test-coverage-next test-coverage-next: go test -short -failfast -timeout=20m -tags sqlite -cover ./... --args test.gocoverdir="$$PWD/coverage" go tool covdata percent -i=coverage go tool covdata textfmt -i=./coverage -o coverage.new.out # Generates the SDK .PHONY: sdk sdk: .bin/ory node_modules go tool swagger generate spec -m -o spec/swagger.json \ -c github.com/ory/kratos \ -c github.com/ory/x/healthx \ -c github.com/ory/x/crdbx \ -c github.com/ory/x/openapix ory dev swagger sanitize ./spec/swagger.json go tool swagger validate ./spec/swagger.json CIRCLE_PROJECT_USERNAME=ory CIRCLE_PROJECT_REPONAME=kratos \ ory dev openapi migrate \ --health-path-tags metadata \ -p https://raw.githubusercontent.com/ory/x/master/healthx/openapi/patch.yaml \ -p file://.schema/openapi/patches/meta.yaml \ -p file://.schema/openapi/patches/schema.yaml \ -p file://.schema/openapi/patches/selfservice.yaml \ -p file://.schema/openapi/patches/security.yaml \ -p file://.schema/openapi/patches/session.yaml \ -p file://.schema/openapi/patches/identity.yaml \ -p file://.schema/openapi/patches/courier.yaml \ -p file://.schema/openapi/patches/generic_error.yaml \ -p file://.schema/openapi/patches/nulls.yaml \ -p file://.schema/openapi/patches/common.yaml \ spec/swagger.json spec/api.json rm -rf pkg/httpclient mkdir -p pkg/httpclient/ npm run openapi-generator-cli -- generate -i "spec/api.json" \ -g go \ -o "pkg/httpclient" \ --git-user-id ory \ --git-repo-id client-go \ --git-host github.com \ --api-name-suffix "API" \ -c .schema/openapi/gen.go.yml (cd pkg/httpclient; rm -rf go.mod go.sum test api docs) rm -rf pkg/client-go mkdir -p pkg/client-go/ npm run openapi-generator-cli -- generate -i "spec/api.json" \ -g go \ -o "pkg/client-go" \ --git-user-id ory \ --git-repo-id client-go \ --git-host github.com \ --api-name-suffix "API" \ -c .schema/openapi/gen.go.yml (cd pkg/client-go; go mod edit -module github.com/ory/client-go go.mod; rm -rf test api docs; go mod tidy) make format .PHONY: quickstart quickstart: docker pull oryd/kratos:latest docker pull oryd/kratos-selfservice-ui-node:latest docker-compose -f quickstart.yml -f quickstart-standalone.yml up --build --force-recreate .PHONY: quickstart-dev quickstart-dev: docker build -f .docker/Dockerfile-build -t oryd/kratos:latest . docker-compose -f quickstart.yml -f quickstart-standalone.yml -f quickstart-latest.yml $(QUICKSTART_OPTIONS) up --build --force-recreate authors: # updates the AUTHORS file curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/ci/master/authors/authors.sh | env PRODUCT="Ory Kratos" bash # Formats the code .PHONY: format format: .bin/ory node_modules .bin/buf .bin/ory dev headers copyright --exclude=gen --exclude=pkg/httpclient --exclude=pkg/client-go --exclude test/e2e/proxy/node_modules --exclude test/e2e/node_modules --exclude node_modules --exclude=oryx go tool goimports -w -local github.com/ory . npm exec -- prettier --write 'test/e2e/**/*{.ts,.js}' npm exec -- prettier --write '.github' .bin/buf format --write # Build local docker image .PHONY: docker docker: DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build -f .docker/Dockerfile-build --build-arg=COMMIT=$(VCS_REF) --build-arg=BUILD_DATE=$(BUILD_DATE) -t oryd/kratos:${IMAGE_TAG} . .PHONY: test-e2e test-e2e: node_modules test-resetdb kratos-config-e2e source script/test-envs.sh test/e2e/run.sh sqlite test/e2e/run.sh postgres test/e2e/run.sh cockroach test/e2e/run.sh mysql .PHONY: test-e2e-playwright test-e2e-playwright: node_modules test-resetdb kratos-config-e2e source script/test-envs.sh test/e2e/run.sh --only-setup (cd test/e2e; DB=memory npm run playwright) .PHONY: test-refresh test-refresh: UPDATE_SNAPSHOTS=true go test -tags sqlite,json1,refresh -short ./... .PHONY: pre-release pre-release: go tool yq '.services.kratos.image = "oryd/kratos:'$$DOCKER_TAG'"' -i quickstart.yml go tool yq '.services.kratos-migrate.image = "oryd/kratos:'$$DOCKER_TAG'"' -i quickstart.yml go tool yq '.services.kratos-selfservice-ui-node.image = "oryd/kratos-selfservice-ui-node:'$$DOCKER_TAG'"' -i quickstart.yml .PHONY: post-release post-release: echo "nothing to do" licenses: .bin/licenses node_modules # checks open-source licenses .bin/licenses .bin/licenses: Makefile curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/ci/master/licenses/install | sh node_modules: package-lock.json npm ci touch node_modules .PHONY: kratos-config-e2e kratos-config-e2e: sh ./test/e2e/render-kratos-config.sh ================================================ FILE: README.md ================================================

Ory Kratos - Cloud native identity and user management

Chat · Discussions · Newsletter · Docs · Try Ory Network · Jobs

Ory Kratos is an API first identity and user management system for cloud native applications. It centralizes login, registration, recovery, verification, and profile management flows so your services consume them instead of reimplementing them. **Table of contents** - [What is Ory Kratos?](#what-is-ory-kratos) - [Why Ory Kratos](#why-ory-kratos) - [Migrating from Auth0, Okta, and similar providers](#migrating-from-auth0-okta-and-similar-providers) - [Deployment options](#deployment-options) - [Use Ory Kratos on the Ory Network](#use-ory-kratos-on-the-ory-network) - [Self-host Ory Kratos](#self-host-ory-kratos) - [Quickstart](#quickstart) - [Who is using it?](#who-is-using-it) ## What is Ory Kratos? Ory Kratos is an API first identity and user management system that follows [cloud architecture best practices](https://www.ory.com/docs/ecosystem/software-architecture-philosophy). It focuses on core identity workflows that almost every application needs: - Self service login and registration - Account verification and recovery - Multi factor authentication - Profile and account management - Identity schemas and traits - Admin APIs for lifecycle management We recommend starting with the [Ory Kratos introduction docs](https://www.ory.com/kratos/docs/) to learn more about its architecture, feature set, and how it compares to other systems. ### Why Ory Kratos Ory Kratos is designed to: - Remove identity logic from your application code and expose it over HTTP APIs - Work well with any UI framework through browser based and native app flows - Scale to large numbers of identities and devices - Integrate with the rest of the Ory stack for OAuth2, OpenID Connect, and access control - Fit into modern cloud native environments such as Kubernetes and managed platforms ## Migrating from Auth0, Okta, and similar providers If you are migrating from Auth0, Okta, or another identity provider that uses OAuth2 / OpenID Connect based login, consider using **Ory Hydra + Ory Kratos** together: - **Ory Hydra** acts as the OAuth2 and OpenID Connect provider and can replace most authorization server and token issuing capabilities of your existing IdP. - **Ory Kratos** provides identity, credentials, and user-facing flows (login, registration, recovery, verification, profile management). This combination is often a drop-in replacement for OAuth2 and OpenID Connect capabilities at the protocol level. In practice, you update client configuration and endpoints to point to Hydra, migrate identities into Kratos, and keep your applications speaking the same OAuth2 / OIDC protocols they already use. ## Deployment options You can run Ory Kratos in two main ways: - As a managed service on the Ory Network - As a self hosted service under your own control, with or without the Ory Enterprise License ### Use Ory Kratos on the Ory Network The [Ory Network](https://www.ory.com/cloud) is the fastest way to use Ory services in production. **Ory Identities** is powered by the open source Ory Kratos server and is API compatible. The Ory Network provides: - Identity and credential management that scales to billions of users and devices - Registration, login, and account management flows for passkeys, biometrics, social login, SSO, and multi factor authentication - Prebuilt login, registration, and account management pages and components - OAuth2 and OpenID Connect for single sign on, API access, and machine to machine authorization - Low latency permission checks based on the Zanzibar model with the Ory Permission Language - GDPR friendly storage with data locality and compliance in mind - Web based Ory Console and Ory CLI for administration and operations - Cloud native APIs compatible with the open source servers - Fair, usage based [pricing](https://www.ory.com/pricing) Sign up for a [free developer account](https://console.ory.sh/registration?utm_source=github&utm_medium=banner&utm_campaign=kratos-readme) to get started. ### Self-host Ory Kratos You can run Ory Kratos yourself for full control over infrastructure, deployment, and customization. The [install guide](https://www.ory.com/kratos/docs/install) explains how to: - Install Kratos on Linux, macOS, Windows, and Docker - Configure databases such as PostgreSQL, MySQL, and CockroachDB - Deploy to Kubernetes and other orchestration systems - Build Kratos from source This guide uses the open source distribution to get you started without license requirements. It is a great fit for individuals, researchers, hackers, and companies that want to experiment, prototype, or run unimportant workloads without SLAs. You get the full core engine, and you are free to inspect, extend, and build it from source. If you run Kratos as part of a business-critical system, for example login and account recovery for all your users, you should use a commercial agreement to reduce operational and security risk. The **Ory Enterprise License (OEL)** layers on top of self-hosted Kratos and provides: - Additional enterprise features that are not available in the open source version such as SCIM, SAML, organization login ("SSO"), CAPTCHAs and more - Regular security releases, including CVE patches, with service level agreements - Support for advanced scaling, multi-tenancy, and complex deployments - Premium support options with SLAs, direct access to engineers, and onboarding help - Access to a private Docker registry with frequent and vetted, up-to-date enterprise builds For guaranteed CVE fixes, current enterprise builds, advanced features, and support in production, you need a valid [Ory Enterprise License](https://www.ory.com/ory-enterprise-license) and access to the Ory Enterprise Docker registry. To learn more, [contact the Ory team](https://www.ory.com/contact/). ## Quickstart Install the [Ory CLI](https://www.ory.com/docs/guides/cli/installation) and create a new project to try Ory Identities. ```bash # Install the Ory CLI if you do not have it yet: bash <(curl https://raw.githubusercontent.com/ory/meta/master/install.sh) -b . ory sudo mv ./ory /usr/local/bin/ # Sign in or sign up ory auth # Create a new project ory create project --create-workspace "Ory Open Source" --name "GitHub Quickstart" --use-project ory open ax login ``` ### Who is using it? The Ory community stands on the shoulders of individuals, companies, and maintainers. The Ory team thanks everyone involved - from submitting bug reports and feature requests, to contributing patches and documentation. The Ory community counts more than 50.000 members and is growing. The Ory stack protects 7.000.000.000+ API requests every day across thousands of companies. None of this would have been possible without each and everyone of you! The following list represents companies that have accompanied us along the way and that have made outstanding contributions to our ecosystem. _If you think that your company deserves a spot here, reach out to office@ory.com now_!
Name Logo Website Case Study
OpenAI OpenAI openai.com OpenAI Case Study
Fandom Fandom fandom.com Fandom Case Study
Lumin Lumin luminpdf.com Lumin Case Study
Sencrop Sencrop sencrop.com Sencrop Case Study
OSINT Industries OSINT Industries osint.industries OSINT Industries Case Study
HGV HGV hgv.it HGV Case Study
Maxroll Maxroll maxroll.gg Maxroll Case Study
Zezam Zezam zezam.io Zezam Case Study
T.RowePrice T.RowePrice troweprice.com
Mistral Mistral mistral.ai
Axel Springer Axel Springer axelspringer.com
Hemnet Hemnet hemnet.se
Cisco Cisco cisco.com
Presidencia de la República Dominicana Presidencia de la República Dominicana presidencia.gob.do
Moonpig Moonpig moonpig.com
Booster Booster choosebooster.com
Zaptec Zaptec zaptec.com
Klarna Klarna klarna.com
Raspberry PI Foundation Raspberry PI Foundation raspberrypi.org
Tulip Tulip Retail tulip.com
Hootsuite Hootsuite hootsuite.com
Segment Segment segment.com
Arduino Arduino arduino.cc
Sainsbury's Sainsbury's sainsburys.co.uk
Contraste Contraste contraste.com
inMusic InMusic inmusicbrands.com
Buhta Buhta buhta.com
Amplitude amplitude.com amplitude.com
TIER IV Kyma Project Serlo Padis
Cloudbear Security Onion Solutions Factly All My Funds
Nortal OrderMyGear R2Devops Paralus
dyrector.io pinniped.dev pvotal.tech
Many thanks to all individual contributors ================================================ FILE: SECURITY.md ================================================ # Ory Security Policy This policy outlines Ory's security commitments and practices for users across different licensing and deployment models. To learn more about Ory's security service level agreements (SLAs) and processes, please [contact us](https://www.ory.com/contact/). ## Ory Network Users - **Security SLA:** Ory addresses vulnerabilities in the Ory Network according to the following guidelines: - Critical: Typically addressed within 14 days. - High: Typically addressed within 30 days. - Medium: Typically addressed within 90 days. - Low: Typically addressed within 180 days. - Informational: Addressed as necessary. These timelines are targets and may vary based on specific circumstances. - **Release Schedule:** Updates are deployed to the Ory Network as vulnerabilities are resolved. - **Version Support:** The Ory Network always runs the latest version, ensuring up-to-date security fixes. ## Ory Enterprise License Customers - **Security SLA:** Ory addresses vulnerabilities based on their severity: - Critical: Typically addressed within 14 days. - High: Typically addressed within 30 days. - Medium: Typically addressed within 90 days. - Low: Typically addressed within 180 days. - Informational: Addressed as necessary. These timelines are targets and may vary based on specific circumstances. - **Release Schedule:** Updates are made available as vulnerabilities are resolved. Ory works closely with enterprise customers to ensure timely updates that align with their operational needs. - **Version Support:** Ory may provide security support for multiple versions, depending on the terms of the enterprise agreement. ## Apache 2.0 License Users - **Security SLA:** Ory does not provide a formal SLA for security issues under the Apache 2.0 License. - **Release Schedule:** Releases prioritize new functionality and include fixes for known security vulnerabilities at the time of release. While major releases typically occur one to two times per year, Ory does not guarantee a fixed release schedule. - **Version Support:** Security patches are only provided for the latest release version. ## Reporting a Vulnerability For details on how to report security vulnerabilities, visit our [security policy documentation](https://www.ory.com/docs/ecosystem/security). ================================================ FILE: anonymous_sessions.md ================================================ # Feasibility Report: Anonymous Sessions in Ory Kratos ## Executive summary Anonymous sessions (sessions not tied to an authenticated identity) are **not natively supported** in Kratos today. The session model is deeply coupled to the identity model at the database, struct, and API level. Implementing anonymous sessions is feasible but requires changes across multiple layers. This report outlines the current architecture constraints, proposes API designs, and evaluates implementation approaches. --- ## 1. Current architecture analysis ### 1.1 Session–Identity coupling The `Session` struct has a **non-nullable** `IdentityID uuid.UUID` field and a hard foreign key constraint at the database level: ```cloud/kratos/kratos-oss/persistence/sql/migrations/sql/20191100000003000000_sessions.postgres.up.sql#L1-10 CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ``` The persistence layer explicitly rejects sessions without an identity: ```cloud/kratos/kratos-oss/persistence/sql/persister_session.go#L244-248 s.NID = p.NetworkID(ctx) if s.Identity != nil { s.IdentityID = s.Identity.ID } else if s.IdentityID.IsNil() { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("cannot upsert session without an identity or identity ID set")) ``` ### 1.2 Session activation requires an identity `ManagerHTTP.ActivateSession` hard-requires a non-nil, active identity: ```cloud/kratos/kratos-oss/session/manager_http.go#L314-324 func (s *ManagerHTTP) ActivateSession(r *http.Request, session *Session, i *identity.Identity, authenticatedAt time.Time) (err error) { // ... if i == nil { return errors.WithStack(x.PseudoPanic.WithReasonf("Identity must not be nil when activating a session.")) } if !i.IsActive() { return errors.WithStack(ErrIdentityDisabled.WithDetail("identity_id", i.ID)) } ``` ### 1.3 The `/sessions/whoami` endpoint always returns identity data The `whoami` handler unconditionally reads identity data and sets the `X-Kratos-Authenticated-Identity-Id` header: ```cloud/kratos/kratos-oss/session/handler.go#L260-261 // Set userId as the X-Kratos-Authenticated-Identity-Id header. w.Header().Set("X-Kratos-Authenticated-Identity-Id", s.Identity.ID.String()) ``` ### 1.4 JWT tokenization uses identity as subject The tokenizer requires the session's identity for the `sub` claim: ```cloud/kratos/kratos-oss/session/tokenizer.go#L71-82 func SetSubjectClaim(claims jwt.MapClaims, session *Session, subjectSource string) error { switch subjectSource { case "", "id": claims["sub"] = session.IdentityID.String() case "external_id": if session.Identity.ExternalID == "" { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("The session's identity does not have an external ID set, but it is required for the subject claim.")) } claims["sub"] = session.Identity.ExternalID.String() ``` ### 1.5 Hooks assume identity existence Multiple hooks (e.g., `SessionDestroyer`, `AddressVerifier`, `SessionIssuer`) dereference `s.Identity.ID` without nil checks: ```cloud/kratos/kratos-oss/selfservice/hook/session_destroyer.go#L37-44 func (e *SessionDestroyer) ExecuteLoginPostHook(_ http.ResponseWriter, r *http.Request, _ node.UiNodeGroup, _ *login.Flow, s *session.Session) error { return otelx.WithSpan(r.Context(), "selfservice.hook.SessionDestroyer.ExecuteLoginPostHook", func(ctx context.Context) error { if _, err := e.r.SessionPersister().RevokeSessionsIdentityExcept(ctx, s.Identity.ID, s.ID); err != nil { return err } return nil }) } ``` ### 1.6 There is zero existing concept of "anonymous" or "guest" in the codebase A codebase-wide search for `anonymous`, `guest`, `ephemeral` in the context of sessions returned **no relevant results**. This is a greenfield feature. --- ## 2. Proposed API design ### 2.1 New endpoint: create anonymous session ```/dev/null/api.yaml#L1-30 # POST /sessions/anonymous # Creates an anonymous session without requiring authentication. # Request (Browser flow): # No body required. Sets session cookie automatically. # Request (API flow): # No body required. # Response 200: { "session": { "id": "uuid", "active": true, "expires_at": "2024-01-01T00:00:00Z", "issued_at": "2024-01-01T00:00:00Z", "authenticated_at": "2024-01-01T00:00:00Z", "authenticator_assurance_level": "aal0", "authentication_methods": [ { "method": "anonymous", "aal": "aal0", "completed_at": "..." } ], "identity": null, "anonymous": true, "devices": [...] }, "session_token": "ory_st_..." // Only for API flows } ``` ### 2.2 Modified `/sessions/whoami` behavior The whoami endpoint should gracefully handle anonymous sessions: ```/dev/null/api.yaml#L1-22 # GET /sessions/whoami # Returns the current session. For anonymous sessions, identity is null. # Response 200 (anonymous session): { "id": "uuid", "active": true, "anonymous": true, "authenticator_assurance_level": "aal0", "authentication_methods": [ { "method": "anonymous", "aal": "aal0" } ], "identity": null, "devices": [...] } # Response 200 (authenticated session): # Same as today, with "anonymous": false ``` ### 2.3 Session promotion: anonymous → authenticated When a user logs in or registers while holding an anonymous session, the session should be promotable: ```/dev/null/api.yaml#L1-15 # POST /self-service/login?flow= # If the user has an active anonymous session cookie/token, # the login flow promotes the anonymous session to an authenticated one. # Behavior: # 1. Anonymous session is revoked # 2. New authenticated session is created # 3. The anonymous session ID is available in the login hook context # so that application logic can migrate anonymous data (e.g., cart) # New hook context field: # "previous_anonymous_session_id": "uuid" (available in post-login webhooks) ``` ### 2.4 Configuration ```/dev/null/config.yaml#L1-14 session: anonymous: # Enable anonymous session creation enabled: false # Lifespan of anonymous sessions (shorter than authenticated by default) lifespan: 1h # Maximum number of anonymous sessions per IP (rate limiting) max_per_ip: 100 # Cookie name for anonymous sessions (separate from authenticated sessions) cookie: name: ory_kratos_anonymous_session ``` --- ## 3. Implementation approaches ### Approach A: Phantom identity (recommended) Create a lightweight "anonymous" identity behind the scenes for each anonymous session. This is the **lowest-risk** option. | Aspect | Detail | |---|---| | **Core idea** | When an anonymous session is requested, create a special `Identity` with `state: active`, a dedicated `schema_id: "anonymous"`, and empty traits. The session's `IdentityID` FK is satisfied. | | **Session struct change** | Add `Anonymous bool` field to `Session` (new DB column `is_anonymous`). | | **Existing code impact** | Minimal. All existing code that reads `IdentityID` or `Identity` continues to work. `whoami` can check `s.Anonymous` and null out the identity in the response. | | **Migration** | One new column: `ALTER TABLE sessions ADD COLUMN is_anonymous BOOL NOT NULL DEFAULT false`. | | **Promotion** | On login/registration, update the anonymous session's `IdentityID` to the real identity and set `is_anonymous = false`. Or revoke and create new. | | **Cleanup** | Expired anonymous sessions are cleaned up by existing `DeleteExpiredSessions`. The phantom identities can be garbage-collected when their sessions expire. | | **Drawbacks** | Creates identity records that aren't "real" users. Inflates identity counts. Needs logic to exclude anonymous identities from list/count endpoints. | ### Approach B: Nullable IdentityID Make `IdentityID` nullable across the entire stack. | Aspect | Detail | |---|---| | **Core idea** | Change `IdentityID uuid.UUID` → `IdentityID uuid.NullUUID` in the `Session` struct. Change DB column to nullable. | | **Blast radius** | **Very large.** Every code path that references `IdentityID` or `Identity` must handle nil: `UpsertSession`, `ActivateSession`, `GetSessionByToken`, `DoesSessionSatisfy`, `SetSessionDeviceInformation`, `Tokenizer`, all hooks, all self-service flows, OpenAPI spec, generated clients. | | **Migration** | `ALTER TABLE sessions ALTER COLUMN identity_id DROP NOT NULL; ALTER TABLE sessions DROP CONSTRAINT sessions_identity_id_fkey; ADD CONSTRAINT ... ON DELETE SET NULL`. | | **Drawbacks** | High risk of nil-pointer panics. Breaks the invariant that every session has an owner. Difficult to validate completeness of nil-handling. | ### Approach C: Separate anonymous session table and handler Create a distinct `anonymous_sessions` table with its own handler. | Aspect | Detail | |---|---| | **Core idea** | `anonymous_sessions` table with `id`, `token`, `expires_at`, `metadata`, `devices`. Completely separate from authenticated sessions. | | **Blast radius** | Low on existing code. New code is isolated. | | **Migration** | New table only, no changes to existing schema. | | **Drawbacks** | Duplicates session management logic (cookie issuance, token handling, expiry, etc.). Two parallel systems to maintain. `whoami` must check both tables. Session promotion requires cross-table coordination. | --- ## 4. Impact matrix | Component | Approach A (Phantom) | Approach B (Nullable) | Approach C (Separate) | |---|---|---|---| | `session.Session` struct | +1 field | Change `IdentityID` type | No change | | `session.Persister` | Minor guard | Major refactor | New interface | | `session.ManagerHTTP` | New method + guards | Refactor `ActivateSession`, `FetchFromRequest`, `DoesSessionSatisfy` | New manager | | `session.Handler` | New route + `whoami` guard | Guards in every handler | New handler | | `session.Tokenizer` | Guard for anonymous | Guard for nullable identity | Separate tokenizer logic | | DB migration | 1 column | ALTER + FK change | New table | | Self-service flows | Hook context extension | Nil-handling everywhere | Isolated | | Hooks | Nil-guard in ~5 hooks | Nil-guard in ~5 hooks | N/A | | OpenAPI spec | New endpoint + field | Modified `session` schema | New endpoints + schema | | Identity handler/pool | Exclude anonymous from counts | No change | No change | | Risk | **Low-Medium** | **High** | **Low** | | Effort | **Medium** (~2-3 weeks) | **High** (~4-6 weeks) | **Medium** (~2-3 weeks) | --- ## 5. Recommendation **Approach A (Phantom Identity)** is recommended. It satisfies the FK constraint naturally, minimizes blast radius on existing code, and leverages all existing session infrastructure (cookies, tokens, expiry, cleanup, caching). The main trade-off—phantom identities inflating counts—is manageable by filtering on the `is_anonymous` column or a dedicated `schema_id`. ### Key implementation steps for Approach A: 1. **Add `Anonymous` field** to `Session` struct + DB migration. 2. **Add new `CredentialsType`**: `CredentialsTypeAnonymous = "anonymous"` for the AMR. 3. **Add `POST /sessions/anonymous` endpoint** in `session.Handler` that: - Creates a phantom identity with `schema_id: "anonymous"` and empty traits. - Creates and activates a session with `Anonymous: true`, `AAL: aal0`. - Issues cookie or returns token. 4. **Guard `whoami`**: If `s.Anonymous`, null out identity in response and skip the `X-Kratos-Authenticated-Identity-Id` header. 5. **Guard hooks**: Add nil/anonymous checks in `SessionDestroyer` and other hooks. 6. **Session promotion**: In the login/registration post-hook, detect if an anonymous session exists, revoke it, and pass the old session ID to webhooks via `transient_payload` or a new hook context field. 7. **Configuration**: Add `session.anonymous.enabled` and `session.anonymous.lifespan`. 8. **Identity list filtering**: Exclude anonymous identities from `/admin/identities` by default (or add a filter parameter). 9. **Cleanup job**: Extend `DeleteExpiredSessions` to also garbage-collect orphaned phantom identities whose sessions are all expired/revoked. --- ## 6. Open questions 1. **Should anonymous sessions share the same cookie name?** Using a separate cookie avoids interference but complicates promotion. Using the same cookie makes promotion seamless but means authenticated sessions overwrite anonymous ones. 2. **Should anonymous sessions be tokenizable (JWT)?** The `sub` claim has no meaningful identity. A session-ID-only JWT could work, but consumers expecting an identity subject would break. 3. **Rate limiting**: Without authentication, anonymous session creation is a DoS vector. Per-IP rate limiting and short lifespans are essential. 4. **Multi-tenancy (NID)**: Anonymous sessions should respect network isolation like authenticated sessions. No additional work needed since phantom identities inherit the NID. 5. **Hydra/OAuth2 integration**: Anonymous sessions should likely **not** be usable as OAuth2 login sessions. The `AcceptLoginRequest` flow requires an authenticated identity. ================================================ FILE: buf.gen.yaml ================================================ version: v2 managed: enabled: true override: - file_option: go_package_prefix value: github.com/ory/kratos plugins: - remote: buf.build/protocolbuffers/go out: gen opt: paths=source_relative inputs: - directory: proto ================================================ FILE: buf.yaml ================================================ version: v2 modules: - path: proto lint: use: - DEFAULT breaking: use: - FILE ================================================ FILE: cipher/aes.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cipher import ( "context" "encoding/hex" "github.com/gtank/cryptopasta" "github.com/ory/herodot" "github.com/pkg/errors" ) type AES struct { c SecretsProvider } func NewCryptAES(c SecretsProvider) *AES { return &AES{c: c} } // Encrypt return a AES encrypt of plaintext func (a *AES) Encrypt(ctx context.Context, message []byte) (string, error) { if len(message) == 0 { // do nothing if empty instead of return an error // return nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Can not encrypt empty string.")) return "", nil } if len(a.c.SecretsCipher(ctx)) == 0 { return "", errors.WithStack(herodot.ErrMisconfiguration.WithReason("Unable to encrypt message because no cipher secrets were configured.")) } ciphertext, err := cryptopasta.Encrypt(message, &a.c.SecretsCipher(ctx)[0]) if err != nil { return "", errors.WithStack(herodot.ErrForbidden.WithWrap(err)) } return hex.EncodeToString(ciphertext), nil } // Decrypt returns the decrypted aes data func (a *AES) Decrypt(ctx context.Context, ciphertext string) ([]byte, error) { if len(ciphertext) == 0 { // do nothing if empty instead of return an error // return "", errors.WithStack(herodot.ErrInternalServerError.WithReason("Can not decrypt empty message.")) return nil, nil } secrets := a.c.SecretsCipher(ctx) if len(secrets) == 0 { return nil, errors.WithStack(herodot.ErrMisconfiguration.WithReason("Unable to decipher the encrypted message because no AES secrets were configured.")) } decode, err := hex.DecodeString(ciphertext) if err != nil { return nil, errors.WithStack(herodot.ErrBadRequest.WithWrap(err)) } for i := range secrets { plaintext, err := cryptopasta.Decrypt(decode, &secrets[i]) if err == nil { return plaintext, nil } } return nil, errors.WithStack(herodot.ErrForbidden.WithReason("Unable to decipher the encrypted message.")) } ================================================ FILE: cipher/chacha20.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cipher import ( "context" "crypto/rand" "encoding/hex" "io" "math" "github.com/pkg/errors" "golang.org/x/crypto/chacha20poly1305" "github.com/ory/herodot" ) type XChaCha20Poly1305 struct { c SecretsProvider } func NewCryptChaCha20(c SecretsProvider) *XChaCha20Poly1305 { return &XChaCha20Poly1305{c: c} } // Encrypt returns a ChaCha encryption of plaintext func (c *XChaCha20Poly1305) Encrypt(ctx context.Context, message []byte) (string, error) { if len(message) == 0 { return "", nil } if len(c.c.SecretsCipher(ctx)) == 0 { return "", errors.WithStack(herodot.ErrMisconfiguration.WithReason("Unable to encrypt message because no cipher secrets were configured.")) } aead, err := chacha20poly1305.NewX(c.c.SecretsCipher(ctx)[0][:]) if err != nil { return "", herodot.ErrInternalServerError.WithWrap(err).WithReason("Unable to generate key") } // Make sure the size calculation does not overflow. if len(message) > math.MaxInt-aead.NonceSize()-aead.Overhead() { return "", errors.WithStack(herodot.ErrInternalServerError.WithReason("plaintext too large")) } nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(message)+aead.Overhead()) _, err = io.ReadFull(rand.Reader, nonce) if err != nil { return "", errors.WithStack(herodot.ErrInternalServerError.WithWrap(err).WithReason("Unable to generate nonce")) } encryptedMsg := aead.Seal(nonce, nonce, message, nil) return hex.EncodeToString(encryptedMsg), nil } // Decrypt decrypts data using 256 bit key func (c *XChaCha20Poly1305) Decrypt(ctx context.Context, ciphertext string) ([]byte, error) { if len(ciphertext) == 0 { return nil, nil } secrets := c.c.SecretsCipher(ctx) if len(secrets) == 0 { return nil, errors.WithStack(herodot.ErrMisconfiguration.WithReason("Unable to decipher the encrypted message because no cipher secrets were configured.")) } rawCiphertext, err := hex.DecodeString(ciphertext) if err != nil { return nil, errors.WithStack(herodot.ErrBadRequest.WithWrap(err).WithReason("Unable to decode hex encrypted string")) } for i := range secrets { aead, err := chacha20poly1305.NewX(secrets[i][:]) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithWrap(err).WithReason("Unable to instantiate chacha20")) } if len(ciphertext) < aead.NonceSize() { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("cipher text too short")) } nonce, ciphertext := rawCiphertext[:aead.NonceSize()], rawCiphertext[aead.NonceSize():] plaintext, err := aead.Open(nil, nonce, ciphertext, nil) if err == nil { return plaintext, nil } } return nil, errors.WithStack(herodot.ErrForbidden.WithReason("Unable to decrypt string")) } ================================================ FILE: cipher/cipher.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cipher import "context" // Cipher provides methods for encrypt and decrypt string type Cipher interface { // Encrypt encrypts the (binary) message and returns a hex-encoded binary ciphertext // or an error if the encryption failed. // // If the message is empty, the ciphertext is also empty and no error is returned. Encrypt(ctx context.Context, message []byte) (string, error) // Decrypt takes a hex-encoded binary ciphertext and decrypts it or returns an error if the decryption // failed. // // If the ciphertext is empty a nil byte slice is returned. Decrypt(ctx context.Context, encrypted string) ([]byte, error) } type Provider interface { Cipher(ctx context.Context) Cipher } type SecretsProvider interface { SecretsCipher(ctx context.Context) [][32]byte } ================================================ FILE: cipher/cipher_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cipher_test import ( "context" "encoding/hex" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/herodot" "github.com/ory/kratos/cipher" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/x/configx" "github.com/ory/x/contextx" ) var goodSecret = []string{"secret-thirty-two-character-long"} func TestCipher(t *testing.T) { ctx := context.Background() _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValue(config.ViperKeySecretsDefault, goodSecret)) ciphers := []cipher.Cipher{ cipher.NewCryptAES(reg.Config()), cipher.NewCryptChaCha20(reg.Config()), } for _, c := range ciphers { t.Run(fmt.Sprintf("cipher=%T", c), func(t *testing.T) { t.Parallel() t.Run("case=all_work", func(t *testing.T) { t.Parallel() testAllWork(ctx, t, c) }) t.Run("case=encryption_failed", func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{""}) // secret have to be set _, err := c.Encrypt(ctx, []byte("not-empty")) require.Error(t, err) var hErr *herodot.DefaultError require.ErrorAs(t, err, &hErr) assert.Equal(t, "Unable to encrypt message because no cipher secrets were configured.", hErr.Reason()) ctx = contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{"bad-length"}) // bad secret length _, err = c.Encrypt(ctx, []byte("not-empty")) require.ErrorAs(t, err, &hErr) assert.Equal(t, "Unable to encrypt message because no cipher secrets were configured.", hErr.Reason()) }) t.Run("case=decryption_failed", func(t *testing.T) { t.Parallel() _, err := c.Decrypt(ctx, hex.EncodeToString([]byte("bad-data"))) require.Error(t, err) _, err = c.Decrypt(ctx, "not-empty") require.Error(t, err) _, err = c.Decrypt(contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{""}), "not-empty") require.Error(t, err) }) }) } c := cipher.NewNoop() t.Run(fmt.Sprintf("cipher=%T", c), func(t *testing.T) { t.Parallel() testAllWork(ctx, t, c) }) } func testAllWork(ctx context.Context, t *testing.T, c cipher.Cipher) { message := "my secret message!" encryptedSecret, err := c.Encrypt(ctx, []byte(message)) require.NoError(t, err) decryptedSecret, err := c.Decrypt(ctx, encryptedSecret) require.NoError(t, err, "encrypted", encryptedSecret) assert.Equal(t, message, string(decryptedSecret)) // data to encrypt return blank result _, err = c.Encrypt(ctx, []byte("")) require.NoError(t, err) // empty encrypted data return blank _, err = c.Decrypt(ctx, "") require.NoError(t, err) } ================================================ FILE: cipher/noop.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cipher import ( "context" "encoding/hex" ) // Noop is default cipher implementation witch does not do encryption type Noop struct{} func NewNoop() *Noop { return &Noop{} } // Encrypt encode message to hex func (*Noop) Encrypt(_ context.Context, message []byte) (string, error) { return hex.EncodeToString(message), nil } // Decrypt decode the hex message func (*Noop) Decrypt(_ context.Context, ciphertext string) ([]byte, error) { return hex.DecodeString(ciphertext) } ================================================ FILE: cmd/cleanup/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cleanup import ( "github.com/spf13/cobra" "github.com/ory/x/configx" ) func NewCleanupCmd() *cobra.Command { c := &cobra.Command{ Use: "cleanup", Short: "Various cleanup helpers", } configx.RegisterFlags(c.PersistentFlags()) return c } func RegisterCommandRecursive(parent *cobra.Command) { c := NewCleanupCmd() parent.AddCommand(c) c.AddCommand(NewCleanupSQLCmd()) } ================================================ FILE: cmd/cleanup/sql.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cleanup import ( "fmt" "time" "github.com/ory/x/cmdx" "github.com/spf13/cobra" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/cmd/cliclient" "github.com/ory/x/configx" ) // cleanupSqlCmd represents the sql command func NewCleanupSQLCmd() *cobra.Command { c := &cobra.Command{ Use: "sql ", Short: "Cleanup sql database from expired flows and sessions", Long: `Run this command as frequently as you need. It is recommended to run this command close to the SQL instance (e.g. same subnet) instead of over the public internet. This decreases risk of failure and decreases time required. You can read in the database URL using the -e flag, for example: export DSN=... kratos cleanup sql -e ### WARNING ### Before running this command on an existing database, create a back up! `, RunE: func(cmd *cobra.Command, args []string) error { err := cliclient.NewCleanupHandler().CleanupSQL(cmd, args) if err != nil { _, _ = fmt.Fprintln(cmd.OutOrStdout(), err) return cmdx.FailSilently(cmd) } return nil }, } configx.RegisterFlags(c.PersistentFlags()) c.Flags().BoolP("read-from-env", "e", true, "If set, reads the database connection string from the environment variable DSN or config file key dsn.") c.Flags().Duration(config.ViperKeyDatabaseCleanupSleepTables, time.Minute, "How long to wait between each table cleanup") c.Flags().IntP(config.ViperKeyDatabaseCleanupBatchSize, "b", 100, "Set the number of records to be cleaned per run") c.Flags().Duration("keep-last", 0, "Don't remove records younger than") return c } ================================================ FILE: cmd/cleanup/sql_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cleanup import ( "bytes" "io" "strings" "testing" ) func Test_ExecuteCleanupFailedDSN(t *testing.T) { cmd := NewCleanupSQLCmd() b := bytes.NewBufferString("") cmd.SetOut(b) cmd.SetArgs([]string{"--read-from-env=false"}) _ = cmd.Execute() out, err := io.ReadAll(b) if err != nil { t.Fatal(err) } if !strings.Contains(string(out), "expected to get the DSN as an argument") { t.Fatalf("expected \"%s\" got \"%s\"", "expected to get the DSN as an argument", string(out)) } _ = cmd.Execute() } ================================================ FILE: cmd/cliclient/cleanup.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cliclient import ( "github.com/pkg/errors" "github.com/ory/x/contextx" "github.com/ory/x/configx" "github.com/spf13/cobra" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/x/flagx" ) type CleanupHandler struct{} func NewCleanupHandler() *CleanupHandler { return &CleanupHandler{} } func (h *CleanupHandler) CleanupSQL(cmd *cobra.Command, args []string) error { opts := []configx.OptionModifier{ configx.WithFlags(cmd.Flags()), configx.SkipValidation(), } if !flagx.MustGetBool(cmd, "read-from-env") { if len(args) != 1 { return errors.New(`expected to get the DSN as an argument, or the "read-from-env" flag`) } opts = append(opts, configx.WithValue(config.ViperKeyDSN, args[0])) } d, err := driver.NewWithoutInit( cmd.Context(), cmd.ErrOrStderr(), driver.WithConfigOptions(opts...), ) if len(d.Config().DSN(cmd.Context())) == 0 { return errors.New(`required config value "dsn" was not set`) } else if err != nil { return errors.Wrap(err, "An error occurred initializing cleanup") } err = d.Init(cmd.Context(), &contextx.Default{}) if err != nil { return errors.Wrap(err, "An error occurred initializing cleanup") } keepLast := flagx.MustGetDuration(cmd, "keep-last") err = d.Persister().CleanupDatabase( cmd.Context(), d.Config().DatabaseCleanupSleepTables(cmd.Context()), keepLast, d.Config().DatabaseCleanupBatchSize(cmd.Context())) if err != nil { return errors.Wrap(err, "An error occurred while cleaning up expired data") } return nil } ================================================ FILE: cmd/cliclient/client.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cliclient import ( "fmt" "net/http" "net/url" "os" "time" "github.com/pkg/errors" "github.com/hashicorp/go-retryablehttp" "github.com/spf13/cobra" "github.com/spf13/pflag" kratos "github.com/ory/kratos/pkg/httpclient" ) const ( envKeyEndpoint = "KRATOS_ADMIN_URL" FlagEndpoint = "endpoint" ) type ContextKey int const ( ClientContextKey ContextKey = iota + 1 ) type ClientContext struct { Endpoint string HTTPClient *http.Client } func NewClient(cmd *cobra.Command) (*kratos.APIClient, error) { if f, ok := cmd.Context().Value(ClientContextKey).(func(cmd *cobra.Command) (*ClientContext, error)); ok { cc, err := f(cmd) if err != nil { return nil, err } conf := kratos.NewConfiguration() conf.HTTPClient = cc.HTTPClient conf.Servers = kratos.ServerConfigurations{{URL: cc.Endpoint}} return kratos.NewAPIClient(conf), nil } else if f != nil { return nil, errors.Errorf("ClientContextKey was expected to be *client.OryKratos but it contained an invalid type %T ", f) } endpoint, err := cmd.Flags().GetString(FlagEndpoint) if err != nil { return nil, errors.WithStack(err) } if endpoint == "" { endpoint = os.Getenv(envKeyEndpoint) } if endpoint == "" { return nil, errors.Errorf("you have to set the remote endpoint, try --help for details") } u, err := url.Parse(endpoint) if err != nil { return nil, errors.Wrapf(err, `could not parse the endpoint URL "%s"`, endpoint) } conf := kratos.NewConfiguration() conf.HTTPClient = retryablehttp.NewClient().StandardClient() conf.HTTPClient.Timeout = time.Second * 10 conf.Servers = kratos.ServerConfigurations{{URL: u.String()}} return kratos.NewAPIClient(conf), nil } func RegisterClientFlags(flags *pflag.FlagSet) { flags.StringP(FlagEndpoint, FlagEndpoint[:1], "", fmt.Sprintf("The URL of Ory Kratos' Admin API. Alternatively set using the %s environmental variable.", envKeyEndpoint)) } ================================================ FILE: cmd/cliclient/migrate.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cliclient import ( "fmt" "github.com/pkg/errors" "github.com/ory/x/popx" "github.com/ory/x/contextx" "github.com/ory/x/configx" "github.com/spf13/cobra" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/x/cmdx" "github.com/ory/x/flagx" ) type MigrateHandler struct{} func NewMigrateHandler() *MigrateHandler { return &MigrateHandler{} } func (h *MigrateHandler) getPersister(cmd *cobra.Command, args []string, opts []driver.RegistryOption) (d driver.Registry, err error) { if flagx.MustGetBool(cmd, "read-from-env") { d, err = driver.NewWithoutInit( cmd.Context(), cmd.ErrOrStderr(), driver.WithConfigOptions( configx.WithFlags(cmd.Flags()), configx.SkipValidation(), )) if err != nil { return nil, err } if len(d.Config().DSN(cmd.Context())) == 0 { fmt.Println(cmd.UsageString()) fmt.Println("") fmt.Println("When using flag -e, environment variable DSN must be set") return nil, cmdx.FailSilently(cmd) } } else { if len(args) != 1 { fmt.Println(cmd.UsageString()) return nil, cmdx.FailSilently(cmd) } d, err = driver.NewWithoutInit( cmd.Context(), cmd.ErrOrStderr(), driver.WithConfigOptions( configx.WithFlags(cmd.Flags()), configx.SkipValidation(), configx.WithValue(config.ViperKeyDSN, args[0]), )) if err != nil { return nil, err } } err = d.Init(cmd.Context(), &contextx.Default{}, append(opts, driver.SkipNetworkInit)...) if err != nil { return nil, errors.Wrap(err, "an error occurred initializing migrations") } return d, nil } func (h *MigrateHandler) MigrateSQLDown(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error { p, err := h.getPersister(cmd, args, opts) if err != nil { return err } return popx.MigrateSQLDown(cmd, p.Persister()) } func (h *MigrateHandler) MigrateSQLStatus(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error { p, err := h.getPersister(cmd, args, opts) if err != nil { return err } return popx.MigrateStatus(cmd, p.Persister()) } func (h *MigrateHandler) MigrateSQLUp(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error { p, err := h.getPersister(cmd, args, opts) if err != nil { return err } return popx.MigrateSQLUp(cmd, p.Persister()) } ================================================ FILE: cmd/clidoc/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "bytes" "encoding/json" "fmt" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "os" "path/filepath" "regexp" "sort" "strings" "time" "github.com/pkg/errors" "github.com/ory/kratos/cmd" "github.com/ory/kratos/text" "github.com/ory/x/clidoc" ) var ( aSecondAgo = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(-time.Second) inAMinute = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(time.Minute) ) var messages map[string]*text.Message func init() { text.Until = func(t time.Time) time.Duration { return time.Minute } text.Since = func(time.Time) time.Duration { return time.Minute } messages = map[string]*text.Message{ "NewInfoNodeLabelVerifyOTP": text.NewInfoNodeLabelVerifyOTP(), "NewInfoNodeLabelVerificationCode": text.NewInfoNodeLabelVerificationCode(), "NewInfoNodeLabelRecoveryCode": text.NewInfoNodeLabelRecoveryCode(), "NewInfoNodeInputPassword": text.NewInfoNodeInputPassword(), "NewInfoNodeInputPhoneNumber": text.NewInfoNodeInputPhoneNumber(), "NewInfoNodeLabelGenerated": text.NewInfoNodeLabelGenerated("{title}", "{name}"), "NewInfoNodeLabelSave": text.NewInfoNodeLabelSave(), "NewInfoNodeLabelSubmit": text.NewInfoNodeLabelSubmit(), "NewInfoNodeLabelID": text.NewInfoNodeLabelID(), "NewErrorValidationSettingsFlowExpired": text.NewErrorValidationSettingsFlowExpired(aSecondAgo), "NewInfoSelfServiceSettingsTOTPQRCode": text.NewInfoSelfServiceSettingsTOTPQRCode(), "NewInfoSelfServiceSettingsTOTPSecret": text.NewInfoSelfServiceSettingsTOTPSecret("{secret}"), "NewInfoSelfServiceSettingsTOTPSecretLabel": text.NewInfoSelfServiceSettingsTOTPSecretLabel(), "NewInfoSelfServiceSettingsUpdateSuccess": text.NewInfoSelfServiceSettingsUpdateSuccess(), "NewInfoSelfServiceSettingsUpdateUnlinkTOTP": text.NewInfoSelfServiceSettingsUpdateUnlinkTOTP(), "NewInfoSelfServiceSettingsRevealLookup": text.NewInfoSelfServiceSettingsRevealLookup(), "NewInfoSelfServiceSettingsRegenerateLookup": text.NewInfoSelfServiceSettingsRegenerateLookup(), "NewInfoSelfServiceSettingsDisableLookup": text.NewInfoSelfServiceSettingsDisableLookup(), "NewInfoSelfServiceSettingsLookupConfirm": text.NewInfoSelfServiceSettingsLookupConfirm(), "NewInfoSelfServiceSettingsLookupSecretList": text.NewInfoSelfServiceSettingsLookupSecretList([]string{"{secrets_list}"}, []interface{}{ text.NewInfoSelfServiceSettingsLookupSecret("{secret}"), text.NewInfoSelfServiceSettingsLookupSecretUsed(aSecondAgo), }), "NewInfoSelfServiceSettingsLookupSecret": text.NewInfoSelfServiceSettingsLookupSecret("{secret}"), "NewInfoSelfServiceSettingsLookupSecretUsed": text.NewInfoSelfServiceSettingsLookupSecretUsed(aSecondAgo), "NewInfoSelfServiceSettingsLookupSecretsLabel": text.NewInfoSelfServiceSettingsLookupSecretsLabel(), "NewInfoSelfServiceSettingsUpdateLinkOIDC": text.NewInfoSelfServiceSettingsUpdateLinkOIDC("{provider}"), "NewInfoSelfServiceSettingsUpdateUnlinkOIDC": text.NewInfoSelfServiceSettingsUpdateUnlinkOIDC("{provider}"), "NewInfoSelfServiceRegisterWebAuthnDisplayName": text.NewInfoSelfServiceRegisterWebAuthnDisplayName(), "NewInfoSelfServiceRemoveWebAuthn": text.NewInfoSelfServiceRemoveWebAuthn("{display_name}", aSecondAgo), "NewInfoSelfServiceRemovePasskey": text.NewInfoSelfServiceRemovePasskey("{display_name}", aSecondAgo), "NewErrorValidationVerificationFlowExpired": text.NewErrorValidationVerificationFlowExpired(aSecondAgo), "NewInfoSelfServiceVerificationSuccessful": text.NewInfoSelfServiceVerificationSuccessful(), "NewVerificationEmailSent": text.NewVerificationEmailSent(), "NewVerificationEmailWithCodeSent": text.NewVerificationEmailWithCodeSent(), "NewErrorValidationVerificationTokenInvalidOrAlreadyUsed": text.NewErrorValidationVerificationTokenInvalidOrAlreadyUsed(), "NewErrorValidationVerificationRetrySuccess": text.NewErrorValidationVerificationRetrySuccess(), "NewErrorValidationVerificationStateFailure": text.NewErrorValidationVerificationStateFailure(), "NewErrorValidationVerificationCodeInvalidOrAlreadyUsed": text.NewErrorValidationVerificationCodeInvalidOrAlreadyUsed(), "NewErrorSystemGeneric": text.NewErrorSystemGeneric("{reason}"), "NewErrorSystemNoAuthenticationMethodsAvailable": text.NewErrorSystemNoAuthenticationMethodsAvailable(), "NewValidationErrorGeneric": text.NewValidationErrorGeneric("{reason}"), "NewValidationErrorRequired": text.NewValidationErrorRequired("{property}"), "NewErrorValidationMinLength": text.NewErrorValidationMinLength(5, 3), "NewErrorValidationMaxLength": text.NewErrorValidationMaxLength(5, 6), "NewErrorValidationInvalidFormat": text.NewErrorValidationInvalidFormat("{pattern}"), "NewErrorValidationMinimum": text.NewErrorValidationMinimum(5, 3), "NewErrorValidationExclusiveMinimum": text.NewErrorValidationExclusiveMinimum(5, 5), "NewErrorValidationMaximum": text.NewErrorValidationMaximum(5, 6), "NewErrorValidationExclusiveMaximum": text.NewErrorValidationExclusiveMaximum(5, 5), "NewErrorValidationMultipleOf": text.NewErrorValidationMultipleOf(7, 3), "NewErrorValidationMaxItems": text.NewErrorValidationMaxItems(3, 4), "NewErrorValidationMinItems": text.NewErrorValidationMinItems(3, 2), "NewErrorValidationUniqueItems": text.NewErrorValidationUniqueItems(0, 2), "NewErrorValidationWrongType": text.NewErrorValidationWrongType([]string{"{allowed_types_list}"}, "{actual_type}"), "NewErrorValidationConst": text.NewErrorValidationConst("{expected}"), "NewErrorValidationConstGeneric": text.NewErrorValidationConstGeneric(), "NewErrorValidationPasswordPolicyViolationGeneric": text.NewErrorValidationPasswordPolicyViolationGeneric("{reason}"), "NewErrorValidationPasswordIdentifierTooSimilar": text.NewErrorValidationPasswordIdentifierTooSimilar(), "NewErrorValidationPasswordMinLength": text.NewErrorValidationPasswordMinLength(6, 5), "NewErrorValidationPasswordMaxLength": text.NewErrorValidationPasswordMaxLength(72, 80), "NewErrorValidationPasswordTooManyBreaches": text.NewErrorValidationPasswordTooManyBreaches(101), "NewErrorValidationPasswordNewSameAsOld": text.NewErrorValidationPasswordNewSameAsOld(), "NewErrorValidationInvalidCredentials": text.NewErrorValidationInvalidCredentials(), "NewErrorValidationDuplicateCredentials": text.NewErrorValidationDuplicateCredentials(), "NewErrorValidationDuplicateCredentialsWithHints": text.NewErrorValidationDuplicateCredentialsWithHints([]string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}, "{credential_identifier_hint}"), "NewErrorValidationDuplicateCredentialsOnOIDCLink": text.NewErrorValidationDuplicateCredentialsOnOIDCLink(), "NewErrorValidationTOTPVerifierWrong": text.NewErrorValidationTOTPVerifierWrong(), "NewErrorValidationLookupAlreadyUsed": text.NewErrorValidationLookupAlreadyUsed(), "NewErrorValidationLookupInvalid": text.NewErrorValidationLookupInvalid(), "NewErrorValidationIdentifierMissing": text.NewErrorValidationIdentifierMissing(), "NewErrorValidationAddressNotVerified": text.NewErrorValidationAddressNotVerified(), "NewErrorValidationNoTOTPDevice": text.NewErrorValidationNoTOTPDevice(), "NewErrorValidationNoLookup": text.NewErrorValidationNoLookup(), "NewErrorValidationNoWebAuthnDevice": text.NewErrorValidationNoWebAuthnDevice(), "NewInfoLoginReAuth": text.NewInfoLoginReAuth(), "NewInfoLoginMFA": text.NewInfoLoginMFA(), "NewInfoLoginTOTPLabel": text.NewInfoLoginTOTPLabel(), "NewInfoLoginLookupLabel": text.NewInfoLoginLookupLabel(), "NewInfoLogin": text.NewInfoLogin(), "NewInfoLoginAndLink": text.NewInfoLoginAndLink(), "NewInfoLoginLinkMessage": text.NewInfoLoginLinkMessage("{duplicateIdentifier}", "{provider}", "{newLoginUrl}", []string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}), "NewInfoLoginTOTP": text.NewInfoLoginTOTP(), "NewInfoLoginLookup": text.NewInfoLoginLookup(), "NewInfoLoginVerify": text.NewInfoLoginVerify(), "NewInfoLoginWith": text.NewInfoLoginWith("{provider}", "{providerID}"), "NewInfoLoginWithAndLink": text.NewInfoLoginWithAndLink("{provider}"), "NewErrorValidationLoginFlowExpired": text.NewErrorValidationLoginFlowExpired(aSecondAgo), "NewErrorValidationLoginNoStrategyFound": text.NewErrorValidationLoginNoStrategyFound(), "NewErrorValidationRegistrationNoStrategyFound": text.NewErrorValidationRegistrationNoStrategyFound(), "NewErrorValidationSettingsNoStrategyFound": text.NewErrorValidationSettingsNoStrategyFound(), "NewErrorValidationRecoveryNoStrategyFound": text.NewErrorValidationRecoveryNoStrategyFound(), "NewErrorValidationVerificationNoStrategyFound": text.NewErrorValidationVerificationNoStrategyFound(), "NewInfoSelfServiceLoginWebAuthn": text.NewInfoSelfServiceLoginWebAuthn(), "NewInfoRegistration": text.NewInfoRegistration(), "NewInfoRegistrationWith": text.NewInfoRegistrationWith("{provider}", "{providerID}"), "NewInfoRegistrationContinue": text.NewInfoRegistrationContinue(), "NewInfoRegistrationBack": text.NewInfoRegistrationBack(), "NewInfoSelfServiceChooseCredentials": text.NewInfoSelfServiceChooseCredentials(), "NewErrorValidationRegistrationFlowExpired": text.NewErrorValidationRegistrationFlowExpired(aSecondAgo), "NewErrorValidationRecoveryFlowExpired": text.NewErrorValidationRecoveryFlowExpired(aSecondAgo), "NewRecoverySuccessful": text.NewRecoverySuccessful(inAMinute), "NewRecoveryEmailSent": text.NewRecoveryEmailSent(), "NewRecoveryEmailWithCodeSent": text.NewRecoveryEmailWithCodeSent(), "NewRecoveryCodeRecoverySelectAddressSent": text.NewRecoveryCodeRecoverySelectAddressSent("{masked_address}"), "NewRecoveryAskAnyRecoveryAddress": text.NewRecoveryAskAnyRecoveryAddress(), "NewRecoveryAskForFullAddress": text.NewRecoveryAskForFullAddress(), "NewRecoveryAskToChooseAddress": text.NewRecoveryAskToChooseAddress(), "NewRecoveryBack": text.NewRecoveryBack(), "NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed": text.NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed(), "NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed": text.NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed(), "NewErrorValidationRecoveryRetrySuccess": text.NewErrorValidationRecoveryRetrySuccess(), "NewErrorValidationRecoveryStateFailure": text.NewErrorValidationRecoveryStateFailure(), "NewInfoNodeInputEmail": text.NewInfoNodeInputEmail(), "NewInfoNodeResendOTP": text.NewInfoNodeResendOTP(), "NewInfoNodeLoginAndLinkCredential": text.NewInfoNodeLoginAndLinkCredential(), "NewInfoNodeLabelContinue": text.NewInfoNodeLabelContinue(), "NewInfoSelfServiceSettingsRegisterWebAuthn": text.NewInfoSelfServiceSettingsRegisterWebAuthn(), "NewInfoSelfServiceSettingsRegisterPasskey": text.NewInfoSelfServiceSettingsRegisterPasskey(), "NewInfoLoginWebAuthnPasswordless": text.NewInfoLoginWebAuthnPasswordless(), "NewInfoSelfServiceRegistrationRegisterWebAuthn": text.NewInfoSelfServiceRegistrationRegisterWebAuthn(), "NewInfoSelfServiceContinueLoginWebAuthn": text.NewInfoSelfServiceContinueLoginWebAuthn(), "NewInfoSelfServiceLoginPasskey": text.NewInfoSelfServiceLoginPasskey(), "NewInfoSelfServiceRegistrationRegisterPasskey": text.NewInfoSelfServiceRegistrationRegisterPasskey(), "NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(), "NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(), "NewRegistrationEmailWithCodeSent": text.NewRegistrationEmailWithCodeSent(), "NewLoginCodeSent": text.NewLoginCodeSent(), "NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed": text.NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed(), "NewErrorValidationLoginCodeInvalidOrAlreadyUsed": text.NewErrorValidationLoginCodeInvalidOrAlreadyUsed(), "NewErrorValidationNoCodeUser": text.NewErrorValidationNoCodeUser(), "NewInfoNodeLabelRegistrationCode": text.NewInfoNodeLabelRegistrationCode(), "NewInfoNodeLabelLoginCode": text.NewInfoNodeLabelLoginCode(), "NewErrorValidationLoginRetrySuccessful": text.NewErrorValidationLoginRetrySuccessful(), "NewErrorValidationTraitsMismatch": text.NewErrorValidationTraitsMismatch(), "NewInfoSelfServiceLoginCode": text.NewInfoSelfServiceLoginCode(), "NewErrorValidationRegistrationRetrySuccessful": text.NewErrorValidationRegistrationRetrySuccessful(), "NewInfoSelfServiceRegistrationRegisterCode": text.NewInfoSelfServiceRegistrationRegisterCode(), "NewErrorValidationLoginLinkedCredentialsDoNotMatch": text.NewErrorValidationLoginLinkedCredentialsDoNotMatch(), "NewErrorValidationAddressUnknown": text.NewErrorValidationAddressUnknown(), "NewInfoSelfServiceLoginCodeMFA": text.NewInfoSelfServiceLoginCodeMFA(), "NewInfoLoginPassword": text.NewInfoLoginPassword(), "NewErrorValidationAccountNotFound": text.NewErrorValidationAccountNotFound(), "NewInfoSelfServiceLoginAAL2CodeAddress": text.NewInfoSelfServiceLoginAAL2CodeAddress("{channel}", "{address}"), "NewErrorCaptchaFailed": text.NewErrorCaptchaFailed(), "NewCaptchaContainerMessage": text.NewCaptchaContainerMessage(), "NewErrorValidationEmail": text.NewErrorValidationEmail("{value}"), "NewErrorValidationPhone": text.NewErrorValidationPhone("{value}"), } } func main() { if os.Args[1] == "elements" { path, err := filepath.Abs(os.Args[2]) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to determine absolute path for elements generation: %+v", err) os.Exit(1) } if err := generateElements(path); err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to generate locales for elements: %+v", err) os.Exit(1) } return } if err := clidoc.Generate(cmd.NewRootCmd(nil, nil), []string{filepath.Join(os.Args[2], "cli")}); err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to generate CLI docs: %+v", err) os.Exit(1) } if err := validateAllMessages(filepath.Join(os.Args[1], "text")); err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to validate messages: %+v\n", err) os.Exit(1) } sortedMessages := sortMessages() for i := 1; i < len(sortedMessages); i++ { if sortedMessages[i].ID == sortedMessages[i-1].ID { _, _ = fmt.Fprintf(os.Stderr, "Message ID %d is used more than once: %q %q\n", sortedMessages[i].ID, sortedMessages[i].Text, sortedMessages[i-1].Text) os.Exit(1) } } if err := writeMessages(filepath.Join(os.Args[2], "concepts/ui-messages.md"), sortedMessages); err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to generate message table: %+v\n", err) os.Exit(1) } if err := writeMessagesJson(filepath.Join(os.Args[2], "concepts/messages.json"), sortedMessages); err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to generate messages.json: %+v\n", err) os.Exit(1) } fmt.Println("All files have been generated and updated.") } func codeEncode(in interface{}) string { out, err := json.MarshalIndent(in, "", " ") if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to encode to JSON: %+v", err) os.Exit(1) } return string(out) } func sortMessages() []*text.Message { var toSort []*text.Message for _, m := range messages { toSort = append(toSort, m) } sort.Slice(toSort, func(i, j int) bool { if toSort[i].ID == toSort[j].ID { return toSort[i].Text < toSort[j].Text } return toSort[i].ID < toSort[j].ID }) return toSort } func writeMessages(path string, sortedMessages []*text.Message) error { //nolint:gosec content, err := os.ReadFile(path) // #nosec if err != nil { return err } var w bytes.Buffer for _, m := range sortedMessages { fmt.Fprintf(&w, `###### %s (%d) %s `, m.Text, m.ID, "```json\n"+codeEncode(m)+"\n```") } r := regexp.MustCompile(`(?s)(.*?)`) result := r.ReplaceAllString(string(content), "\n"+w.String()+"\n") f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) // #nosec if err != nil { return err } if _, err := f.WriteString(result); err != nil { return err } if err := f.Close(); err != nil { return err } return nil } func writeMessagesJson(path string, sortedMessages []*text.Message) error { result := codeEncode(sortedMessages) f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) // #nosec if err != nil { return err } if _, err := f.WriteString(result); err != nil { return err } if err := f.Close(); err != nil { return err } return nil } func validateAllMessages(path string) error { type message struct { ID, Name string } usedIDs := make([]message, 0, len(messages)) set := token.NewFileSet() packs, err := parser.ParseDir(set, path, nil, 0) if err != nil { return errors.Wrapf(err, "unable to parse text directory") } info := &types.Info{ Defs: make(map[*ast.Ident]types.Object), } //nolint:staticcheck var pack *ast.Package for _, p := range packs { if p.Name == "text" { pack = p break } } allFiles := make([]*ast.File, 0) for fn, f := range pack.Files { if strings.HasSuffix(fn, "/id.go") { allFiles = append(allFiles, f) } } _, err = (&types.Config{Importer: importer.Default()}).Check("text", set, allFiles, info) if err != nil { return err // type error } for _, f := range pack.Files { for _, d := range f.Decls { switch decl := d.(type) { case *ast.FuncDecl: if name := decl.Name.String(); decl.Name.IsExported() && strings.HasPrefix(name, "New") { if _, ok := messages[name]; !ok { return errors.Errorf("expected to find message %s in the list for the documentation generation but could not", name) } } case *ast.GenDecl: if decl.Tok == token.CONST { for _, spec := range decl.Specs { value := spec.(*ast.ValueSpec) // safe because decl.Tok is token.CONST if t, ok := value.Type.(*ast.Ident); ok { if t.Name == "ID" { for _, name := range value.Names { c := info.ObjectOf(name) if c == nil { return errors.Errorf("expected to find const %s in text/id.go", name.Name) } usedIDs = append(usedIDs, message{ ID: c.(*types.Const).Val().ExactString(), Name: name.Name, }) } } } } } } } } sort.Slice(usedIDs, func(i, j int) bool { return usedIDs[i].ID < usedIDs[j].ID }) for i := 1; i < len(usedIDs); i++ { if usedIDs[i].ID == usedIDs[i-1].ID { return errors.Errorf("message ID %s is used more than once: %s %s", usedIDs[i].ID, usedIDs[i].Name, usedIDs[i-1].Name) } } return nil } func generateElements(messageFilePath string) error { // If the file at messageFilePath does not exist, create it f, err := os.OpenFile(messageFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) // #nosec if err != nil { return errors.Wrapf(err, "unable to open or create message file at %s", messageFilePath) } if err := f.Close(); err != nil { return errors.Wrapf(err, "unable to close message file at %s", messageFilePath) } sortedMessages := sortMessages() if err := writeMessagesJson(messageFilePath, sortedMessages); err != nil { return errors.Wrapf(err, "unable to write messages json to %s", messageFilePath) } return nil } ================================================ FILE: cmd/clidoc/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/stretchr/testify/require" ) func TestMessages(t *testing.T) { require.NoError(t, validateAllMessages("../../text")) } ================================================ FILE: cmd/courier/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "github.com/spf13/cobra" "github.com/ory/kratos/driver" "github.com/ory/x/configx" ) // NewCourierCmd creates a new courier command func NewCourierCmd() *cobra.Command { c := &cobra.Command{ Use: "courier", Short: "Commands related to the Ory Kratos message courier", } configx.RegisterFlags(c.PersistentFlags()) return c } func RegisterCommandRecursive(parent *cobra.Command, dOpts []driver.RegistryOption) { c := NewCourierCmd() parent.AddCommand(c) c.AddCommand(NewWatchCmd(dOpts)) } ================================================ FILE: cmd/courier/watch.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" "github.com/urfave/negroni" "golang.org/x/sync/errgroup" "github.com/ory/graceful" "github.com/ory/kratos/driver" "github.com/ory/x/configx" "github.com/ory/x/prometheusx" "github.com/ory/x/reqlog" ) func NewWatchCmd(dOpts []driver.RegistryOption) *cobra.Command { c := &cobra.Command{ Use: "watch", Short: "Starts the Ory Kratos message courier", RunE: func(cmd *cobra.Command, args []string) error { r, err := driver.New(cmd.Context(), cmd.ErrOrStderr(), append(dOpts, driver.WithConfigOptions(configx.WithFlags(cmd.Flags())))...) if err != nil { return err } return StartCourier(cmd.Context(), r) }, } c.PersistentFlags().Int("expose-metrics-port", 0, "The port to expose the metrics endpoint on (not exposed by default)") return c } func StartCourier(ctx context.Context, r driver.Registry) error { eg, ctx := errgroup.WithContext(ctx) if port := r.Config().CourierExposeMetricsPort(ctx); port != 0 { eg.Go(func() error { return ServeMetrics(ctx, r, port) }) } eg.Go(func() error { return Watch(ctx, r) }) return eg.Wait() } func ServeMetrics(ctx context.Context, r driver.Registry, port int) error { cfg := r.Config().ServeAdmin(ctx) l := r.Logger() n := negroni.New() router := http.NewServeMux() router.Handle(prometheusx.MetricsPrometheusPath, promhttp.Handler()) n.Use(reqlog.NewMiddlewareFromLogger(l, "admin#"+cfg.BaseURL.String())) n.UseHandler(router) var handler http.Handler = n //#nosec G112 -- the correct settings are set by graceful.WithDefaults server := graceful.WithDefaults(&http.Server{ Addr: configx.GetAddress(cfg.Host, port), Handler: handler, }) l.WithField("addr", server.Addr).Info("Starting the metrics httpd") if err := graceful.GracefulContext(ctx, server.ListenAndServe, server.Shutdown); err != nil { l.Errorln("Failed to gracefully shutdown metrics httpd") return err } l.Println("Metrics httpd was shutdown gracefully") return nil } func Watch(ctx context.Context, r driver.Registry) error { ctx, cancel := context.WithCancel(ctx) r.Logger().Println("Courier worker started.") if err := graceful.Graceful(func() error { c, err := r.Courier(ctx) if err != nil { return err } return c.Work(ctx) }, func(_ context.Context) error { cancel() return nil }); err != nil { r.Logger().WithError(err).Error("Failed to run courier worker.") return err } r.Logger().Println("Courier worker was shutdown gracefully.") return nil } ================================================ FILE: cmd/courier/watch_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "fmt" "net/http" "testing" "time" "github.com/phayes/freeport" "github.com/stretchr/testify/require" "github.com/ory/kratos/pkg" "github.com/ory/x/configx" ) func TestStartCourier(t *testing.T) { t.Run("case=without metrics", func(t *testing.T) { _, r := pkg.NewFastRegistryWithMocks(t) go func() { _ = StartCourier(t.Context(), r) }() time.Sleep(time.Second) require.Equal(t, r.Config().CourierExposeMetricsPort(t.Context()), 0) }) t.Run("case=with metrics", func(t *testing.T) { port, err := freeport.GetFreePort() require.NoError(t, err) _, r := pkg.NewFastRegistryWithMocks(t, configx.WithValue("expose-metrics-port", port)) go func() { _ = StartCourier(t.Context(), r) }() time.Sleep(time.Second) res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/metrics/prometheus", port)) require.NoError(t, err) require.Equal(t, 200, res.StatusCode) }) } ================================================ FILE: cmd/daemon/serve.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package daemon import ( "context" "crypto/tls" "net/http" "time" "github.com/pkg/errors" "github.com/rs/cors" "github.com/spf13/cobra" "github.com/urfave/negroni" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "golang.org/x/sync/errgroup" "github.com/ory/analytics-go/v5" "github.com/ory/graceful" "github.com/ory/kratos/cmd/courier" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/schema" "github.com/ory/kratos/selfservice/errorx" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/logout" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/kratos/selfservice/strategy/oidc" "github.com/ory/kratos/session" "github.com/ory/kratos/x" "github.com/ory/kratos/x/nosurfx" "github.com/ory/x/healthx" "github.com/ory/x/httprouterx" "github.com/ory/x/metricsx" "github.com/ory/x/networkx" "github.com/ory/x/otelx" "github.com/ory/x/otelx/semconv" "github.com/ory/x/prometheusx" "github.com/ory/x/reqlog" "github.com/ory/x/urlx" ) func init() { graceful.DefaultShutdownTimeout = 120 * time.Second } var httpMetrics = prometheusx.NewHTTPMetrics("kratos", prometheusx.HTTPPrefix, config.Version, config.Commit, config.Date) func servePublic(ctx context.Context, r *driver.RegistryDefault, cmd *cobra.Command) (func() error, error) { cfg := r.Config().ServePublic(ctx) l := r.Logger() n := negroni.New() for _, mw := range r.HTTPMiddlewares() { n.Use(mw) } publicLogger := reqlog.NewMiddlewareFromLogger(l, "public#"+cfg.BaseURL.String()) if cfg.RequestLog.DisableHealth { publicLogger.ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath) } n.UseFunc(semconv.Middleware) n.Use(publicLogger) n.Use(x.HTTPLoaderContextMiddleware(r)) n.UseFunc(httprouterx.NoCacheNegroni) n.Use(sqa(ctx, cmd, r)) router := httprouterx.NewRouterPublic(httpMetrics) csrf := nosurfx.NewCSRFHandler(otelx.SpanNameRecorderMiddleware(router), r) // we need to always load the CORS middleware even if it is disabled, to allow hot-enabling CORS n.UseFunc(func(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) { cfg, enabled := r.Config().CORSPublic(req.Context()) if !enabled { next(w, req) return } cors.New(cfg).ServeHTTP(w, req, next) }) r.WithCSRFHandler(csrf) n.UseHandler(r.CSRFHandler()) // Disable CSRF for these endpoints csrf.DisablePath(healthx.AliveCheckPath) csrf.DisablePath(healthx.ReadyCheckPath) csrf.DisablePath(healthx.VersionPath) csrf.DisablePath(prometheusx.MetricsPrometheusPath) r.RegisterPublicRoutes(ctx, router) var handler http.Handler = n if tracer := r.Tracer(ctx); tracer.IsLoaded() { handler = otelx.NewMiddleware(handler, "servePublic", otelhttp.WithTracerProvider(tracer.Provider()), ) } handler = http.MaxBytesHandler(handler, 5*1024*1024 /* 5 MB */) // Important: this must be the outermost handler or our tracing breaks certFunc, err := cfg.TLS.GetCertFunc(ctx, l, "public") if err != nil { return nil, err } //#nosec G112 -- the correct settings are set by graceful.WithDefaults server := graceful.WithDefaults(&http.Server{ Handler: handler, TLSConfig: &tls.Config{GetCertificate: certFunc, MinVersion: tls.VersionTLS12}, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, }) addr := cfg.GetAddress() return func() error { l.WithField("addr", addr).Info("Starting the public httpd") if err := graceful.GracefulContext(ctx, func() error { listener, err := networkx.MakeListener(addr, &cfg.Socket) if err != nil { return err } if certFunc == nil { return server.Serve(listener) } return server.ServeTLS(listener, "", "") }, server.Shutdown); err != nil { if !errors.Is(err, context.Canceled) { l.Errorf("Failed to gracefully shutdown public httpd: %s", err) return err } } l.Println("Public httpd was shutdown gracefully") return nil }, nil } func serveAdmin(ctx context.Context, r *driver.RegistryDefault, cmd *cobra.Command) (func() error, error) { cfg := r.Config().ServeAdmin(ctx) l := r.Logger() n := negroni.New() for _, mw := range r.HTTPMiddlewares() { n.Use(mw) } adminLogger := reqlog.NewMiddlewareFromLogger(l, "admin#"+cfg.BaseURL.String()) if cfg.RequestLog.DisableHealth { adminLogger.ExcludePaths( httprouterx.AdminPrefix+healthx.AliveCheckPath, httprouterx.AdminPrefix+healthx.ReadyCheckPath, httprouterx.AdminPrefix+prometheusx.MetricsPrometheusPath, ) } n.UseFunc(semconv.Middleware) n.Use(adminLogger) n.UseFunc(httprouterx.AddAdminPrefixIfNotPresentNegroni) n.UseFunc(httprouterx.NoCacheNegroni) n.Use(x.HTTPLoaderContextMiddleware(r)) n.Use(sqa(ctx, cmd, r)) router := httprouterx.NewRouterAdminWithPrefix(httpMetrics) r.RegisterAdminRoutes(ctx, router) n.UseHandler(router) n.UseFunc(otelx.SpanNameRecorderNegroniFunc) var handler http.Handler = n if tracer := r.Tracer(ctx); tracer.IsLoaded() { handler = otelx.NewMiddleware(handler, "serveAdmin", otelhttp.WithTracerProvider(tracer.Provider()), ) } handler = http.MaxBytesHandler(handler, 5*1024*1024 /* 5 MB */) // Important: this must be the outermost handler or our tracing breaks certFunc, err := cfg.TLS.GetCertFunc(ctx, l, "admin") if err != nil { return nil, err } //#nosec G112 -- the correct settings are set by graceful.WithDefaults server := graceful.WithDefaults(&http.Server{ Handler: handler, TLSConfig: &tls.Config{GetCertificate: certFunc, MinVersion: tls.VersionTLS12}, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 120 * time.Second, IdleTimeout: 600 * time.Second, }) addr := cfg.GetAddress() return func() error { l.WithField("addr", addr).Info("Starting the admin httpd") if err := graceful.GracefulContext(ctx, func() error { listener, err := networkx.MakeListener(addr, &cfg.Socket) if err != nil { return err } if certFunc == nil { return server.Serve(listener) } return server.ServeTLS(listener, "", "") }, server.Shutdown); err != nil { if !errors.Is(err, context.Canceled) { l.Errorf("Failed to gracefully shutdown admin httpd: %s", err) return err } } l.Println("Admin httpd was shutdown gracefully") return nil }, nil } func sqa(ctx context.Context, cmd *cobra.Command, d driver.Registry) *metricsx.Service { urls := []string{ d.Config().ServePublic(ctx).BaseURL.Host, d.Config().ServeAdmin(ctx).BaseURL.Host, d.Config().SelfServiceFlowLoginUI(ctx).Host, d.Config().SelfServiceFlowSettingsUI(ctx).Host, d.Config().SelfServiceFlowErrorURL(ctx).Host, d.Config().SelfServiceFlowRegistrationUI(ctx).Host, d.Config().SelfServiceFlowRecoveryUI(ctx).Host, d.Config().ServePublic(ctx).Host, d.Config().ServeAdmin(ctx).Host, } if c, y := d.Config().CORSPublic(ctx); y { urls = append(urls, c.AllowedOrigins...) } host := urlx.ExtractPublicAddress(urls...) // Creates only ones // instance return metricsx.New( cmd, d.Logger(), d.Config().GetProvider(ctx), &metricsx.Options{ Service: "kratos", DeploymentId: metricsx.Hash(d.Persister().NetworkID(ctx).String()), DBDialect: d.Persister().GetConnection(ctx).Dialect.Details().Dialect, IsDevelopment: d.Config().IsInsecureDevMode(ctx), WriteKey: "qQlI6q8Q4WvkzTjKQSor4sHYOikHIvvi", WhitelistedPaths: []string{ "/", healthx.AliveCheckPath, healthx.ReadyCheckPath, healthx.VersionPath, oidc.RouteBase, login.RouteInitBrowserFlow, login.RouteInitAPIFlow, login.RouteGetFlow, login.RouteSubmitFlow, logout.RouteInitBrowserFlow, logout.RouteSubmitFlow, logout.RouteAPIFlow, registration.RouteInitBrowserFlow, registration.RouteInitAPIFlow, registration.RouteGetFlow, registration.RouteSubmitFlow, session.RouteWhoami, httprouterx.AdminPrefix + "/" + schema.SchemasPath, httprouterx.AdminPrefix + identity.RouteCollection, settings.RouteInitBrowserFlow, settings.RouteInitAPIFlow, settings.RouteGetFlow, settings.RouteSubmitFlow, verification.RouteInitAPIFlow, verification.RouteInitBrowserFlow, verification.RouteGetFlow, verification.RouteSubmitFlow, recovery.RouteInitAPIFlow, recovery.RouteInitBrowserFlow, recovery.RouteGetFlow, recovery.RouteSubmitFlow, link.RouteAdminCreateRecoveryLink, errorx.RouteGet, prometheusx.MetricsPrometheusPath, }, BuildVersion: config.Version, BuildHash: config.Commit, BuildTime: config.Date, Config: &analytics.Config{ Endpoint: "https://sqa.ory.sh", GzipCompressionLevel: 6, BatchMaxSize: 500 * 1000, BatchSize: 1000, Interval: time.Hour * 6, }, Hostname: host, }, ) } func courierTask(ctx context.Context, d driver.Registry) func() error { return func() error { if d.Config().IsBackgroundCourierEnabled(ctx) { return courier.Watch(ctx, d) } return nil } } func ServeAll(d *driver.RegistryDefault) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() g, ctx := errgroup.WithContext(ctx) cmd.SetContext(ctx) // construct all tasks upfront to avoid race conditions publicSrv, err := servePublic(ctx, d, cmd) if err != nil { return errors.WithStack(err) } adminSrv, err := serveAdmin(ctx, d, cmd) if err != nil { return errors.WithStack(err) } tasks := []func() error{ publicSrv, adminSrv, courierTask(ctx, d), } for _, task := range tasks { g.Go(task) } return g.Wait() } } ================================================ FILE: cmd/daemon/serve_test.go ================================================ // Copyright © 2026 Ory Corp // SPDX-License-Identifier: Apache-2.0 package daemon import ( "fmt" "io" "net/http" "testing" "time" "github.com/phayes/freeport" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" "github.com/ory/x/configx" "github.com/ory/kratos/pkg" ) func TestMetricsRouterPaths(t *testing.T) { // TODO refactor this test to be parallelizable once we rewrite the server setup to be properly testable ports, err := freeport.GetFreePorts(2) require.NoError(t, err) adminPort, publicPort := ports[0], ports[1] cmd := &cobra.Command{} cmd.Flags().Bool("sqa-opt-out", true, "") _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]any{ "serve.admin.port": adminPort, "serve.public.port": publicPort, })) startAdmin, err := serveAdmin(t.Context(), reg, cmd) require.NoError(t, err) startPublic, err := servePublic(t.Context(), reg, cmd) require.NoError(t, err) eg, _ := errgroup.WithContext(t.Context()) eg.Go(startAdmin) eg.Go(startPublic) require.EventuallyWithT(t, func(t *assert.CollectT) { resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/health/ready", publicPort)) require.NoError(t, err) body, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Equalf(t, http.StatusOK, resp.StatusCode, "%s", body) }, 2*time.Second, 10*time.Millisecond) // Make some requests that should be recorded in the metrics req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://127.0.0.1:%d/sessions/session-id", publicPort), nil) _, err = http.DefaultClient.Do(req) require.NoError(t, err) _, err = http.Get(fmt.Sprintf("http://127.0.0.1:%d/admin/identities/some-id/sessions", adminPort)) require.NoError(t, err) res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/admin/metrics/prometheus", adminPort)) require.NoError(t, err) require.EqualValues(t, http.StatusOK, res.StatusCode) respBody, err := io.ReadAll(res.Body) body := string(respBody) require.NoError(t, err) assert.Contains(t, body, `endpoint="/sessions/{param}"`) assert.Contains(t, body, `endpoint="/admin/identities/{param}/sessions"`) } ================================================ FILE: cmd/hashers/argon2/calibrate.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package argon2 import ( "fmt" "io" "runtime" "strconv" "time" "github.com/ory/x/configx" "github.com/ory/x/cmdx" "github.com/fatih/color" "github.com/inhies/go-bytesize" "github.com/spf13/cobra" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hash" ) const ( FlagStartMemory = "start-memory" FlagMaxMemory = "max-memory" FlagAdjustMemory = "adjust-memory-by" FlagStartIterations = "start-iterations" FlagMaxConcurrent = "max-concurrent" FlagRuns = "probe-runs" ) type ( colorWriter struct { c *color.Color w io.Writer } loadResult struct { r *resultTable v *config.Argon2 } loadResults []*loadResult ) var _ cmdx.Table = loadResults{} func (l loadResults) Header() []string { return append((&resultTable{}).Header(), "MEMOry PARAM", "ITERATIONS PARAM") } func (l loadResults) Table() [][]string { t := make([][]string, len(l)) for i, r := range l { t[i] = append(r.r.Columns(), fmt.Sprintf("%d", r.v.Memory), fmt.Sprintf("%d", r.v.Iterations)) } return t } func (l loadResults) Interface() interface{} { return l } func (l loadResults) Len() int { return len(l) } func (c *colorWriter) Write(o []byte) (int, error) { return c.c.Fprintf(c.w, "%s", o) } func newCalibrateCmd() *cobra.Command { var ( maxMemory, adjustMemory bytesize.ByteSize = 0, 512 * bytesize.MB runs int ) flagConfig := &argon2Config{ localConfig: config.Argon2{}, } cmd := &cobra.Command{ Use: "calibrate ", Args: cobra.ExactArgs(1), Short: "Computes Optimal Argon2 Parameters", Long: `This command helps you calibrate the configuration parameters for Argon2. Password hashing is a trade-off between security, resource consumption, and user experience. Resource consumption should not be too high and the login should not take too long. We recommend that the login process takes between half a second and one second for password hashing, giving a good balance between security and user experience. Please note that the values depend on the machine you run the hashing on. If you have RAM constraints, please set the memory dedicated to Ory Kratos to avoid out of memory panics.`, RunE: func(cmd *cobra.Command, args []string) error { progressPrinter := cmdx.NewLoudErrPrinter(cmd) resultPrinter := cmdx.NewLoudPrinter(cmd, &colorWriter{c: color.New(color.FgGreen), w: cmd.ErrOrStderr()}) conf, err := configProvider(cmd, flagConfig) if err != nil { return err } // always take start flags, or their default conf.localConfig.Memory = flagConfig.localConfig.Memory conf.localConfig.Iterations = flagConfig.localConfig.Iterations desiredDuration := conf.localConfig.ExpectedDuration reqPerMin, err := strconv.ParseInt(args[0], 0, 0) if err != nil { // we want the error and usage string printed so just return return err } hasher := hash.NewHasherArgon2(conf) var currentDuration time.Duration _, _ = progressPrinter.Printf("Increasing memory to get over %s:\n", desiredDuration) for { if maxMemory != 0 && conf.localConfig.Memory > maxMemory { // don't further increase memory _, _ = progressPrinter.Println(" ouch, hit the memory limit there") conf.localConfig.Memory = maxMemory break } currentDuration, err = probe(cmd, hasher, runs, progressPrinter) if err != nil { return err } _, _ = progressPrinter.Printf(" took %s with %s of memory\n", currentDuration, conf.localConfig.Memory) if currentDuration > desiredDuration { if conf.localConfig.Memory <= adjustMemory { // adjusting the memory would now result in <= 0B adjustMemory = adjustMemory >> 1 } conf.localConfig.Memory -= adjustMemory break } // adjust config conf.localConfig.Memory += adjustMemory } _, _ = progressPrinter.Printf("Decreasing memory to get under %s:\n", desiredDuration) for { currentDuration, err = probe(cmd, hasher, runs, progressPrinter) if err != nil { return err } _, _ = progressPrinter.Printf(" took %s with %s of memory\n", currentDuration, conf.localConfig.Memory) if currentDuration < desiredDuration { break } for conf.localConfig.Memory <= adjustMemory { // adjusting the memory would now result in <= 0B adjustMemory = adjustMemory >> 1 } // adjust config conf.localConfig.Memory -= adjustMemory } _, _ = resultPrinter.Printf("Settled on %s of memory.\n", conf.localConfig.Memory) _, _ = progressPrinter.Printf("Increasing iterations to get over %s:\n", desiredDuration) for { currentDuration, err = probe(cmd, hasher, runs, progressPrinter) if err != nil { return err } _, _ = progressPrinter.Printf(" took %s with %d iterations\n", currentDuration, conf.localConfig.Iterations) if currentDuration > desiredDuration { if conf.localConfig.Iterations > 1 { conf.localConfig.Iterations -= 1 } break } // adjust config conf.localConfig.Iterations += 1 } _, _ = progressPrinter.Printf("Decreasing iterations to get under %s:\n", desiredDuration) for { currentDuration, err = probe(cmd, hasher, runs, progressPrinter) if err != nil { return err } _, _ = progressPrinter.Printf(" took %s with %d iterations\n", currentDuration, conf.localConfig.Iterations) // break also when iterations is 1; this catches the case where 1 was only slightly under the desired time and took longer a bit longer on another run if currentDuration < desiredDuration || conf.localConfig.Iterations == 1 { break } // adjust config conf.localConfig.Iterations -= 1 } _, _ = resultPrinter.Printf("Settled on %d iterations.\n", conf.localConfig.Iterations) results := make(loadResults, 5) for i := 0; i < len(results); i++ { _, _ = progressPrinter.Printf("\nRunning load test to simulate %d login requests per minute.\n", reqPerMin) res, err := runLoadTest(cmd, conf, int(reqPerMin)) if err != nil { return err } cv, _ := conf.HasherArgon2() ccv := *cv results[i] = &loadResult{r: res, v: &ccv} _, _ = progressPrinter.Println("The load test result is:") _, _ = progressPrinter.Println() cmdx.PrintRow(cmd, res) switch { // too fast case res.MedianTime < conf.localConfig.ExpectedDuration: _, _ = progressPrinter.Printf("The median was %s under the minimal duration of %s, going to increase the hash cost.\n", conf.localConfig.ExpectedDuration-res.MedianTime, conf.localConfig.ExpectedDuration) // try to increase memory first if res.MaxMem+64*bytesize.MB < maxMemory { // only small amounts of memory are sensible as we already benchmarked a single, non-concurrent request conf.localConfig.Memory += 64 * bytesize.MB _, _ = progressPrinter.Printf("Increasing memory to %s\n", conf.localConfig.Memory) } else { // increasing memory is not allowed by maxMemory, therefore increase CPU load conf.localConfig.Iterations++ _, _ = progressPrinter.Printf("Increasing iterations to %d\n", conf.localConfig.Iterations) } // too much memory case res.MaxMem > conf.localConfig.DedicatedMemory: _, _ = progressPrinter.Printf("The required memory was %s more than the maximum allowed of %s.\n", res.MaxMem-maxMemory, conf.localConfig.DedicatedMemory) //nolint:gosec // disable G115 conf.localConfig.Memory -= (res.MaxMem - conf.localConfig.DedicatedMemory) / bytesize.ByteSize(reqPerMin) _, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory) // too slow case res.MaxTime > conf.localConfig.ExpectedDeviation+conf.localConfig.ExpectedDuration: _, _ = progressPrinter.Printf("The longest request took %s longer than the longest acceptable time of %s, going to decrease the hash cost.\n", res.MaxTime-conf.localConfig.ExpectedDeviation+conf.localConfig.ExpectedDuration, conf.localConfig.ExpectedDeviation+conf.localConfig.ExpectedDuration) // try to decrease iterations first if conf.localConfig.Iterations > 1 { conf.localConfig.Iterations-- _, _ = progressPrinter.Printf("Decreasing iterations to %d\n", conf.localConfig.Iterations) } else { // decreasing iterations is not possible anymore, decreasing memory // only small amounts of memory are sensible as we already benchmarked a single, non-concurrent request conf.localConfig.Memory -= 64 * bytesize.MB _, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory) } // too high deviation case res.StdDev > conf.localConfig.ExpectedDeviation: _, _ = progressPrinter.Printf("The deviation was %s more than the expected deviation of %s.\n", res.StdDev-conf.localConfig.ExpectedDeviation, conf.localConfig.ExpectedDeviation) // try to decrease iterations first if conf.localConfig.Iterations > 1 { conf.localConfig.Iterations-- _, _ = progressPrinter.Printf("Decreasing iterations to %d\n", conf.localConfig.Iterations) } else { // decreasing iterations is not possible anymore, decreasing memory // only small amounts of memory are sensible as we already benchmarked a single, non-concurrent request conf.localConfig.Memory -= 64 * bytesize.MB _, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory) } // all values seem reasonable default: _, _ = progressPrinter.Println("These values look good to me.") _, _ = progressPrinter.Println() cmdx.PrintRow(cmd, conf) return nil } } _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Could not automatically determine good parameters. Have a look at all the measurements taken and select acceptable values yourself. Have a look in the docs for more information: https://www.ory.sh/kratos/docs/debug/performance-out-of-memory-password-hashing-argon2") cmdx.PrintTable(cmd, results) return nil }, } flags := cmd.Flags() flags.IntVarP(&runs, FlagRuns, "r", 2, "Runs per probe, median of all runs is taken as the result.") flags.VarP(&flagConfig.localConfig.Memory, FlagStartMemory, "m", "Amount of memory to start probing at.") flags.Var(&maxMemory, FlagMaxMemory, "Maximum memory allowed (0 means no limit).") flags.Var(&adjustMemory, FlagAdjustMemory, "Amount by which the memory is adjusted in every step while probing.") flags.Uint32VarP(&flagConfig.localConfig.Iterations, FlagStartIterations, "i", 1, "Number of iterations to start probing at.") flags.Uint8(FlagMaxConcurrent, 16, "Maximum number of concurrent hashing operations.") registerArgon2ConstantConfigFlags(flags, flagConfig) cmdx.RegisterFormatFlags(flags) configx.RegisterFlags(flags) return cmd } func probe(cmd *cobra.Command, hasher hash.Hasher, runs int, progressPrinter *cmdx.ConditionalPrinter) (time.Duration, error) { // force GC at the start of the experiment runtime.GC() start := time.Now() var mid time.Time for i := 0; i < runs; i++ { mid = time.Now() _, err := hasher.Generate(cmd.Context(), []byte("password")) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not generate a hash: %s\n", err) return 0, cmdx.FailSilently(cmd) } _, _ = progressPrinter.Printf(" took %s in try %d\n", time.Since(mid), i) } return time.Duration(int64(time.Since(start)) / int64(runs)), nil } ================================================ FILE: cmd/hashers/argon2/hash.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package argon2 import ( "fmt" "time" "github.com/spf13/cobra" "github.com/ory/kratos/hash" "github.com/ory/x/cmdx" "github.com/ory/x/configx" "github.com/ory/x/flagx" ) const ( FlagParallel = "parallel" ) func newHashCmd() *cobra.Command { flagConfig := &argon2Config{} cmd := &cobra.Command{ Use: "hash [ ...]", Short: "Hash a list of passwords for benchmarking the hashing parameters", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { conf, err := configProvider(cmd, flagConfig) if err != nil { return err } flagConfig.ctx = cmd.Context() hasher := hash.NewHasherArgon2(conf) hashes := make([][]byte, len(args)) errs := make(chan error, len(args)) start := time.Now() for i, pw := range args { go func(i int, pw string) { start := time.Now() h, err := hasher.Generate(cmd.Context(), []byte(pw)) _, _ = fmt.Fprintf(cmd.OutOrStdout(), "password %d: %s\n", i, time.Since(start)) hashes[i] = h errs <- err }(i, pw) if !flagx.MustGetBool(cmd, FlagParallel) { if err := <-errs; err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not generate hash: %s\n", err.Error()) return cmdx.FailSilently(cmd) } } } if flagx.MustGetBool(cmd, FlagParallel) { for i := 0; i < len(args); i++ { if err := <-errs; err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not generate hash: %s\n", err.Error()) return cmdx.FailSilently(cmd) } } } _, _ = fmt.Fprintf(cmd.OutOrStdout(), "total: %s\n", time.Since(start)) return nil }, } flags := cmd.Flags() flags.Bool(FlagParallel, false, "Run all hashing operations in parallel.") registerArgon2ConfigFlags(flags, flagConfig) configx.RegisterFlags(flags) return cmd } ================================================ FILE: cmd/hashers/argon2/loadtest.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package argon2 import ( "context" "fmt" "math/rand" "runtime" "strconv" "time" "github.com/ory/x/flagx" "github.com/fatih/color" "github.com/inhies/go-bytesize" "github.com/montanaflynn/stats" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" "github.com/ory/kratos/hash" "github.com/ory/x/cmdx" "github.com/ory/x/configx" ) type resultTable struct { TotalTime time.Duration `json:"total_time"` MedianTime time.Duration `json:"median_request_time"` StdDev time.Duration `json:"std_deviation"` MinTime time.Duration `json:"min_request_time"` MaxTime time.Duration `json:"max_request_time"` MaxMem bytesize.ByteSize `json:"mem_used"` } var ( ErrSampleTimeExceeded = fmt.Errorf("the sample time was exceeded") ErrMemoryConsumptionExceeded = fmt.Errorf("the memory consumption was exceeded") _ cmdx.TableRow = &resultTable{} ) func (r *resultTable) Header() []string { return []string{"TOTAL SAMPLE TIME", "MEDIAN REQUEST TIME", "STANDARD DEVIATION", "MIN REQUEST TIME", "MAX REQUEST TIME", "MEMOry USED"} } func (r *resultTable) Columns() []string { return []string{ r.TotalTime.String(), r.MedianTime.String(), r.StdDev.String(), r.MinTime.String(), r.MaxTime.String(), r.MaxMem.String(), } } func (r *resultTable) Interface() interface{} { return r } func newLoadTestCmd() *cobra.Command { flagConf := &argon2Config{} cmd := &cobra.Command{ Use: "load-test ", Short: "Simulate the password hashing with a number of concurrent requests/minute.", Long: "Simulates a number of concurrent authentication requests per minute. Gives statistical data about the measured performance and resource consumption. Can be used to tune and test the hashing parameters for peak demand situations.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { perMinute, err := strconv.ParseInt(args[0], 0, 0) if err != nil { return err } flagConf.ctx = cmd.Context() conf, err := configProvider(cmd, flagConf) if err != nil { return err } if !flagx.MustGetBool(cmd, cmdx.FlagQuiet) { _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "The hashing configuration used is:") cmdx.PrintRow(cmd, conf) } res, err := runLoadTest(cmd, conf, int(perMinute)) if err != nil { return err } cmdx.PrintRow(cmd, res) return nil }, } registerArgon2ConfigFlags(cmd.Flags(), flagConf) configx.RegisterFlags(cmd.Flags()) cmdx.RegisterFormatFlags(cmd.Flags()) return cmd } func runLoadTest(cmd *cobra.Command, conf *argon2Config, reqPerMin int) (*resultTable, error) { // force GC at the start of the experiment runtime.GC() sampleTime := time.Minute / 3 reqNum := reqPerMin / int(time.Minute/sampleTime) progressPrinter := cmdx.NewLoudErrPrinter(cmd) _, _ = progressPrinter.Printf("It takes about %s to collect all necessary data, please be patient.\n", sampleTime) ctx, cancel := context.WithCancel(cmd.Context()) hasher := hash.NewHasherArgon2(conf) allDone := make(chan struct{}) startAll := time.Now() var cancelReason error var memStats []uint64 go func() { clock := time.NewTicker(time.Second) defer func() { clock.Stop() }() for { select { case <-cmd.Context().Done(): return case <-allDone: return case <-clock.C: // cancel if the allowed time is exceeded by 110% if time.Since(startAll) > ((sampleTime+conf.localConfig.ExpectedDuration+conf.localConfig.ExpectedDeviation)/100)*110 { cancelReason = ErrSampleTimeExceeded cancel() return } ms := runtime.MemStats{} runtime.ReadMemStats(&ms) // cancel if memory is exceeded by 110% if ms.HeapAlloc > (uint64(conf.localConfig.DedicatedMemory)/100)*110 { cancelReason = ErrMemoryConsumptionExceeded cancel() return } memStats = append(memStats, ms.HeapAlloc) } } }() go func() { // don't read std_in when quiet if flagx.MustGetBool(cmd, cmdx.FlagQuiet) { return } input := make([]byte, 1) for { n, err := cmd.InOrStdin().Read(input) if err != nil { return } if n != 0 { _, _ = color.New(color.FgRed).Fprintln(cmd.ErrOrStderr(), "I SAID BE PATIENT!!!") return } select { case <-allDone: return case <-cmd.Context().Done(): return case <-time.After(time.Millisecond): } } }() calcTimes := make([]time.Duration, reqNum) eg, _ := errgroup.WithContext(ctx) for i := 0; i < reqNum; i++ { eg.Go(func(i int) func() error { return func() error { // wait randomly before starting, max. sample time //#nosec G404 -- just a timeout to collect statistical data t := time.Duration(rand.Intn(int(sampleTime))) timer := time.NewTimer(t) defer timer.Stop() select { case <-ctx.Done(): return nil case <-timer.C: } start := time.Now() done := make(chan struct{}) var err error go func() { _, err = hasher.Generate(ctx, []byte("password")) close(done) }() select { case <-ctx.Done(): return nil case <-done: if err != nil { return err } calcTimes[i] = time.Since(start) return nil } } }(i)) } if err := eg.Wait(); err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Error during hashing: %+v\n", err) return nil, cmdx.FailSilently(cmd) } switch cancelReason { case ErrSampleTimeExceeded: memUsed, err2 := stats.LoadRawData(memStats).Max() if err2 != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unexpected maths error: %+v\nRaw Data: %+v\n", cancelReason, memStats) } _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "The hashing load test took too long. This indicates that you don't have enough resources to handle %d login requests per minute with the desired minimal time of %s. The memory used was %s. Either dedicate more CPU/memory, or decrease the hashing cost (memory and iterations parameters).\n", reqPerMin, conf.localConfig.ExpectedDuration, bytesize.ByteSize(memUsed)) return nil, cmdx.FailSilently(cmd) case ErrMemoryConsumptionExceeded: _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "The hashing load test exceeded the memory limit of %s. This indicates that you don't have enough resources to handle %d login requests per minute with the desired minimal time of %s. Either dedicate more memory, or decrease the hashing cost (memory and iterations parameters).\n", conf.localConfig.DedicatedMemory, reqPerMin, conf.localConfig.ExpectedDuration) return nil, cmdx.FailSilently(cmd) } totalTime := time.Since(startAll) close(allDone) calcData := stats.LoadRawData(calcTimes) duration := func(f func() (float64, error)) time.Duration { v, err := f() if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unexpected maths error: %+v\nRaw Data: %+v\n", err, calcTimes) } return time.Duration(int64(v)) } memUsed, err := stats.LoadRawData(memStats).Max() if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unexpected maths error: %+v\nRaw Data: %+v\n", err, memStats) } return &resultTable{ TotalTime: totalTime, MedianTime: duration(calcData.Mean), StdDev: duration(calcData.StandardDeviation), MinTime: duration(calcData.Min), MaxTime: duration(calcData.Max), MaxMem: bytesize.ByteSize(memUsed), }, nil } ================================================ FILE: cmd/hashers/argon2/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package argon2 import ( "context" "fmt" "reflect" "strings" "github.com/ory/x/contextx" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/ory/kratos/driver/config" "github.com/ory/x/cmdx" "github.com/ory/x/configx" "github.com/ory/x/logrusx" ) const ( FlagIterations = "iterations" FlagParallelism = "parallelism" FlagSaltLength = "salt-length" FlagKeyLength = "key-length" FlagMemory = "memory" FlagDedicatedMemory = "dedicated-memory" FlagMinimalDuration = "min-duration" FlagExpectedDeviation = "expected-deviation" ) var rootCmd = &cobra.Command{ Use: "argon2", } func RegisterCommandRecursive(parent *cobra.Command) { parent.AddCommand(rootCmd) rootCmd.AddCommand(newCalibrateCmd(), newHashCmd(), newLoadTestCmd()) } func registerArgon2ConstantConfigFlags(flags *pflag.FlagSet, c *argon2Config) { // set default value first c.localConfig.DedicatedMemory = config.Argon2DefaultDedicatedMemory flags.Var(&c.localConfig.DedicatedMemory, FlagDedicatedMemory, "Amount of memory dedicated for password hashing. Kratos will try to not consume more memory.") flags.DurationVar(&c.localConfig.ExpectedDuration, FlagMinimalDuration, config.Argon2DefaultDuration, "Minimal duration a hashing operation (~login request) takes.") flags.DurationVar(&c.localConfig.ExpectedDeviation, FlagExpectedDeviation, config.Argon2DefaultDeviation, "Expected deviation of the time a hashing operation (~login request) takes.") flags.Uint8Var(&c.localConfig.Parallelism, FlagParallelism, config.Argon2DefaultParallelism, "Number of threads to use.") flags.Uint32Var(&c.localConfig.SaltLength, FlagSaltLength, config.Argon2DefaultSaltLength, "Length of the salt in bytes.") flags.Uint32Var(&c.localConfig.KeyLength, FlagKeyLength, config.Argon2DefaultKeyLength, "Length of the key in bytes.") } func registerArgon2ConfigFlags(flags *pflag.FlagSet, c *argon2Config) { flags.Uint32Var(&c.localConfig.Iterations, FlagIterations, 1, "Number of iterations to start probing at.") // set default value first c.localConfig.Memory = config.Argon2DefaultMemory flags.Var(&c.localConfig.Memory, FlagMemory, "Memory to use.") registerArgon2ConstantConfigFlags(flags, c) } func configProvider(cmd *cobra.Command, flagConf *argon2Config) (*argon2Config, error) { l := logrusx.New("Ory Kratos", config.Version) conf := &argon2Config{} var err error conf.config, err = config.New( cmd.Context(), l, cmd.ErrOrStderr(), &contextx.Default{}, configx.WithFlags(cmd.Flags()), configx.SkipValidation(), configx.WithContext(cmd.Context()), configx.WithImmutables("hashers"), ) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unable to initialize the config provider: %s\n", err) return nil, cmdx.FailSilently(cmd) } conf.localConfig = *conf.config.HasherArgon2(cmd.Context()) if cmd.Flags().Changed(FlagIterations) { conf.localConfig.Iterations = flagConf.localConfig.Iterations } if cmd.Flags().Changed(FlagParallelism) { conf.localConfig.Parallelism = flagConf.localConfig.Parallelism } if cmd.Flags().Changed(FlagMemory) { conf.localConfig.Memory = flagConf.localConfig.Memory } if cmd.Flags().Changed(FlagDedicatedMemory) { conf.localConfig.DedicatedMemory = flagConf.localConfig.DedicatedMemory } if cmd.Flags().Changed(FlagKeyLength) { conf.localConfig.KeyLength = flagConf.localConfig.KeyLength } if cmd.Flags().Changed(FlagSaltLength) { conf.localConfig.SaltLength = flagConf.localConfig.SaltLength } if cmd.Flags().Changed(FlagExpectedDeviation) { conf.localConfig.ExpectedDeviation = flagConf.localConfig.ExpectedDeviation } if cmd.Flags().Changed(FlagMinimalDuration) { conf.localConfig.ExpectedDuration = flagConf.localConfig.ExpectedDuration } return conf, nil } type ( argon2Config struct { localConfig config.Argon2 config *config.Config ctx context.Context } ) var _ cmdx.TableRow = (*argon2Config)(nil) func (c *argon2Config) Header() []string { var header []string t := reflect.TypeOf(c.localConfig) for i := 0; i < t.NumField(); i++ { header = append(header, strings.ReplaceAll(strings.ToUpper(t.Field(i).Tag.Get("json")), "_", " ")) } return header } func (c *argon2Config) Columns() []string { conf, _ := c.HasherArgon2() return []string{ conf.Memory.String(), fmt.Sprintf("%d", conf.Iterations), fmt.Sprintf("%d", conf.Parallelism), fmt.Sprintf("%d", conf.SaltLength), fmt.Sprintf("%d", conf.KeyLength), conf.ExpectedDuration.String(), conf.ExpectedDeviation.String(), conf.DedicatedMemory.String(), } } func (c *argon2Config) Interface() interface{} { i, _ := c.HasherArgon2() return i } func (c *argon2Config) Config() *config.Config { ac, _ := c.HasherArgon2() for k, v := range map[string]interface{}{ config.ViperKeyHasherArgon2ConfigIterations: ac.Iterations, config.ViperKeyHasherArgon2ConfigMemory: ac.Memory, config.ViperKeyHasherArgon2ConfigParallelism: ac.Parallelism, config.ViperKeyHasherArgon2ConfigDedicatedMemory: ac.DedicatedMemory, config.ViperKeyHasherArgon2ConfigKeyLength: ac.KeyLength, config.ViperKeyHasherArgon2ConfigSaltLength: ac.SaltLength, config.ViperKeyHasherArgon2ConfigExpectedDuration: ac.ExpectedDuration, config.ViperKeyHasherArgon2ConfigExpectedDeviation: ac.ExpectedDeviation, } { _ = c.config.Set(c.ctx, k, v) } return c.config } func (c *argon2Config) HasherArgon2() (*config.Argon2, error) { if c.localConfig.Memory == 0 { c.localConfig.Memory = config.Argon2DefaultMemory } return &c.localConfig, nil } ================================================ FILE: cmd/hashers/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hashers import ( "github.com/spf13/cobra" "github.com/ory/kratos/cmd/hashers/argon2" ) func NewRootCmd() *cobra.Command { c := &cobra.Command{ Use: "hashers", Short: "This command contains helpers around hashing", } return c } func RegisterCommandRecursive(parent *cobra.Command) { rootCmd := NewRootCmd() parent.AddCommand(rootCmd) argon2.RegisterCommandRecursive(rootCmd) } ================================================ FILE: cmd/identities/definitions.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities import ( "strings" kratos "github.com/ory/kratos/pkg/httpclient" "github.com/ory/x/cmdx" ) type ( outputIdentity kratos.Identity outputIdentityCollection struct { Identities []kratos.Identity `json:"identities"` NextPageToken string `json:"next_page_token"` includePageToken bool } ) func (outputIdentity) Header() []string { return []string{"ID", "VERIFIED ADDRESSES", "RECOVERY ADDRESSES", "SCHEMA ID", "SCHEMA URL"} } func (i outputIdentity) Columns() []string { data := [5]string{ i.Id, cmdx.None, cmdx.None, cmdx.None, cmdx.None, } addresses := make([]string, 0, len(i.VerifiableAddresses)) for _, a := range i.VerifiableAddresses { if len(a.Value) > 0 { addresses = append(addresses, a.Value) } } data[1] = strings.Join(addresses, ", ") addresses = addresses[:0] for _, a := range i.RecoveryAddresses { if len(a.Value) > 0 { addresses = append(addresses, a.Value) } } data[2] = strings.Join(addresses, ", ") data[3] = i.SchemaId data[4] = i.SchemaUrl return data[:] } func (i outputIdentity) Interface() interface{} { return i } func (outputIdentityCollection) Header() []string { return outputIdentity{}.Header() } func (c outputIdentityCollection) Table() [][]string { rows := make([][]string, len(c.Identities)) for i, ident := range c.Identities { rows[i] = outputIdentity(ident).Columns() } return append(rows, []string{""}, []string{"NEXT PAGE TOKEN", c.NextPageToken}, ) } func (c outputIdentityCollection) Interface() interface{} { if c.includePageToken { return c } return c.Identities } func (c *outputIdentityCollection) Len() int { return len(c.Identities) } ================================================ FILE: cmd/identities/delete.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities import ( "fmt" "github.com/spf13/cobra" "github.com/ory/kratos/cmd/cliclient" "github.com/ory/kratos/pkg/clihelpers" "github.com/ory/x/cmdx" ) func NewDeleteCmd() *cobra.Command { cmd := &cobra.Command{ Use: "delete", Short: "Delete resources", } cmd.AddCommand(NewDeleteIdentityCmd()) cliclient.RegisterClientFlags(cmd.PersistentFlags()) cmdx.RegisterFormatFlags(cmd.PersistentFlags()) return cmd } func NewDeleteIdentityCmd() *cobra.Command { return &cobra.Command{ Use: "identity id-0 [id-1] [id-2] [id-n]", Short: "Delete one or more identities by their ID(s)", Long: fmt.Sprintf(`This command deletes one or more identities by ID. To delete an identity by some selector, e.g. the recovery email address, use the list command in combination with jq. %s`, clihelpers.WarningJQIsComplicated), Example: `To delete the identity with the recovery email address "foo@bar.com", run: {{ .CommandPath }} $({{ .Root.Name }} list identities --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { c, err := cliclient.NewClient(cmd) if err != nil { return err } var ( deleted = make([]cmdx.OutputIder, 0, len(args)) failed = make(map[string]error) ) for _, a := range args { _, err := c.IdentityAPI.DeleteIdentity(cmd.Context(), a).Execute() if err != nil { failed[a] = cmdx.PrintOpenAPIError(cmd, err) continue } deleted = append(deleted, cmdx.OutputIder(a)) } if len(deleted) == 1 { cmdx.PrintRow(cmd, &deleted[0]) } else if len(deleted) > 1 { cmdx.PrintTable(cmd, &cmdx.OutputIderCollection{Items: deleted}) } cmdx.PrintErrors(cmd, failed) if len(failed) != 0 { return cmdx.FailSilently(cmd) } return nil }, } } ================================================ FILE: cmd/identities/delete_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities_test import ( "context" "errors" "strings" "testing" "github.com/tidwall/gjson" "github.com/ory/kratos/cmd/identities" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/x" "github.com/ory/x/sqlcon" ) func TestDeleteCmd(t *testing.T) { reg, cmd := setup(t, identities.NewDeleteIdentityCmd) t.Run("case=deletes successfully", func(t *testing.T) { // create identity to delete i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i)) stdOut := cmd.ExecNoErr(t, i.ID.String()) // expect ID and no error assert.Equal(t, i.ID.String(), gjson.Parse(stdOut).String()) // expect identity to be deleted _, err := reg.Persister().GetIdentity(context.Background(), i.ID, identity.ExpandNothing) assert.True(t, errors.Is(err, sqlcon.ErrNoRows)) }) t.Run("case=deletes three identities", func(t *testing.T) { is, ids := makeIdentities(t, reg, 3) stdOut := cmd.ExecNoErr(t, ids...) assert.Equal(t, `["`+strings.Join(ids, "\",\"")+"\"]\n", stdOut) for _, i := range is { _, err := reg.Persister().GetIdentity(context.Background(), i.ID, identity.ExpandNothing) assert.Error(t, err) } }) t.Run("case=fails with unknown ID", func(t *testing.T) { stdErr := cmd.ExecExpectedErr(t, x.NewUUID().String()) assert.Contains(t, stdErr, "Unable to locate the resource", stdErr) }) } ================================================ FILE: cmd/identities/get.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities import ( "fmt" kratos "github.com/ory/kratos/pkg/httpclient" "github.com/ory/kratos/x" "github.com/ory/x/cmdx" "github.com/ory/x/stringsx" "github.com/ory/kratos/pkg/clihelpers" "github.com/spf13/cobra" "github.com/ory/kratos/cmd/cliclient" ) const ( FlagIncludeCreds = "include-credentials" ) func NewGetCmd() *cobra.Command { var cmd = &cobra.Command{ Use: "get", Short: "Get resources", } cmd.AddCommand(NewGetIdentityCmd()) cliclient.RegisterClientFlags(cmd.PersistentFlags()) cmdx.RegisterFormatFlags(cmd.PersistentFlags()) return cmd } func NewGetIdentityCmd() *cobra.Command { var ( includeCreds []string ) cmd := &cobra.Command{ Use: "identity [id-1] [id-2] [id-n]", Short: "Get one or more identities by their ID(s)", Long: fmt.Sprintf(`This command gets all the details about an identity. To get an identity by some selector, e.g. the recovery email address, use the list command in combination with jq. %s`, clihelpers.WarningJQIsComplicated), Example: `To get the identities with the recovery email address at the domain "ory.sh", run: {{ .CommandPath }} $({{ .Root.Name }} ls identities --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { c, err := cliclient.NewClient(cmd) if err != nil { return err } // we check includeCreds argument is valid for _, opt := range includeCreds { e := stringsx.SwitchExact(opt) if !e.AddCase("oidc") { cmd.PrintErrln(`You have to put a valid value of credentials type to be included, try --help for details.`) return cmdx.FailSilently(cmd) } } identities := make([]kratos.Identity, 0, len(args)) failed := make(map[string]error) for _, id := range args { identity, _, err := c.IdentityAPI. GetIdentity(cmd.Context(), id). IncludeCredential(includeCreds). Execute() if x.SDKError(err) != nil { failed[id] = cmdx.PrintOpenAPIError(cmd, err) continue } identities = append(identities, *identity) } if len(identities) == 1 { cmdx.PrintRow(cmd, (*outputIdentity)(&identities[0])) } else if len(identities) > 1 { cmdx.PrintTable(cmd, &outputIdentityCollection{Identities: identities, includePageToken: false}) } cmdx.PrintErrors(cmd, failed) if len(failed) != 0 { return cmdx.FailSilently(cmd) } return nil }, } flags := cmd.Flags() // include credential flag to add third party tokens in returned data flags.StringArrayVarP(&includeCreds, FlagIncludeCreds, "i", []string{}, `Include third party tokens (only "oidc" supported) `) return cmd } ================================================ FILE: cmd/identities/get_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities_test import ( "context" "encoding/json" "testing" "github.com/ory/kratos/cmd/identities" "github.com/ory/x/assertx" "github.com/ory/kratos/x" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" ) func TestGetCmd(t *testing.T) { reg, cmd := setup(t, identities.NewGetIdentityCmd) t.Run("case=gets a single identity", func(t *testing.T) { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.MetadataPublic = []byte(`"public"`) i.MetadataAdmin = []byte(`"admin"`) require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i)) stdOut := cmd.ExecNoErr(t, i.ID.String()) ij, err := json.Marshal(identity.WithCredentialsNoConfigAndAdminMetadataInJSON(*i)) require.NoError(t, err) assertx.EqualAsJSONExcept(t, json.RawMessage(ij), json.RawMessage(stdOut), []string{"created_at", "updated_at", "AdditionalProperties"}) }) t.Run("case=gets three identities", func(t *testing.T) { is, ids := makeIdentities(t, reg, 3) stdOut := cmd.ExecNoErr(t, ids...) isj, err := json.Marshal(is) require.NoError(t, err) assertx.EqualAsJSONExcept(t, json.RawMessage(isj), json.RawMessage(stdOut), []string{"created_at", "updated_at", "AdditionalProperties"}) }) t.Run("case=fails with unknown ID", func(t *testing.T) { stdErr := cmd.ExecExpectedErr(t, x.NewUUID().String()) assert.Contains(t, stdErr, "Unable to locate the resource", stdErr) }) t.Run("case=gets a single identity with oidc credentials", func(t *testing.T) { applyCredentials := func(identifier, accessToken, refreshToken, idToken string, encrypt bool) identity.Credentials { toJson := func(c identity.CredentialsOIDC) []byte { out, err := json.Marshal(&c) require.NoError(t, err) return out } transform := func(token string) string { if encrypt { s, err := reg.Cipher(context.Background()).Encrypt(context.Background(), []byte(token)) require.NoError(t, err) return s } return token } return identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"bar:" + identifier}, Config: toJson(identity.CredentialsOIDC{Providers: []identity.CredentialsOIDCProvider{ { Subject: "foo", Provider: "bar", InitialAccessToken: transform(accessToken + "0"), InitialRefreshToken: transform(refreshToken + "0"), InitialIDToken: transform(idToken + "0"), Organization: "foo-org-id", }, { Subject: "baz", Provider: "zab", InitialAccessToken: transform(accessToken + "1"), InitialRefreshToken: transform(refreshToken + "1"), InitialIDToken: transform(idToken + "1"), Organization: "bar-org-id", }, }}), } } i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.MetadataPublic = []byte(`"public"`) i.MetadataAdmin = []byte(`"admin"`) i.SetCredentials(identity.CredentialsTypeOIDC, applyCredentials("uniqueIdentifier", "accessBar", "refreshBar", "idBar", true)) // duplicate identity with decrypted tokens di := i.CopyWithoutCredentials() di.SetCredentials(identity.CredentialsTypeOIDC, applyCredentials("uniqueIdentifier", "accessBar", "refreshBar", "idBar", false)) require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i)) stdOut := cmd.ExecNoErr(t, "--"+identities.FlagIncludeCreds, "oidc", i.ID.String()) ij, err := json.Marshal(identity.WithCredentialsAndAdminMetadataInJSON(*di)) require.NoError(t, err) ii := []string{"id", "schema_url", "state_changed_at", "created_at", "updated_at", "credentials.oidc.created_at", "credentials.oidc.updated_at", "credentials.oidc.version", "AdditionalProperties"} assertx.EqualAsJSONExcept(t, json.RawMessage(ij), json.RawMessage(stdOut), ii) }) } ================================================ FILE: cmd/identities/helpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities import ( "fmt" "io" "os" "github.com/spf13/cobra" "github.com/tidwall/gjson" "github.com/ory/x/cmdx" ) func parseIdentities(raw []byte) (rawIdentities []string) { res := gjson.ParseBytes(raw) if !res.IsArray() { return []string{res.Raw} } res.ForEach(func(_, v gjson.Result) bool { rawIdentities = append(rawIdentities, v.Raw) return true }) return } func readIdentities(cmd *cobra.Command, args []string) (map[string]string, error) { rawIdentities := make(map[string]string) if len(args) == 0 { fc, err := io.ReadAll(cmd.InOrStdin()) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "STD_IN: Could not read: %s\n", err) return nil, cmdx.FailSilently(cmd) } for i, id := range parseIdentities(fc) { rawIdentities[fmt.Sprintf("STD_IN[%d]", i)] = id } return rawIdentities, nil } for _, fn := range args { fc, err := os.ReadFile(fn) // #nosec G304 -- file is supplied by user if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s: Could not open identity file: %s\n", fn, err) return nil, cmdx.FailSilently(cmd) } for i, id := range parseIdentities(fc) { rawIdentities[fmt.Sprintf("%s[%d]", fn, i)] = id } } return rawIdentities, nil } ================================================ FILE: cmd/identities/helpers_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities_test import ( "context" "testing" "github.com/ory/x/cmdx" "github.com/ory/kratos/identity" "github.com/spf13/cobra" "github.com/stretchr/testify/require" "github.com/ory/kratos/cmd/cliclient" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" ) func setup(t *testing.T, newCmd func() *cobra.Command) (*driver.RegistryDefault, *cmdx.CommandExecuter) { conf, reg := pkg.NewFastRegistryWithMocks(t) _, admin := testhelpers.NewKratosServerWithCSRF(t, reg) testhelpers.SetDefaultIdentitySchema(conf, "file://./stubs/identity.schema.json") // setup command return reg, &cmdx.CommandExecuter{ New: func() *cobra.Command { cmd := newCmd() cliclient.RegisterClientFlags(cmd.Flags()) cmdx.RegisterFormatFlags(cmd.Flags()) return cmd }, PersistentArgs: []string{"--" + cliclient.FlagEndpoint, admin.URL, "--" + cmdx.FlagFormat, string(cmdx.FormatJSON)}, } } func makeIdentities(t *testing.T, reg driver.Registry, n int) (is []*identity.Identity, ids []string) { for j := 0; j < n; j++ { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.MetadataPublic = []byte(`{"foo":"bar"}`) require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i)) is = append(is, i) ids = append(ids, i.ID.String()) } return } ================================================ FILE: cmd/identities/import.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identities import ( "encoding/json" "fmt" kratos "github.com/ory/kratos/pkg/httpclient" "github.com/ory/x/cmdx" "github.com/spf13/cobra" "github.com/ory/kratos/cmd/cliclient" ) func NewImportCmd() *cobra.Command { var cmd = &cobra.Command{ Use: "import", Short: "Import resources", } cmd.AddCommand(NewImportIdentitiesCmd()) cliclient.RegisterClientFlags(cmd.PersistentFlags()) cmdx.RegisterFormatFlags(cmd.PersistentFlags()) return cmd } // NewImportIdentitiesCmd represents the import command func NewImportIdentitiesCmd() *cobra.Command { return &cobra.Command{ Use: "identities file-1.json [file-2.json] [file-3.json] [file-n.json]", Short: "Import one or more identities from files or STD_IN", Example: `Create an example identity: cat > ./file.json <", Deprecated: "Please use `kratos migrate sql` instead.", Short: "Create SQL schemas and apply migration plans", Long: `Run this command on a fresh SQL installation and when you upgrade Ory Kratos to a new minor version. It is recommended to run this command close to the SQL instance (e.g. same subnet) instead of over the public internet. This decreases risk of failure and decreases time required. You can read in the database URL using the -e flag, for example: export DSN=... kratos migrate sql -e ### WARNING ### Before running this command on an existing database, create a back up! `, RunE: func(cmd *cobra.Command, args []string) error { return cliclient.NewMigrateHandler().MigrateSQLUp(cmd, args, opts...) }, } configx.RegisterFlags(c.PersistentFlags()) c.PersistentFlags().BoolP("read-from-env", "e", false, "If set, reads the database connection string from the environment variable DSN or config file key dsn.") c.Flags().BoolP("yes", "y", false, "If set all confirmation requests are accepted without user interaction.") c.AddCommand(NewMigrateSQLStatusCmd(opts...)) c.AddCommand(NewMigrateSQLUpCmd(opts...)) c.AddCommand(NewMigrateSQLDownCmd(opts...)) return c } ================================================ FILE: cmd/remote/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package remote import ( "github.com/spf13/cobra" "github.com/ory/x/cmdx" "github.com/ory/kratos/cmd/cliclient" ) var remoteCmd = &cobra.Command{ Use: "remote", Short: "Helpers and management for remote Ory Kratos instances", } func RegisterCommandRecursive(parent *cobra.Command) { parent.AddCommand(remoteCmd) remoteCmd.AddCommand(versionCmd) remoteCmd.AddCommand(statusCmd) } func init() { cliclient.RegisterClientFlags(remoteCmd.PersistentFlags()) cmdx.RegisterFormatFlags(remoteCmd.PersistentFlags()) } ================================================ FILE: cmd/remote/status.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package remote import ( "github.com/ory/x/cmdx" "github.com/spf13/cobra" "github.com/ory/kratos/cmd/cliclient" ) type statusState struct { Alive bool `json:"alive"` Ready bool `json:"ready"` } func (s *statusState) Header() []string { return []string{"ALIVE", "READY"} } func (s *statusState) Columns() []string { f := [2]string{ "false", "false", } if s.Alive { f[0] = "true" } if s.Ready { f[1] = "true" } return f[:] } func (s *statusState) Interface() interface{} { return s } var statusCmd = &cobra.Command{ Use: "status", Short: "Print the alive and readiness status of a Ory Kratos instance", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { c, err := cliclient.NewClient(cmd) if err != nil { return err } state := &statusState{} defer cmdx.PrintRow(cmd, state) alive, _, err := c.MetadataAPI.IsAlive(cmd.Context()).Execute() if err != nil { return err } state.Alive = alive.Status == "ok" ready, _, err := c.MetadataAPI.IsReady(cmd.Context()).Execute() if err != nil { return err } state.Ready = ready.Status == "ok" return nil }, } ================================================ FILE: cmd/remote/version.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package remote import ( "github.com/spf13/cobra" "github.com/ory/kratos/cmd/cliclient" "github.com/ory/x/cmdx" ) type versionValue struct { Version string `json:"version"` } func (v *versionValue) Header() []string { return []string{"VERSION"} } func (v *versionValue) Columns() []string { return []string{v.Version} } func (v *versionValue) Interface() interface{} { return v } var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version of an Ory Kratos instance", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { c, err := cliclient.NewClient(cmd) if err != nil { return err } resp, _, err := c.MetadataAPI.GetVersion(cmd.Context()).Execute() if err != nil { return err } cmdx.PrintRow(cmd, &versionValue{Version: resp.Version}) return nil }, } ================================================ FILE: cmd/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmd import ( "errors" "fmt" "runtime" "github.com/spf13/cobra" "github.com/ory/kratos/cmd/cleanup" "github.com/ory/kratos/cmd/courier" "github.com/ory/kratos/cmd/hashers" "github.com/ory/kratos/cmd/identities" "github.com/ory/kratos/cmd/jsonnet" "github.com/ory/kratos/cmd/migrate" "github.com/ory/kratos/cmd/remote" "github.com/ory/kratos/cmd/serve" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/x/cmdx" "github.com/ory/x/jsonnetsecure" "github.com/ory/x/profilex" ) func NewRootCmd(driverOpts ...driver.RegistryOption) (cmd *cobra.Command) { cmd = &cobra.Command{ Use: "kratos", } cmdx.EnableUsageTemplating(cmd) courier.RegisterCommandRecursive(cmd, driverOpts) cmd.AddCommand(identities.NewGetCmd()) cmd.AddCommand(identities.NewDeleteCmd()) cmd.AddCommand(jsonnet.NewFormatCmd()) hashers.RegisterCommandRecursive(cmd) cmd.AddCommand(identities.NewImportCmd()) cmd.AddCommand(jsonnet.NewLintCmd()) cmd.AddCommand(identities.NewListCmd()) migrate.RegisterCommandRecursive(cmd) serve.RegisterCommandRecursive(cmd, driverOpts) cleanup.RegisterCommandRecursive(cmd) remote.RegisterCommandRecursive(cmd) cmd.AddCommand(identities.NewValidateCmd()) cmd.AddCommand(cmdx.Version(&config.Version, &config.Commit, &config.Date)) // Registers a hidden "jsonnet" subcommand for process-isolated Jsonnet VMs. cmd.AddCommand(jsonnetsecure.NewJsonnetCmd()) return cmd } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the RootCmd. func Execute() int { defer profilex.Profile().Stop() jsonnetPool := jsonnetsecure.NewProcessPool(runtime.GOMAXPROCS(0)) defer jsonnetPool.Close() c := NewRootCmd(driver.WithJsonnetPool(jsonnetPool)) if err := c.Execute(); err != nil { if !errors.Is(err, cmdx.ErrNoPrintButFail) { _, _ = fmt.Fprintln(c.ErrOrStderr(), err) } return 1 } return 0 } ================================================ FILE: cmd/root_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmd import ( "testing" "github.com/ory/x/cmdx" ) func TestUsageStrings(t *testing.T) { cmdx.AssertUsageTemplates(t, NewRootCmd()) } ================================================ FILE: cmd/serve/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package serve import ( "github.com/spf13/cobra" "github.com/ory/kratos/cmd/daemon" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/x/configx" ) // NewServeCmd returns the serve command func NewServeCmd(dOpts ...driver.RegistryOption) (serveCmd *cobra.Command) { serveCmd = &cobra.Command{ Use: "serve", Short: "Run the Ory Kratos server", RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() d, err := driver.New(ctx, cmd.ErrOrStderr(), append(dOpts, driver.WithConfigOptions(configx.WithFlags(cmd.Flags())))...) if err != nil { return err } if d.Config().IsInsecureDevMode(ctx) { d.Logger().Warn(` YOU ARE RUNNING Ory KRATOS IN DEV MODE. SECURITY IS DISABLED. DON'T DO THIS IN PRODUCTION! `) } configVersion := d.Config().ConfigVersion(ctx) if configVersion == config.UnknownVersion { d.Logger().Warn("The config has no version specified. Add the version to improve your development experience.") } else if config.Version != "" && configVersion != config.Version { d.Logger().Warnf("Config version is '%s' but kratos runs on version '%s'", configVersion, config.Version) } return daemon.ServeAll(d)(cmd, args) }, } configx.RegisterFlags(serveCmd.PersistentFlags()) serveCmd.PersistentFlags().Bool("sqa-opt-out", false, "Disable anonymized telemetry reports - for more information please visit https://www.ory.sh/docs/ecosystem/sqa") serveCmd.PersistentFlags().Bool("dev", false, "Disables critical security features to make development easier") serveCmd.PersistentFlags().Bool("watch-courier", false, "Run the message courier as a background task, to simplify single-instance setup") return serveCmd } func RegisterCommandRecursive(parent *cobra.Command, dOpts []driver.RegistryOption) { parent.AddCommand(NewServeCmd(dOpts...)) } ================================================ FILE: cmd/serve/root_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package serve_test import ( "testing" "github.com/ory/kratos/pkg/testhelpers" ) func TestServe(t *testing.T) { _, _ = testhelpers.StartE2EServer(t, "./stub/kratos.yml", nil) } func TestServeTLSBase64(t *testing.T) { _, _, certBase64, keyBase64 := testhelpers.GenerateTLSCertificateFilesForTests(t) publicPort, adminPort := testhelpers.StartE2EServerOnly(t, "./stub/kratos.yml", true, testhelpers.ConfigOptions{ "serve.public.tls.key.base64": keyBase64, "serve.public.tls.cert.base64": certBase64, "serve.admin.tls.key.base64": keyBase64, "serve.admin.tls.cert.base64": certBase64, }, ) testhelpers.CheckE2EServerOnHTTPS(t, publicPort, adminPort) } func TestServeTLSPaths(t *testing.T) { certPath, keyPath, _, _ := testhelpers.GenerateTLSCertificateFilesForTests(t) publicPort, adminPort := testhelpers.StartE2EServerOnly(t, "./stub/kratos.yml", true, testhelpers.ConfigOptions{ "serve.public.tls.key.path": keyPath, "serve.public.tls.cert.path": certPath, "serve.admin.tls.key.path": keyPath, "serve.admin.tls.cert.path": certPath, }, ) testhelpers.CheckE2EServerOnHTTPS(t, publicPort, adminPort) } ================================================ FILE: cmd/serve/stub/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } }, "name": { "type": "object", "properties": { "first": { "title": "First Name", "type": "string" }, "last": { "title": "Last Name", "type": "string" } } } }, "required": [ "email" ], "additionalProperties": false } } } ================================================ FILE: cmd/serve/stub/kratos.yml ================================================ dsn: memory serve: public: base_url: http://127.0.0.1:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://127.0.0.1:4455/ allowed_return_urls: - http://127.0.0.1:4455 methods: password: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings privileged_session_max_age: 15m recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery verification: enabled: true ui_url: http://127.0.0.1:4455/verify after: default_browser_return_url: http://127.0.0.1:4455/ logout: after: default_browser_return_url: http://127.0.0.1:4455/auth/login login: ui_url: http://127.0.0.1:4455/auth/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/auth/registration after: password: hooks: - hook: session log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE pagination: - "test pagination secret" hashers: argon2: parallelism: 1 memory: 128MB iterations: 2 salt_length: 16 key_length: 16 identity: schemas: - id: default url: file://stub/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true&legacy_ssl=true ================================================ FILE: codecov.yml ================================================ coverage: status: project: default: target: 60% threshold: 10% only_pulls: true ignore: - "test" - "pkg" - "docs" - "proto" - "gen" - "examples" - "contrib" - "selfservice/strategy/oidc/provider_netid.go" # No way to test this provider automatically - "**/*_test.go" ================================================ FILE: continuity/container.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity import ( "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/x/pointerx" "github.com/ory/x/sqlxx" "github.com/ory/kratos/x" ) type Container struct { ID uuid.UUID `json:"id" db:"id" rw:"r"` NID uuid.UUID `json:"-" db:"nid"` Name string `json:"name" db:"name"` IdentityID *uuid.UUID `json:"identity_id" db:"identity_id"` // ExpiresAt defines when this container expires. ExpiresAt time.Time `json:"expires_at" db:"expires_at"` // Payload is the container's payload. Payload sqlxx.NullJSONRawMessage `json:"payload" db:"payload"` // CreatedAt is a helper struct field for gobuffalo.pop. CreatedAt time.Time `json:"created_at" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } func (c *Container) UTC() *Container { c.CreatedAt = c.CreatedAt.UTC() c.UpdatedAt = c.UpdatedAt.UTC() c.ExpiresAt = c.ExpiresAt.UTC() return c } func (Container) TableName() string { return "continuity_containers" } func NewContainer(name string, o managerOptions) *Container { return &Container{ ID: uuid.Nil, Name: name, IdentityID: x.PointToUUID(o.iid), ExpiresAt: time.Now().Add(o.ttl).UTC().Truncate(time.Second), Payload: sqlxx.NullJSONRawMessage(o.payload), } } func (c *Container) Valid(identity uuid.UUID) error { if c.ExpiresAt.Before(time.Now()) { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("You must restart the flow because the resumable session has expired.")) } if identity != uuid.Nil && pointerx.Deref(c.IdentityID) != identity { return errors.WithStack(herodot.ErrForbidden.WithReasonf("The flow has been blocked for security reasons because it was initiated by another person..")) } return nil } ================================================ FILE: continuity/container_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity import ( "fmt" "testing" "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/require" "github.com/ory/kratos/x" ) func TestContainer(t *testing.T) { id := x.NewUUID() for k, tc := range []struct { c *Container i uuid.UUID pass bool }{ { c: &Container{ ExpiresAt: time.Now().Add(-time.Minute), }, pass: false, }, { c: &Container{ ExpiresAt: time.Now().Add(-time.Minute), }, i: x.NewUUID(), pass: false, }, { c: &Container{ IdentityID: x.PointToUUID(x.NewUUID()), ExpiresAt: time.Now().Add(-time.Minute), }, i: x.NewUUID(), pass: false, }, { c: &Container{ ExpiresAt: time.Now().Add(time.Minute), }, pass: true, }, { c: &Container{ IdentityID: x.PointToUUID(id), ExpiresAt: time.Now().Add(time.Minute), }, i: id, pass: true, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { err := tc.c.Valid(tc.i) if tc.pass { require.NoError(t, err) } else { require.Error(t, err) } }) } } ================================================ FILE: continuity/manager.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity import ( "bytes" "context" "encoding/json" "net/http" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/kratos/identity" ) type ManagementProvider interface { ContinuityManager() Manager } type Manager interface { Pause(ctx context.Context, w http.ResponseWriter, r *http.Request, name string, opts ...ManagerOption) error Continue(ctx context.Context, w http.ResponseWriter, r *http.Request, name string, opts ...ManagerOption) (*Container, error) Abort(ctx context.Context, w http.ResponseWriter, r *http.Request, name string) error } type managerOptions struct { iid uuid.UUID ttl time.Duration setExpiresIn time.Duration payload json.RawMessage payloadRaw interface{} } type ManagerOption func(*managerOptions) error func newManagerOptions(opts []ManagerOption) (*managerOptions, error) { var o = &managerOptions{ ttl: time.Minute * 10, } for _, opt := range opts { if err := opt(o); err != nil { return nil, err } } return o, nil } func WithIdentity(i *identity.Identity) ManagerOption { return func(o *managerOptions) error { if i != nil { o.iid = i.ID } return nil } } func WithLifespan(ttl time.Duration) ManagerOption { return func(o *managerOptions) error { o.ttl = ttl return nil } } func WithPayload(payload interface{}) ManagerOption { return func(o *managerOptions) error { var b bytes.Buffer if err := json.NewEncoder(&b).Encode(payload); err != nil { return errors.WithStack(err) } o.payload = b.Bytes() o.payloadRaw = payload return nil } } func WithExpireInsteadOfDelete(duration time.Duration) ManagerOption { return func(o *managerOptions) error { o.setExpiresIn = duration return nil } } ================================================ FILE: continuity/manager_cookie.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity import ( "bytes" "context" "encoding/json" "net/http" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" "github.com/ory/kratos/session" "github.com/ory/kratos/x" ) var ( _ Manager = new(ManagerCookie) ErrNotResumable = *herodot.ErrBadRequest.WithError("no resumable session found").WithReasonf("The browser does not contain the necessary cookie to resume the session. This is a security violation and was blocked. Please clear your browser's cookies and cache and try again!") ) const CookieName = "ory_kratos_continuity" type ( managerCookieDependencies interface { PersistenceProvider x.CookieProvider session.ManagementProvider otelx.Provider } ManagerCookie struct { d managerCookieDependencies } ) func NewManagerCookie(d managerCookieDependencies) *ManagerCookie { return &ManagerCookie{d: d} } func (m *ManagerCookie) Pause(ctx context.Context, w http.ResponseWriter, r *http.Request, name string, opts ...ManagerOption) (err error) { ctx, span := m.d.Tracer(ctx).Tracer().Start(ctx, "continuity.ManagerCookie.Pause") defer otelx.End(span, &err) if len(name) == 0 { return errors.Errorf("continuity container name must be set") } o, err := newManagerOptions(opts) if err != nil { return err } c := NewContainer(name, *o) if err := m.d.ContinuityPersister().SaveContinuitySession(ctx, c); err != nil { return errors.WithStack(err) } if err := x.SessionPersistValues(w, r, m.d.ContinuityCookieManager(ctx), CookieName, map[string]interface{}{ name: c.ID.String(), }); err != nil { return err } return nil } func (m *ManagerCookie) Continue(ctx context.Context, w http.ResponseWriter, r *http.Request, name string, opts ...ManagerOption) (container *Container, err error) { ctx, span := m.d.Tracer(ctx).Tracer().Start(ctx, "continuity.ManagerCookie.Continue") defer otelx.End(span, &err) container, err = m.container(ctx, w, r, name) if err != nil { return nil, err } o, err := newManagerOptions(opts) if err != nil { return nil, err } if err := container.Valid(o.iid); err != nil { return nil, err } if o.payloadRaw != nil && container.Payload != nil { if err := json.NewDecoder(bytes.NewBuffer(container.Payload)).Decode(o.payloadRaw); err != nil { return nil, errors.WithStack(err) } } if o.setExpiresIn > 0 { if err := m.d.ContinuityPersister().SetContinuitySessionExpiry( ctx, container.ID, time.Now().UTC().Add(o.setExpiresIn).Truncate(time.Second), ); err != nil && !errors.Is(err, sqlcon.ErrNoRows) { return nil, err } } else { if err := x.SessionUnsetKey(w, r, m.d.ContinuityCookieManager(ctx), CookieName, name); err != nil { return nil, err } if err := m.d.ContinuityPersister().DeleteContinuitySession(ctx, container.ID); err != nil && !errors.Is(err, sqlcon.ErrNoRows) { return nil, err } } return container, nil } func (m *ManagerCookie) sessionID(ctx context.Context, w http.ResponseWriter, r *http.Request, name string) (uuid.UUID, error) { s, err := x.SessionGetString(r, m.d.ContinuityCookieManager(ctx), CookieName, name) if err != nil { _ = x.SessionUnsetKey(w, r, m.d.ContinuityCookieManager(ctx), CookieName, name) return uuid.Nil, errors.WithStack(ErrNotResumable.WithDebugf("%+v", err)) } sid, err := uuid.FromString(s) if err != nil { _ = x.SessionUnsetKey(w, r, m.d.ContinuityCookieManager(ctx), CookieName, name) return uuid.Nil, errors.WithStack(ErrNotResumable.WithDebug("session id is not a valid uuid")) } return sid, nil } func (m *ManagerCookie) container(ctx context.Context, w http.ResponseWriter, r *http.Request, name string) (*Container, error) { sid, err := m.sessionID(ctx, w, r, name) if err != nil { return nil, err } container, err := m.d.ContinuityPersister().GetContinuitySession(ctx, sid) // If an error happens, we need to clean up the cookie. if err != nil { _ = x.SessionUnsetKey(w, r, m.d.ContinuityCookieManager(ctx), CookieName, name) } if errors.Is(err, sqlcon.ErrNoRows) { return nil, errors.WithStack(ErrNotResumable.WithDebugf("Resumable ID from cookie could not be found in the datastore: %+v", err)) } else if err != nil { return nil, err } else if container.ExpiresAt.Before(time.Now()) { _ = x.SessionUnsetKey(w, r, m.d.ContinuityCookieManager(ctx), CookieName, name) return nil, errors.WithStack(ErrNotResumable.WithDebugf("Resumable session has expired")) } return container, err } func (m ManagerCookie) Abort(ctx context.Context, w http.ResponseWriter, r *http.Request, name string) (err error) { ctx, span := m.d.Tracer(ctx).Tracer().Start(ctx, "continuity.ManagerCookie.Abort") defer otelx.End(span, &err) sid, err := m.sessionID(ctx, w, r, name) if errors.Is(err, &ErrNotResumable) { // We do not care about an error here return nil } else if err != nil { return err } if err := x.SessionUnsetKey(w, r, m.d.ContinuityCookieManager(ctx), CookieName, name); err != nil { return err } if err := m.d.ContinuityPersister().DeleteContinuitySession(ctx, sid); err != nil && !errors.Is(err, sqlcon.ErrNoRows) { return errors.WithStack(err) } return nil } ================================================ FILE: continuity/manager_options_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestManagerOptions(t *testing.T) { for k, tc := range []struct { err bool e func(t *testing.T, actual *managerOptions) opts []ManagerOption }{ { e: func(t *testing.T, actual *managerOptions) { assert.EqualValues(t, time.Minute*10, actual.ttl) }, }, { opts: []ManagerOption{WithLifespan(time.Minute * 5)}, e: func(t *testing.T, actual *managerOptions) { assert.EqualValues(t, time.Minute*5, actual.ttl) }, }, { opts: []ManagerOption{WithPayload(map[string]interface{}{"foo": "bar"})}, e: func(t *testing.T, actual *managerOptions) { assert.EqualValues(t, map[string]interface{}{"foo": "bar"}, actual.payloadRaw) assert.JSONEq(t, `{"foo":"bar"}`, string(actual.payload)) }, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { o, err := newManagerOptions(tc.opts) if tc.err { require.Error(t, err) return } require.NoError(t, err) tc.e(t, o) }) } } ================================================ FILE: continuity/manager_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity_test import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/herodot" "github.com/ory/kratos/continuity" "github.com/ory/kratos/identity" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/x" "github.com/ory/x/configx" "github.com/ory/x/ioutilx" "github.com/ory/x/logrusx" ) type persisterTestCase struct { ro []continuity.ManagerOption wo []continuity.ManagerOption expected *persisterTestPayload expectedErr error } type persisterTestPayload struct { Foo string `json:"foo"` } func TestManager(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://../test/stub/identity/empty.schema.json")), ) i := identity.NewIdentity("") require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(t.Context(), i)) newServer := func(t *testing.T, p continuity.Manager, tc *persisterTestCase) *httptest.Server { writer := herodot.NewJSONWriter(logrusx.New("", "")) router := http.NewServeMux() router.HandleFunc("PUT /{name}", func(w http.ResponseWriter, r *http.Request) { if err := p.Pause(r.Context(), w, r, r.PathValue("name"), tc.ro...); err != nil { writer.WriteError(w, r, err) return } w.WriteHeader(http.StatusNoContent) }) router.HandleFunc("POST /{name}", func(w http.ResponseWriter, r *http.Request) { if err := p.Pause(r.Context(), w, r, r.PathValue("name"), tc.ro...); err != nil { writer.WriteError(w, r, err) return } c, err := p.Continue(r.Context(), w, r, r.PathValue("name"), tc.wo...) if err != nil { writer.WriteError(w, r, err) return } writer.Write(w, r, c) }) router.HandleFunc("GET /{name}", func(w http.ResponseWriter, r *http.Request) { c, err := p.Continue(r.Context(), w, r, r.PathValue("name"), tc.ro...) if err != nil { writer.WriteError(w, r, err) return } writer.Write(w, r, c) }) router.HandleFunc("DELETE /{name}", func(w http.ResponseWriter, r *http.Request) { err := p.Abort(r.Context(), w, r, r.PathValue("name")) if err != nil { writer.WriteError(w, r, err) return } w.WriteHeader(http.StatusNoContent) }) ts := httptest.NewServer(router) t.Cleanup(ts.Close) return ts } newClient := func() *http.Client { return &http.Client{Jar: testhelpers.EasyCookieJar(t, nil)} } p := reg.ContinuityManager() cl := newClient() t.Run("case=continue cookie resets when signature is invalid", func(t *testing.T) { ts := newServer(t, p, new(persisterTestCase)) href := ts.URL + "/" + x.NewUUID().String() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) req := testhelpers.NewTestHTTPRequest(t, "GET", href, nil) require.Len(t, res.Cookies(), 1) for _, c := range res.Cookies() { // Change something in the string c.Value = strings.Replace(c.Value, "a", "b", 1) req.AddCookie(c) } res, err = http.DefaultClient.Do(req) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) require.Equal(t, http.StatusBadRequest, res.StatusCode) body := ioutilx.MustReadAll(res.Body) assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable.ReasonField) require.Len(t, res.Cookies(), 1, "continuing the flow with a broken cookie should instruct the browser to forget it") assert.EqualValues(t, res.Cookies()[0].Name, continuity.CookieName) }) t.Run("case=can deal with duplicate cookies", func(t *testing.T) { tc := &persisterTestCase{expected: &persisterTestPayload{"bar"}} ts := newServer(t, p, tc) href := ts.URL + "/" + x.NewUUID().String() res, err := http.DefaultClient.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) // We change the key to another one href = ts.URL + "/" + x.NewUUID().String() req := testhelpers.NewTestHTTPRequest(t, "GET", href, nil) require.Len(t, res.Cookies(), 1) for _, c := range res.Cookies() { req.AddCookie(c) } tc.ro = []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"})} res, err = http.DefaultClient.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) require.Len(t, res.Cookies(), 1) for _, c := range res.Cookies() { req.AddCookie(c) } res, err = http.DefaultClient.Do(req) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) require.Len(t, res.Cookies(), 1, "continuing the flow with a broken cookie should instruct the browser to forget it") assert.EqualValues(t, res.Cookies()[0].Name, continuity.CookieName) var b bytes.Buffer require.NoError(t, json.NewEncoder(&b).Encode(tc.expected)) body := ioutilx.MustReadAll(res.Body) assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body) assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body) }) t.Run("case=pause and use session with expiry", func(t *testing.T) { cl := newClient() tc := &persisterTestCase{ ro: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"}), continuity.WithExpireInsteadOfDelete(time.Minute)}, wo: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{}), continuity.WithExpireInsteadOfDelete(time.Minute)}, } ts := newServer(t, p, tc) genid := func() string { return ts.URL + "/" + x.NewUUID().String() } href := genid() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusOK, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusOK, res.StatusCode) tc.ro = []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"}), continuity.WithExpireInsteadOfDelete(-time.Minute)} tc.wo = []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{""}), continuity.WithExpireInsteadOfDelete(-time.Minute)} res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusOK, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) require.Equal(t, http.StatusBadRequest, res.StatusCode) body := ioutilx.MustReadAll(res.Body) require.NoError(t, res.Body.Close()) assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable.ReasonField) }) for k, tc := range []persisterTestCase{ {}, { ro: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"})}, wo: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{})}, expected: &persisterTestPayload{"bar"}, }, { ro: []continuity.ManagerOption{continuity.WithIdentity(i)}, wo: []continuity.ManagerOption{continuity.WithIdentity(i)}, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { cl := newClient() ts := newServer(t, p, &tc) genid := func() string { return ts.URL + "/" + x.NewUUID().String() } t.Run("case=resume non-existing session", func(t *testing.T) { href := genid() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) body := ioutilx.MustReadAll(res.Body) require.Equal(t, http.StatusBadRequest, res.StatusCode) assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable.ReasonField) }) t.Run("case=pause and resume session", func(t *testing.T) { href := genid() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) body := ioutilx.MustReadAll(res.Body) if tc.expectedErr != nil { require.Equal(t, http.StatusGone, res.StatusCode, "%s", body) return } require.Equal(t, http.StatusOK, res.StatusCode, "%s", body) var b bytes.Buffer require.NoError(t, json.NewEncoder(&b).Encode(tc.expected)) assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body) assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body) }) t.Run("case=pause and retry session", func(t *testing.T) { href := genid() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) require.Equal(t, http.StatusBadRequest, res.StatusCode) body := ioutilx.MustReadAll(res.Body) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable.ReasonField) }) t.Run("case=pause and resume session in the same request", func(t *testing.T) { href := genid() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "POST", href, nil)) require.NoError(t, err) require.Equal(t, http.StatusOK, res.StatusCode) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) var b bytes.Buffer require.NoError(t, json.NewEncoder(&b).Encode(tc.expected)) body := ioutilx.MustReadAll(res.Body) assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body) assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body) }) t.Run("case=pause, abort, and continue session with failure", func(t *testing.T) { href := genid() res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil)) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.Equal(t, http.StatusNoContent, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "DELETE", href, nil)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) require.Equal(t, http.StatusNoContent, res.StatusCode) res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) require.Equal(t, http.StatusBadRequest, res.StatusCode) body := ioutilx.MustReadAll(res.Body) assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable.ReasonField) }) }) } } ================================================ FILE: continuity/persistence.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package continuity import ( "context" "time" "github.com/gofrs/uuid" ) type PersistenceProvider interface { ContinuityPersister() Persister } type Persister interface { SaveContinuitySession(ctx context.Context, c *Container) error GetContinuitySession(ctx context.Context, id uuid.UUID) (*Container, error) DeleteContinuitySession(ctx context.Context, id uuid.UUID) error SetContinuitySessionExpiry(ctx context.Context, id uuid.UUID, expiresAt time.Time) error DeleteExpiredContinuitySessions(ctx context.Context, deleteOlder time.Time, pageSize int) error } ================================================ FILE: continuity/test/persistence.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "testing" "time" "github.com/gofrs/uuid" "github.com/ory/kratos/pkg/testhelpers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/continuity" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence" "github.com/ory/kratos/x" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlxx" ) func TestPersister(ctx context.Context, p interface { persistence.Persister continuity.Persister identity.PrivilegedPool }) func(t *testing.T) { var createIdentity = func(t *testing.T) *identity.Identity { id := identity.Identity{} require.NoError(t, p.CreateIdentity(ctx, &id)) return &id } var createContainer = func(t *testing.T) continuity.Container { m := sqlxx.NullJSONRawMessage(`{"foo": "bar"}`) return continuity.Container{Name: "foo", IdentityID: x.PointToUUID(createIdentity(t).ID), ExpiresAt: time.Now().Add(time.Hour).UTC().Truncate(time.Second), Payload: m, } } return func(t *testing.T) { nid, p := testhelpers.NewNetworkUnlessExisting(t, ctx, p) t.Run("case=not found", func(t *testing.T) { _, err := p.GetContinuitySession(ctx, x.NewUUID()) require.EqualError(t, err, sqlcon.ErrNoRows.Error()) }) t.Run("case=save and find", func(t *testing.T) { expected := createContainer(t) require.NoError(t, p.SaveContinuitySession(ctx, &expected)) actual, err := p.GetContinuitySession(ctx, expected.ID) require.NoError(t, err) actual.UpdatedAt, actual.CreatedAt, expected.UpdatedAt, expected.CreatedAt = time.Time{}, time.Time{}, time.Time{}, time.Time{} assert.EqualValues(t, expected.UTC(), actual.UTC()) }) t.Run("case=save and delete", func(t *testing.T) { expected := createContainer(t) require.NoError(t, p.SaveContinuitySession(ctx, &expected)) require.NotEqual(t, uuid.Nil, expected.ID) require.NoError(t, p.DeleteContinuitySession(ctx, expected.ID)) _, err := p.GetContinuitySession(ctx, expected.ID) require.EqualError(t, err, sqlcon.ErrNoRows.Error()) }) t.Run("case=network", func(t *testing.T) { id := x.NewUUID() t.Run("sets id on creation", func(t *testing.T) { expected := createContainer(t) expected.ID = id require.NoError(t, p.SaveContinuitySession(ctx, &expected)) assert.EqualValues(t, id, expected.ID) assert.EqualValues(t, nid, expected.NID) actual, err := p.GetContinuitySession(ctx, id) require.NoError(t, err) assert.EqualValues(t, id, actual.ID) assert.EqualValues(t, nid, actual.NID) }) t.Run("can not get on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.GetLoginFlow(ctx, id) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) t.Run("can not delete on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) err := p.DeleteContinuitySession(ctx, id) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=set expiry", func(t *testing.T) { // Create a new continuity session expected := createContainer(t) require.NoError(t, p.SaveContinuitySession(ctx, &expected)) // Set the expiry of the continuity session newExpiry := time.Now().Add(48 * time.Hour).UTC().Truncate(time.Second) require.NoError(t, p.SetContinuitySessionExpiry(ctx, expected.ID, newExpiry)) // Retrieve the continuity session actual, err := p.GetContinuitySession(ctx, expected.ID) require.NoError(t, err) // Check if the expiry has been updated assert.EqualValues(t, newExpiry, actual.ExpiresAt) t.Run("can not update on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) newExpiry := time.Now().Add(12 * time.Hour).UTC().Truncate(time.Second) err := p.SetContinuitySessionExpiry(ctx, expected.ID, newExpiry) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=cleanup", func(t *testing.T) { id := x.NewUUID() yesterday := time.Now().Add(-24 * time.Hour).UTC().Truncate(time.Second) m := sqlxx.NullJSONRawMessage(`{"foo": "bar"}`) expected := continuity.Container{Name: "foo", IdentityID: x.PointToUUID(createIdentity(t).ID), ExpiresAt: yesterday, Payload: m, } expected.ID = id t.Run("can cleanup", func(t *testing.T) { require.NoError(t, p.SaveContinuitySession(ctx, &expected)) assert.EqualValues(t, id, expected.ID) assert.EqualValues(t, nid, expected.NID) require.NoError(t, p.DeleteExpiredContinuitySessions(ctx, time.Now(), 5)) _, err := p.GetContinuitySession(ctx, id) require.Error(t, err) }) }) } } ================================================ FILE: contrib/quickstart/.dockerignore ================================================ * ================================================ FILE: contrib/quickstart/kratos/all-strategies/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "code": { "identifier": true, "via": "email" }, "passkey": { "display_name": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } }, "name": { "type": "object", "properties": { "first": { "title": "First Name", "type": "string" }, "last": { "title": "Last Name", "type": "string" } } } }, "required": ["email"], "additionalProperties": false } } } ================================================ FILE: contrib/quickstart/kratos/all-strategies/kratos.yml ================================================ version: v0.13.0 dsn: memory serve: public: base_url: http://localhost:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ session: whoami: required_aal: aal1 selfservice: default_browser_return_url: http://localhost:4455/ allowed_return_urls: - http://localhost:4455 - http://localhost:19006/Callback - exp://localhost:8081/--/Callback methods: password: enabled: true webauthn: enabled: true config: passwordless: true rp: display_name: Your Application name # Set 'id' to the top-level domain. id: localhost # Set 'origin' to the exact URL of the page that prompts the user to use WebAuthn. You must include the scheme, host, and port. origin: http://localhost:4455 passkey: enabled: true config: rp: display_name: Your Application name # Set 'id' to the top-level domain. id: localhost # Set 'origin' to the exact URL of the page that prompts the user to use WebAuthn. You must include the scheme, host, and port. origins: - http://localhost:4455 flows: error: ui_url: http://localhost:4455/error settings: ui_url: http://localhost:4455/settings privileged_session_max_age: 15m required_aal: aal1 recovery: enabled: true ui_url: http://localhost:4455/recovery use: code verification: enabled: false ui_url: http://localhost:4455/verification use: code after: default_browser_return_url: http://localhost:4455/ logout: after: default_browser_return_url: http://localhost:4455/login login: ui_url: http://localhost:4455/login lifespan: 10m registration: enable_legacy_one_step: false lifespan: 10m ui_url: http://localhost:4455/registration after: passkey: hooks: - hook: session webauthn: hooks: - hook: session password: hooks: - hook: session - hook: show_verification_ui log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE cipher: - 32-LONG-SECRET-NOT-SECURE-AT-ALL ciphers: algorithm: xchacha20-poly1305 hashers: algorithm: bcrypt bcrypt: cost: 8 identity: default_schema_id: default schemas: - id: default url: file://./contrib/quickstart/kratos/all-strategies/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: contrib/quickstart/kratos/cloud/Caddyfile ================================================ { http_port 4455 auto_https off } :4455 { route /ui/* { uri strip_prefix /ui reverse_proxy kratos-selfservice-ui-node:4438 { header_up Host {http.request.hostport} } } reverse_proxy /* kratos:4433 { header_up Host {http.request.hostport} } } ================================================ FILE: contrib/quickstart/kratos/cloud/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } }, "name": { "type": "object", "properties": { "first": { "title": "First Name", "type": "string" }, "last": { "title": "Last Name", "type": "string" } } } }, "required": [ "email" ], "additionalProperties": false } } } ================================================ FILE: contrib/quickstart/kratos/cloud/kratos.yml ================================================ version: v0.13.0 dsn: memory serve: public: base_url: http://localhost:4455/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://localhost:4455/ui/welcome allowed_return_urls: - http://localhost:4455 methods: password: enabled: true flows: error: ui_url: http://localhost:4455/ui/error settings: ui_url: http://localhost:4455/ui/settings privileged_session_max_age: 15m recovery: enabled: true ui_url: http://localhost:4455/ui/recovery verification: enabled: true ui_url: http://localhost:4455/ui/verification after: default_browser_return_url: http://localhost:4455/ui/welcome logout: after: default_browser_return_url: http://localhost:4455/ui/login login: ui_url: http://localhost:4455/ui/login registration: ui_url: http://localhost:4455/ui/registration after: password: hooks: - hook: session log: level: info format: text secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE hashers: algorithm: bcrypt bcrypt: cost: 8 identity: default_schema_id: preset://email schemas: - id: preset://email url: file:///etc/config/kratos/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: contrib/quickstart/kratos/cloud/quickstart.yml ================================================ version: '3.7' services: kratos: volumes: - type: volume source: kratos-sqlite target: /var/lib/sqlite read_only: false - type: bind source: ./contrib/quickstart/kratos/cloud target: /etc/config/kratos kratos-migrate: volumes: - type: volume source: kratos-sqlite target: /var/lib/sqlite read_only: false - type: bind source: ./contrib/quickstart/kratos/cloud target: /etc/config/kratos kratos-selfservice-ui-node: ports: - "4438:4438" environment: - PORT=4438 - KRATOS_BROWSER_URL=http://localhost:4455/ kratos-caddy: image: caddy:2.4.5-alpine ports: - "4455:4455" volumes: - type: bind source: ./contrib/quickstart/kratos/cloud/Caddyfile target: /etc/caddy/Caddyfile command: caddy run -watch -config /etc/caddy/Caddyfile restart: on-failure networks: - intranet ================================================ FILE: contrib/quickstart/kratos/email-password/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } }, "name": { "type": "object", "properties": { "first": { "title": "First Name", "type": "string" }, "last": { "title": "Last Name", "type": "string" } } } }, "required": [ "email" ], "additionalProperties": false } } } ================================================ FILE: contrib/quickstart/kratos/email-password/kratos.yml ================================================ version: v0.13.0 dsn: memory serve: public: base_url: http://127.0.0.1:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://127.0.0.1:4455/welcome allowed_return_urls: - http://127.0.0.1:4455 - http://localhost:19006/Callback - exp://localhost:8081/--/Callback methods: password: enabled: true totp: config: issuer: Kratos enabled: true lookup_secret: enabled: true link: enabled: true code: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings privileged_session_max_age: 15m required_aal: highest_available recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery use: code verification: enabled: true ui_url: http://127.0.0.1:4455/verification use: code after: default_browser_return_url: http://127.0.0.1:4455/welcome logout: after: default_browser_return_url: http://127.0.0.1:4455/login login: ui_url: http://127.0.0.1:4455/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/registration after: password: hooks: - hook: session - hook: show_verification_ui log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE cipher: - 32-LONG-SECRET-NOT-SECURE-AT-ALL ciphers: algorithm: xchacha20-poly1305 hashers: algorithm: bcrypt bcrypt: cost: 8 identity: default_schema_id: default schemas: - id: default url: file:///etc/config/kratos/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true feature_flags: use_continue_with_transitions: true ================================================ FILE: contrib/quickstart/kratos/passkey/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "Your E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "passkey": { "display_name": true } } } } }, "required": ["email"], "additionalProperties": false } } } ================================================ FILE: contrib/quickstart/kratos/passkey/kratos.yml ================================================ serve: public: base_url: http://localhost:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ session: whoami: required_aal: aal1 selfservice: default_browser_return_url: http://localhost:4455/welcome allowed_return_urls: - http://localhost:4455 - http://localhost:19006/Callback - exp://example.com/Callback - https://www.ory.sh/ - https://example.org/ - https://www.example.org/ methods: link: config: lifespan: 1h code: config: lifespan: 1h totp: enabled: true config: issuer: issuer.ory.sh lookup_secret: enabled: true webauthn: enabled: true config: passwordless: true rp: id: localhost origins: - http://localhost:4455 display_name: Ory passkey: enabled: true config: rp: id: localhost origins: - http://localhost:4455 display_name: Ory flows: settings: ui_url: http://localhost:4455/settings privileged_session_max_age: 5m required_aal: aal1 logout: after: default_browser_return_url: http://localhost:4455/login registration: ui_url: http://localhost:4455/registration after: password: hooks: - hook: session webauthn: hooks: - hook: session passkey: hooks: - hook: session login: ui_url: http://localhost:4455/login error: ui_url: http://localhost:4455/error verification: ui_url: http://localhost:4455/verify recovery: ui_url: http://localhost:4455/recovery log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE cipher: - 32-LONG-SECRET-NOT-SECURE-AT-ALL ciphers: algorithm: xchacha20-poly1305 hashers: algorithm: bcrypt bcrypt: cost: 8 identity: schemas: - id: default url: file://contrib/quickstart/kratos/passkey/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: contrib/quickstart/kratos/phone-password/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/phone-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "code": { "identifier": true, "via": "email" } }, "verification": { "via": "email" } } }, "phone": { "type": "string", "format": "tel", "title": "Phone number", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "sms" } } } }, "required": ["email", "phone"], "additionalProperties": false } } } ================================================ FILE: contrib/quickstart/kratos/phone-password/kratos.yml ================================================ version: v0.13.0 dsn: memory serve: public: base_url: http://127.0.0.1:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://127.0.0.1:4455/welcome allowed_return_urls: - http://127.0.0.1:4455 - http://localhost:19006/Callback - exp://localhost:8081/--/Callback methods: password: enabled: true totp: config: issuer: Kratos enabled: true lookup_secret: enabled: true link: enabled: true code: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings privileged_session_max_age: 15m required_aal: highest_available recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery use: code verification: enabled: true ui_url: http://127.0.0.1:4455/verification use: code after: default_browser_return_url: http://127.0.0.1:4455/welcome logout: after: default_browser_return_url: http://127.0.0.1:4455/login login: ui_url: http://127.0.0.1:4455/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/registration after: password: hooks: - hook: session - hook: show_verification_ui log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE cipher: - 32-LONG-SECRET-NOT-SECURE-AT-ALL ciphers: algorithm: xchacha20-poly1305 hashers: algorithm: bcrypt bcrypt: cost: 8 identity: default_schema_id: default schemas: - id: default url: file:///etc/config/kratos/identity.schema.json courier: channels: - id: sms type: http request_config: url: https://api.twilio.com/2010-04-01/Accounts/AXXXXXXXXXXXXXX/Messages.json method: POST body: base64://ZnVuY3Rpb24oY3R4KSB7ClRvOiBjdHguUmVjaXBpZW50LApCb2R5OiBjdHguQm9keSwKfQ== headers: Content-Type: application/x-www-form-urlencoded auth: type: basic_auth config: user: AXXXXXXX password: XXXX feature_flags: use_continue_with_transitions: true ================================================ FILE: contrib/quickstart/kratos/webauthn/identity.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } }, "name": { "type": "object", "properties": { "first": { "title": "First Name", "type": "string" }, "last": { "title": "Last Name", "type": "string" } } } }, "required": ["email"], "additionalProperties": false } } } ================================================ FILE: contrib/quickstart/kratos/webauthn/kratos.yml ================================================ version: v0.13.0 dsn: memory serve: public: base_url: http://localhost:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://localhost:4455/welcome allowed_return_urls: - http://localhost:4455 methods: password: enabled: false totp: config: issuer: Kratos enabled: true lookup_secret: enabled: true link: enabled: true code: enabled: true webauthn: config: passwordless: true rp: display_name: Your Application name # Set 'id' to the top-level domain. id: localhost # Set 'origin' to the exact URL of the page that prompts the user to use WebAuthn. You must include the scheme, host, and port. origin: http://localhost:4455 enabled: true flows: error: ui_url: http://localhost:4455/error settings: ui_url: http://localhost:4455/settings privileged_session_max_age: 15m required_aal: highest_available recovery: enabled: true ui_url: http://localhost:4455/recovery use: code verification: enabled: true ui_url: http://localhost:4455/verification use: code after: default_browser_return_url: http://localhost:4455/welcome logout: after: default_browser_return_url: http://localhost:4455/login login: ui_url: http://localhost:4455/login lifespan: 10m registration: lifespan: 10m ui_url: http://localhost:4455/registration after: password: hooks: - hook: session - hook: show_verification_ui log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE cipher: - 32-LONG-SECRET-NOT-SECURE-AT-ALL ciphers: algorithm: xchacha20-poly1305 hashers: algorithm: bcrypt bcrypt: cost: 8 identity: default_schema_id: default schemas: - id: default url: file:///etc/config/kratos/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: contrib/quickstart/oathkeeper/access-rules.yml ================================================ - id: "ory:kratos:public" upstream: preserve_host: true url: "http://kratos:4433" strip_path: /.ory/kratos/public match: url: "http://127.0.0.1:4455/.ory/kratos/public/<**>" methods: - GET - POST - PUT - DELETE - PATCH authenticators: - handler: noop authorizer: handler: allow mutators: - handler: noop - id: "ory:kratos-selfservice-ui-node:anonymous" upstream: preserve_host: true url: "http://kratos-selfservice-ui-node:4435" match: url: "http://127.0.0.1:4455/<{registration,welcome,recovery,verification,login,error,health/{alive,ready},**.css,**.js,**.png,**.svg,**.woff*}>" methods: - GET authenticators: - handler: anonymous authorizer: handler: allow mutators: - handler: noop - id: "ory:kratos-selfservice-ui-node:protected" upstream: preserve_host: true url: "http://kratos-selfservice-ui-node:4435" match: url: "http://127.0.0.1:4455/<{sessions,settings}>" methods: - GET authenticators: - handler: cookie_session authorizer: handler: allow mutators: - handler: id_token errors: - handler: redirect config: to: http://127.0.0.1:4455/login ================================================ FILE: contrib/quickstart/oathkeeper/id_token.jwks.json ================================================ { "keys": [ { "use": "sig", "kty": "RSA", "kid": "a2aa9739-d753-4a0d-87ee-61f101050277", "alg": "RS256", "n": "zpjSl0ySsdk_YC4ZJYYV-cSznWkzndTo0lyvkYmeBkW60YHuHzXaviHqonY_DjFBdnZC0Vs_QTWmBlZvPzTp4Oni-eOetP-Ce3-B8jkGWpKFOjTLw7uwR3b3jm_mFNiz1dV_utWiweqx62Se0SyYaAXrgStU8-3P2Us7_kz5NnBVL1E7aEP40aB7nytLvPhXau-YhFmUfgykAcov0QrnNY0DH0eTcwL19UysvlKx6Uiu6mnbaFE1qx8X2m2xuLpErfiqj6wLCdCYMWdRTHiVsQMtTzSwuPuXfH7J06GTo3I1cEWN8Mb-RJxlosJA_q7hEd43yYisCO-8szX0lgCasw", "e": "AQAB", "d": "x3dfY_rna1UQTmFToBoMn6Edte47irhkra4VSNPwwaeTTvI-oN2TO51td7vo91_xD1nw-0c5FFGi4V2UfRcudBv9LD1rHt_O8EPUh7QtAUeT3_XXgjx1Xxpqu5goMZpkTyGZ-B6JzOY3L8lvWQ_Qeia1EXpvxC-oTOjJnKZeuwIPlcoNKMRU-mIYOnkRFfnUvrDm7N9UZEp3PfI3vhE9AquP1PEvz5KTUYkubsfmupqqR6FmMUm6ulGT7guhBw9A3vxIYbYGKvXLdBvn68mENrEYxXrwmu6ITMh_y208M5rC-hgEHIAIvMu1aVW6jNgyQTunsGST3UyrSbwjI0K9UQ", "p": "77fDvnfHRFEgyi7mh0c6fAdtMEMJ05W8NwTG_D-cSwfWipfTwJJrroWoRwEgdAg5AWGq-MNUzrubTVXoJdC2T4g1o-VRZkKKYoMvav3CvOIMzCBxBs9I_GAKr5NCSk7maksMqiCTMhmkoZ5RPuMYMY_YzxKNAbjBd9qFLfaVAqs", "q": "3KEmPA2XQkf7dvtpY1Xkp1IfMV_UBdmYk7J6dB5BYqzviQWdEFvWaSATJ_7qV1dw0JDZynOgipp8gvoL-RepfjtArhPz41wB3J2xmBYrBr1sJ-x5eqAvMkQk2bd5KTor44e79TRIkmkFYAIdUQ5JdVXPA13S8WUZfb_bAbwaCBk", "dp": "5uyy32AJkNFKchqeLsE6INMSp0RdSftbtfCfM86fZFQno5lA_qjOnO_avJPkTILDT4ZjqoKYxxJJOEXCffNCPPltGvbE5GrDXsUbP8k2-LgWNeoml7XFjIGEqcCFQoohQ1IK4DTDN6cmRh76C0e_Pbdh15D6TydJEIlsdGuu_kM", "dq": "aegFNYCEojFxeTzX6vIZL2RRSt8oJKK-Be__reu0EUzYMtr5-RdMhev6phFMph54LfXKRc9ZOg9MQ4cJ5klAeDKzKpyzTukkj6U20b2aa8LTvxpZec6YuTVSxxu2Ul71IGRQijTNvVIiXWLGddk409Ub6Q7JqkyQfvdwhpWnnUk", "qi": "P68-EwgcRy9ce_PZ75c909cU7dzCiaGcTX1psJiXmQAFBcG0msWfsyHGbllOZG27pKde78ORGJDYDNk1FqTwsogZyCP87EiBmOoqXWnMvKYfJ1DOx7x42LMAGwMD3bgQj9jgRACxFJG4n3NI6uFlFruyl_CLQzwW_rQFHshLK7Q" } ] } ================================================ FILE: contrib/quickstart/oathkeeper/oathkeeper.yml ================================================ log: level: debug format: json serve: proxy: cors: enabled: true allowed_origins: - "*" allowed_methods: - POST - GET - PUT - PATCH - DELETE allowed_headers: - Authorization - Content-Type exposed_headers: - Content-Type allow_credentials: true debug: true errors: fallback: - json handlers: redirect: enabled: true config: to: http://127.0.0.1:4455/login when: - error: - unauthorized - forbidden request: header: accept: - text/html json: enabled: true config: verbose: true access_rules: matching_strategy: glob repositories: - file:///etc/config/oathkeeper/access-rules.yml authenticators: anonymous: enabled: true config: subject: guest cookie_session: enabled: true config: check_session_url: http://kratos:4433/sessions/whoami preserve_path: true extra_from: "@this" subject_from: "identity.id" only: - ory_kratos_session noop: enabled: true authorizers: allow: enabled: true mutators: noop: enabled: true id_token: enabled: true config: issuer_url: http://127.0.0.1:4455/ jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json claims: | { "session": {{ .Extra | toJson }} } ================================================ FILE: corpx/faker.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // #nosec G404 -- used in tests only package corpx import ( "math/rand" "net/http" "reflect" "sync" "time" "github.com/go-faker/faker/v4" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow" "github.com/ory/kratos/session" "github.com/ory/kratos/ui/node" "github.com/ory/kratos/x" "github.com/ory/x/randx" ) var setup sync.Once func RegisterFakes() { setup.Do(registerFakes) } func registerFakes() { _ = faker.SetRandomMapAndSliceSize(4) if err := faker.AddProvider("ptr_geo_location", func(v reflect.Value) (interface{}, error) { return new("Munich, Germany"), nil }); err != nil { panic(err) } if err := faker.AddProvider("ptr_ipv4", func(v reflect.Value) (interface{}, error) { return new(faker.IPv4()), nil }); err != nil { panic(err) } if err := faker.AddProvider("birthdate", func(v reflect.Value) (interface{}, error) { return time.Now().Add(time.Duration(rand.Int())).Round(time.Second).UTC(), nil }); err != nil { panic(err) } if err := faker.AddProvider("time_types", func(v reflect.Value) (interface{}, error) { es := make([]time.Time, rand.Intn(5)) for k := range es { es[k] = time.Now().Add(time.Duration(rand.Int())).Round(time.Second).UTC() } return es, nil }); err != nil { panic(err) } if err := faker.AddProvider("http_header", func(v reflect.Value) (interface{}, error) { headers := http.Header{} for i := 0; i <= rand.Intn(5); i++ { values := make([]string, rand.Intn(4)+1) for k := range values { values[k] = randx.MustString(8, randx.AlphaNum) } headers[randx.MustString(8, randx.AlphaNum)] = values } return headers, nil }); err != nil { panic(err) } if err := faker.AddProvider("http_method", func(v reflect.Value) (interface{}, error) { methods := []string{"POST", "PUT", "GET", "PATCH"} return methods[rand.Intn(len(methods))], nil }); err != nil { panic(err) } if err := faker.AddProvider("identity_credentials_type", func(v reflect.Value) (interface{}, error) { methods := []identity.CredentialsType{identity.CredentialsTypePassword, identity.CredentialsTypePassword} return string(methods[rand.Intn(len(methods))]), nil }); err != nil { panic(err) } if err := faker.AddProvider("string", func(v reflect.Value) (interface{}, error) { return randx.MustString(25, randx.AlphaNum), nil }); err != nil { panic(err) } if err := faker.AddProvider("time_type", func(v reflect.Value) (interface{}, error) { return time.Now().Add(time.Duration(rand.Int())).Round(time.Second).UTC(), nil }); err != nil { panic(err) } if err := faker.AddProvider("ui_node_attributes", func(v reflect.Value) (interface{}, error) { var a node.Attributes switch rand.Intn(4) { case 0: a = new(node.InputAttributes) case 1: a = new(node.ImageAttributes) case 2: a = new(node.AnchorAttributes) case 3: a = new(node.TextAttributes) } if err := faker.FakeData(a); err != nil { return nil, err } return a, nil }); err != nil { panic(err) } if err := faker.AddProvider("uuid", func(v reflect.Value) (interface{}, error) { return x.NewUUID(), nil }); err != nil { panic(err) } if err := faker.AddProvider("identity", func(v reflect.Value) (interface{}, error) { var i identity.Identity return &i, faker.FakeData(&i) }); err != nil { panic(err) } if err := faker.AddProvider("flow_type", func(v reflect.Value) (interface{}, error) { if rand.Intn(2) == 0 { return flow.TypeAPI, nil } return flow.TypeBrowser, nil }); err != nil { panic(err) } if err := faker.AddProvider("session_device", func(v reflect.Value) (interface{}, error) { var d session.Device return &d, faker.FakeData(&d) }); err != nil { panic(err) } if err := faker.AddProvider("aal_type", func(v reflect.Value) (interface{}, error) { return "aal1", nil }); err != nil { panic(err) } } ================================================ FILE: courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=admin.json ================================================ { "error": { "code": 404, "status": "Not Found", "message": "Unable to locate the resource" } } ================================================ FILE: courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=public.json ================================================ { "error": { "code": 404, "status": "Not Found", "message": "Unable to locate the resource" } } ================================================ FILE: courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=admin.json ================================================ { "error": { "code": 400, "status": "Bad Request", "message": "uuid: incorrect UUID length 10 in string \"not-a-uuid\"" } } ================================================ FILE: courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=public.json ================================================ { "error": { "code": 400, "status": "Bad Request", "message": "uuid: incorrect UUID length 10 in string \"not-a-uuid\"" } } ================================================ FILE: courier/channel.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" ) type Channel interface { ID() string Dispatch(ctx context.Context, msg Message) error } ================================================ FILE: courier/courier.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "time" "github.com/cenkalti/backoff" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/x/httpx" "github.com/ory/x/jsonnetsecure" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/driver/config" ) type ( Dependencies interface { PersistenceProvider otelx.Provider logrusx.Provider ConfigProvider httpx.ClientProvider jsonnetsecure.VMProvider } Courier interface { Work(ctx context.Context) error QueueEmail(ctx context.Context, t EmailTemplate) (uuid.UUID, error) QueueSMS(ctx context.Context, t SMSTemplate) (uuid.UUID, error) DispatchQueue(ctx context.Context) error DispatchMessage(ctx context.Context, msg Message) error UseBackoff(b backoff.BackOff) FailOnDispatchError() } Provider interface { Courier(ctx context.Context) (Courier, error) } ConfigProvider interface { CourierConfig() config.CourierConfigs } courier struct { deps Dependencies failOnDispatchError bool backoff backoff.BackOff newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error) } ) func NewCourier(ctx context.Context, deps Dependencies) (Courier, error) { return NewCourierWithCustomTemplates(ctx, deps, NewEmailTemplateFromMessage) } func NewCourierWithCustomTemplates(_ context.Context, deps Dependencies, newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error)) (Courier, error) { return &courier{ deps: deps, backoff: backoff.NewExponentialBackOff(), newEmailTemplateFromMessage: newEmailTemplateFromMessage, }, nil } func (c *courier) FailOnDispatchError() { c.failOnDispatchError = true } func (c *courier) Work(ctx context.Context) error { errChan := make(chan error) defer close(errChan) go c.watchMessages(ctx, errChan) select { case <-ctx.Done(): if errors.Is(ctx.Err(), context.Canceled) { return nil } return ctx.Err() case err := <-errChan: return errors.WithStack(err) } } func (c *courier) UseBackoff(b backoff.BackOff) { c.backoff = b } func (c *courier) watchMessages(ctx context.Context, errChan chan error) { wait := c.deps.CourierConfig().CourierWorkerPullWait(ctx) c.backoff.Reset() for { if err := backoff.Retry(func() error { return c.DispatchQueue(ctx) }, c.backoff); err != nil { errChan <- errors.WithStack(err) return } time.Sleep(wait) } } ================================================ FILE: courier/courier_dispatcher.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "time" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/x/events" "github.com/ory/x/otelx" ) func (c *courier) channels(ctx context.Context, id string) (Channel, error) { cs, err := c.deps.CourierConfig().CourierChannels(ctx) if err != nil { return nil, err } for _, channel := range cs { if channel.ID != id { continue } switch channel.Type { case "smtp": courierChannel, err := NewSMTPChannelWithCustomTemplates(c.deps, channel.SMTPConfig, c.newEmailTemplateFromMessage) if err != nil { return nil, err } return courierChannel, nil case "http": return newHttpChannel(channel.ID, &channel.RequestConfig, c.deps), nil default: return nil, errors.Errorf("unknown courier channel type: %s", channel.Type) } } return nil, errors.Errorf("no courier channels configured for: %s", id) } func (c *courier) DispatchMessage(ctx context.Context, msg Message) (err error) { ctx, span := c.deps.Tracer(ctx).Tracer().Start(ctx, "courier.DispatchMessage", trace.WithAttributes( attribute.Stringer("message.id", msg.ID), attribute.Stringer("message.nid", msg.NID), attribute.Stringer("message.type", msg.Type), attribute.String("message.template_type", string(msg.TemplateType)), attribute.Int("message.send_count", msg.SendCount), )) defer otelx.End(span, &err) logger := c.deps.Logger(). WithField("message_id", msg.ID). WithField("message_nid", msg.NID). WithField("message_type", msg.Type). WithField("message_template_type", msg.TemplateType). WithField("message_subject", msg.Subject). WithField("trace_id", span.SpanContext().TraceID()) if err := c.deps.CourierPersister().IncrementMessageSendCount(ctx, msg.ID); err != nil { logger. WithError(err). Error(`Unable to increment the message's "send_count" field`) return err } channel, err := c.channels(ctx, msg.Channel.String()) if err != nil { return err } span.SetAttributes(attribute.String("channel.id", channel.ID())) logger = logger. WithField("channel", channel.ID()) if err := channel.Dispatch(ctx, msg); err != nil { return err } span.AddEvent(events.NewCourierMessageDispatched(ctx, msg.ID, msg.Channel.String(), string(msg.TemplateType))) if err := c.deps.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusSent); err != nil { logger. WithError(err). Error(`Unable to set the message status to "sent".`) return err } dispatchDuration := time.Since(msg.CreatedAt).Milliseconds() logger.WithField("dispatch_duration_ms", dispatchDuration).Debug("Courier sent out message.") return nil } func (c *courier) DispatchQueue(ctx context.Context) (err error) { ctx, span := c.deps.Tracer(ctx).Tracer().Start(ctx, "courier.DispatchQueue") defer otelx.End(span, &err) maxRetries := c.deps.CourierConfig().CourierMessageRetries(ctx) pullCount := c.deps.CourierConfig().CourierWorkerPullCount(ctx) //nolint:gosec // disable G115 messages, err := c.deps.CourierPersister().NextMessages(ctx, uint8(pullCount)) if err != nil { if errors.Is(err, ErrQueueEmpty) { return nil } return err } span.SetAttributes(attribute.Int("messages_count", len(messages))) for k, msg := range messages { logger := c.deps.Logger(). WithField("message_id", msg.ID). WithField("message_nid", msg.NID). WithField("message_type", msg.Type). WithField("message_template_type", msg.TemplateType). WithField("message_subject", msg.Subject) if msg.SendCount > maxRetries { if err := c.deps.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusAbandoned); err != nil { logger. WithError(err). Error(`Unable to set the retried message's status to "abandoned".`) return err } span.AddEvent(events.NewCourierMessageAbandoned(ctx, msg.ID, msg.Channel.String(), string(msg.TemplateType))) // Skip the message logger. Warnf(`Message was abandoned because it did not deliver after %d attempts`, msg.SendCount) } else if err := c.DispatchMessage(ctx, msg); err != nil { logger. WithError(err). Warn(`Unable to dispatch message.`) if err := c.deps.CourierPersister().RecordDispatch(ctx, msg.ID, CourierMessageDispatchStatusFailed, err); err != nil { logger. WithError(err). Error(`Unable to record failure log entry.`) if c.failOnDispatchError { return err } } for _, replace := range messages[k:] { if err := c.deps.CourierPersister().SetMessageStatus(ctx, replace.ID, MessageStatusQueued); err != nil { logger. WithError(err). Error(`Unable to reset the failed message's status to "queued".`) if c.failOnDispatchError { return err } } } if c.failOnDispatchError { return err } } else if err := c.deps.CourierPersister().RecordDispatch(ctx, msg.ID, CourierMessageDispatchStatusSuccess, nil); err != nil { logger. WithError(err). Error(`Unable to record success log entry.`) // continue with execution, as the message was successfully dispatched } } return nil } ================================================ FILE: courier/courier_dispatcher_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/kratos/courier" templates "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/x/configx" ) func queueNewMessage(t *testing.T, c courier.Courier) uuid.UUID { t.Helper() id, err := c.QueueEmail(t.Context(), templates.NewTestStub(&templates.TestStubModel{ To: "test-recipient-1@example.org", Subject: "test-subject-1", Body: "test-body-1", })) require.NoError(t, err) return id } func TestDispatchMessageWithInvalidSMTP(t *testing.T) { _, reg := pkg.NewRegistryDefaultWithDSN(t, "", configx.WithValues(map[string]any{ config.ViperKeyCourierMessageRetries: 5, config.ViperKeyCourierSMTPURL: "http://foo.url", })) c, err := reg.Courier(t.Context()) require.NoError(t, err) t.Run("case=failed sending", func(t *testing.T) { id := queueNewMessage(t, c) message, err := reg.CourierPersister().LatestQueuedMessage(t.Context()) require.NoError(t, err) require.Equal(t, id, message.ID) err = c.DispatchMessage(t.Context(), *message) // sending the email fails, because there is no SMTP server at foo.url require.Error(t, err) messages, err := reg.CourierPersister().NextMessages(t.Context(), 10) require.NoError(t, err) require.Len(t, messages, 1) }) } func TestDispatchMessage(t *testing.T) { _, reg := pkg.NewRegistryDefaultWithDSN(t, "", configx.WithValues(map[string]any{ config.ViperKeyCourierMessageRetries: 5, config.ViperKeyCourierSMTPURL: "http://foo.url", })) c, err := reg.Courier(t.Context()) require.NoError(t, err) t.Run("case=invalid channel", func(t *testing.T) { message := courier.Message{ Channel: "invalid-channel", Status: courier.MessageStatusQueued, Type: courier.MessageTypeEmail, Recipient: testhelpers.RandomEmail(), Subject: "test-subject-1", Body: "test-body-1", TemplateType: "stub", } require.NoError(t, reg.CourierPersister().AddMessage(t.Context(), &message)) assert.ErrorContains(t, c.DispatchMessage(t.Context(), message), "no courier channels configured for: invalid-channel") }) } func TestDispatchQueue(t *testing.T) { _, reg := pkg.NewRegistryDefaultWithDSN(t, "", configx.WithValue(config.ViperKeyCourierMessageRetries, 1)) c, err := reg.Courier(t.Context()) require.NoError(t, err) c.FailOnDispatchError() id := queueNewMessage(t, c) require.NotEqual(t, uuid.Nil, id) // Fails to deliver the first time err = c.DispatchQueue(t.Context()) require.Error(t, err) // Retry once, as we set above - still fails err = c.DispatchQueue(t.Context()) require.Error(t, err) // Now it has been retried once, which means 2 > 1 is true and it is no longer tried err = c.DispatchQueue(t.Context()) require.NoError(t, err) var message courier.Message err = reg.Persister().GetConnection(t.Context()). Where("status = ?", courier.MessageStatusAbandoned). Eager("Dispatches"). First(&message) require.NoError(t, err) require.Equal(t, id, message.ID) require.Len(t, message.Dispatches, 2) require.Contains(t, gjson.GetBytes(message.Dispatches[0].Error, "reason").String(), "failed to send email via smtp") require.Contains(t, gjson.GetBytes(message.Dispatches[1].Error, "reason").String(), "failed to send email via smtp") } ================================================ FILE: courier/email_templates.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "encoding/json" "net/http" "github.com/ory/kratos/courier/template" "github.com/pkg/errors" "github.com/ory/kratos/courier/template/email" ) type ( Template interface { json.Marshaler TemplateType() template.TemplateType } EmailTemplate interface { Template EmailSubject(context.Context) (string, error) EmailBody(context.Context) (string, error) EmailBodyPlaintext(context.Context) (string, error) EmailRecipient() (string, error) } RequestHeadersCarrier interface { RequestHeaders() http.Header } ) func NewEmailTemplateFromMessage(d template.Dependencies, msg Message) (EmailTemplate, error) { switch msg.TemplateType { case template.TypeRecoveryInvalid: var t email.RecoveryInvalidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewRecoveryInvalid(d, &t), nil case template.TypeRecoveryValid: var t email.RecoveryValidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewRecoveryValid(d, &t), nil case template.TypeRecoveryCodeInvalid: var t email.RecoveryCodeInvalidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewRecoveryCodeInvalid(d, &t), nil case template.TypeRecoveryCodeValid: var t email.RecoveryCodeValidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewRecoveryCodeValid(d, &t), nil case template.TypeVerificationInvalid: var t email.VerificationInvalidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewVerificationInvalid(d, &t), nil case template.TypeVerificationValid: var t email.VerificationValidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewVerificationValid(d, &t), nil case template.TypeVerificationCodeInvalid: var t email.VerificationCodeInvalidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewVerificationCodeInvalid(d, &t), nil case template.TypeVerificationCodeValid: var t email.VerificationCodeValidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewVerificationCodeValid(d, &t), nil case template.TypeTestStub: var t email.TestStubModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewTestStub(&t), nil case template.TypeLoginCodeValid: var t email.LoginCodeValidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewLoginCodeValid(d, &t), nil case template.TypeRegistrationCodeValid: var t email.RegistrationCodeValidModel if err := json.Unmarshal(msg.TemplateData, &t); err != nil { return nil, err } return email.NewRegistrationCodeValid(d, &t), nil default: return nil, errors.Errorf("received unexpected message template type: %s", msg.TemplateType) } } ================================================ FILE: courier/email_templates_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "context" "encoding/json" "fmt" "testing" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/pkg" ) func TestNewEmailTemplateFromMessage(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) ctx := context.Background() for tmplType, expectedTmpl := range map[template.TemplateType]courier.EmailTemplate{ template.TypeRecoveryInvalid: email.NewRecoveryInvalid(reg, &email.RecoveryInvalidModel{To: "foo"}), template.TypeRecoveryValid: email.NewRecoveryValid(reg, &email.RecoveryValidModel{To: "bar", RecoveryURL: "http://foo.bar"}), template.TypeRecoveryCodeValid: email.NewRecoveryCodeValid(reg, &email.RecoveryCodeValidModel{To: "bar", RecoveryCode: "12345678"}), template.TypeRecoveryCodeInvalid: email.NewRecoveryCodeInvalid(reg, &email.RecoveryCodeInvalidModel{To: "bar"}), template.TypeVerificationInvalid: email.NewVerificationInvalid(reg, &email.VerificationInvalidModel{To: "baz"}), template.TypeVerificationValid: email.NewVerificationValid(reg, &email.VerificationValidModel{To: "faz", VerificationURL: "http://bar.foo"}), template.TypeVerificationCodeInvalid: email.NewVerificationCodeInvalid(reg, &email.VerificationCodeInvalidModel{To: "baz"}), template.TypeVerificationCodeValid: email.NewVerificationCodeValid(reg, &email.VerificationCodeValidModel{To: "faz", VerificationURL: "http://bar.foo", VerificationCode: "123456678"}), template.TypeTestStub: email.NewTestStub(&email.TestStubModel{To: "far", Subject: "test subject", Body: "test body"}), template.TypeLoginCodeValid: email.NewLoginCodeValid(reg, &email.LoginCodeValidModel{To: "far", LoginCode: "123456"}), template.TypeRegistrationCodeValid: email.NewRegistrationCodeValid(reg, &email.RegistrationCodeValidModel{To: "far", RegistrationCode: "123456"}), } { t.Run(fmt.Sprintf("case=%s", tmplType), func(t *testing.T) { tmplData, err := json.Marshal(expectedTmpl) require.NoError(t, err) m := courier.Message{TemplateType: tmplType, TemplateData: tmplData} actualTmpl, err := courier.NewEmailTemplateFromMessage(reg, m) require.NoError(t, err) require.IsType(t, expectedTmpl, actualTmpl) expectedRecipient, err := expectedTmpl.EmailRecipient() require.NoError(t, err) actualRecipient, err := actualTmpl.EmailRecipient() require.NoError(t, err) require.Equal(t, expectedRecipient, actualRecipient) expectedSubject, err := expectedTmpl.EmailSubject(ctx) require.NoError(t, err) actualSubject, err := actualTmpl.EmailSubject(ctx) require.NoError(t, err) require.Equal(t, expectedSubject, actualSubject) expectedBody, err := expectedTmpl.EmailBody(ctx) require.NoError(t, err) actualBody, err := actualTmpl.EmailBody(ctx) require.NoError(t, err) require.Equal(t, expectedBody, actualBody) expectedBodyPlaintext, err := expectedTmpl.EmailBodyPlaintext(ctx) require.NoError(t, err) actualBodyPlaintext, err := actualTmpl.EmailBodyPlaintext(ctx) require.NoError(t, err) require.Equal(t, expectedBodyPlaintext, actualBodyPlaintext) }) } } ================================================ FILE: courier/handler.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "net/http" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/x/nosurfx" "github.com/ory/kratos/x/redir" "github.com/ory/x/httprouterx" "github.com/ory/x/httpx" "github.com/ory/x/logrusx" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" ) const ( AdminRouteCourier = "/courier" AdminRouteListMessages = AdminRouteCourier + "/messages" AdminRouteGetMessage = AdminRouteCourier + "/messages/{msgID}" ) type ( handlerDependencies interface { httpx.WriterProvider logrusx.Provider nosurfx.CSRFProvider PersistenceProvider config.Provider } Handler struct { r handlerDependencies } HandlerProvider interface { CourierHandler() *Handler } ) func NewHandler(r handlerDependencies) *Handler { return &Handler{r: r} } func (h *Handler) RegisterPublicRoutes(public *httprouterx.RouterPublic) { h.r.CSRFHandler().IgnoreGlobs(httprouterx.AdminPrefix+AdminRouteListMessages, AdminRouteListMessages) public.GET(httprouterx.AdminPrefix+AdminRouteListMessages, redir.RedirectToAdminRoute(h.r)) public.GET(httprouterx.AdminPrefix+AdminRouteGetMessage, redir.RedirectToAdminRoute(h.r)) } func (h *Handler) RegisterAdminRoutes(admin *httprouterx.RouterAdmin) { admin.GET(AdminRouteListMessages, h.listCourierMessages) admin.GET(AdminRouteGetMessage, h.getCourierMessage) } // Paginated Courier Message List Response // // swagger:response listCourierMessages // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type listCourierMessagesResponse struct { keysetpagination.ResponseHeaders // List of identities // // in:body Body []Message } // Paginated List Courier Message Parameters // // swagger:parameters listCourierMessages type ListCourierMessagesParameters struct { keysetpagination.RequestParameters // Status filters out messages based on status. // If no value is provided, it doesn't take effect on filter. // // required: false // in: query Status *MessageStatus `json:"status"` // Recipient filters out messages based on recipient. // If no value is provided, it doesn't take effect on filter. // // required: false // in: query Recipient string `json:"recipient"` } // swagger:route GET /admin/courier/messages courier listCourierMessages // // # List Messages // // Lists all messages by given status and recipient. // // Produces: // - application/json // // Security: // oryAccessToken: // // Schemes: http, https // // Responses: // 200: listCourierMessages // 400: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) listCourierMessages(w http.ResponseWriter, r *http.Request) { keys := h.r.Config().SecretsPagination(r.Context()) filter, paginator, err := parseMessagesFilter(r, keys) if err != nil { h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, err) return } messages, nextPage, err := h.r.CourierPersister().ListMessages(r.Context(), filter, paginator) if err != nil { h.r.Writer().WriteError(w, r, err) return } if !h.r.Config().IsInsecureDevMode(r.Context()) { for i := range messages { messages[i].Body = "" messages[i].Subject = "" } } u := *r.URL keysetpagination.SetLinkHeader(w, keys, &u, nextPage) h.r.Writer().Write(w, r, messages) } func parseMessagesFilter(r *http.Request, keys [][32]byte) (ListCourierMessagesParameters, []keysetpagination.Option, error) { var status *MessageStatus if r.URL.Query().Has("status") { ms, err := ToMessageStatus(r.URL.Query().Get("status")) if err != nil { return ListCourierMessagesParameters{}, nil, errors.WithStack(err) } status = &ms } opts, err := keysetpagination.ParseQueryParams(keys, r.URL.Query()) if err != nil { return ListCourierMessagesParameters{}, nil, errors.WithStack(err) } return ListCourierMessagesParameters{ Status: status, Recipient: r.URL.Query().Get("recipient"), }, opts, nil } // Get Courier Message Parameters // // swagger:parameters getCourierMessage // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type getCourierMessage struct { // MessageID is the ID of the message. // // required: true // in: path MessageID string `json:"id"` } // swagger:route GET /admin/courier/messages/{id} courier getCourierMessage // // # Get a Message // // Gets a specific messages by the given ID. // // Produces: // - application/json // // Security: // oryAccessToken: // // Schemes: http, https // // Responses: // 200: message // 400: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-medium func (h *Handler) getCourierMessage(w http.ResponseWriter, r *http.Request) { msgID, err := uuid.FromString(r.PathValue("msgID")) if err != nil { h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()).WithDebugf("could not parse parameter {id} as UUID, got %s", r.PathValue("id"))) return } message, err := h.r.CourierPersister().FetchMessage(r.Context(), msgID) if err != nil { h.r.Writer().WriteError(w, r, err) return } if !h.r.Config().IsInsecureDevMode(r.Context()) { message.Body = "" message.Subject = "" } h.r.Writer().Write(w, r, message) } ================================================ FILE: courier/handler_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "context" "errors" "fmt" "io" "net/http" "net/http/httptest" "testing" "github.com/go-faker/faker/v4" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/kratos/courier" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/x/httprouterx" "github.com/ory/x/ioutilx" "github.com/ory/x/snapshotx" "github.com/ory/x/urlx" "github.com/ory/x/uuidx" ) var defaultPageToken = courier.Message{}.DefaultPageToken().Encrypt(nil) func TestHandler(t *testing.T) { ctx := context.Background() conf, reg := pkg.NewFastRegistryWithMocks(t) // Start kratos server publicTS, adminTS := testhelpers.NewKratosServerWithCSRF(t, reg) tss := []struct { name string s *httptest.Server }{ { name: "public", s: publicTS, }, { name: "admin", s: adminTS, }, } mockServerURL := urlx.ParseOrPanic(publicTS.URL) conf.MustSet(ctx, config.ViperKeyAdminBaseURL, adminTS.URL) conf.MustSet(ctx, config.ViperKeyPublicBaseURL, mockServerURL.String()) get := func(t *testing.T, base *httptest.Server, href string, expectCode int) gjson.Result { t.Helper() res, err := base.Client().Get(base.URL + href) require.NoError(t, err) body, err := io.ReadAll(res.Body) require.NoError(t, err) require.NoError(t, res.Body.Close()) assert.EqualValuesf(t, expectCode, res.StatusCode, "%s", body) return gjson.ParseBytes(body) } getList := func(t *testing.T, tsName string, qs string) gjson.Result { t.Helper() href := courier.AdminRouteListMessages + qs ts := adminTS if tsName == "public" { href = httprouterx.AdminPrefix + href ts = publicTS } parsed := get(t, ts, href, http.StatusOK) require.Truef(t, parsed.IsArray(), "%s", parsed.Raw) return parsed } t.Run("case=should return an empty list of messages", func(t *testing.T) { for _, name := range []string{"public", "admin"} { t.Run("endpoint="+name, func(t *testing.T) { parsed := getList(t, name, "") assert.Len(t, parsed.Array(), 0) }) } }) t.Run("case=list messages", func(t *testing.T) { // Arrange test data const msgCount = 10 // total message count const procCount = 5 // how many messages' status should be equal to `processing` const rcptOryCount = 2 // how many messages' recipient should be equal to `noreply@ory.sh` messages := make([]courier.Message, msgCount) for i := range messages { require.NoError(t, faker.FakeData(&messages[i])) messages[i].Type = courier.MessageTypeEmail messages[i].Body = "body content" if i < rcptOryCount { messages[i].Recipient = "noreply@ory.sh" } require.NoError(t, reg.CourierPersister().AddMessage(context.Background(), &messages[i])) } for i := range procCount { require.NoError(t, reg.CourierPersister().SetMessageStatus(context.Background(), messages[i].ID, courier.MessageStatusProcessing)) } t.Run("paging", func(t *testing.T) { t.Run("case=should return first half of the messages", func(t *testing.T) { qs := fmt.Sprintf("?page_token=%s&page_size=%d", defaultPageToken, msgCount/2) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), msgCount/2) }) } }) t.Run("case=should error with random page token", func(t *testing.T) { qs := fmt.Sprintf(`?page_token=%s&page_size=%s`, uuidx.NewV4().String(), "250") for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { path := courier.AdminRouteListMessages + qs if tc.name == "public" { path = httprouterx.AdminPrefix + path } resp, err := tc.s.Client().Get(tc.s.URL + path) require.NoError(t, err) assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) } }) }) t.Run("filtering", func(t *testing.T) { t.Run("case=should return all queued messages", func(t *testing.T) { qs := fmt.Sprintf(`?page_token=%s&page_size=250&status=queued`, defaultPageToken) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), msgCount-procCount) for _, item := range parsed.Array() { assert.Equal(t, "queued", item.Get("status").String()) } }) } }) t.Run("case=should return all processing messages", func(t *testing.T) { qs := fmt.Sprintf(`?page_token=%s&page_size=250&status=processing`, defaultPageToken) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), procCount) for _, item := range parsed.Array() { assert.Equal(t, "processing", item.Get("status").String()) } }) } }) t.Run("case=should return all messages with recipient equals to noreply@ory.sh", func(t *testing.T) { qs := fmt.Sprintf(`?page_token=%s&page_size=250&recipient=noreply@ory.sh`, defaultPageToken) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), rcptOryCount) for _, item := range parsed.Array() { assert.Equal(t, "noreply@ory.sh", item.Get("recipient").String()) } }) } }) }) t.Run("case=body should be redacted if kratos is not in dev mode", func(t *testing.T) { conf.MustSet(ctx, "dev", false) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { parsed := getList(t, tc.name, "") require.Lenf(t, parsed.Array(), msgCount, "%s", parsed.Raw) for _, item := range parsed.Array() { assert.Equal(t, "", item.Get("body").String()) assert.Equal(t, "", item.Get("subject").String()) } }) } }) t.Run("case=body should not be redacted if kratos is in dev mode", func(t *testing.T) { conf.MustSet(ctx, "dev", true) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { parsed := getList(t, tc.name, "") require.Lenf(t, parsed.Array(), msgCount, "%s", parsed.Raw) for _, item := range parsed.Array() { assert.Equal(t, "body content", item.Get("body").String()) assert.NotEqual(t, "", item.Get("subject").String()) } }) } }) t.Run("case=should return with http status BadRequest when given status is invalid", func(t *testing.T) { qs := fmt.Sprintf(`?page_token=%s&page_size=250&status=invalid_status`, defaultPageToken) res, err := adminTS.Client().Get(adminTS.URL + courier.AdminRouteListMessages + qs) require.NoError(t, err) assert.Equal(t, http.StatusBadRequest, res.StatusCode, "status code should be equal to StatusBadRequest") }) }) t.Run("handler=getCourierMessage", func(t *testing.T) { message := courier.Message{} require.NoError(t, faker.FakeData(&message)) message.Type = courier.MessageTypeEmail message.Body = "body content" require.NoError(t, reg.CourierPersister().AddMessage(context.Background(), &message)) require.NoError(t, reg.CourierPersister().RecordDispatch(ctx, message.ID, courier.CourierMessageDispatchStatusSuccess, errors.New("some error"))) getCourierMessag := func(s *httptest.Server, id string) gjson.Result { r, err := s.Client().Get(s.URL + "/admin/courier/messages/" + id) require.NoError(t, err) return gjson.ParseBytes(ioutilx.MustReadAll(r.Body)) } t.Run("case=should return a message by id", func(t *testing.T) { conf.MustSet(ctx, "dev", true) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { body := getCourierMessag(tc.s, message.ID.String()) assert.Equal(t, message.ID.String(), body.Get("id").String()) assert.Equal(t, message.Recipient, body.Get("recipient").String()) assert.Equal(t, message.Body, body.Get("body").String()) assert.Equal(t, message.Subject, body.Get("subject").String()) // assert Eager works assert.NotEmpty(t, body.Get("dispatches").Array()) }) } }) t.Run("case=does not contain body if not in production", func(t *testing.T) { conf.MustSet(ctx, "dev", false) for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { body := getCourierMessag(tc.s, message.ID.String()) assert.Equal(t, message.ID.String(), body.Get("id").String()) assert.Equal(t, "", body.Get("body").String()) assert.Equal(t, "", body.Get("subject").String()) }) } }) t.Run("case=returns an error if parameter is malformed", func(t *testing.T) { for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { body := getCourierMessag(tc.s, "not-a-uuid") snapshotx.SnapshotTJSON(t, body.Raw) }) } }) t.Run("case=returns an error if no message is found", func(t *testing.T) { for _, tc := range tss { t.Run("endpoint="+tc.name, func(t *testing.T) { body := getCourierMessag(tc.s, uuid.Nil.String()) snapshotx.SnapshotTJSON(t, body.Raw) }) } }) }) } ================================================ FILE: courier/http_channel.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "encoding/json" "fmt" "io" "time" "github.com/pkg/errors" "github.com/ory/x/httpx" "github.com/ory/x/logrusx" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/request" "github.com/ory/x/jsonnetsecure" "github.com/ory/x/otelx" ) type ( httpChannel struct { id string requestConfig *request.Config d channelDependencies } channelDependencies interface { otelx.Provider logrusx.Provider httpx.ClientProvider jsonnetsecure.VMProvider ConfigProvider } ) var _ Channel = new(httpChannel) func newHttpChannel(id string, requestConfig *request.Config, d channelDependencies) *httpChannel { return &httpChannel{ id: id, requestConfig: requestConfig, d: d, } } func (c *httpChannel) ID() string { return c.id } type httpDataModel struct { Recipient string `json:"recipient"` Subject string `json:"subject"` Body string `json:"body"` // HTMLBody optionally contains the HTML version of an email template when available. HTMLBody string `json:"html_body,omitempty"` TemplateType template.TemplateType `json:"template_type"` TemplateData Template `json:"template_data"` MessageType string `json:"message_type"` RequestHeaders json.RawMessage `json:"request_headers"` } func (c *httpChannel) Dispatch(ctx context.Context, msg Message) (err error) { ctx, span := c.d.Tracer(ctx).Tracer().Start(ctx, "courier.httpChannel.Dispatch") defer otelx.End(span, &err) builder, err := request.NewBuilder(ctx, c.requestConfig, c.d) if err != nil { return errors.WithStack(err) } tmpl, err := newTemplate(c.d, msg) if err != nil { return errors.WithStack(err) } td := httpDataModel{ Recipient: msg.Recipient, Subject: msg.Subject, Body: msg.Body, TemplateType: msg.TemplateType, TemplateData: tmpl, RequestHeaders: msg.RequestHeaders, MessageType: msg.Type.String(), } c.tryPopulateHTMLBody(ctx, tmpl, &td) req, err := builder.BuildRequest(ctx, td) if err != nil { return errors.WithStack(err) } req = req.WithContext(ctx) res, err := c.d.HTTPClient(ctx, // fail fast and let the courier retry if needed instead of blocking the queue httpx.ResilientClientWithMaxRetry(0), httpx.ResilientClientWithConnectionTimeout(10*time.Second), ).Do(req) if err != nil { return errors.WithStack(err) } defer func() { _ = res.Body.Close() }() res.Body = io.NopCloser(io.LimitReader(res.Body, 1024)) logger := c.d.Logger(). WithField("http_server", c.requestConfig.URL). WithField("message_id", msg.ID). WithField("message_nid", msg.NID). WithField("message_type", msg.Type). WithField("message_template_type", msg.TemplateType). WithField("message_subject", msg.Subject) if res.StatusCode >= 200 && res.StatusCode < 300 { logger.Debug("Courier sent out mailer.") return nil } err = errors.Errorf( "unable to dispatch mail delivery because upstream server replied with status code %d", res.StatusCode, ) body, _ := io.ReadAll(res.Body) logger. WithError(err). WithField("http_response_body", string(body)). Error("sending mail via HTTP failed.") return errors.WithStack(err) } func (c *httpChannel) tryPopulateHTMLBody(ctx context.Context, tmpl Template, td *httpDataModel) { if emailTmpl, ok := tmpl.(EmailTemplate); ok { // Only get the HTML body from the template; plaintext body comes from msg.Body // to maintain backward compatibility with existing behavior if htmlBody, err := emailTmpl.EmailBody(ctx); err != nil { c.d.Logger().WithError(err).Error("Unable to get email HTML body from template.") } else { td.HTMLBody = htmlBody } } } func newTemplate(d template.Dependencies, msg Message) (Template, error) { switch msg.Type { case MessageTypeEmail: return NewEmailTemplateFromMessage(d, msg) case MessageTypeSMS: return NewSMSTemplateFromMessage(d, msg) default: return nil, fmt.Errorf("received unexpected message type: %s", msg.Type) } } ================================================ FILE: courier/http_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/x/configx" ) func TestQueueHTTPEmail(t *testing.T) { type sendEmailRequestBody struct { IdentityID string `json:"identity_id"` IdentityEmail string `json:"identity_email"` Recipient string `json:"recipient"` TemplateType string `json:"template_type"` To string `json:"to"` RecoveryCode string `json:"recovery_code"` RecoveryURL string `json:"recovery_url"` VerificationURL string `json:"verification_url"` VerificationCode string `json:"verification_code"` Body string `json:"body"` HTMLBody string `json:"html_body"` Subject string `json:"subject"` } expectedEmail := []*email.TestStubModel{ { To: "test-2@test.com", Subject: "test-mailer-subject-1", Body: "test-mailer-body-1", HTMLBody: "test-mailer-body-html-1", }, { To: "test-2@test.com", Subject: "test-mailer-subject-2", Body: "test-mailer-body-2", }, } actual := make(chan sendEmailRequestBody, len(expectedEmail)) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rb, err := io.ReadAll(r.Body) require.NoError(t, err) var body sendEmailRequestBody err = json.Unmarshal(rb, &body) require.NoError(t, err) assert.NotEmpty(t, r.Header["Authorization"]) assert.Equal(t, "Basic bWU6MTIzNDU=", r.Header["Authorization"][0]) actual <- body })) t.Cleanup(srv.Close) requestConfig := fmt.Sprintf(`{ "url": "%s", "method": "POST", "auth": { "type": "basic_auth", "config": { "user": "me", "password": "12345" } }, "body": "file://./stub/request.config.mailer.jsonnet" }`, srv.URL) _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]any{ config.ViperKeyCourierDeliveryStrategy: "http", config.ViperKeyCourierHTTPRequestConfig: requestConfig, config.ViperKeyCourierSMTPURL: "http://foo.url", })) courier, err := reg.Courier(t.Context()) require.NoError(t, err) for _, message := range expectedEmail { id, err := courier.QueueEmail(t.Context(), email.NewTestStub(message)) require.NoError(t, err) require.NotEqual(t, uuid.Nil, id) } require.NoError(t, courier.DispatchQueue(t.Context())) close(actual) require.Len(t, actual, len(expectedEmail)) i := 0 for message := range actual { expected := expectedEmail[i] assert.Equal(t, expected.To, message.To) assert.Equal(t, expected.Body, message.Body) assert.Equal(t, expected.HTMLBody, message.HTMLBody) assert.Equal(t, expected.Subject, message.Subject) i++ } } ================================================ FILE: courier/message.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "encoding/json" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/courier/template" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" "github.com/ory/x/sqlxx" "github.com/ory/x/stringsx" ) // A Message's Status // // swagger:model courierMessageStatus type MessageStatus int const ( MessageStatusQueued MessageStatus = iota + 1 MessageStatusSent MessageStatusProcessing MessageStatusAbandoned ) const ( messageStatusQueuedText = "queued" messageStatusSentText = "sent" messageStatusProcessingText = "processing" messageStatusAbandonedText = "abandoned" ) func ToMessageStatus(str string) (MessageStatus, error) { switch s := stringsx.SwitchExact(str); { case s.AddCase(MessageStatusQueued.String()): return MessageStatusQueued, nil case s.AddCase(MessageStatusSent.String()): return MessageStatusSent, nil case s.AddCase(MessageStatusProcessing.String()): return MessageStatusProcessing, nil case s.AddCase(MessageStatusAbandoned.String()): return MessageStatusAbandoned, nil default: return 0, errors.WithStack(herodot.ErrBadRequest.WithWrap(s.ToUnknownCaseErr()).WithReason("Message status is not valid")) } } func (ms MessageStatus) String() string { switch ms { case MessageStatusQueued: return messageStatusQueuedText case MessageStatusSent: return messageStatusSentText case MessageStatusProcessing: return messageStatusProcessingText case MessageStatusAbandoned: return messageStatusAbandonedText default: return "" } } func (ms MessageStatus) IsValid() error { switch ms { case MessageStatusQueued, MessageStatusSent, MessageStatusProcessing, MessageStatusAbandoned: return nil default: return errors.WithStack(herodot.ErrBadRequest.WithReason("Message status is not valid")) } } func (ms MessageStatus) MarshalJSON() ([]byte, error) { if err := ms.IsValid(); err != nil { return nil, err } return json.Marshal(ms.String()) } func (ms *MessageStatus) UnmarshalJSON(data []byte) error { var str string if err := json.Unmarshal(data, &str); err != nil { return err } s, err := ToMessageStatus(str) if err != nil { return err } *ms = s return nil } // A Message's Type // // It can either be `email` or `phone` // // swagger:model courierMessageType type MessageType int const ( MessageTypeEmail MessageType = iota + 1 MessageTypeSMS ) const ( messageTypeEmailText = "email" messageTypeSMSText = "sms" ) func ToMessageType(str string) (MessageType, error) { switch s := stringsx.SwitchExact(str); { case s.AddCase(messageTypeEmailText): return MessageTypeEmail, nil case s.AddCase(messageTypeSMSText): return MessageTypeSMS, nil default: return 0, errors.WithStack(herodot.ErrBadRequest.WithWrap(s.ToUnknownCaseErr()).WithReason("Message type is not valid")) } } func (mt MessageType) String() string { switch mt { case MessageTypeEmail: return messageTypeEmailText case MessageTypeSMS: return messageTypeSMSText default: return "" } } func (mt MessageType) IsValid() error { switch mt { case MessageTypeEmail, MessageTypeSMS: return nil default: return errors.WithStack(herodot.ErrBadRequest.WithReason("Message type is not valid")) } } func (mt MessageType) MarshalJSON() ([]byte, error) { if err := mt.IsValid(); err != nil { return nil, err } return json.Marshal(mt.String()) } func (mt *MessageType) UnmarshalJSON(data []byte) error { var str string if err := json.Unmarshal(data, &str); err != nil { return err } t, err := ToMessageType(str) if err != nil { return err } *mt = t return nil } // swagger:model message type Message struct { // required: true ID uuid.UUID `json:"id" faker:"-" db:"id"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` // required: true Status MessageStatus `json:"status" db:"status"` // required: true Type MessageType `json:"type" db:"type"` // required: true Recipient string `json:"recipient" db:"recipient"` // required: true Body string `json:"body" db:"body"` // required: true Subject string `json:"subject" db:"subject"` // required: true TemplateType template.TemplateType `json:"template_type" db:"template_type"` Channel sqlxx.NullString `json:"channel" db:"channel"` TemplateData []byte `json:"-" db:"template_data"` RequestHeaders []byte `json:"-" faker:"-" db:"request_headers"` // required: true SendCount int `json:"send_count" db:"send_count"` // Dispatches store information about the attempts of delivering a message // May contain an error if any happened, or just the `success` state. Dispatches []MessageDispatch `json:"dispatches,omitempty" has_many:"courier_message_dispatches" order_by:"created_at desc" faker:"-"` // CreatedAt is a helper struct field for gobuffalo.pop. // required: true CreatedAt time.Time `json:"created_at" faker:"-" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. // required: true UpdatedAt time.Time `json:"updated_at" faker:"-" db:"updated_at"` } func (m Message) PageToken() keysetpagination.PageToken { return keysetpagination.NewPageToken( keysetpagination.Column{ Name: "created_at", Order: keysetpagination.OrderDescending, Value: m.CreatedAt, }, keysetpagination.Column{ Name: "id", Value: m.ID, }, ) } func (m Message) DefaultPageToken() keysetpagination.PageToken { return Message{ID: uuid.Nil, CreatedAt: time.Date(2200, 12, 31, 23, 59, 59, 0, time.UTC)}.PageToken() } func (m Message) TableName() string { return "courier_messages" } func (m *Message) GetID() uuid.UUID { return m.ID } ================================================ FILE: courier/message_dispatch.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "time" "github.com/gofrs/uuid" "github.com/ory/x/sqlxx" ) // swagger:enum CourierMessageDispatchStatus type CourierMessageDispatchStatus string const ( CourierMessageDispatchStatusFailed CourierMessageDispatchStatus = "failed" CourierMessageDispatchStatusSuccess CourierMessageDispatchStatus = "success" ) // MessageDispatch represents an attempt of sending a courier message // It contains the status of the attempt (failed or successful) and the error if any occured // // swagger:model messageDispatch type MessageDispatch struct { // The ID of this message dispatch // required: true ID uuid.UUID `json:"id" db:"id"` // The ID of the message being dispatched // required: true MessageID uuid.UUID `json:"message_id" db:"message_id"` // The status of this dispatch // Either "failed" or "success" // required: true Status CourierMessageDispatchStatus `json:"status" db:"status"` // An optional error Error sqlxx.JSONRawMessage `json:"error,omitempty" db:"error"` // CreatedAt is a helper struct field for gobuffalo.pop. // required: true CreatedAt time.Time `json:"created_at" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. // required: true UpdatedAt time.Time `json:"updated_at" db:"updated_at"` NID uuid.UUID `json:"-" db:"nid"` } func (MessageDispatch) TableName() string { return "courier_message_dispatches" } ================================================ FILE: courier/message_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "testing" "github.com/stretchr/testify/require" "github.com/ory/herodot" "github.com/ory/kratos/courier" ) func TestMessageStatusValidity(t *testing.T) { invalid := courier.MessageStatus(0) require.ErrorIs(t, invalid.IsValid(), herodot.ErrBadRequest, "IsValid() should return an error when message status is invalid") } func TestToMessageStatus(t *testing.T) { t.Run("case=should return corresponding MessageStatus for given str", func(t *testing.T) { for str, exp := range map[string]courier.MessageStatus{ "queued": courier.MessageStatusQueued, "sent": courier.MessageStatusSent, "processing": courier.MessageStatusProcessing, "abandoned": courier.MessageStatusAbandoned, } { result, err := courier.ToMessageStatus(str) require.NoError(t, err) require.Equal(t, exp, result) } }) t.Run("case=should return error for invalid message status str", func(t *testing.T) { result, err := courier.ToMessageStatus("invalid") require.Error(t, err, herodot.ErrBadRequest) require.Error(t, result.IsValid(), herodot.ErrBadRequest) }) } func TestMessageTypeValidity(t *testing.T) { invalid := courier.MessageType(0) require.ErrorIs(t, invalid.IsValid(), herodot.ErrBadRequest, "IsValid() should return an error when message type is invalid") } func TestToMessageType(t *testing.T) { t.Run("case=should return corresponding MessageType for given str", func(t *testing.T) { for str, exp := range map[string]courier.MessageType{ "email": courier.MessageTypeEmail, "sms": courier.MessageTypeSMS, } { result, err := courier.ToMessageType(str) require.NoError(t, err) require.Equal(t, exp, result) } }) t.Run("case=should return error for invalid message type str", func(t *testing.T) { result, err := courier.ToMessageType("invalid") require.ErrorIs(t, err, herodot.ErrBadRequest) require.ErrorIs(t, result.IsValid(), herodot.ErrBadRequest) }) } ================================================ FILE: courier/persistence.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "github.com/gofrs/uuid" "github.com/pkg/errors" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" ) var ErrQueueEmpty = errors.New("queue is empty") type ( Persister interface { AddMessage(context.Context, *Message) error NextMessages(context.Context, uint8) ([]Message, error) SetMessageStatus(context.Context, uuid.UUID, MessageStatus) error LatestQueuedMessage(ctx context.Context) (*Message, error) IncrementMessageSendCount(context.Context, uuid.UUID) error // ListMessages lists all messages in the store given the page, itemsPerPage, status and recipient. // Returns list of messages, total count of messages satisfied by given filter, and error if any ListMessages(context.Context, ListCourierMessagesParameters, []keysetpagination.Option) ([]Message, *keysetpagination.Paginator, error) // FetchMessage returns a message with the id or nil and an error if not found FetchMessage(context.Context, uuid.UUID) (*Message, error) // Records an attempt of sending out a courier message // Returns an error if it fails RecordDispatch(ctx context.Context, msgID uuid.UUID, status CourierMessageDispatchStatus, err error) error } PersistenceProvider interface { CourierPersister() Persister } ) ================================================ FILE: courier/sms.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "encoding/json" "github.com/gofrs/uuid" ) func (c *courier) QueueSMS(ctx context.Context, t SMSTemplate) (uuid.UUID, error) { recipient, err := t.PhoneNumber() if err != nil { return uuid.Nil, err } templateData, err := json.Marshal(t) if err != nil { return uuid.Nil, err } body, err := t.SMSBody(ctx) if err != nil { return uuid.Nil, err } requestHeaders := []byte(`{}`) if t, ok := t.(RequestHeadersCarrier); ok { requestHeaders, err = json.Marshal(t.RequestHeaders()) if err != nil { return uuid.Nil, err } } message := &Message{ Status: MessageStatusQueued, Type: MessageTypeSMS, Channel: "sms", Recipient: recipient, TemplateType: t.TemplateType(), TemplateData: templateData, RequestHeaders: requestHeaders, Body: body, } if err := c.deps.CourierPersister().AddMessage(ctx, message); err != nil { return uuid.Nil, err } return message.ID, nil } ================================================ FILE: courier/sms_templates.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "encoding/json" "github.com/pkg/errors" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/sms" ) type SMSTemplate interface { Template SMSBody(context.Context) (string, error) PhoneNumber() (string, error) } func NewSMSTemplateFromMessage(d template.Dependencies, m Message) (SMSTemplate, error) { switch m.TemplateType { case template.TypeVerificationCodeValid: var t sms.VerificationCodeValidModel if err := json.Unmarshal(m.TemplateData, &t); err != nil { return nil, err } return sms.NewVerificationCodeValid(d, &t), nil case template.TypeRecoveryCodeValid: var t sms.RecoveryCodeValidModel if err := json.Unmarshal(m.TemplateData, &t); err != nil { return nil, err } return sms.NewRecoveryCodeValid(d, &t), nil case template.TypeTestStub: var t sms.TestStubModel if err := json.Unmarshal(m.TemplateData, &t); err != nil { return nil, err } return sms.NewTestStub(&t), nil case template.TypeLoginCodeValid: var t sms.LoginCodeValidModel if err := json.Unmarshal(m.TemplateData, &t); err != nil { return nil, err } return sms.NewLoginCodeValid(d, &t), nil case template.TypeRegistrationCodeValid: var t sms.RegistrationCodeValidModel if err := json.Unmarshal(m.TemplateData, &t); err != nil { return nil, err } return sms.NewRegistrationCodeValid(d, &t), nil default: return nil, errors.Errorf("received unexpected message template type: %s", m.TemplateType) } } ================================================ FILE: courier/sms_templates_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "context" "encoding/json" "fmt" "testing" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/sms" "github.com/ory/kratos/pkg" ) func TestSMSTemplateType(t *testing.T) { for expectedType, tmpl := range map[template.TemplateType]courier.SMSTemplate{ template.TypeVerificationCodeValid: &sms.VerificationCodeValid{}, template.TypeTestStub: &sms.TestStub{}, } { t.Run(fmt.Sprintf("case=%s", expectedType), func(t *testing.T) { require.Equal(t, expectedType, tmpl.TemplateType()) }) } } func TestNewSMSTemplateFromMessage(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) ctx := context.Background() for tmplType, expectedTmpl := range map[template.TemplateType]courier.SMSTemplate{ template.TypeVerificationCodeValid: sms.NewVerificationCodeValid(reg, &sms.VerificationCodeValidModel{To: "+12345678901"}), template.TypeTestStub: sms.NewTestStub(&sms.TestStubModel{To: "+12345678901", Body: "test body"}), } { t.Run(fmt.Sprintf("case=%s", tmplType), func(t *testing.T) { tmplData, err := json.Marshal(expectedTmpl) require.NoError(t, err) m := courier.Message{TemplateType: tmplType, TemplateData: tmplData} actualTmpl, err := courier.NewSMSTemplateFromMessage(reg, m) require.NoError(t, err) require.IsType(t, expectedTmpl, actualTmpl) expectedRecipient, err := expectedTmpl.PhoneNumber() require.NoError(t, err) actualRecipient, err := actualTmpl.PhoneNumber() require.NoError(t, err) require.Equal(t, expectedRecipient, actualRecipient) expectedBody, err := expectedTmpl.SMSBody(ctx) require.NoError(t, err) actualBody, err := actualTmpl.SMSBody(ctx) require.NoError(t, err) require.Equal(t, expectedBody, actualBody) }) } } ================================================ FILE: courier/sms_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier/template/sms" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/x/configx" ) func TestQueueSMS(t *testing.T) { expectedSender := "Kratos Test" expectedSMS := []*sms.TestStubModel{ { To: "+12065550101", Body: "test-sms-body-1", }, { To: "+12065550102", Body: "test-sms-body-2", }, } actual := make(chan *sms.TestStubModel, len(expectedSMS)) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type sendSMSRequestBody struct { To string From string Body string } rb, err := io.ReadAll(r.Body) require.NoError(t, err) var body sendSMSRequestBody err = json.Unmarshal(rb, &body) require.NoError(t, err) assert.NotEmpty(t, r.Header["Authorization"]) assert.Equal(t, "Basic bWU6MTIzNDU=", r.Header["Authorization"][0]) assert.Equal(t, body.From, expectedSender) actual <- &sms.TestStubModel{ To: body.To, Body: body.Body, } })) t.Cleanup(srv.Close) requestConfig := fmt.Sprintf(`{ "url": "%s", "method": "POST", "body": "file://./stub/request.config.twilio.jsonnet", "auth": { "type": "basic_auth", "config": { "user": "me", "password": "12345" } } }`, srv.URL) _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]any{ config.ViperKeyCourierChannels: fmt.Sprintf(`[{ "id": "sms", "type": "http", "request_config": %s }]`, requestConfig), config.ViperKeyCourierSMTPURL: "http://foo.url", })) c, err := reg.Courier(t.Context()) require.NoError(t, err) for _, message := range expectedSMS { id, err := c.QueueSMS(t.Context(), sms.NewTestStub(message)) require.NoError(t, err) require.NotEqual(t, uuid.Nil, id) } require.NoError(t, c.DispatchQueue(t.Context())) close(actual) require.Len(t, actual, len(expectedSMS)) i := 0 for message := range actual { expected := expectedSMS[i] assert.Equal(t, expected.To, message.To) assert.Equal(t, expected.Body, message.Body) i++ } } func TestDisallowedInternalNetwork(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]any{ config.ViperKeyCourierChannels: `[ { "id": "sms", "type": "http", "request_config": { "url": "http://127.0.0.1/", "method": "GET", "body": "file://./stub/request.config.twilio.jsonnet" } } ]`, config.ViperKeyCourierSMTPURL: "http://foo.url", config.ViperKeyClientHTTPNoPrivateIPRanges: true, })) c, err := reg.Courier(t.Context()) require.NoError(t, err) c.(interface { FailOnDispatchError() }).FailOnDispatchError() _, err = c.QueueSMS(t.Context(), sms.NewTestStub(&sms.TestStubModel{ To: "+12065550101", Body: "test-sms-body-1", })) require.NoError(t, err) err = c.DispatchQueue(t.Context()) assert.ErrorContains(t, err, "is not a permitted destination") } ================================================ FILE: courier/smtp.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "crypto/tls" "encoding/json" "net/mail" "net/url" "strconv" "time" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/driver/config" "github.com/gofrs/uuid" gomail "github.com/ory/mail/v3" ) type SMTPClient struct { *gomail.Dialer } func NewSMTPClient(deps Dependencies, cfg *config.SMTPConfig) (*SMTPClient, error) { uri, err := url.Parse(cfg.ConnectionURI) if err != nil { return nil, errors.WithStack(herodot.ErrMisconfiguration.WithReasonf("The SMTP connection URI is malformed. Please contact a system administrator.")) } var tlsCertificates []tls.Certificate if cfg.ClientCertPath != "" && cfg.ClientKeyPath != "" { clientCert, err := tls.LoadX509KeyPair(cfg.ClientCertPath, cfg.ClientKeyPath) if err == nil { tlsCertificates = append(tlsCertificates, clientCert) } else { deps.Logger(). WithError(err). Error("Unable to load tls certificate and private key for smtp client.") } } password, _ := uri.User.Password() port, _ := strconv.ParseInt(uri.Port(), 10, 0) dialer := &gomail.Dialer{ Host: uri.Hostname(), Port: int(port), Username: uri.User.Username(), Password: password, LocalName: cfg.LocalName, Timeout: time.Second * 10, RetryFailure: true, } sslSkipVerify, _ := strconv.ParseBool(uri.Query().Get("skip_ssl_verify")) serverName := uri.Query().Get("server_name") if serverName == "" { serverName = uri.Hostname() } tlsConfig := &tls.Config{ InsecureSkipVerify: sslSkipVerify, // #nosec G402 -- This is ok (and required!) because it is configurable and disabled by default. Certificates: tlsCertificates, ServerName: serverName, MinVersion: tls.VersionTLS12, } // SMTP schemes // smtp: smtp clear text (with uri parameter) or with StartTLS (enforced by default) // smtps: smtp with implicit TLS (recommended way in 2021 to avoid StartTLS downgrade attacks // and defaulting to fully-encrypted protocols https://datatracker.ietf.org/doc/html/rfc8314) switch uri.Scheme { case "smtp": // Enforcing StartTLS by default for security best practices (config review, etc.) skipStartTLS, _ := strconv.ParseBool(uri.Query().Get("disable_starttls")) if !skipStartTLS { dialer.TLSConfig = tlsConfig // Enforcing StartTLS dialer.StartTLSPolicy = gomail.MandatoryStartTLS } case "smtps": dialer.TLSConfig = tlsConfig dialer.SSL = true } return &SMTPClient{ Dialer: dialer, }, nil } func (c *courier) QueueEmail(ctx context.Context, t EmailTemplate) (uuid.UUID, error) { recipient, err := t.EmailRecipient() if err != nil { return uuid.Nil, errors.WithStack(err) } if _, err := mail.ParseAddress(recipient); err != nil { return uuid.Nil, errors.WithStack(err) } subject, err := t.EmailSubject(ctx) if err != nil { return uuid.Nil, errors.WithStack(err) } bodyPlaintext, err := t.EmailBodyPlaintext(ctx) if err != nil { return uuid.Nil, errors.WithStack(err) } templateData, err := json.Marshal(t) if err != nil { return uuid.Nil, errors.WithStack(err) } requestHeaders := []byte(`{}`) if t, ok := t.(RequestHeadersCarrier); ok { requestHeaders, err = json.Marshal(t.RequestHeaders()) if err != nil { return uuid.Nil, err } } message := &Message{ Status: MessageStatusQueued, Type: MessageTypeEmail, Channel: "email", Recipient: recipient, Body: bodyPlaintext, Subject: subject, TemplateType: t.TemplateType(), TemplateData: templateData, RequestHeaders: requestHeaders, } if err := c.deps.CourierPersister().AddMessage(ctx, message); err != nil { return uuid.Nil, errors.WithStack(err) } return message.ID, nil } ================================================ FILE: courier/smtp_channel.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "context" "net" "net/textproto" "strconv" "time" "github.com/pkg/errors" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" "github.com/ory/herodot" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/driver/config" "github.com/ory/mail/v3" "github.com/ory/x/otelx" ) type ( SMTPChannel struct { smtpClient *SMTPClient d Dependencies newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error) } ) var _ Channel = new(SMTPChannel) func NewSMTPChannel(deps Dependencies, cfg *config.SMTPConfig) (*SMTPChannel, error) { return NewSMTPChannelWithCustomTemplates(deps, cfg, NewEmailTemplateFromMessage) } func NewSMTPChannelWithCustomTemplates(deps Dependencies, cfg *config.SMTPConfig, newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error)) (*SMTPChannel, error) { smtpClient, err := NewSMTPClient(deps, cfg) if err != nil { return nil, err } return &SMTPChannel{ smtpClient: smtpClient, d: deps, newEmailTemplateFromMessage: newEmailTemplateFromMessage, }, nil } func (c *SMTPChannel) ID() string { return "email" } func (c *SMTPChannel) Dispatch(ctx context.Context, msg Message) (err error) { ctx, span := c.d.Tracer(ctx).Tracer().Start(ctx, "courier.SMTPChannel.Dispatch") defer otelx.End(span, &err) if c.smtpClient.Host == "" { return errors.WithStack(herodot.ErrInternalServerError.WithErrorf("Courier tried to deliver an email but %s is not set!", config.ViperKeyCourierSMTPURL)) } channels, err := c.d.CourierConfig().CourierChannels(ctx) if err != nil { return err } var cfg *config.SMTPConfig for _, channel := range channels { if channel.ID == "email" && channel.SMTPConfig != nil { cfg = channel.SMTPConfig break } } if cfg == nil { return errors.WithStack(herodot.ErrInternalServerError.WithErrorf("Courier tried to deliver an email but SMTP channel is misconfigured.")) } gm := mail.NewMessage() if cfg.FromName == "" { gm.SetHeader("From", cfg.FromAddress) } else { gm.SetAddressHeader("From", cfg.FromAddress, cfg.FromName) } gm.SetHeader("To", msg.Recipient) gm.SetHeader("Subject", msg.Subject) headers := cfg.Headers for k, v := range headers { gm.SetHeader(k, v) } gm.SetBody("text/plain", msg.Body) logger := c.d.Logger(). WithField("smtp_server", net.JoinHostPort(c.smtpClient.Host, strconv.Itoa(c.smtpClient.Port))). WithField("smtp_ssl_enabled", c.smtpClient.SSL). WithField("message_from", cfg.FromAddress). WithField("message_id", msg.ID). WithField("message_nid", msg.NID). WithField("message_type", msg.Type). WithField("message_template_type", msg.TemplateType). WithField("message_subject", msg.Subject). WithField("trace_id", span.SpanContext().TraceID()) tmpl, err := c.newEmailTemplateFromMessage(c.d, msg) if err != nil { logger. WithError(err).Error(`Unable to get email template from message.`) } else if htmlBody, err := tmpl.EmailBody(ctx); err != nil { logger. WithError(err).Error(`Unable to get email body from template.`) } else { gm.AddAlternative("text/html", htmlBody) } dialCtx, dialSpan := c.d.Tracer(ctx).Tracer().Start(ctx, "courier.SMTPChannel.Dispatch.Dial", trace.WithAttributes( semconv.NetPeerName(c.smtpClient.Host), semconv.NetPeerPort(c.smtpClient.Port), semconv.NetProtocolName("smtp"), )) snd, err := c.smtpClient.Dial(dialCtx) otelx.End(dialSpan, &err) if err != nil { logger. WithError(err). WithField("smtp_host", c.smtpClient.Host). WithField("smtp_port", c.smtpClient.Port). Error("Unable to dial SMTP connection.") return errors.WithStack(herodot.ErrInternalServerError. WithError(err.Error()).WithReason("failed to send email via smtp")) } defer func() { _ = snd.Close() }() sendCtx, sendSpan := c.d.Tracer(ctx).Tracer().Start(ctx, "courier.SMTPChannel.Dispatch.Send") err = mail.Send(sendCtx, snd, gm) otelx.End(sendSpan, &err) if err != nil { logger. WithError(err). Error("Unable to send email using SMTP connection.") var protoErr *textproto.Error var mailErr *mail.SendError switch { case errors.As(err, &mailErr) && errors.As(mailErr.Cause, &protoErr) && protoErr.Code >= 500: fallthrough case errors.As(err, &protoErr) && protoErr.Code >= 500: // See https://en.wikipedia.org/wiki/List_of_SMTP_server_return_codes // If the SMTP server responds with 5xx, sending the message should not be retried (without changing something about the request) if err := c.d.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusAbandoned); err != nil { logger. WithError(err). Error(`Unable to reset the retried message's status to "abandoned".`) return errors.WithStack(err) } } return errors.WithStack(herodot.ErrInternalServerError. WithError(err.Error()).WithReason("failed to send email via smtp")) } dispatchDuration := time.Since(msg.CreatedAt).Milliseconds() logger.WithField("dispatch_duration_ms", dispatchDuration).Debug("Courier sent out message.") return nil } ================================================ FILE: courier/smtp_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier_test import ( "context" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "flag" "fmt" "io" "math/big" "net/http" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/kratos/courier" templates "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/kratos/x" gomail "github.com/ory/mail/v3" "github.com/ory/x/configx" ) func TestNewSMTPClientPreventLeak(t *testing.T) { // Test for https://hackerone.com/reports/2384028 invalidURL := "sm<>t>p://f%oo::bar:baz@my-server:1234:122/" conf, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValue(config.ViperKeyCourierSMTPURL, invalidURL)) channels, err := conf.CourierChannels(t.Context()) require.NoError(t, err) require.Len(t, channels, 1) _, err = courier.NewSMTPClient(reg, channels[0].SMTPConfig) require.Error(t, err) assert.NotContains(t, err.Error(), invalidURL) } func TestNewSMTP(t *testing.T) { conf, reg := pkg.NewFastRegistryWithMocks(t) setupSMTPClient := func(stringURL string) *courier.SMTPClient { conf.MustSet(t.Context(), config.ViperKeyCourierSMTPURL, stringURL) channels, err := conf.CourierChannels(t.Context()) require.NoError(t, err) require.Len(t, channels, 1) c, err := courier.NewSMTPClient(reg, channels[0].SMTPConfig) require.NoError(t, err) return c } // Should enforce StartTLS => dialer.StartTLSPolicy = gomail.MandatoryStartTLS and dialer.SSL = false smtp := setupSMTPClient("smtp://foo:bar@my-server:1234/") assert.Equal(t, smtp.StartTLSPolicy, gomail.MandatoryStartTLS, "StartTLS not enforced") assert.Equal(t, smtp.SSL, false, "Implicit TLS should not be enabled") // Should enforce TLS => dialer.SSL = true smtp = setupSMTPClient("smtps://foo:bar@my-server:1234/") assert.Equal(t, smtp.SSL, true, "Implicit TLS should be enabled") // Should allow cleartext => dialer.StartTLSPolicy = gomail.OpportunisticStartTLS and dialer.SSL = false smtp = setupSMTPClient("smtp://foo:bar@my-server:1234/?disable_starttls=true") assert.Equal(t, smtp.StartTLSPolicy, gomail.OpportunisticStartTLS, "StartTLS is enforced") assert.Equal(t, smtp.SSL, false, "Implicit TLS should not be enabled") // Test cert based SMTP client auth clientCert, clientKey, err := generateTestClientCert(t) require.NoError(t, err) t.Cleanup(func() { _ = os.Remove(clientCert.Name()) }) //nolint:gosec t.Cleanup(func() { _ = os.Remove(clientKey.Name()) }) //nolint:gosec conf.MustSet(t.Context(), config.ViperKeyCourierSMTPClientCertPath, clientCert.Name()) conf.MustSet(t.Context(), config.ViperKeyCourierSMTPClientKeyPath, clientKey.Name()) clientPEM, err := tls.LoadX509KeyPair(clientCert.Name(), clientKey.Name()) require.NoError(t, err) smtpWithCert := setupSMTPClient("smtps://subdomain.my-server:1234/?server_name=my-server") assert.Equal(t, smtpWithCert.SSL, true, "Implicit TLS should be enabled") assert.Equal(t, smtpWithCert.Host, "subdomain.my-server", "SMTP Dialer host should match") assert.Equal(t, smtpWithCert.TLSConfig.ServerName, "my-server", "TLS config server name should match") assert.Equal(t, smtpWithCert.TLSConfig.ServerName, "my-server", "TLS config server name should match") assert.Contains(t, smtpWithCert.TLSConfig.Certificates, clientPEM, "TLS config should contain client pem") // error case: invalid client key require.NoError(t, conf.Set(t.Context(), config.ViperKeyCourierSMTPClientKeyPath, clientCert.Name())) // mixup client key and client cert smtpWithCert = setupSMTPClient("smtps://subdomain.my-server:1234/?server_name=my-server") assert.Equal(t, len(smtpWithCert.TLSConfig.Certificates), 0, "TLS config certificates should be empty") } func TestQueueEmail(t *testing.T) { smtp, api := x.StartMailhog(t, true) _, reg := pkg.NewRegistryDefaultWithDSN(t, "", configx.WithValues(map[string]any{ config.ViperKeyCourierSMTPURL: smtp, config.ViperKeyCourierSMTPFrom: "test-stub@ory.sh", config.ViperKeyCourierSMTPFromName: "Bob", config.ViperKeyCourierSMTPHeaders + ".test-stub-header1": "foo", config.ViperKeyCourierSMTPHeaders + ".test-stub-header2": "bar", config.ViperKeyCourierMessageRetries: 50, })) c, err := reg.Courier(t.Context()) require.NoError(t, err) c.FailOnDispatchError() _, err = c.QueueEmail(t.Context(), templates.NewTestStub(&templates.TestStubModel{ To: "invalid-email", Subject: "test-subject-1", Body: "test-body-1", })) require.Error(t, err) id, err := c.QueueEmail(t.Context(), templates.NewTestStub(&templates.TestStubModel{ To: "test-recipient-1@example.org", Subject: "test-subject-1", Body: "test-body-1", })) require.NoError(t, err) require.NotZero(t, id) id, err = c.QueueEmail(t.Context(), templates.NewTestStub(&templates.TestStubModel{ To: "test-recipient-2@example.org", Subject: "test-subject-2", Body: "test-body-2", })) require.NoError(t, err) require.NotZero(t, id) id, err = c.QueueEmail(t.Context(), templates.NewTestStub(&templates.TestStubModel{ To: "test-recipient-3@example.org", Subject: "test-subject-3", Body: "test-body-3", })) require.NoError(t, err) require.NotZero(t, id) require.EventuallyWithT(t, func(t *assert.CollectT) { require.NoError(t, c.DispatchQueue(context.Background())) }, time.Second, 10*time.Millisecond) res, err := http.Get(api + "/api/v2/messages") require.NoError(t, err) defer func() { _ = res.Body.Close() }() body, err := io.ReadAll(res.Body) require.NoError(t, err) require.Equalf(t, http.StatusOK, res.StatusCode, "%s", body) require.EqualValues(t, 3, gjson.GetBytes(body, "total").Int()) for k := 1; k <= 3; k++ { assert.Contains(t, string(body), fmt.Sprintf("test-subject-%d", k)) assert.Contains(t, string(body), fmt.Sprintf("test-body-%d", k)) assert.Contains(t, string(body), fmt.Sprintf("test-recipient-%d@example.org", k)) assert.Contains(t, string(body), "test-stub@ory.sh") } assert.Contains(t, string(body), "Bob") assert.Contains(t, string(body), `"test-stub-header1":["foo"]`) assert.Contains(t, string(body), `"test-stub-header2":["bar"]`) } func generateTestClientCert(t *testing.T) (clientCert *os.File, clientKey *os.File, err error) { hostName := flag.String("host", "127.0.0.1", "Hostname to certify") priv, err := rsa.GenerateKey(rand.Reader, 1024) // #nosec G403 -- test code require.NoError(t, err) now := time.Now() certTemplate := x509.Certificate{ SerialNumber: big.NewInt(1234), Subject: pkix.Name{ CommonName: *hostName, Organization: []string{"myorg"}, }, NotBefore: now.Add(-300 * time.Second), NotAfter: now.Add(24 * time.Hour), SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, } cert, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &priv.PublicKey, priv) require.NoError(t, err) clientCert, err = os.CreateTemp("./test", "testCert") require.NoError(t, err) defer func() { _ = clientCert.Close() }() require.NoError(t, pem.Encode(clientCert, &pem.Block{Type: "CERTIFICATE", Bytes: cert})) clientKey, err = os.CreateTemp("./test", "testKey") require.NoError(t, err) defer func() { _ = clientKey.Close() }() require.NoError(t, pem.Encode(clientKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})) return clientCert, clientKey, nil } ================================================ FILE: courier/stub/request.config.mailer.jsonnet ================================================ function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, to: if "template_data" in ctx && "to" in ctx.template_data then ctx.template_data.to else null, recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null, recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null, verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null, verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null, subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null, body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null, html_body: if "template_data" in ctx && "html_body" in ctx.template_data then ctx.template_data.html_body else null } ================================================ FILE: courier/stub/request.config.twilio.jsonnet ================================================ function(ctx) { from: "Kratos Test", to: ctx.recipient, body: ctx.body } ================================================ FILE: courier/template/courier/builtin/templates/login_code/valid/email.body.gotmpl ================================================ Login to your account with the following code: {{ .LoginCode }} It expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/login_code/valid/email.body.plaintext.gotmpl ================================================ Login to your account with the following code: {{ .LoginCode }} It expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/login_code/valid/email.subject.gotmpl ================================================ Use code {{ .LoginCode }} to log in ================================================ FILE: courier/template/courier/builtin/templates/login_code/valid/sms.body.gotmpl ================================================ Your login code is: {{ .LoginCode }} It expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/otp/sms.body.gotmpl ================================================ Your verification code is: {{ .Code }} ================================================ FILE: courier/template/courier/builtin/templates/recovery/invalid/email.body.gotmpl ================================================ Hi, you (or someone else) entered this email address when trying to recover access to an account. However, this email address is not on our database of registered users and therefore the attempt has failed. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/recovery/invalid/email.body.plaintext.gotmpl ================================================ Hi, you (or someone else) entered this email address when trying to recover access to an account. However, this email address is not on our database of registered users and therefore the attempt has failed. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/recovery/invalid/email.subject.gotmpl ================================================ Account access attempted ================================================ FILE: courier/template/courier/builtin/templates/recovery/valid/email.body.gotmpl ================================================ Recover access to your account by clicking the following link: {{ .RecoveryURL }} If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/recovery/valid/email.body.plaintext.gotmpl ================================================ Recover access to your account by clicking the following link: {{ .RecoveryURL }} If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/recovery/valid/email.subject.gotmpl ================================================ Recover access to your account ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/invalid/email.body.gotmpl ================================================ Hi, you (or someone else) entered this email address when trying to recover access to an account. However, this email address is not on our database of registered users and therefore the attempt has failed. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/invalid/email.body.plaintext.gotmpl ================================================ Hi, you (or someone else) entered this email address when trying to recover access to an account. However, this email address is not on our database of registered users and therefore the attempt has failed. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/invalid/email.subject.gotmpl ================================================ Account access attempted ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/valid/email.body.gotmpl ================================================ Recover access to your account by entering the following code: {{ .RecoveryCode }} If this was not you, do nothing. This code expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/valid/email.body.plaintext.gotmpl ================================================ Recover access to your account by entering the following code: {{ .RecoveryCode }} If this was not you, do nothing. This code expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/valid/email.subject.gotmpl ================================================ Use code {{ .RecoveryCode }} to recover access to your account ================================================ FILE: courier/template/courier/builtin/templates/recovery_code/valid/sms.body.gotmpl ================================================ Your recovery code is: {{ .RecoveryCode }} @{{ .RequestURLDomain }} #{{ .RecoveryCode }} ================================================ FILE: courier/template/courier/builtin/templates/registration_code/valid/email.body.gotmpl ================================================ Complete your account registration with the following code: {{ .RegistrationCode }} This code expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/registration_code/valid/email.body.plaintext.gotmpl ================================================ Complete your account registration with the following code: {{ .RegistrationCode }} This code expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/registration_code/valid/email.subject.gotmpl ================================================ Use code {{ .RegistrationCode }} to complete your account registration ================================================ FILE: courier/template/courier/builtin/templates/registration_code/valid/sms.body.gotmpl ================================================ Your registration code is: {{ .RegistrationCode }} It expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/test_stub/email.body.gotmpl ================================================ stub email body {{ if .HTMLBody }}{{ .HTMLBody }}{{ else }}{{ .Body }}{{ end }} ================================================ FILE: courier/template/courier/builtin/templates/test_stub/email.body.html.en_US.gotmpl ================================================ {{ $l := cat "lang=" .lang }} {{ nospace $l }} ================================================ FILE: courier/template/courier/builtin/templates/test_stub/email.body.html.gotmpl ================================================ {{- if eq .lang "en_US" -}} {{ template "email.body.html.en_US.gotmpl" . }} {{- end -}} ================================================ FILE: courier/template/courier/builtin/templates/test_stub/email.body.plaintext.gotmpl ================================================ stub email body {{ .Body }} ================================================ FILE: courier/template/courier/builtin/templates/test_stub/email.body.sprig.gotmpl ================================================ {{ $t1 := title .input }} {{ $t1 := nospace $t1 }} {{ $t2 := upper $t1 }} {{ $t3 := cat $t1 "," $t2 }} {{ nospace $t3 }} ================================================ FILE: courier/template/courier/builtin/templates/test_stub/email.subject.gotmpl ================================================ stub email subject {{ .Subject }} ================================================ FILE: courier/template/courier/builtin/templates/verification/invalid/email.body.gotmpl ================================================ Someone asked to verify this email address, but we were unable to find an account for this address. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/verification/invalid/email.body.plaintext.gotmpl ================================================ Someone asked to verify this email address, but we were unable to find an account for this address. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/verification/invalid/email.subject.gotmpl ================================================ Someone tried to verify this email address ================================================ FILE: courier/template/courier/builtin/templates/verification/valid/email.body.gotmpl ================================================ Verify your account by opening the following link: {{ .VerificationURL }} If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/verification/valid/email.body.plaintext.gotmpl ================================================ Verify your account by opening the following link: {{ .VerificationURL }} If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/verification/valid/email.subject.gotmpl ================================================ Please verify your email address ================================================ FILE: courier/template/courier/builtin/templates/verification_code/invalid/email.body.gotmpl ================================================ Hi, someone asked to verify this email address, but we were unable to find an account for this address. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/verification_code/invalid/email.body.plaintext.gotmpl ================================================ Hi, someone asked to verify this email address, but we were unable to find an account for this address. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. ================================================ FILE: courier/template/courier/builtin/templates/verification_code/invalid/email.subject.gotmpl ================================================ Someone tried to verify this email address ================================================ FILE: courier/template/courier/builtin/templates/verification_code/valid/email.body.gotmpl ================================================ Verify your account with the following code: {{ .VerificationCode }} or clicking the following link: {{ .VerificationURL }} If this was not you, do nothing. This code / link expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/verification_code/valid/email.body.plaintext.gotmpl ================================================ Verify your account with the following code: {{ .VerificationCode }} or clicking the following link: {{ .VerificationURL }} If this was not you, do nothing. This code / link expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/courier/builtin/templates/verification_code/valid/email.subject.gotmpl ================================================ Use code {{ .VerificationCode }} to verify your account ================================================ FILE: courier/template/courier/builtin/templates/verification_code/valid/sms.body.gotmpl ================================================ Your verification code is: {{ .VerificationCode }} If this was not you, do nothing. It expires in {{ .ExpiresInMinutes }} minutes. ================================================ FILE: courier/template/email/login_code_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "net/http" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( LoginCodeValid struct { deps template.Dependencies model *LoginCodeValidModel } LoginCodeValidModel struct { To string `json:"to"` LoginCode string `json:"login_code"` Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` UserRequestHeaders http.Header `json:"-"` } ) func NewLoginCodeValid(d template.Dependencies, m *LoginCodeValidModel) *LoginCodeValid { return &LoginCodeValid{deps: d, model: m} } func (t *LoginCodeValid) EmailRecipient() (string, error) { return t.model.To, nil } func (t *LoginCodeValid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/email.subject.gotmpl", "login_code/valid/email.subject*", t.model, t.deps.CourierConfig().CourierTemplatesLoginCodeValid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *LoginCodeValid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/email.body.gotmpl", "login_code/valid/email.body*", t.model, t.deps.CourierConfig().CourierTemplatesLoginCodeValid(ctx).Body.HTML) } func (t *LoginCodeValid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/email.body.plaintext.gotmpl", "login_code/valid/email.body.plaintext*", t.model, t.deps.CourierConfig().CourierTemplatesLoginCodeValid(ctx).Body.PlainText) } func (t *LoginCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *LoginCodeValid) TemplateType() template.TemplateType { return template.TypeLoginCodeValid } func (t *LoginCodeValid) RequestHeaders() http.Header { return t.model.UserRequestHeaders } ================================================ FILE: courier/template/email/login_code_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestLoginCodeValid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewLoginCodeValid(reg, &email.LoginCodeValidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/login_code/valid", template.TypeLoginCodeValid) }) } ================================================ FILE: courier/template/email/recovery_code_invalid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( RecoveryCodeInvalid struct { deps template.Dependencies model *RecoveryCodeInvalidModel } RecoveryCodeInvalidModel struct { To string `json:"to"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` } ) func NewRecoveryCodeInvalid(d template.Dependencies, m *RecoveryCodeInvalidModel) *RecoveryCodeInvalid { return &RecoveryCodeInvalid{deps: d, model: m} } func (t *RecoveryCodeInvalid) EmailRecipient() (string, error) { return t.model.To, nil } func (t *RecoveryCodeInvalid) EmailSubject(ctx context.Context) (string, error) { filesystem := os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)) remoteURL := t.deps.CourierConfig().CourierTemplatesRecoveryCodeInvalid(ctx).Subject subject, err := template.LoadText(ctx, t.deps, filesystem, "recovery_code/invalid/email.subject.gotmpl", "recovery_code/invalid/email.subject*", t.model, remoteURL) return strings.TrimSpace(subject), err } func (t *RecoveryCodeInvalid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "recovery_code/invalid/email.body.gotmpl", "recovery_code/invalid/email.body*", t.model, t.deps.CourierConfig().CourierTemplatesRecoveryCodeInvalid(ctx).Body.HTML) } func (t *RecoveryCodeInvalid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "recovery_code/invalid/email.body.plaintext.gotmpl", "recovery_code/invalid/email.body.plaintext*", t.model, t.deps.CourierConfig().CourierTemplatesRecoveryCodeInvalid(ctx).Body.PlainText) } func (t *RecoveryCodeInvalid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *RecoveryCodeInvalid) TemplateType() template.TemplateType { return template.TypeRecoveryCodeInvalid } ================================================ FILE: courier/template/email/recovery_code_invalid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestRecoveryCodeInvalid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewRecoveryCodeInvalid(reg, &email.RecoveryCodeInvalidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("case=test remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/recovery_code/invalid", template.TypeRecoveryCodeInvalid) }) } ================================================ FILE: courier/template/email/recovery_code_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "net/http" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( RecoveryCodeValid struct { deps template.Dependencies model *RecoveryCodeValidModel } RecoveryCodeValidModel struct { To string `json:"to"` RecoveryCode string `json:"recovery_code"` Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` UserRequestHeaders http.Header `json:"-"` } ) func NewRecoveryCodeValid(d template.Dependencies, m *RecoveryCodeValidModel) *RecoveryCodeValid { return &RecoveryCodeValid{deps: d, model: m} } func (t *RecoveryCodeValid) EmailRecipient() (string, error) { return t.model.To, nil } func (t *RecoveryCodeValid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "recovery_code/valid/email.subject.gotmpl", "recovery_code/valid/email.subject*", t.model, t.deps.CourierConfig().CourierTemplatesRecoveryCodeValid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *RecoveryCodeValid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "recovery_code/valid/email.body.gotmpl", "recovery_code/valid/email.body*", t.model, t.deps.CourierConfig().CourierTemplatesRecoveryCodeValid(ctx).Body.HTML) } func (t *RecoveryCodeValid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "recovery_code/valid/email.body.plaintext.gotmpl", "recovery_code/valid/email.body.plaintext*", t.model, t.deps.CourierConfig().CourierTemplatesRecoveryCodeValid(ctx).Body.PlainText) } func (t *RecoveryCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *RecoveryCodeValid) TemplateType() template.TemplateType { return template.TypeRecoveryCodeValid } func (t *RecoveryCodeValid) RequestHeaders() http.Header { return t.model.UserRequestHeaders } ================================================ FILE: courier/template/email/recovery_code_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestRecoveryCodeValid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewRecoveryCodeValid(reg, &email.RecoveryCodeValidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/recovery_code/valid", template.TypeRecoveryCodeValid) }) } ================================================ FILE: courier/template/email/recovery_invalid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( RecoveryInvalid struct { d template.Dependencies m *RecoveryInvalidModel } RecoveryInvalidModel struct { To string `json:"to"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` } ) func NewRecoveryInvalid(d template.Dependencies, m *RecoveryInvalidModel) *RecoveryInvalid { return &RecoveryInvalid{d: d, m: m} } func (t *RecoveryInvalid) EmailRecipient() (string, error) { return t.m.To, nil } func (t *RecoveryInvalid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "recovery/invalid/email.subject.gotmpl", "recovery/invalid/email.subject*", t.m, t.d.CourierConfig().CourierTemplatesRecoveryInvalid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *RecoveryInvalid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "recovery/invalid/email.body.gotmpl", "recovery/invalid/email.body*", t.m, t.d.CourierConfig().CourierTemplatesRecoveryInvalid(ctx).Body.HTML) } func (t *RecoveryInvalid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "recovery/invalid/email.body.plaintext.gotmpl", "recovery/invalid/email.body.plaintext*", t.m, t.d.CourierConfig().CourierTemplatesRecoveryInvalid(ctx).Body.PlainText) } func (t *RecoveryInvalid) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *RecoveryInvalid) TemplateType() template.TemplateType { return template.TypeRecoveryInvalid } ================================================ FILE: courier/template/email/recovery_invalid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestRecoverInvalid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewRecoveryInvalid(reg, &email.RecoveryInvalidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("case=test remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/recovery/invalid", template.TypeRecoveryInvalid) }) } ================================================ FILE: courier/template/email/recovery_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( RecoveryValid struct { d template.Dependencies m *RecoveryValidModel } RecoveryValidModel struct { To string `json:"to"` RecoveryURL string `json:"recovery_url"` Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` } ) func NewRecoveryValid(d template.Dependencies, m *RecoveryValidModel) *RecoveryValid { return &RecoveryValid{d: d, m: m} } func (t *RecoveryValid) EmailRecipient() (string, error) { return t.m.To, nil } func (t *RecoveryValid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "recovery/valid/email.subject.gotmpl", "recovery/valid/email.subject*", t.m, t.d.CourierConfig().CourierTemplatesRecoveryValid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *RecoveryValid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "recovery/valid/email.body.gotmpl", "recovery/valid/email.body*", t.m, t.d.CourierConfig().CourierTemplatesRecoveryValid(ctx).Body.HTML) } func (t *RecoveryValid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "recovery/valid/email.body.plaintext.gotmpl", "recovery/valid/email.body.plaintext*", t.m, t.d.CourierConfig().CourierTemplatesRecoveryValid(ctx).Body.PlainText) } func (t *RecoveryValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *RecoveryValid) TemplateType() template.TemplateType { return template.TypeRecoveryValid } ================================================ FILE: courier/template/email/recovery_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestRecoverValid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewRecoveryValid(reg, &email.RecoveryValidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/recovery/valid", template.TypeRecoveryValid) }) } ================================================ FILE: courier/template/email/registration_code_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "net/http" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( RegistrationCodeValid struct { deps template.Dependencies model *RegistrationCodeValidModel } RegistrationCodeValidModel struct { To string `json:"to"` Traits map[string]interface{} `json:"traits"` RegistrationCode string `json:"registration_code"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` UserRequestHeaders http.Header `json:"-"` } ) func NewRegistrationCodeValid(d template.Dependencies, m *RegistrationCodeValidModel) *RegistrationCodeValid { return &RegistrationCodeValid{deps: d, model: m} } func (t *RegistrationCodeValid) EmailRecipient() (string, error) { return t.model.To, nil } func (t *RegistrationCodeValid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "registration_code/valid/email.subject.gotmpl", "registration_code/valid/email.subject*", t.model, t.deps.CourierConfig().CourierTemplatesRegistrationCodeValid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *RegistrationCodeValid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "registration_code/valid/email.body.gotmpl", "registration_code/valid/email.body*", t.model, t.deps.CourierConfig().CourierTemplatesRegistrationCodeValid(ctx).Body.HTML) } func (t *RegistrationCodeValid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "registration_code/valid/email.body.plaintext.gotmpl", "registration_code/valid/email.body.plaintext*", t.model, t.deps.CourierConfig().CourierTemplatesRegistrationCodeValid(ctx).Body.PlainText) } func (t *RegistrationCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *RegistrationCodeValid) TemplateType() template.TemplateType { return template.TypeRegistrationCodeValid } func (t *RegistrationCodeValid) RequestHeaders() http.Header { return t.model.UserRequestHeaders } ================================================ FILE: courier/template/email/registration_code_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestRegistrationCodeValid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewRegistrationCodeValid(reg, &email.RegistrationCodeValidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/registration_code/valid", template.TypeRegistrationCodeValid) }) } ================================================ FILE: courier/template/email/stub.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "github.com/ory/kratos/courier/template" ) type ( TestStub struct { m *TestStubModel } TestStubModel struct { To string `json:"to"` Subject string `json:"subject"` Body string `json:"body"` HTMLBody string `json:"html_body,omitempty"` } ) func NewTestStub(m *TestStubModel) *TestStub { return &TestStub{m: m} } func (t *TestStub) EmailRecipient() (string, error) { return t.m.To, nil } func (t *TestStub) EmailSubject(context.Context) (string, error) { return t.m.Subject, nil } func (t *TestStub) EmailBody(ctx context.Context) (string, error) { if t.m.HTMLBody != "" { return t.m.HTMLBody, nil } return t.EmailBodyPlaintext(ctx) } func (t *TestStub) EmailBodyPlaintext(context.Context) (string, error) { return t.m.Body, nil } func (t *TestStub) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *TestStub) TemplateType() template.TemplateType { return template.TypeTestStub } ================================================ FILE: courier/template/email/verification_code_invalid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( VerificationCodeInvalid struct { d template.Dependencies m *VerificationCodeInvalidModel } VerificationCodeInvalidModel struct { To string `json:"to"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` } ) func NewVerificationCodeInvalid(d template.Dependencies, m *VerificationCodeInvalidModel) *VerificationCodeInvalid { return &VerificationCodeInvalid{d: d, m: m} } func (t *VerificationCodeInvalid) EmailRecipient() (string, error) { return t.m.To, nil } func (t *VerificationCodeInvalid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText( ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/invalid/email.subject.gotmpl", "verification_code/invalid/email.subject*", t.m, t.d.CourierConfig().CourierTemplatesVerificationCodeInvalid(ctx).Subject, ) return strings.TrimSpace(subject), err } func (t *VerificationCodeInvalid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML( ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/invalid/email.body.gotmpl", "verification_code/invalid/email.body*", t.m, t.d.CourierConfig().CourierTemplatesVerificationCodeInvalid(ctx).Body.HTML, ) } func (t *VerificationCodeInvalid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText( ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/invalid/email.body.plaintext.gotmpl", "verification_code/invalid/email.body.plaintext*", t.m, t.d.CourierConfig().CourierTemplatesVerificationCodeInvalid(ctx).Body.PlainText, ) } func (t *VerificationCodeInvalid) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *VerificationCodeInvalid) TemplateType() template.TemplateType { return template.TypeVerificationCodeInvalid } ================================================ FILE: courier/template/email/verification_code_invalid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestVerifyCodeInvalid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewVerificationCodeInvalid(reg, &email.VerificationCodeInvalidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/verification_code/invalid", template.TypeVerificationCodeInvalid) }) } ================================================ FILE: courier/template/email/verification_code_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( VerificationCodeValid struct { d template.Dependencies m *VerificationCodeValidModel } VerificationCodeValidModel struct { To string `json:"to"` VerificationURL string `json:"verification_url"` VerificationCode string `json:"verification_code"` Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` } ) func NewVerificationCodeValid(d template.Dependencies, m *VerificationCodeValidModel) *VerificationCodeValid { return &VerificationCodeValid{d: d, m: m} } func (t *VerificationCodeValid) EmailRecipient() (string, error) { return t.m.To, nil } func (t *VerificationCodeValid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText( ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/valid/email.subject.gotmpl", "verification_code/valid/email.subject*", t.m, t.d.CourierConfig().CourierTemplatesVerificationCodeValid(ctx).Subject, ) return strings.TrimSpace(subject), err } func (t *VerificationCodeValid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/valid/email.body.gotmpl", "verification_code/valid/email.body*", t.m, t.d.CourierConfig().CourierTemplatesVerificationCodeValid(ctx).Body.HTML, ) } func (t *VerificationCodeValid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/valid/email.body.plaintext.gotmpl", "verification_code/valid/email.body.plaintext*", t.m, t.d.CourierConfig().CourierTemplatesVerificationCodeValid(ctx).Body.PlainText, ) } func (t *VerificationCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *VerificationCodeValid) TemplateType() template.TemplateType { return template.TypeVerificationCodeValid } ================================================ FILE: courier/template/email/verification_code_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestVerifyCodeValid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewVerificationCodeValid(reg, &email.VerificationCodeValidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/verification_code/valid", template.TypeVerificationCodeValid) }) } ================================================ FILE: courier/template/email/verification_invalid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( VerificationInvalid struct { d template.Dependencies m *VerificationInvalidModel } VerificationInvalidModel struct { To string `json:"to"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` } ) func NewVerificationInvalid(d template.Dependencies, m *VerificationInvalidModel) *VerificationInvalid { return &VerificationInvalid{d: d, m: m} } func (t *VerificationInvalid) EmailRecipient() (string, error) { return t.m.To, nil } func (t *VerificationInvalid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification/invalid/email.subject.gotmpl", "verification/invalid/email.subject*", t.m, t.d.CourierConfig().CourierTemplatesVerificationInvalid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *VerificationInvalid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification/invalid/email.body.gotmpl", "verification/invalid/email.body*", t.m, t.d.CourierConfig().CourierTemplatesVerificationInvalid(ctx).Body.HTML) } func (t *VerificationInvalid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification/invalid/email.body.plaintext.gotmpl", "verification/invalid/email.body.plaintext*", t.m, t.d.CourierConfig().CourierTemplatesVerificationInvalid(ctx).Body.PlainText) } func (t *VerificationInvalid) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *VerificationInvalid) TemplateType() template.TemplateType { return template.TypeVerificationInvalid } ================================================ FILE: courier/template/email/verification_invalid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestVerifyInvalid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewVerificationInvalid(reg, &email.VerificationInvalidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/verification/invalid", template.TypeVerificationInvalid) }) }) } ================================================ FILE: courier/template/email/verification_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email import ( "context" "encoding/json" "os" "strings" "github.com/ory/kratos/courier/template" ) type ( VerificationValid struct { d template.Dependencies m *VerificationValidModel } VerificationValidModel struct { To string `json:"to"` VerificationURL string `json:"verification_url"` Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` } ) func NewVerificationValid(d template.Dependencies, m *VerificationValidModel) *VerificationValid { return &VerificationValid{d: d, m: m} } func (t *VerificationValid) EmailRecipient() (string, error) { return t.m.To, nil } func (t *VerificationValid) EmailSubject(ctx context.Context) (string, error) { subject, err := template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification/valid/email.subject.gotmpl", "verification/valid/email.subject*", t.m, t.d.CourierConfig().CourierTemplatesVerificationValid(ctx).Subject) return strings.TrimSpace(subject), err } func (t *VerificationValid) EmailBody(ctx context.Context) (string, error) { return template.LoadHTML(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification/valid/email.body.gotmpl", "verification/valid/email.body*", t.m, t.d.CourierConfig().CourierTemplatesVerificationValid(ctx).Body.HTML) } func (t *VerificationValid) EmailBodyPlaintext(ctx context.Context) (string, error) { return template.LoadText(ctx, t.d, os.DirFS(t.d.CourierConfig().CourierTemplatesRoot(ctx)), "verification/valid/email.body.plaintext.gotmpl", "verification/valid/email.body.plaintext*", t.m, t.d.CourierConfig().CourierTemplatesVerificationValid(ctx).Body.PlainText) } func (t *VerificationValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *VerificationValid) TemplateType() template.TemplateType { return template.TypeVerificationValid } ================================================ FILE: courier/template/email/verification_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package email_test import ( "context" "testing" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/courier/template/email" "github.com/ory/kratos/courier/template/testhelpers" "github.com/ory/kratos/pkg" ) func TestVerifyValid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("test=with courier templates directory", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) tpl := email.NewVerificationValid(reg, &email.VerificationValidModel{}) testhelpers.TestRendered(t, ctx, tpl) }) t.Run("test=with remote resources", func(t *testing.T) { testhelpers.TestRemoteTemplates(t, "../courier/builtin/templates/verification/valid", template.TypeVerificationValid) }) } ================================================ FILE: courier/template/load_template.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package template import ( "bytes" "context" "embed" htemplate "html/template" "io" "io/fs" "path/filepath" "text/template" "github.com/ory/x/fetcher" "github.com/ory/x/httpx" "github.com/Masterminds/sprig/v3" lru "github.com/hashicorp/golang-lru/v2" "github.com/pkg/errors" ) //go:embed courier/builtin/templates/* var templates embed.FS var Cache, _ = lru.New[string, Template](16) type Template interface { Execute(wr io.Writer, data interface{}) error } type templateDependencies interface { httpx.ClientProvider } func loadBuiltInTemplate(filesystem fs.FS, name string, html bool) (Template, error) { if t, found := Cache.Get(name); found { return t, nil } file, err := filesystem.Open(name) if err != nil { // try to fallback to bundled templates var fallbackErr error file, fallbackErr = templates.Open(filepath.Join("courier/builtin/templates", name)) if fallbackErr != nil { // return original error from os.DirFS return nil, errors.WithStack(err) } } defer func() { _ = file.Close() }() var b bytes.Buffer if _, err := io.Copy(&b, file); err != nil { return nil, errors.WithStack(err) } var tpl Template if html { t, err := htemplate.New(name).Funcs(sprig.HtmlFuncMap()).Parse(b.String()) if err != nil { return nil, errors.WithStack(err) } tpl = t } else { t, err := template.New(name).Funcs(sprig.TxtFuncMap()).Parse(b.String()) if err != nil { return nil, errors.WithStack(err) } tpl = t } _ = Cache.Add(name, tpl) return tpl, nil } func loadRemoteTemplate(ctx context.Context, d templateDependencies, url string, html bool) (t Template, err error) { if t, found := Cache.Get(url); found { return t, nil } f := fetcher.NewFetcher(fetcher.WithClient(d.HTTPClient(ctx))) bb, err := f.FetchContext(ctx, url) if err != nil { return nil, errors.WithStack(err) } b := bb.Bytes() if html { t, err = htemplate.New(url).Funcs(sprig.HermeticHtmlFuncMap()).Parse(string(b)) if err != nil { return nil, errors.WithStack(err) } } else { t, err = template.New(url).Funcs(sprig.HermeticTxtFuncMap()).Parse(string(b)) if err != nil { return nil, errors.WithStack(err) } } Cache.Add(url, t) return t, nil } func loadTemplate(filesystem fs.FS, name, pattern string, html bool) (Template, error) { if t, found := Cache.Get(name); found { return t, nil } matches, _ := fs.Glob(filesystem, name) // make sure the file exists in the fs, otherwise fallback to built in templates if matches == nil { return loadBuiltInTemplate(filesystem, name, html) } glob := name if pattern != "" { // pattern matching is used when we have more than one gotmpl for different use cases, such as i18n support // e.g. some_template/template_name* will match some_template/template_name.body.en_US.gotmpl matches, _ = fs.Glob(filesystem, pattern) // set the glob string to match patterns if matches != nil { glob = pattern } } var tpl Template if html { t, err := htemplate.New(filepath.Base(name)).Funcs(sprig.HermeticHtmlFuncMap()).ParseFS(filesystem, glob) if err != nil { return nil, errors.WithStack(err) } tpl = t } else { t, err := template.New(filepath.Base(name)).Funcs(sprig.HermeticTxtFuncMap()).ParseFS(filesystem, glob) if err != nil { return nil, errors.WithStack(err) } tpl = t } _ = Cache.Add(name, tpl) return tpl, nil } func LoadText(ctx context.Context, d templateDependencies, filesystem fs.FS, name, pattern string, model interface{}, remoteURL string) (string, error) { var t Template var err error if remoteURL != "" { t, err = loadRemoteTemplate(ctx, d, remoteURL, false) if err != nil { return "", err } } else { t, err = loadTemplate(filesystem, name, pattern, false) if err != nil { return "", err } } var b bytes.Buffer if err := t.Execute(&b, model); err != nil { return "", err } return b.String(), nil } func LoadHTML(ctx context.Context, d templateDependencies, filesystem fs.FS, name, pattern string, model interface{}, remoteURL string) (string, error) { var t Template var err error if remoteURL != "" { t, err = loadRemoteTemplate(ctx, d, remoteURL, true) if err != nil { return "", err } } else { t, err = loadTemplate(filesystem, name, pattern, true) if err != nil { return "", err } } var b bytes.Buffer if err := t.Execute(&b, model); err != nil { return "", err } return b.String(), nil } ================================================ FILE: courier/template/load_template_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package template_test import ( "context" "encoding/base64" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/x/fetcher" lru "github.com/hashicorp/golang-lru/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/x" ) func TestLoadTextTemplate(t *testing.T) { executeTextTemplate := func(t *testing.T, dir, name, pattern string, model map[string]interface{}) string { _, reg := pkg.NewFastRegistryWithMocks(t) tp, err := template.LoadText(t.Context(), reg, os.DirFS(dir), name, pattern, model, "") require.NoError(t, err) return tp } executeHTMLTemplate := func(t *testing.T, dir, name, pattern string, model map[string]interface{}) string { _, reg := pkg.NewFastRegistryWithMocks(t) tp, err := template.LoadHTML(t.Context(), reg, os.DirFS(dir), name, pattern, model, "") require.NoError(t, err) return tp } t.Run("method=from bundled", func(t *testing.T) { actual := executeTextTemplate(t, "courier/builtin/templates/test_stub", "email.body.gotmpl", "", nil) assert.Contains(t, actual, "stub email") }) t.Run("method=fallback to bundled", func(t *testing.T) { template.Cache, _ = lru.New[string, template.Template](16) // prevent Cache hit actual := executeTextTemplate(t, "some/inexistent/dir", "test_stub/email.body.gotmpl", "", nil) assert.Contains(t, actual, "stub email") }) t.Run("method=with Sprig functions", func(t *testing.T) { template.Cache, _ = lru.New[string, template.Template](16) // prevent Cache hit m := map[string]interface{}{"input": "hello world"} // create a simple model actual := executeTextTemplate(t, "courier/builtin/templates/test_stub", "email.body.sprig.gotmpl", "", m) assert.Contains(t, actual, "HelloWorld,HELLOWORLD") }) t.Run("method=sprig should not support non-hermetic", func(t *testing.T) { template.Cache, _ = lru.New[string, template.Template](16) _, reg := pkg.NewFastRegistryWithMocks(t) nonhermetic := []string{"date", "date_in_zone", "date_modify", "now", "htmlDate", "htmlDateInZone", "dateInZone", "dateModify", "env", "expandenv", "getHostByName", "uuidv4", "randNumeric", "randAscii", "randAlpha", "randAlphaNum"} for _, tc := range nonhermetic { t.Run("case=should not support function: "+tc, func(t *testing.T) { _, err := template.LoadText(t.Context(), reg, x.NewStubFS(tc, []byte(fmt.Sprintf("{{ %s }}", tc))), tc, "", map[string]interface{}{}, "") assert.ErrorContains(t, err, fmt.Sprintf("function %q not defined", tc)) }) } }) t.Run("method=html with nested templates", func(t *testing.T) { template.Cache, _ = lru.New[string, template.Template](16) // prevent Cache hit m := map[string]interface{}{"lang": "en_US"} // create a simple model actual := executeHTMLTemplate(t, "courier/builtin/templates/test_stub", "email.body.html.gotmpl", "email.body.html*", m) assert.Contains(t, actual, "lang=en_US") }) t.Run("method=Cache works", func(t *testing.T) { dir := os.TempDir() name := x.NewUUID().String() + ".body.gotmpl" fp := filepath.Join(dir, name) require.NoError(t, os.WriteFile(fp, []byte("cached stub body"), 0o600)) assert.Contains(t, executeTextTemplate(t, dir, name, "", nil), "cached stub body") require.NoError(t, os.RemoveAll(fp)) assert.Contains(t, executeTextTemplate(t, dir, name, "", nil), "cached stub body") }) t.Run("method=remote resource", func(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("case=base64 encoded data", func(t *testing.T) { t.Run("html template", func(t *testing.T) { m := map[string]interface{}{"lang": "en_US"} f, err := os.ReadFile("courier/builtin/templates/test_stub/email.body.html.en_US.gotmpl") require.NoError(t, err) b64 := base64.StdEncoding.EncodeToString(f) tp, err := template.LoadHTML(ctx, reg, nil, "", "", m, "base64://"+b64) require.NoError(t, err) assert.Contains(t, tp, "lang=en_US") }) t.Run("case=plaintext", func(t *testing.T) { m := map[string]interface{}{"Body": "something"} f, err := os.ReadFile("courier/builtin/templates/test_stub/email.body.plaintext.gotmpl") require.NoError(t, err) b64 := base64.StdEncoding.EncodeToString(f) tp, err := template.LoadText(ctx, reg, nil, "", "", m, "base64://"+b64) require.NoError(t, err) assert.Contains(t, tp, "stub email body something") }) }) t.Run("case=file resource", func(t *testing.T) { t.Run("case=html template", func(t *testing.T) { m := map[string]interface{}{"lang": "en_US"} tp, err := template.LoadHTML(ctx, reg, nil, "", "", m, "file://courier/builtin/templates/test_stub/email.body.html.en_US.gotmpl") require.NoError(t, err) assert.Contains(t, tp, "lang=en_US") }) t.Run("case=plaintext", func(t *testing.T) { m := map[string]interface{}{"Body": "something"} tp, err := template.LoadText(ctx, reg, nil, "", "", m, "file://courier/builtin/templates/test_stub/email.body.plaintext.gotmpl") require.NoError(t, err) assert.Contains(t, tp, "stub email body something") }) }) t.Run("case=http resource", func(t *testing.T) { router := http.NewServeMux() router.HandleFunc("GET /html", func(writer http.ResponseWriter, request *http.Request) { http.ServeFile(writer, request, "courier/builtin/templates/test_stub/email.body.html.en_US.gotmpl") }) router.HandleFunc("GET /plaintext", func(writer http.ResponseWriter, request *http.Request) { http.ServeFile(writer, request, "courier/builtin/templates/test_stub/email.body.plaintext.gotmpl") }) ts := httptest.NewServer(router) t.Cleanup(ts.Close) t.Run("case=html template", func(t *testing.T) { m := map[string]interface{}{"lang": "en_US"} tp, err := template.LoadHTML(ctx, reg, nil, "", "", m, ts.URL+"/html") require.NoError(t, err) assert.Contains(t, tp, "lang=en_US") }) t.Run("case=plaintext", func(t *testing.T) { m := map[string]interface{}{"Body": "something"} tp, err := template.LoadText(ctx, reg, nil, "", "", m, ts.URL+"/plaintext") require.NoError(t, err) assert.Contains(t, tp, "stub email body something") }) }) t.Run("case=unsupported resource", func(t *testing.T) { tp, err := template.LoadHTML(ctx, reg, nil, "", "", map[string]interface{}{}, "grpc://unsupported-url") require.ErrorIs(t, err, fetcher.ErrUnknownScheme) require.Empty(t, tp) tp, err = template.LoadText(ctx, reg, nil, "", "", map[string]interface{}{}, "grpc://unsupported-url") require.ErrorIs(t, err, fetcher.ErrUnknownScheme) require.Empty(t, tp) }) t.Run("case=disallowed resources", func(t *testing.T) { require.NoError(t, reg.Config().Set(ctx, config.ViperKeyClientHTTPNoPrivateIPRanges, true)) reg.HTTPClient(ctx).RetryMax = 1 reg.HTTPClient(ctx).RetryWaitMax = time.Millisecond _, err := template.LoadHTML(ctx, reg, nil, "", "", map[string]interface{}{}, "http://localhost:8080/1234") assert.ErrorContains(t, err, "is not a permitted destination") _, err = template.LoadText(ctx, reg, nil, "", "", map[string]interface{}{}, "http://localhost:8080/1234") assert.ErrorContains(t, err, "is not a permitted destination") }) t.Run("method=cache works", func(t *testing.T) { tp1, err := template.LoadText(ctx, reg, nil, "", "", map[string]interface{}{}, "base64://e3sgJGwgOj0gY2F0ICJsYW5nPSIgLmxhbmcgfX0Ke3sgbm9zcGFjZSAkbCB9fQ==") require.NoError(t, err) tp2, err := template.LoadText(ctx, reg, nil, "", "", map[string]interface{}{}, "base64://c3R1YiBlbWFpbCBib2R5IHt7IC5Cb2R5IH19") require.NoError(t, err) assert.NotEqualf(t, tp1, tp2, "Expected remote template 1 and remote template 2 to not be equal") }) }) } ================================================ FILE: courier/template/sms/login_code_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms import ( "context" "encoding/json" "net/http" "os" "github.com/ory/kratos/courier/template" ) type ( LoginCodeValid struct { deps template.Dependencies model *LoginCodeValidModel } LoginCodeValidModel struct { To string `json:"to"` LoginCode string `json:"login_code"` Identity map[string]any `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]any `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` UserRequestHeaders http.Header `json:"-"` } ) func NewLoginCodeValid(d template.Dependencies, m *LoginCodeValidModel) *LoginCodeValid { return &LoginCodeValid{deps: d, model: m} } func (t *LoginCodeValid) PhoneNumber() (string, error) { return t.model.To, nil } func (t *LoginCodeValid) SMSBody(ctx context.Context) (string, error) { return template.LoadText( ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "login_code/valid/sms.body.gotmpl", "login_code/valid/sms.body*", t.model, t.deps.CourierConfig().CourierSMSTemplatesLoginCodeValid(ctx).Body.PlainText, ) } func (t *LoginCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *LoginCodeValid) TemplateType() template.TemplateType { return template.TypeLoginCodeValid } func (t *LoginCodeValid) RequestHeaders() http.Header { return t.model.UserRequestHeaders } ================================================ FILE: courier/template/sms/login_code_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms_test import ( "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier/template/sms" "github.com/ory/kratos/pkg" ) func TestNewLoginCodeValid(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) const ( expectedPhone = "+12345678901" otp = "012345" ) tpl := sms.NewLoginCodeValid(reg, &sms.LoginCodeValidModel{To: expectedPhone, LoginCode: otp}) expectedBody := fmt.Sprintf("Your login code is: %s\n\nIt expires in 0 minutes.\n", otp) actualBody, err := tpl.SMSBody(context.Background()) require.NoError(t, err) assert.Equal(t, expectedBody, actualBody) actualPhone, err := tpl.PhoneNumber() require.NoError(t, err) assert.Equal(t, expectedPhone, actualPhone) } ================================================ FILE: courier/template/sms/recovery_code.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms import ( "context" "encoding/json" "net/http" "os" "github.com/ory/kratos/courier/template" ) type ( RecoveryCodeValid struct { deps template.Dependencies model *RecoveryCodeValidModel } RecoveryCodeValidModel struct { To string `json:"to"` RecoveryCode string `json:"verification_code"` Identity map[string]any `json:"identity"` RequestURL string `json:"request_url"` RequestURLDomain string `json:"request_url_domain"` TransientPayload map[string]any `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` UserRequestHeaders http.Header `json:"-"` } ) func NewRecoveryCodeValid(d template.Dependencies, m *RecoveryCodeValidModel) *RecoveryCodeValid { return &RecoveryCodeValid{deps: d, model: m} } func (t *RecoveryCodeValid) PhoneNumber() (string, error) { return t.model.To, nil } func (t *RecoveryCodeValid) SMSBody(ctx context.Context) (string, error) { return template.LoadText( ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "recovery_code/valid/sms.body.gotmpl", "recovery_code/valid/sms.body*", t.model, t.deps.CourierConfig().CourierSMSTemplatesRecoveryCodeValid(ctx).Body.PlainText, ) } func (t *RecoveryCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *RecoveryCodeValid) TemplateType() template.TemplateType { return template.TypeRecoveryCodeValid } func (t *RecoveryCodeValid) RequestHeaders() http.Header { return t.model.UserRequestHeaders } ================================================ FILE: courier/template/sms/registration_code_valid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms import ( "context" "encoding/json" "net/http" "os" "github.com/ory/kratos/courier/template" ) type ( RegistrationCodeValid struct { deps template.Dependencies model *RegistrationCodeValidModel } RegistrationCodeValidModel struct { To string `json:"to"` RegistrationCode string `json:"registration_code"` Identity map[string]any `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]any `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` UserRequestHeaders http.Header `json:"-"` } ) func NewRegistrationCodeValid(d template.Dependencies, m *RegistrationCodeValidModel) *RegistrationCodeValid { return &RegistrationCodeValid{deps: d, model: m} } func (t *RegistrationCodeValid) PhoneNumber() (string, error) { return t.model.To, nil } func (t *RegistrationCodeValid) SMSBody(ctx context.Context) (string, error) { return template.LoadText( ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "registration_code/valid/sms.body.gotmpl", "registration_code/valid/sms.body*", t.model, t.deps.CourierConfig().CourierSMSTemplatesRegistrationCodeValid(ctx).Body.PlainText, ) } func (t *RegistrationCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *RegistrationCodeValid) TemplateType() template.TemplateType { return template.TypeRegistrationCodeValid } func (t *RegistrationCodeValid) RequestHeaders() http.Header { return t.model.UserRequestHeaders } ================================================ FILE: courier/template/sms/registration_code_valid_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms_test import ( "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier/template/sms" "github.com/ory/kratos/pkg" ) func TestNewRegistrationCodeValid(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) const ( expectedPhone = "+12345678901" otp = "012345" ) tpl := sms.NewRegistrationCodeValid(reg, &sms.RegistrationCodeValidModel{To: expectedPhone, RegistrationCode: otp}) expectedBody := fmt.Sprintf("Your registration code is: %s\n\nIt expires in 0 minutes.\n", otp) actualBody, err := tpl.SMSBody(context.Background()) require.NoError(t, err) assert.Equal(t, expectedBody, actualBody) actualPhone, err := tpl.PhoneNumber() require.NoError(t, err) assert.Equal(t, expectedPhone, actualPhone) } ================================================ FILE: courier/template/sms/stub.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms import ( "context" "encoding/json" "github.com/ory/kratos/courier/template" ) type ( TestStub struct{ m *TestStubModel } TestStubModel struct { To string `json:"to"` Body string `json:"body"` Identity map[string]interface{} `json:"identity"` } ) func NewTestStub(m *TestStubModel) *TestStub { return &TestStub{m: m} } func (t *TestStub) PhoneNumber() (string, error) { return t.m.To, nil } func (t *TestStub) SMSBody(context.Context) (string, error) { return t.m.Body, nil } func (t *TestStub) MarshalJSON() ([]byte, error) { return json.Marshal(t.m) } func (t *TestStub) TemplateType() template.TemplateType { return template.TypeTestStub } ================================================ FILE: courier/template/sms/verification_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms import ( "context" "encoding/json" "os" "github.com/ory/kratos/courier/template" ) type ( VerificationCodeValid struct { deps template.Dependencies model *VerificationCodeValidModel } VerificationCodeValidModel struct { To string `json:"to"` VerificationCode string `json:"verification_code"` Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` ExpiresInMinutes int `json:"expires_in_minutes"` } ) func NewVerificationCodeValid(d template.Dependencies, m *VerificationCodeValidModel) *VerificationCodeValid { return &VerificationCodeValid{deps: d, model: m} } func (t *VerificationCodeValid) PhoneNumber() (string, error) { return t.model.To, nil } func (t *VerificationCodeValid) SMSBody(ctx context.Context) (string, error) { return template.LoadText( ctx, t.deps, os.DirFS(t.deps.CourierConfig().CourierTemplatesRoot(ctx)), "verification_code/valid/sms.body.gotmpl", "verification_code/valid/sms.body*", t.model, t.deps.CourierConfig().CourierSMSTemplatesVerificationCodeValid(ctx).Body.PlainText, ) } func (t *VerificationCodeValid) MarshalJSON() ([]byte, error) { return json.Marshal(t.model) } func (t *VerificationCodeValid) TemplateType() template.TemplateType { return template.TypeVerificationCodeValid } ================================================ FILE: courier/template/sms/verification_code_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sms_test import ( "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier/template/sms" "github.com/ory/kratos/pkg" ) func TestNewOTPMessage(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t) const ( expectedPhone = "+12345678901" otp = "012345" ) tpl := sms.NewVerificationCodeValid(reg, &sms.VerificationCodeValidModel{To: expectedPhone, VerificationCode: otp}) expectedBody := fmt.Sprintf("Your verification code is: %s\n\nIf this was not you, do nothing. It expires in 0 minutes.\n", otp) actualBody, err := tpl.SMSBody(context.Background()) require.NoError(t, err) assert.Equal(t, expectedBody, actualBody) actualPhone, err := tpl.PhoneNumber() require.NoError(t, err) assert.Equal(t, expectedPhone, actualPhone) } ================================================ FILE: courier/template/template.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package template import ( "github.com/ory/kratos/driver/config" "github.com/ory/x/httpx" ) type Dependencies interface { CourierConfig() config.CourierConfigs httpx.ClientProvider } ================================================ FILE: courier/template/testhelpers/testhelpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package testhelpers import ( "context" "encoding/base64" "net/http" "net/http/httptest" "os" "path" "testing" "github.com/ory/kratos/courier/template/email" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/courier/template" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" ) func SetupRemoteConfig(t *testing.T, ctx context.Context, plaintext string, html string, subject string) *driver.RegistryDefault { _, reg := pkg.NewVeryFastRegistryWithoutDB(t) require.NoError(t, reg.Config().Set(ctx, config.ViperKeyCourierTemplatesRecoveryInvalidEmail, &config.CourierEmailTemplate{ Body: &config.CourierEmailBodyTemplate{ PlainText: plaintext, HTML: html, }, Subject: subject, })) return reg } func TestRendered(t *testing.T, ctx context.Context, tpl interface { EmailBody(context.Context) (string, error) EmailSubject(context.Context) (string, error) }, ) { rendered, err := tpl.EmailBody(ctx) require.NoError(t, err) assert.NotEmpty(t, rendered) rendered, err = tpl.EmailSubject(ctx) require.NoError(t, err) assert.NotEmpty(t, rendered) } func TestRemoteTemplates(t *testing.T, basePath string, tmplType template.TemplateType) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) toBase64 := func(filePath string) string { f, err := os.ReadFile(filePath) // #nosec G304 -- test code require.NoError(t, err) return base64.StdEncoding.EncodeToString(f) } getTemplate := func(tmpl template.TemplateType, d template.Dependencies) interface { EmailBody(context.Context) (string, error) EmailSubject(context.Context) (string, error) } { switch tmpl { case template.TypeRecoveryInvalid: return email.NewRecoveryInvalid(d, &email.RecoveryInvalidModel{}) case template.TypeRecoveryValid: return email.NewRecoveryValid(d, &email.RecoveryValidModel{}) case template.TypeRecoveryCodeValid: return email.NewRecoveryCodeValid(d, &email.RecoveryCodeValidModel{}) case template.TypeRecoveryCodeInvalid: return email.NewRecoveryCodeInvalid(d, &email.RecoveryCodeInvalidModel{}) case template.TypeTestStub: return email.NewTestStub(&email.TestStubModel{}) case template.TypeVerificationInvalid: return email.NewVerificationInvalid(d, &email.VerificationInvalidModel{}) case template.TypeVerificationValid: return email.NewVerificationValid(d, &email.VerificationValidModel{}) case template.TypeVerificationCodeInvalid: return email.NewVerificationCodeInvalid(d, &email.VerificationCodeInvalidModel{}) case template.TypeVerificationCodeValid: return email.NewVerificationCodeValid(d, &email.VerificationCodeValidModel{}) case template.TypeLoginCodeValid: return email.NewLoginCodeValid(d, &email.LoginCodeValidModel{}) case template.TypeRegistrationCodeValid: return email.NewRegistrationCodeValid(d, &email.RegistrationCodeValidModel{}) default: return nil } } t.Run("case=http resource", func(t *testing.T) { t.Parallel() router := http.NewServeMux() router.HandleFunc("GET /{filename}", func(writer http.ResponseWriter, request *http.Request) { http.ServeFile(writer, request, path.Join(basePath, request.PathValue("filename"))) }) ts := httptest.NewServer(router) defer ts.Close() tpl := getTemplate(tmplType, SetupRemoteConfig(t, ctx, ts.URL+"/email.body.plaintext.gotmpl", ts.URL+"/email.body.gotmpl", ts.URL+"/email.subject.gotmpl")) require.NotNil(t, tpl, "Expected to find template for %s in %s", tmplType, basePath) TestRendered(t, ctx, tpl) }) t.Run("case=base64 resource", func(t *testing.T) { t.Parallel() tpl := getTemplate(tmplType, SetupRemoteConfig(t, ctx, "base64://"+toBase64(path.Join(basePath, "email.body.plaintext.gotmpl")), "base64://"+toBase64(path.Join(basePath, "email.body.gotmpl")), "base64://"+toBase64(path.Join(basePath, "email.subject.gotmpl")))) require.NotNil(t, tpl, "Expected to find template for %s in %s", tmplType, basePath) TestRendered(t, ctx, tpl) }) t.Run("case=file resource", func(t *testing.T) { t.Parallel() tpl := getTemplate(tmplType, SetupRemoteConfig(t, ctx, "file://"+path.Join(basePath, "email.body.plaintext.gotmpl"), "file://"+path.Join(basePath, "email.body.gotmpl"), "file://"+path.Join(basePath, "email.subject.gotmpl"))) require.NotNil(t, tpl, "Expected to find template for %s in %s", tmplType, basePath) TestRendered(t, ctx, tpl) }) t.Run("case=partial subject override", func(t *testing.T) { t.Parallel() tpl := getTemplate(tmplType, SetupRemoteConfig(t, ctx, "", "", "base64://"+toBase64(path.Join(basePath, "email.subject.gotmpl")))) require.NotNil(t, tpl, "Expected to find template for %s in %s", tmplType, basePath) TestRendered(t, ctx, tpl) }) t.Run("case=partial body override", func(t *testing.T) { t.Parallel() tpl := getTemplate(tmplType, SetupRemoteConfig(t, ctx, "base64://"+toBase64(path.Join(basePath, "email.body.plaintext.gotmpl")), "base64://"+toBase64(path.Join(basePath, "email.body.gotmpl")), "")) require.NotNil(t, tpl, "Expected to find template for %s in %s", tmplType, basePath) TestRendered(t, ctx, tpl) }) } ================================================ FILE: courier/template/type.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package template // A Template's type // // swagger:enum TemplateType type TemplateType string const ( TypeRecoveryInvalid TemplateType = "recovery_invalid" TypeRecoveryValid TemplateType = "recovery_valid" TypeRecoveryCodeInvalid TemplateType = "recovery_code_invalid" TypeRecoveryCodeValid TemplateType = "recovery_code_valid" TypeVerificationInvalid TemplateType = "verification_invalid" TypeVerificationValid TemplateType = "verification_valid" TypeVerificationCodeInvalid TemplateType = "verification_code_invalid" TypeVerificationCodeValid TemplateType = "verification_code_valid" TypeTestStub TemplateType = "stub" TypeLoginCodeValid TemplateType = "login_code_valid" TypeRegistrationCodeValid TemplateType = "registration_code_valid" ) ================================================ FILE: courier/test/persistence.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "errors" "fmt" "slices" "testing" "time" "github.com/go-faker/faker/v4" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/kratos/courier" "github.com/ory/kratos/x" "github.com/ory/pop/v6" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" "github.com/ory/x/sqlcon" ) type PersisterWrapper interface { GetConnection(ctx context.Context) *pop.Connection NetworkID(ctx context.Context) uuid.UUID courier.Persister } type NetworkWrapper func(t *testing.T, ctx context.Context) (uuid.UUID, PersisterWrapper) func TestPersister(ctx context.Context, newNetworkUnlessExisting NetworkWrapper, newNetwork NetworkWrapper) func(t *testing.T) { return func(t *testing.T) { nid, p := newNetworkUnlessExisting(t, ctx) t.Run("case=no messages in queue", func(t *testing.T) { m, err := p.NextMessages(ctx, 10) require.ErrorIs(t, err, courier.ErrQueueEmpty) assert.Len(t, m, 0) _, err = p.LatestQueuedMessage(ctx) require.ErrorIs(t, err, courier.ErrQueueEmpty) }) messages := make([]courier.Message, 5) t.Run("case=add messages to the queue", func(t *testing.T) { now := time.Now() for k := range messages { require.NoError(t, faker.FakeData(&messages[k])) require.NoError(t, p.AddMessage(ctx, &messages[k])) require.NoError(t, p.GetConnection(ctx). RawQuery( "UPDATE courier_messages SET created_at = ?, updated_at = ? WHERE id = ? AND nid = ?", now.Add(time.Duration(k)*time.Hour).Round(time.Second), now.Add(time.Duration(k)*time.Hour).Round(time.Second), messages[k].ID, nid). Exec()) } }) t.Run("case=latest message in queue", func(t *testing.T) { expected, err := p.LatestQueuedMessage(ctx) require.NoError(t, err) actual := messages[len(messages)-1] assert.Equal(t, expected.ID, actual.ID) assert.Equal(t, expected.Subject, actual.Subject) }) t.Run("case=pull messages from the queue", func(t *testing.T) { for k, expected := range messages { expected.Status = courier.MessageStatusProcessing t.Run(fmt.Sprintf("message=%d", k), func(t *testing.T) { messages, err := p.NextMessages(ctx, 1) require.NoError(t, err) require.Len(t, messages, 1) actual := messages[0] assert.Equal(t, expected.ID, actual.ID) assert.Equal(t, expected.Subject, actual.Subject) assert.Equal(t, expected.Body, actual.Body) assert.Equal(t, expected.Status, actual.Status) assert.Equal(t, expected.Type, actual.Type) assert.Equal(t, expected.Recipient, actual.Recipient) }) } _, err := p.NextMessages(ctx, 10) require.ErrorIs(t, err, courier.ErrQueueEmpty) }) t.Run("case=setting message status", func(t *testing.T) { require.NoError(t, p.SetMessageStatus(ctx, messages[0].ID, courier.MessageStatusQueued)) ms, err := p.NextMessages(ctx, 1) require.NoError(t, err) require.Len(t, ms, 1) assert.Equal(t, messages[0].ID, ms[0].ID) require.NoError(t, p.SetMessageStatus(ctx, messages[0].ID, courier.MessageStatusSent)) _, err = p.NextMessages(ctx, 1) require.ErrorIs(t, err, courier.ErrQueueEmpty) require.NoError(t, p.SetMessageStatus(ctx, messages[0].ID, courier.MessageStatusAbandoned)) _, err = p.NextMessages(ctx, 1) require.ErrorIs(t, err, courier.ErrQueueEmpty) }) t.Run("case=incrementing send count", func(t *testing.T) { originalSendCount := messages[0].SendCount require.NoError(t, p.SetMessageStatus(ctx, messages[0].ID, courier.MessageStatusQueued)) require.NoError(t, p.IncrementMessageSendCount(ctx, messages[0].ID)) ms, err := p.NextMessages(ctx, 1) require.NoError(t, err) require.Len(t, ms, 1) assert.Equal(t, messages[0].ID, ms[0].ID) assert.Equal(t, originalSendCount+1, ms[0].SendCount) }) t.Run("case=list messages", func(t *testing.T) { // List by status. { status := courier.MessageStatusProcessing filter := courier.ListCourierMessagesParameters{ Status: &status, } ms, _, err := p.ListMessages(ctx, filter, []keysetpagination.Option{}) require.NoError(t, err) require.Len(t, ms, len(messages), messages) // Check that the 'filter by status' works. for _, m := range ms { require.Equal(t, status, m.Status) } // Check that the 'order by created_at desc' works. require.True(t, slices.IsSortedFunc(ms, func(a, b courier.Message) int { return b.CreatedAt.Compare(a.CreatedAt) })) } // Query fewer items than the total, multiple times. { filter := courier.ListCourierMessagesParameters{} maxSize := 2 ms1, pagination, err := p.ListMessages(ctx, filter, []keysetpagination.Option{ keysetpagination.WithSize(2), }) require.NoError(t, err) require.NotNil(t, pagination) require.False(t, pagination.IsLast()) require.Len(t, ms1, maxSize) // Check that the 'order by created_at desc' works. require.True(t, slices.IsSortedFunc(ms1, func(a, b courier.Message) int { return b.CreatedAt.Compare(a.CreatedAt) })) // Second call. // Marshal -> unmarshal the pagination token to be more realistic. encrypted := pagination.PageToken().Encrypt(nil) unmarshalled, err := keysetpagination.ParsePageToken(nil, encrypted) require.NoError(t, err) ms2, pagination, err := p.ListMessages(ctx, filter, []keysetpagination.Option{ keysetpagination.WithSize(2), keysetpagination.WithToken(unmarshalled), }) require.NoError(t, err) require.NotNil(t, pagination) require.False(t, pagination.IsLast()) require.Len(t, ms2, maxSize) // Check that the 'order by created_at desc' works. require.True(t, slices.IsSortedFunc(ms2, func(a, b courier.Message) int { return b.CreatedAt.Compare(a.CreatedAt) })) // Check that the second call returned different elements. require.NotEqual(t, ms1[0].ID, ms2[0].ID) require.NotEqual(t, ms1[1].ID, ms2[1].ID) allElements := append(ms1, ms2...) require.True(t, slices.IsSortedFunc(allElements, func(a, b courier.Message) int { return b.CreatedAt.Compare(a.CreatedAt) })) // Last call ms3, pagination, err := p.ListMessages(ctx, filter, pagination.ToOptions()) require.NoError(t, err) require.NotNil(t, pagination) require.True(t, pagination.IsLast()) require.Len(t, ms3, 1) // Check that the 'order by created_at desc' works. require.True(t, slices.IsSortedFunc(ms3, func(a, b courier.Message) int { return b.CreatedAt.Compare(a.CreatedAt) })) // Check that the third call returned different elements. require.NotEqual(t, ms2[0].ID, ms3[0].ID) allElements = append(ms1, ms2...) allElements = append(allElements, ms3...) require.True(t, slices.IsSortedFunc(allElements, func(a, b courier.Message) int { return b.CreatedAt.Compare(a.CreatedAt) })) } t.Run("on another network", func(t *testing.T) { nid1, p1 := newNetwork(t, ctx) status := courier.MessageStatusProcessing filter := courier.ListCourierMessagesParameters{ Status: &status, } ms, _, err := p1.ListMessages(ctx, filter, []keysetpagination.Option{}) require.NoError(t, err) require.Len(t, ms, 0) // Due to a bug in the pagination query definition, it was possible to retrieve messages from another `network` // using the pagination query. That required that 2 message's `created_at` timestamps were equal, to trigger // the `OR` clause of the paginated query. // This part of the tests "simulates" this behavior, by forcing the same timestamps on multiple messages across // different networks. nid2, p2 := newNetwork(t, ctx) msg1 := courier.Message{ ID: uuid.FromStringOrNil("10000000-0000-0000-0000-000000000000"), NID: nid1, Status: courier.MessageStatusProcessing, } err = p1.GetConnection(ctx).Create(&msg1) require.NoError(t, err) msg2 := courier.Message{ ID: uuid.FromStringOrNil("20000000-0000-0000-0000-000000000000"), NID: nid1, Status: courier.MessageStatusProcessing, } err = p1.GetConnection(ctx).Create(&msg2) require.NoError(t, err) msg3 := courier.Message{ ID: uuid.FromStringOrNil("30000000-0000-0000-0000-000000000000"), NID: nid2, Status: courier.MessageStatusProcessing, } err = p2.GetConnection(ctx).Create(&msg3) require.NoError(t, err) now := time.Now().UTC().Truncate(time.Second) // Set all `created_at` timestamps to the same value to force the `OR` clause of the paginated query. // `created_at` is set by "pop" and does not allow a manual override, apart from using `pop.SetNowFunc`, but that also influences the other tests in this // suite, as it just overrides a global function. require.NoError(t, p1.GetConnection(ctx).RawQuery("UPDATE courier_messages SET created_at = ? WHERE id = ? AND nid = ?", now, msg1.ID, nid1).Exec()) // get the "updated" message from the require.NoError(t, p1.GetConnection(ctx).Where("id = ? AND nid = ?", msg1.ID, msg1.NID).First(&msg1)) require.NoError(t, p1.GetConnection(ctx).RawQuery("UPDATE courier_messages SET created_at = ? WHERE id = ? AND nid = ?", now, msg2.ID, nid1).Exec()) require.NoError(t, p2.GetConnection(ctx).RawQuery("UPDATE courier_messages SET created_at = ? WHERE id = ? AND nid = ?", now, msg3.ID, nid2).Exec()) // Use the updated first message's PageToken as the basis for the paginated request. ms, _, err = p1.ListMessages(ctx, filter, []keysetpagination.Option{keysetpagination.WithToken(msg1.PageToken())}) require.NoError(t, err) // The response should just contain messages from network1, and not from network2. require.Len(t, ms, 1) require.Equal(t, ms[0].NID, nid1) require.Equal(t, ms[0].ID, msg2.ID) }) }) t.Run("case=network", func(t *testing.T) { t.Run("generates id on creation", func(t *testing.T) { expected := courier.Message{ID: uuid.Nil} require.NoError(t, p.AddMessage(ctx, &expected)) assert.NotEqual(t, uuid.Nil, expected.ID) assert.EqualValues(t, nid, expected.NID) assert.EqualValues(t, nid, p.NetworkID(ctx)) actual, err := p.LatestQueuedMessage(ctx) require.NoError(t, err) assert.EqualValues(t, expected.ID, actual.ID) assert.EqualValues(t, nid, actual.NID) actuals, err := p.NextMessages(ctx, 255) require.NoError(t, err) actual = &actuals[0] assert.EqualValues(t, expected.ID, actual.ID) assert.EqualValues(t, nid, actual.NID) }) id := x.NewUUID() t.Run("persists id on creation", func(t *testing.T) { expected := courier.Message{ID: id} require.NoError(t, p.AddMessage(ctx, &expected)) assert.EqualValues(t, id, expected.ID) assert.EqualValues(t, nid, expected.NID) assert.EqualValues(t, nid, p.NetworkID(ctx)) actual, err := p.LatestQueuedMessage(ctx) require.NoError(t, err) assert.EqualValues(t, id, actual.ID) assert.EqualValues(t, nid, actual.NID) actuals, err := p.NextMessages(ctx, 255) require.NoError(t, err) actual = &actuals[0] assert.EqualValues(t, id, actual.ID) assert.EqualValues(t, nid, actual.NID) }) t.Run("can not get on another network", func(t *testing.T) { _, p := newNetwork(t, ctx) _, err := p.LatestQueuedMessage(ctx) require.ErrorIs(t, err, courier.ErrQueueEmpty) _, err = p.NextMessages(ctx, 255) require.ErrorIs(t, err, courier.ErrQueueEmpty) }) t.Run("can not update on another network", func(t *testing.T) { _, p := newNetwork(t, ctx) err := p.SetMessageStatus(ctx, id, courier.MessageStatusProcessing) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=FetchMessage", func(t *testing.T) { msgID := messages[0].ID message, err := p.FetchMessage(ctx, msgID) require.NoError(t, err) require.Equal(t, msgID, message.ID) t.Run("can not get on another network", func(t *testing.T) { _, p := newNetwork(t, ctx) _, err := p.FetchMessage(ctx, msgID) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=RecordDispatch", func(t *testing.T) { msgID := messages[0].ID err := p.RecordDispatch(ctx, msgID, courier.CourierMessageDispatchStatusFailed, errors.New("testerror")) require.NoError(t, err) message, err := p.FetchMessage(ctx, msgID) require.NoError(t, err) require.Len(t, message.Dispatches, 1) assert.Equal(t, "testerror", gjson.GetBytes(message.Dispatches[0].Error, "message").String()) t.Run("can not get on another network", func(t *testing.T) { _, p := newNetwork(t, ctx) _, err := p.FetchMessage(ctx, msgID) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) } } ================================================ FILE: docs/README.md ================================================ # Documentation Please find the documentation at [www.ory.com/docs/kratos](https://www.ory.com/docs/kratos). To contribute to the documentation, please head over to: [github.com/ory/docs/tree/master/docs/kratos](https://github.com/ory/docs/tree/master/docs/kratos) ================================================ FILE: docs/sidebar.json ================================================ [] ================================================ FILE: driver/config/.snapshots/TestCourierEmailHTTP-case=configs_set.json ================================================ { "auth": { "config": { "password": "YourPass", "user": "YourUsername" }, "type": "basic_auth" }, "body": "file://some.jsonnet", "header": { "Content-Type": "application/json" }, "method": "POST", "url": "https://example.com/email" } ================================================ FILE: driver/config/.snapshots/TestCourierSMS-case=configs_set.json ================================================ { "auth": { "config": { "password": "YourPass", "user": "YourUsername" }, "type": "basic_auth" }, "body": "base64://e30=", "header": { "Content-Type": "application/x-www-form-urlencoded" }, "method": "POST", "url": "https://api.twilio.com/2010-04-01/Accounts/YourAccountID/Messages.json" } ================================================ FILE: driver/config/.snapshots/TestCourierSMS-case=defaults.json ================================================ null ================================================ FILE: driver/config/.snapshots/TestDefaultWebhookHeaderAllowlist.json ================================================ [ "Accept", "Accept-Encoding", "Accept-Language", "Content-Length", "Content-Type", "Origin", "Priority", "Referer", "Sec-Ch-Ua", "Sec-Ch-Ua-Mobile", "Sec-Ch-Ua-Platform", "Sec-Fetch-Dest", "Sec-Fetch-Mode", "Sec-Fetch-Site", "Sec-Fetch-User", "True-Client-Ip", "User-Agent" ] ================================================ FILE: driver/config/buildinfo.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package config var ( Version = "master" Date = "undefined" Commit = "undefined" ) ================================================ FILE: driver/config/config.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package config import ( "bytes" "cmp" "context" "crypto/sha512" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "runtime" "strings" "testing" "time" "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "github.com/gofrs/uuid" "github.com/inhies/go-bytesize" "github.com/pkg/errors" "github.com/rs/cors" "github.com/stretchr/testify/require" "golang.org/x/net/publicsuffix" "github.com/ory/kratos/x" "github.com/ory/herodot" "github.com/ory/jsonschema/v3" "github.com/ory/jsonschema/v3/httploader" "github.com/ory/kratos/embedx" "github.com/ory/kratos/request" "github.com/ory/x/configx" "github.com/ory/x/contextx" "github.com/ory/x/crdbx" "github.com/ory/x/httpx" "github.com/ory/x/jsonschemax" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/watcherx" ) const ( DefaultIdentityTraitsSchemaID = "default" DefaultBrowserReturnURL = "default_browser_return_url" DefaultSQLiteMemoryDSN = "sqlite://file::memory:?_fk=true&cache=shared" DefaultPasswordHashingAlgorithm = "argon2" DefaultCipherAlgorithm = "noop" UnknownVersion = "unknown version" ViperKeyDSN = "dsn" ViperKeyCourierSMTPURL = "courier.smtp.connection_uri" ViperKeyCourierSMTPClientCertPath = "courier.smtp.client_cert_path" ViperKeyCourierSMTPClientKeyPath = "courier.smtp.client_key_path" ViperKeyCourierTemplatesPath = "courier.template_override_path" ViperKeyCourierTemplatesRecoveryInvalidEmail = "courier.templates.recovery.invalid.email" ViperKeyCourierTemplatesRecoveryValidEmail = "courier.templates.recovery.valid.email" ViperKeyCourierTemplatesRecoveryCodeInvalidEmail = "courier.templates.recovery_code.invalid.email" ViperKeyCourierTemplatesRecoveryCodeValidEmail = "courier.templates.recovery_code.valid.email" ViperKeyCourierTemplatesVerificationInvalidEmail = "courier.templates.verification.invalid.email" ViperKeyCourierTemplatesVerificationValidEmail = "courier.templates.verification.valid.email" ViperKeyCourierTemplatesVerificationCodeInvalidEmail = "courier.templates.verification_code.invalid.email" ViperKeyCourierTemplatesVerificationCodeValidEmail = "courier.templates.verification_code.valid.email" ViperKeyCourierTemplatesVerificationCodeValidSMS = "courier.templates.verification_code.valid.sms" ViperKeyCourierTemplatesRecoveryCodeValidSMS = "courier.templates.recovery_code.valid.sms" ViperKeyCourierTemplatesLoginCodeValidSMS = "courier.templates.login_code.valid.sms" ViperKeyCourierTemplatesRegistrationCodeValidSMS = "courier.templates.registration_code.valid.sms" ViperKeyCourierDeliveryStrategy = "courier.delivery_strategy" ViperKeyCourierHTTPRequestConfig = "courier.http.request_config" ViperKeyCourierTemplatesLoginCodeValidEmail = "courier.templates.login_code.valid.email" ViperKeyCourierTemplatesRegistrationCodeValidEmail = "courier.templates.registration_code.valid.email" ViperKeyCourierSMTP = "courier.smtp" ViperKeyCourierSMTPFrom = "courier.smtp.from_address" ViperKeyCourierSMTPFromName = "courier.smtp.from_name" ViperKeyCourierSMTPHeaders = "courier.smtp.headers" ViperKeyCourierSMTPLocalName = "courier.smtp.local_name" ViperKeyCourierMessageRetries = "courier.message_retries" ViperKeyCourierWorkerPullCount = "courier.worker.pull_count" ViperKeyCourierWorkerPullWait = "courier.worker.pull_wait" ViperKeyCourierChannels = "courier.channels" ViperKeySecretsDefault = "secrets.default" ViperKeySecretsCookie = "secrets.cookie" ViperKeySecretsCipher = "secrets.cipher" ViperKeySecretsPagination = "secrets.pagination" ViperKeyPublicBaseURL = "serve.public.base_url" ViperKeyAdminBaseURL = "serve.admin.base_url" ViperKeySessionLifespan = "session.lifespan" ViperKeySessionSameSite = "session.cookie.same_site" ViperKeySessionSecure = "session.cookie.secure" ViperKeySessionDomain = "session.cookie.domain" ViperKeySessionName = "session.cookie.name" ViperKeySessionPath = "session.cookie.path" ViperKeySessionPersistentCookie = "session.cookie.persistent" ViperKeySessionTokenizerTemplates = "session.whoami.tokenizer.templates" ViperKeySessionWhoAmIAAL = "session.whoami.required_aal" ViperKeySessionWhoAmICaching = "feature_flags.cacheable_sessions" ViperKeyFeatureFlagFasterSessionExtend = "feature_flags.faster_session_extend" ViperKeySessionWhoAmICachingMaxAge = "feature_flags.cacheable_sessions_max_age" ViperKeyUseContinueWithTransitions = "feature_flags.use_continue_with_transitions" ViperKeyChooseRecoveryAddress = "feature_flags.choose_recovery_address" ViperKeyUseLegacyShowVerificationUI = "feature_flags.legacy_continue_with_verification_ui" ViperKeyLegacyOIDCRegistrationGroup = "feature_flags.legacy_oidc_registration_node_group" ViperKeyUseLegacyRequireVerifiedLoginError = "feature_flags.legacy_require_verified_login_error" ViperKeySessionRefreshMinTimeLeft = "session.earliest_possible_extend" ViperKeyCookieSameSite = "cookies.same_site" ViperKeyCookieDomain = "cookies.domain" ViperKeyCookiePath = "cookies.path" ViperKeyCookieSecure = "cookies.secure" ViperKeySelfServiceStrategyConfig = "selfservice.methods" ViperKeySelfServiceBrowserDefaultReturnTo = "selfservice." + DefaultBrowserReturnURL ViperKeyURLsAllowedReturnToDomains = "selfservice.allowed_return_urls" ViperKeySelfServiceRegistrationEnabled = "selfservice.flows.registration.enabled" ViperKeySelfServiceRegistrationLoginHints = "selfservice.flows.registration.login_hints" ViperKeySelfServiceRegistrationEnableLegacyOneStep = "selfservice.flows.registration.enable_legacy_one_step" ViperKeySelfServiceRegistrationFlowStyle = "selfservice.flows.registration.style" ViperKeySelfServiceRegistrationUI = "selfservice.flows.registration.ui_url" ViperKeySelfServiceRegistrationRequestLifespan = "selfservice.flows.registration.lifespan" ViperKeySelfServiceRegistrationAfter = "selfservice.flows.registration.after" ViperKeySelfServiceRegistrationBeforeHooks = "selfservice.flows.registration.before.hooks" ViperKeySelfServiceLoginUI = "selfservice.flows.login.ui_url" ViperKeySelfServiceLoginFlowStyle = "selfservice.flows.login.style" ViperKeySecurityAccountEnumerationMitigate = "security.account_enumeration.mitigate" ViperKeySelfServiceLoginRequestLifespan = "selfservice.flows.login.lifespan" ViperKeySelfServiceLoginAfter = "selfservice.flows.login.after" ViperKeySelfServiceLoginBeforeHooks = "selfservice.flows.login.before.hooks" ViperKeySelfServiceErrorUI = "selfservice.flows.error.ui_url" ViperKeySelfServiceLogoutBrowserDefaultReturnTo = "selfservice.flows.logout.after." + DefaultBrowserReturnURL ViperKeySelfServiceSettingsURL = "selfservice.flows.settings.ui_url" ViperKeySelfServiceSettingsAfter = "selfservice.flows.settings.after" ViperKeySelfServiceSettingsBeforeHooks = "selfservice.flows.settings.before.hooks" ViperKeySelfServiceSettingsRequestLifespan = "selfservice.flows.settings.lifespan" ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter = "selfservice.flows.settings.privileged_session_max_age" ViperKeySelfServiceSettingsRequiredAAL = "selfservice.flows.settings.required_aal" ViperKeySelfServiceRecoveryAfter = "selfservice.flows.recovery.after" ViperKeySelfServiceRecoveryBeforeHooks = "selfservice.flows.recovery.before.hooks" ViperKeySelfServiceRecoveryEnabled = "selfservice.flows.recovery.enabled" ViperKeySelfServiceRecoveryUse = "selfservice.flows.recovery.use" ViperKeySelfServiceRecoveryUI = "selfservice.flows.recovery.ui_url" ViperKeySelfServiceRecoveryRequestLifespan = "selfservice.flows.recovery.lifespan" ViperKeySelfServiceRecoveryBrowserDefaultReturnTo = "selfservice.flows.recovery.after." + DefaultBrowserReturnURL ViperKeySelfServiceRecoveryNotifyUnknownRecipients = "selfservice.flows.recovery.notify_unknown_recipients" ViperKeySelfServiceVerificationEnabled = "selfservice.flows.verification.enabled" ViperKeySelfServiceVerificationUI = "selfservice.flows.verification.ui_url" ViperKeySelfServiceVerificationRequestLifespan = "selfservice.flows.verification.lifespan" ViperKeySelfServiceVerificationBrowserDefaultReturnTo = "selfservice.flows.verification.after." + DefaultBrowserReturnURL ViperKeySelfServiceVerificationAfter = "selfservice.flows.verification.after" ViperKeySelfServiceVerificationBeforeHooks = "selfservice.flows.verification.before.hooks" ViperKeySelfServiceVerificationUse = "selfservice.flows.verification.use" ViperKeySelfServiceVerificationNotifyUnknownRecipients = "selfservice.flows.verification.notify_unknown_recipients" ViperKeyDefaultIdentitySchemaID = "identity.default_schema_id" ViperKeyIdentitySchemas = "identity.schemas" ViperKeyHasherAlgorithm = "hashers.algorithm" ViperKeyHasherArgon2ConfigMemory = "hashers.argon2.memory" ViperKeyHasherArgon2ConfigIterations = "hashers.argon2.iterations" ViperKeyHasherArgon2ConfigParallelism = "hashers.argon2.parallelism" ViperKeyHasherArgon2ConfigSaltLength = "hashers.argon2.salt_length" ViperKeyHasherArgon2ConfigKeyLength = "hashers.argon2.key_length" ViperKeyHasherArgon2ConfigExpectedDuration = "hashers.argon2.expected_duration" ViperKeyHasherArgon2ConfigExpectedDeviation = "hashers.argon2.expected_deviation" ViperKeyHasherArgon2ConfigDedicatedMemory = "hashers.argon2.dedicated_memory" ViperKeyHasherBcryptCost = "hashers.bcrypt.cost" ViperKeyCipherAlgorithm = "ciphers.algorithm" ViperKeyDatabaseCleanupSleepTables = "database.cleanup.sleep.tables" ViperKeyDatabaseCleanupBatchSize = "database.cleanup.batch_size" ViperKeyLinkLifespan = "selfservice.methods.link.config.lifespan" ViperKeyCodeLifespan = "selfservice.methods.code.config.lifespan" ViperKeyCodeMaxSubmissions = "selfservice.methods.code.config.max_submissions" ViperKeyCodeConfigMissingCredentialFallbackEnabled = "selfservice.methods.code.config.missing_credential_fallback_enabled" ViperKeyPasswordHaveIBeenPwnedHost = "selfservice.methods.password.config.haveibeenpwned_host" ViperKeyPasswordHaveIBeenPwnedEnabled = "selfservice.methods.password.config.haveibeenpwned_enabled" ViperKeyPasswordMaxBreaches = "selfservice.methods.password.config.max_breaches" ViperKeyPasswordMinLength = "selfservice.methods.password.config.min_password_length" ViperKeyPasswordIdentifierSimilarityCheckEnabled = "selfservice.methods.password.config.identifier_similarity_check_enabled" ViperKeyIgnoreNetworkErrors = "selfservice.methods.password.config.ignore_network_errors" ViperKeyPasswordRegistrationProfileGroup = "selfservice.methods.password.config.password_profile_registration_node_group" ViperKeyTOTPIssuer = "selfservice.methods.totp.config.issuer" ViperKeyOIDCBaseRedirectURL = "selfservice.methods.oidc.config.base_redirect_uri" ViperKeySAMLBaseRedirectURL = "selfservice.methods.saml.config.base_redirect_uri" ViperKeyWebAuthnRPDisplayName = "selfservice.methods.webauthn.config.rp.display_name" ViperKeyWebAuthnRPID = "selfservice.methods.webauthn.config.rp.id" ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin" ViperKeyWebAuthnRPOrigins = "selfservice.methods.webauthn.config.rp.origins" ViperKeyWebAuthnPasswordless = "selfservice.methods.webauthn.config.passwordless" ViperKeyPasskeyEnabled = "selfservice.methods.passkey.enabled" ViperKeyPasskeyRPDisplayName = "selfservice.methods.passkey.config.rp.display_name" ViperKeyPasskeyRPID = "selfservice.methods.passkey.config.rp.id" ViperKeyPasskeyRPOrigins = "selfservice.methods.passkey.config.rp.origins" ViperKeyOAuth2ProviderURL = "oauth2_provider.url" ViperKeyOAuth2ProviderHeader = "oauth2_provider.headers" ViperKeyOAuth2ProviderOverrideReturnTo = "oauth2_provider.override_return_to" ViperKeyClientHTTPNoPrivateIPRanges = "clients.http.disallow_private_ip_ranges" ViperKeyClientHTTPPrivateIPExceptionURLs = "clients.http.private_ip_exception_urls" ViperKeyWebhookHeaderAllowlist = "clients.web_hook.header_allowlist" ViperKeyPreviewDefaultReadConsistencyLevel = "preview.default_read_consistency_level" ViperKeyVersion = "version" ViperKeyPasswordMigrationHook = "selfservice.methods.password.config.migrate_hook" ) const ( HighestAvailableAAL = "highest_available" Argon2DefaultMemory = 128 * bytesize.MB Argon2DefaultIterations uint32 = 1 Argon2DefaultSaltLength uint32 = 16 Argon2DefaultKeyLength uint32 = 32 Argon2DefaultDuration = 500 * time.Millisecond Argon2DefaultDeviation = 500 * time.Millisecond Argon2DefaultDedicatedMemory = 1 * bytesize.GB BcryptDefaultCost uint32 = 12 ) // DefaultSessionCookieName returns the default cookie name for the kratos session. const DefaultSessionCookieName = "ory_kratos_session" type ( Argon2 struct { Memory bytesize.ByteSize `json:"memory"` Iterations uint32 `json:"iterations"` Parallelism uint8 `json:"parallelism"` SaltLength uint32 `json:"salt_length"` KeyLength uint32 `json:"key_length"` ExpectedDuration time.Duration `json:"expected_duration"` ExpectedDeviation time.Duration `json:"expected_deviation"` DedicatedMemory bytesize.ByteSize `json:"dedicated_memory"` } Bcrypt struct { Cost uint32 `json:"cost"` } SelfServiceHook struct { Name string `json:"hook"` Config json.RawMessage `json:"config"` } SelfServiceStrategy struct { Enabled bool `json:"enabled"` Config json.RawMessage `json:"config"` } SelfServiceStrategyCode struct { *SelfServiceStrategy PasswordlessEnabled bool `json:"passwordless_enabled"` MFAEnabled bool `json:"mfa_enabled"` } Schema struct { ID string `json:"id" koanf:"id"` URL string `json:"url" koanf:"url"` SelfserviceSelectable bool `json:"selfservice_selectable" koanf:"selfservice_selectable"` } PasswordPolicy struct { HaveIBeenPwnedHost string `json:"haveibeenpwned_host"` HaveIBeenPwnedEnabled bool `json:"haveibeenpwned_enabled"` MaxBreaches uint `json:"max_breaches"` IgnoreNetworkErrors bool `json:"ignore_network_errors"` MinPasswordLength uint `json:"min_password_length"` IdentifierSimilarityCheckEnabled bool `json:"identifier_similarity_check_enabled"` } Schemas []Schema CourierEmailBodyTemplate struct { PlainText string `json:"plaintext"` HTML string `json:"html"` } CourierEmailTemplate struct { Body *CourierEmailBodyTemplate `json:"body"` Subject string `json:"subject"` } CourierSMSTemplate struct { Body *CourierSMSTemplateBody `json:"body"` } CourierSMSTemplateBody struct { PlainText string `json:"plaintext"` } CourierChannel struct { ID string `json:"id" koanf:"id"` Type string `json:"type" koanf:"type"` SMTPConfig *SMTPConfig `json:"smtp_config" koanf:"smtp_config"` RequestConfig request.Config `json:"request_config" koanf:"request_config"` } SMTPConfig struct { ConnectionURI string `json:"connection_uri" koanf:"connection_uri"` ClientCertPath string `json:"client_cert_path" koanf:"client_cert_path"` ClientKeyPath string `json:"client_key_path" koanf:"client_key_path"` FromAddress string `json:"from_address" koanf:"from_address"` FromName string `json:"from_name" koanf:"from_name"` Headers map[string]string `json:"headers" koanf:"headers"` LocalName string `json:"local_name" koanf:"local_name"` } PasswordMigrationHook struct { Enabled bool `json:"enabled" koanf:"enabled"` Config request.Config `json:"config" koanf:"config"` } Config struct { l *logrusx.Logger p *configx.Provider c contextx.Contextualizer identityMetaSchema *jsonschema.Schema stdOutOrErr io.Writer } Provider interface { Config() *Config } CourierConfigs interface { CourierTemplatesRoot(ctx context.Context) string CourierTemplatesVerificationInvalid(ctx context.Context) *CourierEmailTemplate CourierTemplatesVerificationValid(ctx context.Context) *CourierEmailTemplate CourierTemplatesRecoveryInvalid(ctx context.Context) *CourierEmailTemplate CourierTemplatesRecoveryValid(ctx context.Context) *CourierEmailTemplate CourierTemplatesRecoveryCodeInvalid(ctx context.Context) *CourierEmailTemplate CourierTemplatesRecoveryCodeValid(ctx context.Context) *CourierEmailTemplate CourierTemplatesVerificationCodeInvalid(ctx context.Context) *CourierEmailTemplate CourierTemplatesVerificationCodeValid(ctx context.Context) *CourierEmailTemplate CourierTemplatesLoginCodeValid(ctx context.Context) *CourierEmailTemplate CourierTemplatesRegistrationCodeValid(ctx context.Context) *CourierEmailTemplate CourierSMSTemplatesVerificationCodeValid(ctx context.Context) *CourierSMSTemplate CourierSMSTemplatesRecoveryCodeValid(ctx context.Context) *CourierSMSTemplate CourierSMSTemplatesLoginCodeValid(ctx context.Context) *CourierSMSTemplate CourierSMSTemplatesRegistrationCodeValid(ctx context.Context) *CourierSMSTemplate CourierMessageRetries(ctx context.Context) int CourierWorkerPullCount(ctx context.Context) int CourierWorkerPullWait(ctx context.Context) time.Duration CourierChannels(context.Context) ([]*CourierChannel, error) } ) func (c *Argon2) MarshalJSON() ([]byte, error) { type encoded struct { Memory string `json:"memory"` Iterations uint32 `json:"iterations"` Parallelism uint8 `json:"parallelism"` SaltLength uint32 `json:"salt_length"` KeyLength uint32 `json:"key_length"` ExpectedDuration string `json:"minimal_duration"` ExpectedDeviation string `json:"expected_deviation"` DedicatedMemory string `json:"dedicated_memory"` } return json.Marshal(&encoded{ Memory: c.Memory.String(), Iterations: c.Iterations, Parallelism: c.Parallelism, SaltLength: c.SaltLength, KeyLength: c.KeyLength, ExpectedDuration: c.ExpectedDuration.String(), ExpectedDeviation: c.ExpectedDeviation.String(), DedicatedMemory: c.DedicatedMemory.String(), }) } var Argon2DefaultParallelism = uint8(runtime.NumCPU() * 2) const HookGlobal = "global" func HookStrategyKey(key, strategy string) string { if strategy == HookGlobal { return fmt.Sprintf("%s.hooks", key) } else { return fmt.Sprintf("%s.%s.hooks", key, strategy) } } func (s Schemas) FindSchemaByID(id string) (*Schema, error) { for _, sc := range s { if sc.ID == id { return &sc, nil } } return nil, errors.Errorf("unable to find identity schema with id: %s", id) } func MustNew(t testing.TB, l *logrusx.Logger, ctxer contextx.Contextualizer, opts ...configx.OptionModifier) *Config { p, err := New(t.Context(), l, os.Stderr, ctxer, opts...) require.NoError(t, err) return p } func New(ctx context.Context, l *logrusx.Logger, stdOutOrErr io.Writer, ctxer contextx.Contextualizer, opts ...configx.OptionModifier) (*Config, error) { var c *Config opts = append([]configx.OptionModifier{ configx.WithStderrValidationReporter(), configx.OmitKeysFromTracing("dsn", "courier.smtp.connection_uri", "secrets.default", "secrets.cookie", "secrets.cipher", "client_secret"), configx.WithImmutables("serve", "profiling", "log"), configx.WithExceptImmutables("serve.public.cors.allowed_origins"), configx.WithLogrusWatcher(l), configx.WithLogger(l), configx.WithContext(ctx), configx.AttachWatcher(func(event watcherx.Event, err error) { if c == nil { panic(errors.New("the config provider did not initialise correctly in time")) } if err := c.validateIdentitySchemas(ctx); err != nil { l.WithError(err). Errorf("The changed identity schema configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process.") } }), }, opts...) p, err := configx.New(ctx, embedx.ConfigSchema, opts...) if err != nil { return nil, err } l.UseConfig(p) c = NewCustom(l, p, stdOutOrErr, ctxer) if !p.SkipValidation() { if err := c.validateIdentitySchemas(ctx); err != nil { return nil, err } } return c, nil } func NewCustom(l *logrusx.Logger, p *configx.Provider, stdOutOrErr io.Writer, ctxt contextx.Contextualizer) *Config { l.UseConfig(p) return &Config{l: l, p: p, c: ctxt, stdOutOrErr: stdOutOrErr} } func (p *Config) getIdentitySchemaValidator(ctx context.Context) (*jsonschema.Schema, error) { if p.identityMetaSchema == nil { c := jsonschema.NewCompiler() err := embedx.AddSchemaResources(c, embedx.IdentityMeta) if err != nil { return nil, err } p.identityMetaSchema, err = c.Compile(ctx, embedx.IdentityMeta.GetSchemaID()) if err != nil { return nil, errors.WithStack(err) } } return p.identityMetaSchema, nil } type validateIdentitySchemasContextKey int const validateIdentitySchemasClientKey validateIdentitySchemasContextKey = 1 func SetValidateIdentitySchemaResilientClientOptions(ctx context.Context, options []httpx.ResilientOptions) context.Context { return context.WithValue(ctx, validateIdentitySchemasClientKey, options) } func (p *Config) validateIdentitySchemas(ctx context.Context) error { opts := []httpx.ResilientOptions{ httpx.ResilientClientWithLogger(p.l), httpx.ResilientClientWithMaxRetry(2), httpx.ResilientClientWithConnectionTimeout(30 * time.Second), } if o, ok := ctx.Value(validateIdentitySchemasClientKey).([]httpx.ResilientOptions); ok { opts = o } if p.ClientHTTPNoPrivateIPRanges(ctx) { opts = append(opts, httpx.ResilientClientDisallowInternalIPs()) } ctx = context.WithValue(ctx, httploader.ContextKey, httpx.NewResilientClient(opts...)) j, err := p.getIdentitySchemaValidator(ctx) if err != nil { return err } ss, err := p.IdentityTraitsSchemas(ctx) if err != nil { return err } for _, s := range ss { resource, err := jsonschema.LoadURL(ctx, s.URL) if err != nil { return errors.WithStack(err) } defer func() { _ = resource.Close() }() schema, err := io.ReadAll(io.LimitReader(resource, 1024*1024)) if err != nil { return errors.WithStack(err) } if err = j.Validate(bytes.NewBuffer(schema)); err != nil { p.formatJsonErrors(schema, err) return errors.WithStack(err) } } return nil } func (p *Config) formatJsonErrors(schema []byte, err error) { _, _ = fmt.Fprintln(p.stdOutOrErr, "") jsonschemax.FormatValidationErrorForCLI(p.stdOutOrErr, schema, err) } func (p *Config) ServePublic(ctx context.Context) *configx.Serve { return p.GetProvider(ctx).Serve("serve.public", p.IsInsecureDevMode(ctx), configx.Serve{ Port: 4433, }) } func (p *Config) ServeAdmin(ctx context.Context) *configx.Serve { return p.GetProvider(ctx).Serve("serve.admin", p.IsInsecureDevMode(ctx), configx.Serve{ Port: 4434, }) } func (p *Config) CORSPublic(ctx context.Context) (cors.Options, bool) { return p.GetProvider(ctx).CORS("serve.public", cors.Options{ AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, AllowedHeaders: []string{"Authorization", "Content-Type", "Cookie"}, ExposedHeaders: []string{"Content-Type", "Set-Cookie"}, AllowCredentials: true, }) } // Deprecated: use context-based [contextx.WithConfigValue] instead. func (p *Config) Set(_ context.Context, key string, value interface{}) error { return p.p.Set(key, value) } // Deprecated: use context-based [contextx.WithConfigValue] instead. func (p *Config) MustSet(_ context.Context, key string, value interface{}) { if err := p.p.Set(key, value); err != nil { p.l.WithError(err).Fatalf("Unable to set %q to %q.", key, value) } } func (p *Config) SessionName(ctx context.Context) string { return cmp.Or(p.GetProvider(ctx).String(ViperKeySessionName), DefaultSessionCookieName) } func (p *Config) HasherArgon2(ctx context.Context) *Argon2 { // warn about usage of default values and point to the docs // warning will require https://github.com/ory/viper/issues/19 return &Argon2{ Memory: p.GetProvider(ctx).ByteSizeF(ViperKeyHasherArgon2ConfigMemory, Argon2DefaultMemory), //nolint:gosec // disable G115 Iterations: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigIterations, int(Argon2DefaultIterations))), //nolint:gosec // disable G115 Parallelism: uint8(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigParallelism, int(Argon2DefaultParallelism))), //nolint:gosec // disable G115 SaltLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigSaltLength, int(Argon2DefaultSaltLength))), //nolint:gosec // disable G115 KeyLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigKeyLength, int(Argon2DefaultKeyLength))), ExpectedDuration: p.GetProvider(ctx).DurationF(ViperKeyHasherArgon2ConfigExpectedDuration, Argon2DefaultDuration), ExpectedDeviation: p.GetProvider(ctx).DurationF(ViperKeyHasherArgon2ConfigExpectedDeviation, Argon2DefaultDeviation), DedicatedMemory: p.GetProvider(ctx).ByteSizeF(ViperKeyHasherArgon2ConfigDedicatedMemory, Argon2DefaultDedicatedMemory), } } func (p *Config) HasherBcrypt(ctx context.Context) *Bcrypt { cost := uint32(p.GetProvider(ctx).IntF(ViperKeyHasherBcryptCost, int(BcryptDefaultCost))) // #nosec G115 -- if the user configures a cost > MaxUint32, go falls back to MaxUint32 if !p.IsInsecureDevMode(ctx) && cost < BcryptDefaultCost { cost = BcryptDefaultCost } return &Bcrypt{Cost: cost} } func (p *Config) DefaultIdentityTraitsSchemaURL(ctx context.Context) (*url.URL, error) { ss, err := p.IdentityTraitsSchemas(ctx) if err != nil { return nil, err } search := p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID) found, err := ss.FindSchemaByID(search) if err != nil { return nil, err } return p.ParseURI(found.URL) } func (p *Config) DefaultIdentityTraitsSchemaID(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID) } func (p *Config) IdentityTraitsSchemaURL(ctx context.Context, schemaID string) (*url.URL, error) { ss, err := p.IdentityTraitsSchemas(ctx) if err != nil { return nil, err } found, err := ss.FindSchemaByID(schemaID) if err != nil { // default to default schema search := p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID) found, err = ss.FindSchemaByID(search) if err != nil { return nil, err } } return p.ParseURI(found.URL) } func (p *Config) TOTPIssuer(ctx context.Context) string { return p.GetProvider(ctx).StringF(ViperKeyTOTPIssuer, p.SelfPublicURL(ctx).Hostname()) } func (p *Config) OIDCRedirectURIBase(ctx context.Context) *url.URL { return p.GetProvider(ctx).URIF(ViperKeyOIDCBaseRedirectURL, p.SelfPublicURL(ctx)) } func (p *Config) SAMLRedirectURIBase(ctx context.Context) *url.URL { return p.GetProvider(ctx).URIF(ViperKeySAMLBaseRedirectURL, p.SelfPublicURL(ctx)) } func (p *Config) IdentityTraitsSchemas(ctx context.Context) (ss Schemas, err error) { if err = p.GetProvider(ctx).Unmarshal(ViperKeyIdentitySchemas, &ss); err != nil { return ss, nil } return ss, nil } func (p *Config) DSN(ctx context.Context) string { pp := p.GetProvider(ctx) dsn := pp.String(ViperKeyDSN) if dsn == "memory" { return DefaultSQLiteMemoryDSN } if len(dsn) > 0 { return dsn } // Print a stack trace to aid debugging. p.l.Fatalf("%+v", errors.Errorf("dsn must be set")) return "" } func (p *Config) DisableAPIFlowEnforcement(ctx context.Context) bool { if p.IsInsecureDevMode(ctx) && os.Getenv("DEV_DISABLE_API_FLOW_ENFORCEMENT") == "true" { p.l.Warn("Because \"DEV_DISABLE_API_FLOW_ENFORCEMENT=true\" and the \"--dev\" flag are set, self-service API flows will no longer check if the interaction is actually a browser flow. This is very dangerous as it allows bypassing of anti-CSRF measures, leaving the deployment highly vulnerable. This option should only be used for automated testing and never come close to real user data anywhere.") return true } return false } func (p *Config) ClientHTTPNoPrivateIPRanges(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyClientHTTPNoPrivateIPRanges) } func (p *Config) ClientHTTPPrivateIPExceptionURLs(ctx context.Context) []string { return p.GetProvider(ctx).Strings(ViperKeyClientHTTPPrivateIPExceptionURLs) } func (p *Config) SelfServiceFlowRegistrationEnabled(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySelfServiceRegistrationEnabled) } func (p *Config) SelfServiceFlowRegistrationLoginHints(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySelfServiceRegistrationLoginHints) } func (p *Config) SelfServiceFlowRegistrationPasswordMethodProfileGroup(ctx context.Context) string { switch g := p.GetProvider(ctx).String(ViperKeyPasswordRegistrationProfileGroup); g { case "password": return "password" default: return "default" } } func (p *Config) SelfServiceLegacyOIDCRegistrationGroup(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyLegacyOIDCRegistrationGroup) } func (p *Config) SelfServiceFlowRegistrationTwoSteps(ctx context.Context) bool { // The default in previous versions that legacy one-step would be disabled. If legacy is enabled, it means the // user has explicitly set the key to true, in which case we respect it. if useOneStep := p.GetProvider(ctx).Bool(ViperKeySelfServiceRegistrationEnableLegacyOneStep); useOneStep { p.l.Warnf("Found use of deprecated configuration key %q. Please use key %q instead and delete key %[1]q. Will use value from %[1]q to configure registration style.", ViperKeySelfServiceRegistrationEnableLegacyOneStep, ViperKeySelfServiceRegistrationFlowStyle) return false } // In all other cases, we use the new key which (like the old key) defaults to `profile_first` / two-step registration. switch style := p.GetProvider(ctx).String(ViperKeySelfServiceRegistrationFlowStyle); style { case "profile_first": return true default: return false } } func (p *Config) SelfServiceFlowIdentitySchema(ctx context.Context, requestedSchema string) (string, error) { if requestedSchema == p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID) { return requestedSchema, nil } schemas, err := p.IdentityTraitsSchemas(ctx) if err != nil { return "", errors.WithStack(err) } for _, schema := range schemas { if schema.ID == requestedSchema { if !schema.SelfserviceSelectable { return "", errors.WithStack(herodot.ErrBadRequest.WithReasonf("Requested identity schema %q is not enabled for self-service flows.", requestedSchema)) } return requestedSchema, nil } } return "", errors.WithStack(herodot.ErrBadRequest.WithReasonf("Requested identity schema %q does not exist.", requestedSchema)) } func (p *Config) SelfServiceFlowVerificationEnabled(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySelfServiceVerificationEnabled) } func (p *Config) UseLegacyShowVerificationUI(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyUseLegacyShowVerificationUI) } func (p *Config) UseLegacyRequireVerifiedLoginError(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyUseLegacyRequireVerifiedLoginError) } func (p *Config) SelfServiceFlowRecoveryEnabled(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySelfServiceRecoveryEnabled) } func (p *Config) SelfServiceFlowRecoveryUse(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeySelfServiceRecoveryUse) } func (p *Config) SelfServiceFlowLoginBeforeHooks(ctx context.Context) []SelfServiceHook { return p.selfServiceHooks(ctx, ViperKeySelfServiceLoginBeforeHooks) } func (p *Config) SelfServiceFlowRecoveryBeforeHooks(ctx context.Context) []SelfServiceHook { return p.selfServiceHooks(ctx, ViperKeySelfServiceRecoveryBeforeHooks) } func (p *Config) SelfServiceFlowVerificationBeforeHooks(ctx context.Context) []SelfServiceHook { return p.selfServiceHooks(ctx, ViperKeySelfServiceVerificationBeforeHooks) } func (p *Config) SelfServiceFlowVerificationUse(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeySelfServiceVerificationUse) } func (p *Config) SelfServiceFlowVerificationNotifyUnknownRecipients(ctx context.Context) bool { return p.GetProvider(ctx).BoolF(ViperKeySelfServiceVerificationNotifyUnknownRecipients, false) } func (p *Config) SelfServiceFlowSettingsBeforeHooks(ctx context.Context) []SelfServiceHook { return p.selfServiceHooks(ctx, ViperKeySelfServiceSettingsBeforeHooks) } func (p *Config) SelfServiceFlowRegistrationBeforeHooks(ctx context.Context) []SelfServiceHook { hooks := p.selfServiceHooks(ctx, ViperKeySelfServiceRegistrationBeforeHooks) if p.SelfServiceFlowRegistrationTwoSteps(ctx) { hooks = append(hooks, SelfServiceHook{"two_step_registration", json.RawMessage("{}")}) } return hooks } func (p *Config) selfServiceHooks(ctx context.Context, key string) []SelfServiceHook { pp := p.GetProvider(ctx) val := pp.Get(key) if val == nil { return []SelfServiceHook{} } config, err := json.Marshal(val) if err != nil { p.l.WithError(err).Fatalf("Unable to decode values from configuration key: %s", key) } var hooks []SelfServiceHook if err := json.Unmarshal(config, &hooks); err != nil { p.l.WithError(err).Fatalf("Unable to encode value \"%s\" from configuration key: %s", config, key) } for k := range hooks { if len(hooks[k].Config) == 0 { hooks[k].Config = json.RawMessage("{}") } } return hooks } func (p *Config) SelfServiceFlowLoginAfterHooks(ctx context.Context, strategy string) []SelfServiceHook { return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceLoginAfter, strategy)) } func (p *Config) SelfServiceFlowSettingsAfterHooks(ctx context.Context, strategy string) []SelfServiceHook { return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceSettingsAfter, strategy)) } func (p *Config) SelfServiceFlowRegistrationAfterHooks(ctx context.Context, strategy string) []SelfServiceHook { return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceRegistrationAfter, strategy)) } func (p *Config) SelfServiceStrategy(ctx context.Context, strategy string) *SelfServiceStrategy { pp := p.GetProvider(ctx) config := json.RawMessage("{}") basePath := fmt.Sprintf("%s.%s", ViperKeySelfServiceStrategyConfig, strategy) var err error config, err = json.Marshal(pp.GetF(basePath+".config", config)) if err != nil { p.l.WithError(err).Warn("Unable to marshal self-service strategy configuration.") config = json.RawMessage("{}") } // The default value can easily be overwritten by setting e.g. `{"selfservice": "null"}` which means that // we need to forcibly set these values here: defaultEnabled := false switch strategy { case "identifier_first": defaultEnabled = p.SelfServiceLoginFlowIdentifierFirstEnabled(ctx) case "code", "password", "profile": defaultEnabled = true } // Backwards compatibility for the old "passwordless_enabled" key // This force-enables the code strategy, if passwordless is enabled, because in earlier versions it was possible to // disable the code strategy, but enable passwordless enabled := pp.BoolF(basePath+".enabled", defaultEnabled) if strategy == "code" { enabled = enabled || pp.Bool(basePath+".passwordless_enabled") } return &SelfServiceStrategy{ Enabled: enabled, Config: config, } } func (p *Config) SelfServiceCodeStrategy(ctx context.Context) *SelfServiceStrategyCode { pp := p.GetProvider(ctx) config := json.RawMessage("{}") basePath := ViperKeySelfServiceStrategyConfig + ".code" var err error config, err = json.Marshal(pp.GetF(basePath+".config", config)) if err != nil { p.l.WithError(err).Warn("Unable to marshal self service strategy configuration.") config = json.RawMessage("{}") } return &SelfServiceStrategyCode{ SelfServiceStrategy: &SelfServiceStrategy{ Enabled: pp.BoolF(basePath+".enabled", true), Config: config, }, PasswordlessEnabled: pp.BoolF(basePath+".passwordless_enabled", false), MFAEnabled: pp.BoolF(basePath+".mfa_enabled", false), } } func (p *Config) SecretsDefault(ctx context.Context) [][]byte { pp := p.GetProvider(ctx) secrets := pp.Strings(ViperKeySecretsDefault) if len(secrets) == 0 { secrets = []string{uuid.Must(uuid.NewV4()).String()} p.MustSet(ctx, ViperKeySecretsDefault, secrets) } result := make([][]byte, len(secrets)) for k, v := range secrets { result[k] = []byte(v) } return result } func (p *Config) SecretsSession(ctx context.Context) [][]byte { secrets := p.GetProvider(ctx).Strings(ViperKeySecretsCookie) if len(secrets) == 0 { return p.SecretsDefault(ctx) } result := make([][]byte, len(secrets)) for k, v := range secrets { result[k] = []byte(v) } return result } func (p *Config) SecretsCipher(ctx context.Context) [][32]byte { secrets := p.GetProvider(ctx).Strings(ViperKeySecretsCipher) return ToCipherSecrets(secrets) } func ToCipherSecrets(secrets []string) [][32]byte { var cleanSecrets []string for k := range secrets { if len(secrets[k]) == 32 { cleanSecrets = append(cleanSecrets, secrets[k]) } } if len(cleanSecrets) == 0 { return [][32]byte{} } result := make([][32]byte, len(cleanSecrets)) for n, s := range secrets { for k, v := range []byte(s) { result[n][k] = v } } return result } func (p *Config) SecretsPagination(ctx context.Context) [][32]byte { secrets := p.GetProvider(ctx).Strings(ViperKeySecretsPagination) encryptionKeys := make([][32]byte, len(secrets)) for i, key := range secrets { encryptionKeys[i] = sha512.Sum512_256([]byte(key)) } return encryptionKeys } func (p *Config) SelfServiceBrowserDefaultReturnTo(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceBrowserDefaultReturnTo) } func (p *Config) SelfPublicURL(ctx context.Context) *url.URL { serve := p.ServePublic(ctx) return serve.BaseURL } func (p *Config) SelfAdminURL(ctx context.Context) *url.URL { serve := p.ServeAdmin(ctx) return serve.BaseURL } func (p *Config) WebhookHeaderAllowlist(ctx context.Context) []string { return p.GetProvider(ctx).Strings(ViperKeyWebhookHeaderAllowlist) } func (p *Config) OAuth2ProviderHeader(ctx context.Context) http.Header { hh := map[string]string{} if err := p.GetProvider(ctx).Unmarshal(ViperKeyOAuth2ProviderHeader, &hh); err != nil { p.l.WithError(errors.WithStack(err)). Errorf("Configuration value from key %s could not be decoded.", ViperKeyOAuth2ProviderHeader) return nil } h := make(http.Header) for k, v := range hh { h.Set(k, v) } return h } func (p *Config) OAuth2ProviderOverrideReturnTo(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyOAuth2ProviderOverrideReturnTo) } func (p *Config) OAuth2ProviderURL(ctx context.Context) *url.URL { k := ViperKeyOAuth2ProviderURL v := p.GetProvider(ctx).String(k) if v == "" { return nil } parsed, err := p.ParseAbsoluteOrRelativeURI(v) if err != nil { p.l.WithError(errors.WithStack(err)). Errorf("Configuration value from key %s is not a valid URL: %s", k, v) return nil } return parsed } func (p *Config) SelfServiceFlowLoginUI(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceLoginUI) } func (p *Config) SelfServiceFlowSettingsUI(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceSettingsURL) } func (p *Config) SelfServiceFlowErrorURL(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceErrorUI) } func (p *Config) SelfServiceFlowRegistrationUI(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceRegistrationUI) } func (p *Config) SelfServiceFlowRecoveryUI(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceRecoveryUI) } // SessionLifespan returns time.Hour*24 when the value is not set. func (p *Config) SessionLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySessionLifespan, time.Hour*24) } func (p *Config) SessionPersistentCookie(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySessionPersistentCookie) } func (p *Config) SelfServiceBrowserAllowedReturnToDomains(ctx context.Context) (us []url.URL) { src := p.GetProvider(ctx).Strings(ViperKeyURLsAllowedReturnToDomains) for k, u := range src { if len(u) == 0 { continue } parsed, err := url.ParseRequestURI(u) if err != nil { p.l.WithError(err).Warnf("Ignoring URL \"%s\" from configuration key \"%s.%d\".", u, ViperKeyURLsAllowedReturnToDomains, k) continue } if parsed.Host == "*" { p.l.Warnf("Ignoring wildcard \"%s\" from configuration key \"%s.%d\".", u, ViperKeyURLsAllowedReturnToDomains, k) continue } eTLD, icann := publicsuffix.PublicSuffix(parsed.Host) if len(parsed.Host) > 0 && parsed.Host[:1] == "*" && icann && parsed.Host == fmt.Sprintf("*.%s", eTLD) { p.l.Warnf("Ignoring wildcard \"%s\" from configuration key \"%s.%d\".", u, ViperKeyURLsAllowedReturnToDomains, k) continue } us = append(us, *parsed) } return us } func (p *Config) SelfServiceFlowLoginRequestLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySelfServiceLoginRequestLifespan, time.Hour) } func (p *Config) SelfServiceFlowSettingsFlowLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySelfServiceSettingsRequestLifespan, time.Hour) } func (p *Config) SelfServiceFlowRegistrationRequestLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySelfServiceRegistrationRequestLifespan, time.Hour) } func (p *Config) SelfServiceFlowLogoutRedirectURL(ctx context.Context) *url.URL { return p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceLogoutBrowserDefaultReturnTo, p.SelfServiceBrowserDefaultReturnTo(ctx)) } func (p *Config) CourierEmailStrategy(ctx context.Context) string { return p.GetProvider(ctx).StringF(ViperKeyCourierDeliveryStrategy, "smtp") } func (p *Config) CourierEmailRequestConfig(ctx context.Context) json.RawMessage { if p.CourierEmailStrategy(ctx) != "http" { return nil } config, err := json.Marshal(p.GetProvider(ctx).Get(ViperKeyCourierHTTPRequestConfig)) if err != nil { p.l.WithError(err).Warn("Unable to marshal mailer request configuration.") return nil } return config } func (p *Config) CourierTemplatesRoot(ctx context.Context) string { return p.GetProvider(ctx).StringF(ViperKeyCourierTemplatesPath, "courier/builtin/templates") } func (p *Config) CourierEmailTemplatesHelper(ctx context.Context, key string) *CourierEmailTemplate { courierTemplate := &CourierEmailTemplate{ Body: &CourierEmailBodyTemplate{ PlainText: "", HTML: "", }, Subject: "", } if !p.GetProvider(ctx).Exists(key) { return courierTemplate } config, err := json.Marshal(p.GetProvider(ctx).Get(key)) if err != nil { p.l.WithError(err).Fatalf("Unable to decode values from %s.", key) return courierTemplate } if err := json.Unmarshal(config, courierTemplate); err != nil { p.l.WithError(err).Fatalf("Unable to encode values from %s.", key) return courierTemplate } return courierTemplate } func (p *Config) CourierSMSTemplatesHelper(ctx context.Context, key string) *CourierSMSTemplate { courierTemplate := &CourierSMSTemplate{ Body: &CourierSMSTemplateBody{ PlainText: "", }, } if !p.GetProvider(ctx).Exists(key) { return courierTemplate } config, err := json.Marshal(p.GetProvider(ctx).Get(key)) if err != nil { p.l.WithError(err).Fatalf("Unable to decode values from %s.", key) return courierTemplate } if err := json.Unmarshal(config, courierTemplate); err != nil { p.l.WithError(err).Fatalf("Unable to encode values from %s.", key) return courierTemplate } return courierTemplate } func (p *Config) CourierTemplatesVerificationInvalid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationInvalidEmail) } func (p *Config) CourierTemplatesVerificationValid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationValidEmail) } func (p *Config) CourierTemplatesRecoveryInvalid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryInvalidEmail) } func (p *Config) CourierTemplatesRecoveryValid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryValidEmail) } func (p *Config) CourierTemplatesRecoveryCodeInvalid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryCodeInvalidEmail) } func (p *Config) CourierTemplatesRecoveryCodeValid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryCodeValidEmail) } func (p *Config) CourierTemplatesVerificationCodeInvalid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeInvalidEmail) } func (p *Config) CourierTemplatesVerificationCodeValid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeValidEmail) } func (p *Config) CourierSMSTemplatesVerificationCodeValid(ctx context.Context) *CourierSMSTemplate { return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeValidSMS) } func (p *Config) CourierSMSTemplatesRecoveryCodeValid(ctx context.Context) *CourierSMSTemplate { return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryCodeValidSMS) } func (p *Config) CourierSMSTemplatesLoginCodeValid(ctx context.Context) *CourierSMSTemplate { return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesLoginCodeValidSMS) } func (p *Config) CourierSMSTemplatesRegistrationCodeValid(ctx context.Context) *CourierSMSTemplate { return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesRegistrationCodeValidSMS) } func (p *Config) CourierTemplatesLoginCodeValid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesLoginCodeValidEmail) } func (p *Config) CourierTemplatesRegistrationCodeValid(ctx context.Context) *CourierEmailTemplate { return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRegistrationCodeValidEmail) } func (p *Config) CourierMessageRetries(ctx context.Context) int { return p.GetProvider(ctx).IntF(ViperKeyCourierMessageRetries, 5) } func (p *Config) CourierWorkerPullCount(ctx context.Context) int { return p.GetProvider(ctx).Int(ViperKeyCourierWorkerPullCount) } func (p *Config) CourierWorkerPullWait(ctx context.Context) time.Duration { return p.GetProvider(ctx).Duration(ViperKeyCourierWorkerPullWait) } func (p *Config) CourierSMTPHeaders(ctx context.Context) map[string]string { return p.GetProvider(ctx).StringMap(ViperKeyCourierSMTPHeaders) } func (p *Config) CourierChannels(ctx context.Context) (ccs []*CourierChannel, _ error) { if err := p.GetProvider(ctx).Unmarshal(ViperKeyCourierChannels, &ccs); err != nil { return nil, errors.WithStack(err) } // load legacy configs channel := CourierChannel{ ID: "email", Type: p.CourierEmailStrategy(ctx), } if channel.Type == "smtp" { if err := p.GetProvider(ctx).Unmarshal(ViperKeyCourierSMTP, &channel.SMTPConfig); err != nil { return nil, errors.WithStack(err) } } else { if err := p.GetProvider(ctx).Unmarshal(ViperKeyCourierHTTPRequestConfig, &channel.RequestConfig); err != nil { return nil, errors.WithStack(err) } } ccs = append(ccs, &channel) return ccs, nil } func splitUrlAndFragment(s string) (string, string) { i := strings.IndexByte(s, '#') if i < 0 { return s, "" } return s[:i], s[i+1:] } func (p *Config) ParseAbsoluteOrRelativeURIOrFail(ctx context.Context, key string) *url.URL { parsed, err := p.ParseAbsoluteOrRelativeURI(p.GetProvider(ctx).String(key)) if err != nil { p.l.WithError(errors.WithStack(err)). Fatalf("Configuration value from key %s is not a valid URL: %s", key, p.GetProvider(ctx).String(key)) } return parsed } func (p *Config) ParseURIOrFail(ctx context.Context, key string) *url.URL { parsed, err := p.ParseURI(p.GetProvider(ctx).String(key)) if err != nil { p.l.WithField("reason", "expected scheme to be set"). Fatalf("Configuration value from key %s is not a valid URL: %s", key, p.GetProvider(ctx).String(key)) } return parsed } func (p *Config) ParseAbsoluteOrRelativeURI(rawUrl string) (*url.URL, error) { u, frag := splitUrlAndFragment(rawUrl) parsed, err := url.ParseRequestURI(u) if err != nil { return nil, errors.Wrapf(err, "configuration value not a valid URL: %s", rawUrl) } if frag != "" { parsed.Fragment = frag } return parsed, nil } func (p *Config) ParseURI(rawUrl string) (*url.URL, error) { parsed, err := p.ParseAbsoluteOrRelativeURI(rawUrl) if err != nil { return nil, err } if parsed.Scheme == "" { return nil, errors.Errorf("configuration value is not a valid URL: %s", rawUrl) } return parsed, nil } func (p *Config) Tracing(ctx context.Context) *otelx.Config { return p.GetProvider(ctx).TracingConfig("Ory Kratos") } func (p *Config) IsInsecureDevMode(ctx context.Context) bool { return p.GetProvider(ctx).Bool("dev") } func (p *Config) IsBackgroundCourierEnabled(ctx context.Context) bool { return p.GetProvider(ctx).Bool("watch-courier") } func (p *Config) CourierExposeMetricsPort(ctx context.Context) int { return p.GetProvider(ctx).Int("expose-metrics-port") } func (p *Config) SelfServiceFlowVerificationUI(ctx context.Context) *url.URL { return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceVerificationUI) } func (p *Config) SelfServiceFlowVerificationRequestLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySelfServiceVerificationRequestLifespan, time.Hour) } func (p *Config) SelfServiceFlowVerificationReturnTo(ctx context.Context, defaultReturnTo *url.URL) *url.URL { return p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceVerificationBrowserDefaultReturnTo, defaultReturnTo) } func (p *Config) SelfServiceFlowVerificationAfterHooks(ctx context.Context, strategy string) []SelfServiceHook { return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceVerificationAfter, strategy)) } func (p *Config) SelfServiceFlowRecoveryReturnTo(ctx context.Context, defaultReturnTo *url.URL) *url.URL { return p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceRecoveryBrowserDefaultReturnTo, defaultReturnTo) } func (p *Config) SelfServiceFlowRecoveryRequestLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySelfServiceRecoveryRequestLifespan, time.Hour) } func (p *Config) SelfServiceFlowRecoveryNotifyUnknownRecipients(ctx context.Context) bool { return p.GetProvider(ctx).BoolF(ViperKeySelfServiceRecoveryNotifyUnknownRecipients, false) } func (p *Config) SelfServiceLinkMethodLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeyLinkLifespan, time.Hour) } func (p *Config) SelfServiceLinkMethodBaseURL(ctx context.Context) *url.URL { return cmp.Or(x.BaseURLFromContext(ctx), p.SelfPublicURL(ctx)) } func (p *Config) SelfServiceCodeMethodLifespan(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeyCodeLifespan, time.Hour) } func (p *Config) SelfServiceCodeMethodMaxSubmissions(ctx context.Context) int { return p.GetProvider(ctx).IntF(ViperKeyCodeMaxSubmissions, 5) } func (p *Config) SelfServiceCodeMethodMissingCredentialFallbackEnabled(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyCodeConfigMissingCredentialFallbackEnabled) } func (p *Config) DatabaseCleanupSleepTables(ctx context.Context) time.Duration { return p.GetProvider(ctx).Duration(ViperKeyDatabaseCleanupSleepTables) } func (p *Config) DatabaseCleanupBatchSize(ctx context.Context) int { return p.GetProvider(ctx).Int(ViperKeyDatabaseCleanupBatchSize) } func (p *Config) SelfServiceFlowRecoveryAfterHooks(ctx context.Context, strategy string) []SelfServiceHook { return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceRecoveryAfter, strategy)) } func (p *Config) SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, time.Hour) } func (p *Config) SessionSameSiteMode(ctx context.Context) http.SameSite { if !p.GetProvider(ctx).Exists(ViperKeySessionSameSite) { return p.CookieSameSiteMode(ctx) } switch p.GetProvider(ctx).StringF(ViperKeySessionSameSite, "Lax") { case "Lax": return http.SameSiteLaxMode case "Strict": return http.SameSiteStrictMode case "None": return http.SameSiteNoneMode } return http.SameSiteDefaultMode } func (p *Config) SessionDomain(ctx context.Context) string { if !p.GetProvider(ctx).Exists(ViperKeySessionDomain) { return p.CookieDomain(ctx) } return p.GetProvider(ctx).String(ViperKeySessionDomain) } func (p *Config) SessionCookieSecure(ctx context.Context) bool { if !p.GetProvider(ctx).Exists(ViperKeySessionSecure) { return !p.IsInsecureDevMode(ctx) } return p.GetProvider(ctx).Bool(ViperKeySessionSecure) } func (p *Config) CookieDomain(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeyCookieDomain) } func (p *Config) SessionWhoAmIAAL(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeySessionWhoAmIAAL) } func (p *Config) SessionWhoAmICaching(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySessionWhoAmICaching) } func (p *Config) FeatureFlagFasterSessionExtend(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyFeatureFlagFasterSessionExtend) } func (p *Config) SessionWhoAmICachingMaxAge(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySessionWhoAmICachingMaxAge, 0) } func (p *Config) UseContinueWithTransitions(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyUseContinueWithTransitions) } func (p *Config) ChooseRecoveryAddress(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyChooseRecoveryAddress) } func (p *Config) SessionRefreshMinTimeLeft(ctx context.Context) time.Duration { return p.GetProvider(ctx).DurationF(ViperKeySessionRefreshMinTimeLeft, p.SessionLifespan(ctx)) } func (p *Config) SelfServiceSettingsRequiredAAL(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeySelfServiceSettingsRequiredAAL) } func (p *Config) CookieSameSiteMode(ctx context.Context) http.SameSite { switch p.GetProvider(ctx).StringF(ViperKeyCookieSameSite, "Lax") { case "Lax": return http.SameSiteLaxMode case "Strict": return http.SameSiteStrictMode case "None": return http.SameSiteNoneMode } return http.SameSiteDefaultMode } func (p *Config) SessionPath(ctx context.Context) string { if !p.GetProvider(ctx).Exists(ViperKeySessionPath) { return p.CookiePath(ctx) } return p.GetProvider(ctx).String(ViperKeySessionPath) } func (p *Config) CookiePath(ctx context.Context) string { return p.GetProvider(ctx).String(ViperKeyCookiePath) } func (p *Config) CookieSecure(ctx context.Context) bool { if !p.GetProvider(ctx).Exists(ViperKeyCookieSecure) { return !p.IsInsecureDevMode(ctx) } return p.GetProvider(ctx).Bool(ViperKeyCookieSecure) } func (p *Config) SelfServiceFlowLoginReturnTo(ctx context.Context, strategy string) *url.URL { return p.selfServiceReturnTo(ctx, ViperKeySelfServiceLoginAfter, strategy) } func (p *Config) SelfServiceFlowRegistrationReturnTo(ctx context.Context, strategy string) *url.URL { return p.selfServiceReturnTo(ctx, ViperKeySelfServiceRegistrationAfter, strategy) } func (p *Config) SelfServiceFlowSettingsReturnTo(ctx context.Context, strategy string, defaultReturnTo *url.URL) *url.URL { return p.GetProvider(ctx).RequestURIF( ViperKeySelfServiceSettingsAfter+"."+strategy+"."+DefaultBrowserReturnURL, p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceSettingsAfter+"."+DefaultBrowserReturnURL, defaultReturnTo, ), ) } func (p *Config) selfServiceReturnTo(ctx context.Context, key string, strategy string) *url.URL { return p.GetProvider(ctx).RequestURIF( key+"."+strategy+"."+DefaultBrowserReturnURL, p.GetProvider(ctx).RequestURIF(key+"."+DefaultBrowserReturnURL, p.SelfServiceBrowserDefaultReturnTo(ctx), ), ) } func (p *Config) ConfigVersion(ctx context.Context) string { return p.GetProvider(ctx).StringF(ViperKeyVersion, UnknownVersion) } func (p *Config) PasswordPolicyConfig(ctx context.Context) *PasswordPolicy { return &PasswordPolicy{ HaveIBeenPwnedHost: p.GetProvider(ctx).StringF(ViperKeyPasswordHaveIBeenPwnedHost, "api.pwnedpasswords.com"), HaveIBeenPwnedEnabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordHaveIBeenPwnedEnabled, true), MaxBreaches: uint(p.GetProvider(ctx).Int(ViperKeyPasswordMaxBreaches)), // #nosec G115 -- negative values are prevented by the schema validation IgnoreNetworkErrors: p.GetProvider(ctx).BoolF(ViperKeyIgnoreNetworkErrors, true), MinPasswordLength: uint(p.GetProvider(ctx).IntF(ViperKeyPasswordMinLength, 8)), // #nosec G115 -- negative values are prevented by the schema validation IdentifierSimilarityCheckEnabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordIdentifierSimilarityCheckEnabled, true), } } func (p *Config) WebAuthnForPasswordless(ctx context.Context) bool { return p.GetProvider(ctx).BoolF(ViperKeyWebAuthnPasswordless, false) } func (p *Config) WebAuthnConfig(ctx context.Context) *webauthn.Config { scheme := p.SelfPublicURL(ctx).Scheme id := p.GetProvider(ctx).String(ViperKeyWebAuthnRPID) origin := p.GetProvider(ctx).String(ViperKeyWebAuthnRPOrigin) origins := p.GetProvider(ctx).StringsF(ViperKeyWebAuthnRPOrigins, []string{cmp.Or(origin, scheme+"://"+id)}) return &webauthn.Config{ RPDisplayName: p.GetProvider(ctx).String(ViperKeyWebAuthnRPDisplayName), RPID: id, RPOrigins: origins, AuthenticatorSelection: protocol.AuthenticatorSelection{ UserVerification: protocol.VerificationDiscouraged, }, EncodeUserIDAsString: false, } } func (p *Config) PasskeyConfig(ctx context.Context) *webauthn.Config { scheme := p.SelfPublicURL(ctx).Scheme id := p.GetProvider(ctx).String(ViperKeyPasskeyRPID) origins := p.GetProvider(ctx).StringsF(ViperKeyPasskeyRPOrigins, []string{scheme + "://" + id}) return &webauthn.Config{ RPDisplayName: p.GetProvider(ctx).String(ViperKeyPasskeyRPDisplayName), RPID: id, RPOrigins: origins, AuthenticatorSelection: protocol.AuthenticatorSelection{ AuthenticatorAttachment: "platform", RequireResidentKey: new(true), ResidentKey: protocol.ResidentKeyRequirementRequired, UserVerification: protocol.VerificationPreferred, }, EncodeUserIDAsString: false, } } func (p *Config) HasherPasswordHashingAlgorithm(ctx context.Context) string { configValue := p.GetProvider(ctx).StringF(ViperKeyHasherAlgorithm, DefaultPasswordHashingAlgorithm) switch configValue { case "bcrypt": return configValue case "argon2": fallthrough default: return configValue } } func (p *Config) CipherAlgorithm(ctx context.Context) string { configValue := p.GetProvider(ctx).StringF(ViperKeyCipherAlgorithm, DefaultCipherAlgorithm) switch configValue { case "noop": return configValue case "xchacha20-poly1305": return configValue case "aes": fallthrough default: return configValue } } func (p *Config) GetProvider(ctx context.Context) *configx.Provider { return p.c.Config(ctx, p.p) } type SessionTokenizeFormat struct { TTL time.Duration `koanf:"ttl" json:"ttl"` ClaimsMapperURL string `koanf:"claims_mapper_url" json:"claims_mapper_url"` JWKSURL string `koanf:"jwks_url" json:"jwks_url"` SubjectSource string `koanf:"subject_source" json:"subject_source"` } func (p *Config) TokenizeTemplate(ctx context.Context, key string) (_ *SessionTokenizeFormat, err error) { var result SessionTokenizeFormat path := ViperKeySessionTokenizerTemplates + "." + key if !p.GetProvider(ctx).Exists(path) { return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to find tokenizer template \"%s\".", key)) } if err := p.GetProvider(ctx).Unmarshal(path, &result); err != nil { return nil, errors.WithStack(herodot.ErrMisconfiguration.WithReasonf("Unable to decode tokenizer template \"%s\": %s", key, err)) } return &result, nil } func (p *Config) DefaultConsistencyLevel(ctx context.Context) crdbx.ConsistencyLevel { return crdbx.ConsistencyLevelFromString(p.GetProvider(ctx).String(ViperKeyPreviewDefaultReadConsistencyLevel)) } func (p *Config) PasswordMigrationHook(ctx context.Context) *PasswordMigrationHook { hook := &PasswordMigrationHook{ Enabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordMigrationHook+".enabled", false), } if !hook.Enabled { return hook } _ = p.GetProvider(ctx).Unmarshal(ViperKeyPasswordMigrationHook+".config", &hook.Config) return hook } func (p *Config) SelfServiceLoginFlowIdentifierFirstEnabled(ctx context.Context) bool { switch p.GetProvider(ctx).String(ViperKeySelfServiceLoginFlowStyle) { case "identifier_first": return true default: return false } } func (p *Config) SecurityAccountEnumerationMitigate(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeySecurityAccountEnumerationMitigate) } ================================================ FILE: driver/config/config_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package config_test import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" "math" "net/http" "net/url" "os" "path/filepath" "strings" "testing" "time" "github.com/ory/x/contextx" "github.com/ory/x/httpx" "github.com/ory/x/randx" "github.com/ory/x/snapshotx" "github.com/ghodss/yaml" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/x/configx" "github.com/sirupsen/logrus/hooks/test" "github.com/ory/x/logrusx" "github.com/ory/x/urlx" _ "github.com/ory/jsonschema/v3/fileloader" "github.com/ory/kratos/driver/config" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestViperProvider(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("suite=loaders", func(t *testing.T) { p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.yaml"), configx.WithContext(ctx)) t.Run("group=client config", func(t *testing.T) { assert.False(t, p.ClientHTTPNoPrivateIPRanges(ctx), "Should not have private IP ranges disabled per default") assert.Equal(t, []string{}, p.ClientHTTPPrivateIPExceptionURLs(ctx), "Should return the correct exceptions") p.MustSet(ctx, config.ViperKeyClientHTTPNoPrivateIPRanges, true) assert.True(t, p.ClientHTTPNoPrivateIPRanges(ctx), "Should disallow private IP ranges if set") p.MustSet(ctx, config.ViperKeyClientHTTPPrivateIPExceptionURLs, []string{"https://foobar.com/baz"}) assert.Equal(t, []string{"https://foobar.com/baz"}, p.ClientHTTPPrivateIPExceptionURLs(ctx), "Should return the correct exceptions") }) t.Run("group=urls", func(t *testing.T) { assert.Equal(t, "http://test.kratos.ory.sh/login", p.SelfServiceFlowLoginUI(ctx).String()) assert.Equal(t, "http://test.kratos.ory.sh/settings", p.SelfServiceFlowSettingsUI(ctx).String()) assert.Equal(t, "http://test.kratos.ory.sh/register", p.SelfServiceFlowRegistrationUI(ctx).String()) assert.Equal(t, "http://test.kratos.ory.sh/error", p.SelfServiceFlowErrorURL(ctx).String()) assert.Equal(t, "http://admin.kratos.ory.sh", p.SelfAdminURL(ctx).String()) assert.Equal(t, "http://public.kratos.ory.sh", p.SelfPublicURL(ctx).String()) var ds []string for _, v := range p.SelfServiceBrowserAllowedReturnToDomains(ctx) { ds = append(ds, v.String()) } assert.Equal(t, []string{ "http://return-to-1-test.ory.sh/", "http://return-to-2-test.ory.sh/", "http://*.wildcards.ory.sh", "/return-to-relative-test/", }, ds) pWithFragments := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.WithValues(map[string]interface{}{ config.ViperKeySelfServiceLoginUI: "http://test.kratos.ory.sh/#/login", config.ViperKeySelfServiceSettingsURL: "http://test.kratos.ory.sh/#/settings", config.ViperKeySelfServiceRegistrationUI: "http://test.kratos.ory.sh/#/register", config.ViperKeySelfServiceErrorUI: "http://test.kratos.ory.sh/#/error", }), configx.SkipValidation()) assert.Equal(t, "http://test.kratos.ory.sh/#/login", pWithFragments.SelfServiceFlowLoginUI(ctx).String()) assert.Equal(t, "http://test.kratos.ory.sh/#/settings", pWithFragments.SelfServiceFlowSettingsUI(ctx).String()) assert.Equal(t, "http://test.kratos.ory.sh/#/register", pWithFragments.SelfServiceFlowRegistrationUI(ctx).String()) assert.Equal(t, "http://test.kratos.ory.sh/#/error", pWithFragments.SelfServiceFlowErrorURL(ctx).String()) pWithRelativeFragments := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.WithValues(map[string]interface{}{ config.ViperKeySelfServiceLoginUI: "/login", config.ViperKeySelfServiceSettingsURL: "/settings", config.ViperKeySelfServiceRegistrationUI: "/register", config.ViperKeySelfServiceErrorUI: "/error", }), configx.SkipValidation()) assert.Equal(t, "/login", pWithRelativeFragments.SelfServiceFlowLoginUI(ctx).String()) assert.Equal(t, "/settings", pWithRelativeFragments.SelfServiceFlowSettingsUI(ctx).String()) assert.Equal(t, "/register", pWithRelativeFragments.SelfServiceFlowRegistrationUI(ctx).String()) assert.Equal(t, "/error", pWithRelativeFragments.SelfServiceFlowErrorURL(ctx).String()) for _, v := range []string{ "#/login", "test.kratos.ory.sh/login", } { logger := logrusx.New("", "") logger.Logger.ExitFunc = func(code int) { panic("") } hook := new(test.Hook) logger.Logger.Hooks.Add(hook) pWithIncorrectUrls := config.MustNew(t, logger, &contextx.Default{}, configx.WithValues(map[string]interface{}{ config.ViperKeySelfServiceLoginUI: v, }), configx.SkipValidation()) assert.Panics(t, func() { pWithIncorrectUrls.SelfServiceFlowLoginUI(ctx) }) assert.Equal(t, logrus.FatalLevel, hook.LastEntry().Level) assert.Equal(t, "Configuration value from key selfservice.flows.login.ui_url is not a valid URL: "+v, hook.LastEntry().Message) assert.Equal(t, 1, len(hook.Entries)) } }) t.Run("group=default_return_to", func(t *testing.T) { assert.Equal(t, "https://self-service/login/password/return_to", p.SelfServiceFlowLoginReturnTo(ctx, "password").String()) assert.Equal(t, "https://self-service/login/return_to", p.SelfServiceFlowLoginReturnTo(ctx, "oidc").String()) assert.Equal(t, "https://self-service/registration/return_to", p.SelfServiceFlowRegistrationReturnTo(ctx, "password").String()) assert.Equal(t, "https://self-service/registration/oidc/return_to", p.SelfServiceFlowRegistrationReturnTo(ctx, "oidc").String()) assert.Equal(t, "https://self-service/settings/password/return_to", p.SelfServiceFlowSettingsReturnTo(ctx, "password", p.SelfServiceBrowserDefaultReturnTo(ctx)).String()) assert.Equal(t, "https://self-service/settings/return_to", p.SelfServiceFlowSettingsReturnTo(ctx, "profile", p.SelfServiceBrowserDefaultReturnTo(ctx)).String()) assert.Equal(t, "http://test.kratos.ory.sh:4000/", p.SelfServiceFlowLogoutRedirectURL(ctx).String()) p.MustSet(ctx, config.ViperKeySelfServiceLogoutBrowserDefaultReturnTo, "") assert.Equal(t, "http://return-to-3-test.ory.sh/", p.SelfServiceFlowLogoutRedirectURL(ctx).String()) }) t.Run("group=identity", func(t *testing.T) { c := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.mock.identities.yaml"), configx.SkipValidation()) ds, err := c.DefaultIdentityTraitsSchemaURL(ctx) require.NoError(t, err) assert.Equal(t, "http://test.kratos.ory.sh/default-identity.schema.json", ds.String()) ss, err := c.IdentityTraitsSchemas(ctx) require.NoError(t, err) assert.Equal(t, 2, len(ss)) assert.Contains(t, ss, config.Schema{ ID: "default", URL: "http://test.kratos.ory.sh/default-identity.schema.json", }) assert.Contains(t, ss, config.Schema{ ID: "other", URL: "http://test.kratos.ory.sh/other-identity.schema.json", }) ds, err = c.IdentityTraitsSchemaURL(ctx, "other") require.NoError(t, err) assert.Equal(t, "http://test.kratos.ory.sh/other-identity.schema.json", ds.String()) ds, err = c.IdentityTraitsSchemaURL(ctx, "default") require.NoError(t, err) assert.Equal(t, "http://test.kratos.ory.sh/default-identity.schema.json", ds.String()) ds, err = c.IdentityTraitsSchemaURL(ctx, "") require.NoError(t, err) assert.Equal(t, "http://test.kratos.ory.sh/default-identity.schema.json", ds.String()) ds, err = c.IdentityTraitsSchemaURL(ctx, "does-not-exist") require.NoError(t, err) assert.Equal(t, "http://test.kratos.ory.sh/default-identity.schema.json", ds.String()) }) t.Run("group=serve", func(t *testing.T) { admin := p.ServeAdmin(ctx) assert.Equal(t, "admin.kratos.ory.sh", admin.Host) assert.Equal(t, 1234, admin.Port) public := p.ServePublic(ctx) assert.Equal(t, "public.kratos.ory.sh", public.Host) assert.Equal(t, 1235, public.Port) }) t.Run("group=dsn", func(t *testing.T) { assert.Equal(t, "sqlite://foo.db?mode=memory&_fk=true", p.DSN(ctx)) }) t.Run("group=secrets", func(t *testing.T) { assert.Equal(t, [][]byte{ []byte("session-key-7f8a9b77-1"), []byte("session-key-7f8a9b77-2"), }, p.SecretsSession(ctx)) var cipherExpected [32]byte for k, v := range []byte("secret-thirty-two-character-long") { cipherExpected[k] = v } assert.Equal(t, [][32]byte{ cipherExpected, }, p.SecretsCipher(ctx)) }) t.Run("group=methods", func(t *testing.T) { for _, tc := range []struct { id string config string enabled bool }{ {id: "password", enabled: true, config: `{"haveibeenpwned_host":"api.pwnedpasswords.com","haveibeenpwned_enabled":true,"ignore_network_errors":true,"max_breaches":0,"migrate_hook":{"config":{"emit_analytics_event":true,"method":"POST"},"enabled":false},"min_password_length":8,"identifier_similarity_check_enabled":true}`}, {id: "oidc", enabled: true, config: `{"providers":[{"client_id":"a","client_secret":"b","id":"github","provider":"github","mapper_url":"http://test.kratos.ory.sh/default-identity.schema.json"}]}`}, {id: "totp", enabled: true, config: `{"issuer":"issuer.ory.sh"}`}, } { strategy := p.SelfServiceStrategy(ctx, tc.id) assert.Equal(t, tc.enabled, strategy.Enabled) assert.JSONEq(t, tc.config, string(strategy.Config)) } }) t.Run("method=registration", func(t *testing.T) { assert.Equal(t, true, p.SelfServiceFlowRegistrationEnabled(ctx)) assert.Equal(t, time.Minute*98, p.SelfServiceFlowRegistrationRequestLifespan(ctx)) t.Run("hook=before", func(t *testing.T) { expHooks := []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"https://test.kratos.ory.sh/before_registration_hook"}`)}, {Name: "two_step_registration", Config: json.RawMessage(`{}`)}, } hooks := p.SelfServiceFlowRegistrationBeforeHooks(ctx) assert.Equal(t, expHooks, hooks) // assert.EqualValues(t, "redirect", hook.Name) // assert.JSONEq(t, `{"allow_user_defined_redirect":false,"default_redirect_url":"http://test.kratos.ory.sh:4000/"}`, string(hook.Config)) }) for _, tc := range []struct { strategy string hooks []config.SelfServiceHook }{ { strategy: "password", hooks: []config.SelfServiceHook{ {Name: "session", Config: json.RawMessage(`{}`)}, {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_registration_password_hook"}`)}, // {Name: "verify", Config: json.RawMessage(`{}`)}, // {Name: "redirect", Config: json.RawMessage(`{"allow_user_defined_redirect":false,"default_redirect_url":"http://test.kratos.ory.sh:4000/"}`)}, }, }, { strategy: "oidc", hooks: []config.SelfServiceHook{ // {Name: "verify", Config: json.RawMessage(`{}`)}, {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"GET","url":"https://test.kratos.ory.sh/after_registration_oidc_hook"}`)}, {Name: "session", Config: json.RawMessage(`{}`)}, // {Name: "redirect", Config: json.RawMessage(`{"allow_user_defined_redirect":false,"default_redirect_url":"http://test.kratos.ory.sh:4000/"}`)}, }, }, { strategy: config.HookGlobal, hooks: []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"auth":{"config":{"in":"header","name":"My-Key","value":"My-Key-Value"},"type":"api_key"},"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_registration_global_hook"}`)}, }, }, } { t.Run("hook=after/strategy="+tc.strategy, func(t *testing.T) { hooks := p.SelfServiceFlowRegistrationAfterHooks(ctx, tc.strategy) assert.Equal(t, tc.hooks, hooks) }) } }) t.Run("method=totp", func(t *testing.T) { assert.Equal(t, "issuer.ory.sh", p.TOTPIssuer(ctx)) }) t.Run("method=login", func(t *testing.T) { assert.Equal(t, time.Minute*99, p.SelfServiceFlowLoginRequestLifespan(ctx)) t.Run("hook=before", func(t *testing.T) { expHooks := []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/before_login_hook"}`)}, } hooks := p.SelfServiceFlowLoginBeforeHooks(ctx) require.Len(t, hooks, 1) assert.Equal(t, expHooks, hooks) // assert.EqualValues(t, "redirect", hook.Name) // assert.JSONEq(t, `{"allow_user_defined_redirect":false,"default_redirect_url":"http://test.kratos.ory.sh:4000/"}`, string(hook.Config)) }) for _, tc := range []struct { strategy string hooks []config.SelfServiceHook }{ { strategy: "password", hooks: []config.SelfServiceHook{ {Name: "revoke_active_sessions", Config: json.RawMessage(`{}`)}, {Name: "require_verified_address", Config: json.RawMessage(`{}`)}, {Name: "web_hook", Config: json.RawMessage(`{"auth":{"config":{"password":"super-secret","user":"test-user"},"type":"basic_auth"},"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_login_password_hook"}`)}, }, }, { strategy: "oidc", hooks: []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"GET","url":"https://test.kratos.ory.sh/after_login_oidc_hook"}`)}, {Name: "revoke_active_sessions", Config: json.RawMessage(`{}`)}, }, }, { strategy: config.HookGlobal, hooks: []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_login_global_hook"}`)}, }, }, } { t.Run("hook=after/strategy="+tc.strategy, func(t *testing.T) { hooks := p.SelfServiceFlowLoginAfterHooks(ctx, tc.strategy) assert.Equal(t, tc.hooks, hooks) }) } }) t.Run("method=settings", func(t *testing.T) { assert.Equal(t, time.Minute*99, p.SelfServiceFlowSettingsFlowLifespan(ctx)) assert.Equal(t, time.Minute*5, p.SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx)) for _, tc := range []struct { strategy string hooks []config.SelfServiceHook }{ { strategy: "password", hooks: []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_settings_password_hook"}`)}, }, }, { strategy: "profile", hooks: []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_settings_profile_hook"}`)}, }, }, { strategy: config.HookGlobal, hooks: []config.SelfServiceHook{ {Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"POST","url":"https://test.kratos.ory.sh/after_settings_global_hook"}`)}, }, }, } { t.Run("hook=after/strategy="+tc.strategy, func(t *testing.T) { hooks := p.SelfServiceFlowSettingsAfterHooks(ctx, tc.strategy) assert.Equal(t, tc.hooks, hooks) }) } }) t.Run("method=recovery", func(t *testing.T) { assert.Equal(t, true, p.SelfServiceFlowRecoveryEnabled(ctx)) assert.Equal(t, time.Minute*98, p.SelfServiceFlowRecoveryRequestLifespan(ctx)) assert.Equal(t, "http://test.kratos.ory.sh/recovery", p.SelfServiceFlowRecoveryUI(ctx).String()) hooks := p.SelfServiceFlowRecoveryAfterHooks(ctx, config.HookGlobal) assert.Equal(t, []config.SelfServiceHook{{Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"GET","url":"https://test.kratos.ory.sh/after_recovery_hook"}`)}}, hooks) }) t.Run("method=verification", func(t *testing.T) { assert.Equal(t, time.Minute*97, p.SelfServiceFlowVerificationRequestLifespan(ctx)) assert.Equal(t, "http://test.kratos.ory.sh/verification", p.SelfServiceFlowVerificationUI(ctx).String()) hooks := p.SelfServiceFlowVerificationAfterHooks(ctx, config.HookGlobal) assert.Equal(t, []config.SelfServiceHook{{Name: "web_hook", Config: json.RawMessage(`{"body":"/path/to/template.jsonnet","headers":{"X-Custom-Header":"test"},"method":"GET","url":"https://test.kratos.ory.sh/after_verification_hook"}`)}}, hooks) }) t.Run("group=hashers", func(t *testing.T) { c := p.HasherArgon2(ctx) assert.Equal(t, &config.Argon2{ Memory: 1048576, Iterations: 2, Parallelism: 4, SaltLength: 16, KeyLength: 32, DedicatedMemory: config.Argon2DefaultDedicatedMemory, ExpectedDeviation: config.Argon2DefaultDeviation, ExpectedDuration: config.Argon2DefaultDuration, }, c) }) t.Run("group=set_provider_by_json", func(t *testing.T) { providerConfigJSON := `{"providers": [{"id":"github-test","provider":"github","client_id":"set_json_test","client_secret":"secret","mapper_url":"http://mapper-url","scope":["user:email"]}]}` strategyConfigJSON := fmt.Sprintf(`{"enabled":true, "config": %s}`, providerConfigJSON) p.MustSet(ctx, config.ViperKeySelfServiceStrategyConfig+".oidc", strategyConfigJSON) strategy := p.SelfServiceStrategy(ctx, "oidc") assert.JSONEq(t, providerConfigJSON, string(strategy.Config)) }) }) } func TestBcrypt(t *testing.T) { t.Parallel() ctx := context.Background() p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) require.NoError(t, p.Set(ctx, config.ViperKeyHasherBcryptCost, 4)) require.NoError(t, p.Set(ctx, "dev", false)) assert.EqualValues(t, uint32(12), p.HasherBcrypt(ctx).Cost) require.NoError(t, p.Set(ctx, "dev", true)) assert.EqualValues(t, uint32(4), p.HasherBcrypt(ctx).Cost) require.NoError(t, p.Set(ctx, config.ViperKeyHasherBcryptCost, math.MaxInt64)) // too high assert.EqualValues(t, math.MaxUint32, p.HasherBcrypt(ctx).Cost) } func TestProviderBaseURLs(t *testing.T) { t.Parallel() ctx := context.Background() machineHostname, err := os.Hostname() if err != nil { machineHostname = "127.0.0.1" } p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) assert.Equal(t, "https://"+machineHostname+":4433/", p.SelfPublicURL(ctx).String()) assert.Equal(t, "https://"+machineHostname+":4434/", p.SelfAdminURL(ctx).String()) p = config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation(), configx.WithValues(map[string]interface{}{ "serve.public.port": 4444, "serve.admin.port": 4445, })) assert.Equal(t, "https://"+machineHostname+":4444/", p.SelfPublicURL(ctx).String()) assert.Equal(t, "https://"+machineHostname+":4445/", p.SelfAdminURL(ctx).String()) p = config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation(), configx.WithValues(map[string]interface{}{ "serve.public.host": "public.ory.sh", "serve.admin.host": "admin.ory.sh", "serve.public.port": 4444, "serve.admin.port": 4445, })) assert.Equal(t, "https://public.ory.sh:4444/", p.SelfPublicURL(ctx).String()) assert.Equal(t, "https://admin.ory.sh:4445/", p.SelfAdminURL(ctx).String()) // Set to dev mode p = config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation(), configx.WithValues(map[string]interface{}{ "serve.public.host": "public.ory.sh", "serve.admin.host": "admin.ory.sh", "serve.public.port": 4444, "serve.admin.port": 4445, "dev": true, })) assert.Equal(t, "http://public.ory.sh:4444/", p.SelfPublicURL(ctx).String()) assert.Equal(t, "http://admin.ory.sh:4445/", p.SelfAdminURL(ctx).String()) } func TestDefaultWebhookHeaderAllowlist(t *testing.T) { t.Parallel() ctx := context.Background() p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) snapshotx.SnapshotT(t, p.WebhookHeaderAllowlist(ctx)) } func TestViperProvider_Secrets(t *testing.T) { t.Parallel() ctx := context.Background() p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) def := p.SecretsDefault(ctx) assert.NotEmpty(t, def) assert.Equal(t, def, p.SecretsSession(ctx)) assert.Equal(t, def, p.SecretsDefault(ctx)) assert.Empty(t, p.SecretsCipher(ctx)) err := p.Set(ctx, config.ViperKeySecretsCipher, []string{"short-secret-key"}) require.NoError(t, err) assert.Equal(t, [][32]byte{}, p.SecretsCipher(ctx)) } func TestViperProvider_Defaults(t *testing.T) { t.Parallel() ctx := context.Background() l := logrusx.New("", "") for k, tc := range []struct { init func() *config.Config expect func(t *testing.T, p *config.Config) }{ { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) }, }, { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.WithConfigFiles("stub/.defaults.yml"), configx.SkipValidation()) }, }, { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.WithConfigFiles("stub/.defaults-password.yml"), configx.SkipValidation()) }, }, { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.WithConfigFiles("../../test/e2e/profiles/recovery/.kratos.yml"), configx.SkipValidation()) }, expect: func(t *testing.T, p *config.Config) { assert.True(t, p.SelfServiceFlowRecoveryEnabled(ctx)) assert.False(t, p.SelfServiceFlowVerificationEnabled(ctx)) assert.True(t, p.SelfServiceFlowRegistrationEnabled(ctx)) assert.True(t, p.SelfServiceStrategy(ctx, "password").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "profile").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "link").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "code").Enabled) assert.False(t, p.SelfServiceCodeStrategy(ctx).PasswordlessEnabled) assert.False(t, p.SelfServiceStrategy(ctx, "oidc").Enabled) }, }, { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.WithConfigFiles("../../test/e2e/profiles/verification/.kratos.yml"), configx.SkipValidation()) }, expect: func(t *testing.T, p *config.Config) { assert.False(t, p.SelfServiceFlowRecoveryEnabled(ctx)) assert.True(t, p.SelfServiceFlowVerificationEnabled(ctx)) assert.True(t, p.SelfServiceFlowRegistrationEnabled(ctx)) assert.True(t, p.SelfServiceStrategy(ctx, "password").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "profile").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "link").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "code").Enabled) assert.False(t, p.SelfServiceCodeStrategy(ctx).PasswordlessEnabled) assert.False(t, p.SelfServiceStrategy(ctx, "oidc").Enabled) }, }, { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.WithConfigFiles("../../test/e2e/profiles/oidc/.kratos.yml"), configx.SkipValidation()) }, expect: func(t *testing.T, p *config.Config) { assert.False(t, p.SelfServiceFlowRecoveryEnabled(ctx)) assert.False(t, p.SelfServiceFlowVerificationEnabled(ctx)) assert.True(t, p.SelfServiceStrategy(ctx, "password").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "profile").Enabled) assert.False(t, p.SelfServiceStrategy(ctx, "link").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "code").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "oidc").Enabled) assert.False(t, p.SelfServiceCodeStrategy(ctx).PasswordlessEnabled) }, }, { init: func() *config.Config { return config.MustNew(t, l, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.notify-unknown-recipients.yml"), configx.SkipValidation()) }, expect: func(t *testing.T, p *config.Config) { assert.True(t, p.SelfServiceFlowRecoveryNotifyUnknownRecipients(ctx)) assert.True(t, p.SelfServiceFlowVerificationNotifyUnknownRecipients(ctx)) }, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { p := tc.init() if tc.expect != nil { tc.expect(t, p) return } assert.False(t, p.SelfServiceFlowRecoveryEnabled(ctx)) assert.False(t, p.SelfServiceFlowVerificationEnabled(ctx)) assert.True(t, p.SelfServiceStrategy(ctx, "password").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "profile").Enabled) assert.False(t, p.SelfServiceStrategy(ctx, "link").Enabled) assert.True(t, p.SelfServiceStrategy(ctx, "code").Enabled) assert.False(t, p.SelfServiceStrategy(ctx, "oidc").Enabled) assert.False(t, p.SelfServiceCodeStrategy(ctx).PasswordlessEnabled) assert.False(t, p.SelfServiceFlowRecoveryNotifyUnknownRecipients(ctx)) assert.False(t, p.SelfServiceFlowVerificationNotifyUnknownRecipients(ctx)) }) } t.Run("suite=ui_url", func(t *testing.T) { p := config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) assert.Equal(t, "https://www.ory.sh/kratos/docs/fallback/login", p.SelfServiceFlowLoginUI(ctx).String()) assert.Equal(t, "https://www.ory.sh/kratos/docs/fallback/settings", p.SelfServiceFlowSettingsUI(ctx).String()) assert.Equal(t, "https://www.ory.sh/kratos/docs/fallback/registration", p.SelfServiceFlowRegistrationUI(ctx).String()) assert.Equal(t, "https://www.ory.sh/kratos/docs/fallback/recovery", p.SelfServiceFlowRecoveryUI(ctx).String()) assert.Equal(t, "https://www.ory.sh/kratos/docs/fallback/verification", p.SelfServiceFlowVerificationUI(ctx).String()) }) } func TestViperProvider_ReturnTo(t *testing.T) { t.Parallel() ctx := context.Background() l := logrusx.New("", "") p := config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) p.MustSet(ctx, config.ViperKeySelfServiceBrowserDefaultReturnTo, "https://www.ory.sh/") assert.Equal(t, "https://www.ory.sh/", p.SelfServiceFlowVerificationReturnTo(ctx, urlx.ParseOrPanic("https://www.ory.sh/")).String()) assert.Equal(t, "https://www.ory.sh/", p.SelfServiceFlowRecoveryReturnTo(ctx, urlx.ParseOrPanic("https://www.ory.sh/")).String()) p.MustSet(ctx, config.ViperKeySelfServiceRecoveryBrowserDefaultReturnTo, "https://www.ory.sh/recovery") assert.Equal(t, "https://www.ory.sh/recovery", p.SelfServiceFlowRecoveryReturnTo(ctx, urlx.ParseOrPanic("https://www.ory.sh/")).String()) p.MustSet(ctx, config.ViperKeySelfServiceVerificationBrowserDefaultReturnTo, "https://www.ory.sh/verification") assert.Equal(t, "https://www.ory.sh/verification", p.SelfServiceFlowVerificationReturnTo(ctx, urlx.ParseOrPanic("https://www.ory.sh/")).String()) } func TestSession(t *testing.T) { t.Parallel() ctx := context.Background() l := logrusx.New("", "") p := config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) assert.Equal(t, "ory_kratos_session", p.SessionName(ctx)) p.MustSet(ctx, config.ViperKeySessionName, "ory_session") assert.Equal(t, "ory_session", p.SessionName(ctx)) assert.Equal(t, time.Hour*24, p.SessionRefreshMinTimeLeft(ctx)) p.MustSet(ctx, config.ViperKeySessionRefreshMinTimeLeft, "1m") assert.Equal(t, time.Minute, p.SessionRefreshMinTimeLeft(ctx)) assert.Equal(t, time.Hour*24, p.SessionLifespan(ctx)) p.MustSet(ctx, config.ViperKeySessionLifespan, "1m") assert.Equal(t, time.Minute, p.SessionLifespan(ctx)) assert.Equal(t, true, p.SessionPersistentCookie(ctx)) p.MustSet(ctx, config.ViperKeySessionPersistentCookie, false) assert.Equal(t, false, p.SessionPersistentCookie(ctx)) assert.Equal(t, false, p.SessionWhoAmICaching(ctx)) p.MustSet(ctx, config.ViperKeySessionWhoAmICaching, true) assert.Equal(t, true, p.SessionWhoAmICaching(ctx)) } func TestCookies(t *testing.T) { t.Parallel() ctx := context.Background() l := logrusx.New("", "") p := config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) t.Run("path", func(t *testing.T) { assert.Equal(t, "/", p.CookiePath(ctx)) assert.Equal(t, "/", p.SessionPath(ctx)) p.MustSet(ctx, config.ViperKeyCookiePath, "/cookie") assert.Equal(t, "/cookie", p.CookiePath(ctx)) assert.Equal(t, "/cookie", p.SessionPath(ctx)) p.MustSet(ctx, config.ViperKeySessionPath, "/session") assert.Equal(t, "/cookie", p.CookiePath(ctx)) assert.Equal(t, "/session", p.SessionPath(ctx)) }) t.Run("SameSite", func(t *testing.T) { assert.Equal(t, http.SameSiteLaxMode, p.CookieSameSiteMode(ctx)) assert.Equal(t, http.SameSiteLaxMode, p.SessionSameSiteMode(ctx)) p.MustSet(ctx, config.ViperKeyCookieSameSite, "Strict") assert.Equal(t, http.SameSiteStrictMode, p.CookieSameSiteMode(ctx)) assert.Equal(t, http.SameSiteStrictMode, p.SessionSameSiteMode(ctx)) p.MustSet(ctx, config.ViperKeySessionSameSite, "None") assert.Equal(t, http.SameSiteStrictMode, p.CookieSameSiteMode(ctx)) assert.Equal(t, http.SameSiteNoneMode, p.SessionSameSiteMode(ctx)) }) t.Run("domain", func(t *testing.T) { assert.Equal(t, "", p.CookieDomain(ctx)) assert.Equal(t, "", p.SessionDomain(ctx)) p.MustSet(ctx, config.ViperKeyCookieDomain, "www.cookie.com") assert.Equal(t, "www.cookie.com", p.CookieDomain(ctx)) assert.Equal(t, "www.cookie.com", p.SessionDomain(ctx)) p.MustSet(ctx, config.ViperKeySessionDomain, "www.session.com") assert.Equal(t, "www.cookie.com", p.CookieDomain(ctx)) assert.Equal(t, "www.session.com", p.SessionDomain(ctx)) }) } func TestViperProvider_DSN(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=dsn: memory", func(t *testing.T) { p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) p.MustSet(ctx, config.ViperKeyDSN, "memory") assert.Equal(t, config.DefaultSQLiteMemoryDSN, p.DSN(ctx)) }) t.Run("case=dsn: not memory", func(t *testing.T) { p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) dsn := "sqlite://foo.db?_fk=true" p.MustSet(ctx, config.ViperKeyDSN, dsn) assert.Equal(t, dsn, p.DSN(ctx)) }) t.Run("case=dsn: not set", func(t *testing.T) { dsn := "" var exitCode int l := logrusx.New("", "", logrusx.WithExitFunc(func(i int) { exitCode = i })) p := config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) assert.Equal(t, dsn, p.DSN(ctx)) assert.NotEqual(t, 0, exitCode) }) } func TestViperProvider_ParseURIOrFail(t *testing.T) { t.Parallel() ctx := context.Background() var exitCode int l := logrusx.New("", "", logrusx.WithExitFunc(func(i int) { exitCode = i })) p := config.MustNew(t, l, &contextx.Default{}, configx.SkipValidation()) require.Zero(t, exitCode) const testKey = "testKeyNotUsedInTheRealSchema" for _, tc := range []struct { u string expected url.URL }{ { u: "file:///etc/config/kratos/identity.schema.json", expected: url.URL{ Scheme: "file", Path: "/etc/config/kratos/identity.schema.json", }, }, { u: "file://./identity.schema.json", expected: url.URL{ Scheme: "file", Host: ".", Path: "/identity.schema.json", }, }, { u: "base64://bG9jYWwgc3ViamVjdCA9I", expected: url.URL{ Scheme: "base64", Host: "bG9jYWwgc3ViamVjdCA9I", }, }, { u: "https://foo.bar/schema.json", expected: url.URL{ Scheme: "https", Host: "foo.bar", Path: "/schema.json", }, }, } { t.Run("case=parse "+tc.u, func(t *testing.T) { require.NoError(t, p.Set(ctx, testKey, tc.u)) u := p.ParseURIOrFail(ctx, testKey) require.Zero(t, exitCode) assert.Equal(t, tc.expected, *u) }) } } func TestViperProvider_HaveIBeenPwned(t *testing.T) { t.Parallel() ctx := context.Background() p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) t.Run("case=hipb: host", func(t *testing.T) { p.MustSet(ctx, config.ViperKeyPasswordHaveIBeenPwnedHost, "foo.bar") assert.Equal(t, "foo.bar", p.PasswordPolicyConfig(ctx).HaveIBeenPwnedHost) }) t.Run("case=hibp: enabled", func(t *testing.T) { p.MustSet(ctx, config.ViperKeyPasswordHaveIBeenPwnedEnabled, true) assert.Equal(t, true, p.PasswordPolicyConfig(ctx).HaveIBeenPwnedEnabled) }) t.Run("case=hibp: enabled", func(t *testing.T) { p.MustSet(ctx, config.ViperKeyPasswordHaveIBeenPwnedEnabled, false) assert.Equal(t, false, p.PasswordPolicyConfig(ctx).HaveIBeenPwnedEnabled) }) t.Run("case=hibp: max_breaches", func(t *testing.T) { p.MustSet(ctx, config.ViperKeyPasswordMaxBreaches, 10) assert.Equal(t, uint(10), p.PasswordPolicyConfig(ctx).MaxBreaches) }) t.Run("case=hibp: ignore_network_errors", func(t *testing.T) { p.MustSet(ctx, config.ViperKeyIgnoreNetworkErrors, true) assert.Equal(t, true, p.PasswordPolicyConfig(ctx).IgnoreNetworkErrors) }) t.Run("case=hibp: ignore_network_errors", func(t *testing.T) { p.MustSet(ctx, config.ViperKeyIgnoreNetworkErrors, false) assert.Equal(t, false, p.PasswordPolicyConfig(ctx).IgnoreNetworkErrors) }) } func newTestConfig(t *testing.T, opts ...configx.OptionModifier) (c *config.Config, l *logrusx.Logger, h *test.Hook, exited *bool) { l = logrusx.New("", "") h = new(test.Hook) exited = new(bool) l.Logger.Hooks.Add(h) l.Logger.ExitFunc = func(code int) { *exited = true } c = config.MustNew(t, l, &contextx.Default{}, append([]configx.OptionModifier{configx.SkipValidation()}, opts...)...) return } func TestLoadingTLSConfig(t *testing.T) { t.Parallel() certPath, keyPath, certBase64, keyBase64 := testhelpers.GenerateTLSCertificateFilesForTests(t) t.Run("case=public: no TLS config", func(t *testing.T) { p, l, hook, exited := newTestConfig(t) certFunc, err := p.ServePublic(t.Context()).TLS.GetCertFunc(t.Context(), l, "public") require.NoError(t, err) assert.Nil(t, certFunc) le := hook.LastEntry() require.NotNil(t, le) assert.Equal(t, "TLS has not been configured for public, skipping", le.Message) assert.False(t, *exited) }) t.Run("case=admin: no TLS config", func(t *testing.T) { p, l, hook, exited := newTestConfig(t) certFunc, err := p.ServeAdmin(t.Context()).TLS.GetCertFunc(t.Context(), l, "admin") require.NoError(t, err) assert.Nil(t, certFunc) le := hook.LastEntry() require.NotNil(t, le) assert.Equal(t, "TLS has not been configured for admin, skipping", le.Message) assert.False(t, *exited) }) t.Run("case=public: loading inline base64 certificate", func(t *testing.T) { p, l, hook, exited := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyPublicTLSKeyBase64: keyBase64, keyPublicTLSCertBase64: certBase64, })) certFunc, err := p.ServePublic(t.Context()).TLS.GetCertFunc(t.Context(), l, "public") require.NoError(t, err) assert.NotNil(t, certFunc) le := hook.LastEntry() require.NotNil(t, le) assert.Equal(t, "Setting up HTTPS for public", le.Message) assert.False(t, *exited) }) t.Run("case=public: loading certificate from a file", func(t *testing.T) { p, l, hook, exited := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyPublicTLSKeyPath: keyPath, keyPublicTLSCertPath: certPath, })) certFunc, err := p.ServePublic(t.Context()).TLS.GetCertFunc(t.Context(), l, "public") require.NoError(t, err) assert.NotNil(t, certFunc) le := hook.LastEntry() require.NotNil(t, le) assert.Equal(t, "Setting up HTTPS for public (automatic certificate reloading active)", le.Message) assert.False(t, *exited) }) t.Run("case=public: failing to load inline base64 certificate", func(t *testing.T) { p, l, _, _ := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyPublicTLSKeyBase64: "invalid", keyPublicTLSCertBase64: certBase64, })) certFunc, err := p.ServePublic(t.Context()).TLS.GetCertFunc(t.Context(), l, "public") require.ErrorContains(t, err, "unable to load TLS certificate for interface public") assert.Nil(t, certFunc) }) t.Run("case=public: failing to load certificate from a file", func(t *testing.T) { p, l, _, _ := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyPublicTLSKeyPath: "/dev/null", keyPublicTLSCertPath: "/dev/null", })) certFunc, err := p.ServePublic(t.Context()).TLS.GetCertFunc(t.Context(), l, "public") require.ErrorContains(t, err, "unable to load TLS certificate for interface public") assert.Nil(t, certFunc) }) t.Run("case=admin: loading inline base64 certificate", func(t *testing.T) { p, l, hook, exited := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyAdminTLSKeyBase64: keyBase64, keyAdminTLSCertBase64: certBase64, })) certFunc, err := p.ServeAdmin(t.Context()).TLS.GetCertFunc(t.Context(), l, "admin") require.NoError(t, err) assert.NotNil(t, certFunc) le := hook.LastEntry() require.NotNil(t, le) assert.Equal(t, "Setting up HTTPS for admin", le.Message) assert.False(t, *exited) }) t.Run("case=admin: loading certificate from a file", func(t *testing.T) { p, l, hook, exited := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyAdminTLSKeyPath: keyPath, keyAdminTLSCertPath: certPath, })) certFunc, err := p.ServeAdmin(t.Context()).TLS.GetCertFunc(t.Context(), l, "admin") require.NoError(t, err) assert.NotNil(t, certFunc) le := hook.LastEntry() require.NotNil(t, le) assert.Equal(t, "Setting up HTTPS for admin (automatic certificate reloading active)", le.Message) assert.False(t, *exited) }) t.Run("case=admin: failing to load inline base64 certificate", func(t *testing.T) { p, l, _, _ := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyAdminTLSKeyBase64: "invalid", keyAdminTLSCertBase64: certBase64, })) certFunc, err := p.ServeAdmin(t.Context()).TLS.GetCertFunc(t.Context(), l, "admin") assert.Nil(t, certFunc) require.ErrorContains(t, err, "unable to load TLS certificate for interface admin") }) t.Run("case=admin: failing to load certificate from a file", func(t *testing.T) { p, l, _, _ := newTestConfig(t, configx.WithValues(map[string]interface{}{ keyAdminTLSKeyPath: "/dev/null", keyAdminTLSCertPath: certPath, })) certFunc, err := p.ServeAdmin(t.Context()).TLS.GetCertFunc(t.Context(), l, "admin") require.ErrorContains(t, err, "unable to load TLS certificate for interface admin") assert.Nil(t, certFunc) }) } func TestIdentitySchemaValidation(t *testing.T) { t.Parallel() files := []string{"stub/.identity.test.json", "stub/.identity.other.json"} ctx := context.Background() ctx = config.SetValidateIdentitySchemaResilientClientOptions(ctx, []httpx.ResilientOptions{ httpx.ResilientClientWithMaxRetry(0), httpx.ResilientClientWithConnectionTimeout(time.Millisecond * 100), }) type identity struct { Schemas []map[string]string `json:"schemas"` } type configFile struct { identityFileName string SelfService map[string]string `json:"selfservice"` Courier map[string]map[string]string `json:"courier"` DSN string `json:"dsn"` Identity *identity `json:"identity"` } setup := func(t *testing.T, file string) *configFile { identityTest, err := os.ReadFile(file) // #nosec G304 -- test code assert.NoError(t, err) return &configFile{ identityFileName: file, SelfService: map[string]string{ "default_browser_return_url": "https://some-return-url", }, Courier: map[string]map[string]string{ "smtp": { "connection_uri": "smtp://foo@bar", }, }, DSN: "memory", Identity: &identity{ Schemas: []map[string]string{{"id": "default", "url": "base64://" + base64.StdEncoding.EncodeToString(identityTest)}}, }, } } marshalAndWrite := func(t *testing.T, tmpFile *os.File, identity *configFile) { j, err := yaml.Marshal(identity) assert.NoError(t, err) _, err = tmpFile.Seek(0, 0) require.NoError(t, err) require.NoError(t, tmpFile.Truncate(0)) _, err = io.Writer.Write(tmpFile, j) assert.NoError(t, err) assert.NoError(t, tmpFile.Sync()) } testWatch := func(t *testing.T, ctx context.Context, identity *configFile) (*config.Config, *test.Hook, func([]map[string]string)) { tdir := t.TempDir() assert.NoError(t, os.MkdirAll(tdir, 0o750)) configFileName := randx.MustString(8, randx.Alpha) tmpConfig, err := os.Create(filepath.Join(tdir, configFileName+".config.yaml")) // #nosec G304 -- test code assert.NoError(t, err) t.Cleanup(func() { _ = tmpConfig.Close() }) marshalAndWrite(t, tmpConfig, identity) l := logrusx.New("kratos-"+tmpConfig.Name(), "test") hook := test.NewLocal(l.Logger) conf, err := config.New(ctx, l, os.Stderr, &contextx.Default{}, configx.WithConfigFiles(tmpConfig.Name())) assert.NoError(t, err) // clean the hooks since it will throw an event on first boot hook.Reset() return conf, hook, func(schemas []map[string]string) { identity.Identity.Schemas = schemas marshalAndWrite(t, tmpConfig, identity) } } t.Run("case=skip invalid schema validation", func(t *testing.T) { _, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.invalid.identities.yaml"), configx.SkipValidation()) assert.NoError(t, err) }) t.Run("case=invalid schema should throw error", func(t *testing.T) { var stdErr bytes.Buffer _, err := config.New(ctx, logrusx.New("", ""), &stdErr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.invalid.identities.yaml")) assert.Error(t, err) assert.Contains(t, err.Error(), "minimum 1 properties allowed, but found 0") assert.Contains(t, stdErr.String(), "minimum 1 properties allowed, but found 0") }) t.Run("case=must fail on loading unreachable schemas", func(t *testing.T) { // we make sure that the test runs into DNS issues instead of the context being canceled ctx := config.SetValidateIdentitySchemaResilientClientOptions(ctx, []httpx.ResilientOptions{ httpx.ResilientClientWithMaxRetry(0), httpx.ResilientClientWithConnectionTimeout(5 * time.Second), }) err := make(chan error) go func(err chan error) { _, e := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.mock.identities.yaml")) err <- e }(err) select { case <-time.After(5 * time.Second): t.Fatal("the test could not complete as the context timed out before the identity schema loader timed out") case e := <-err: assert.ErrorContains(t, e, "no such host") } }) t.Run("case=validate schema is validated on file change", func(t *testing.T) { var identities []*configFile for _, f := range files { identities = append(identities, setup(t, f)) } invalidIdentity := setup(t, "stub/.identity.invalid.json") for _, identity := range identities { t.Run("test=identity file "+identity.identityFileName, func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second*30) t.Cleanup(cancel) _, hook, writeSchema := testWatch(t, ctx, identity) writeSchema(invalidIdentity.Identity.Schemas) // There are a bunch of log messages beeing logged. We are looking for a specific one. for { for _, v := range hook.AllEntries() { s, err := v.String() require.NoError(t, err) if strings.Contains(s, "The changed identity schema configuration is invalid and could not be loaded.") { return } } select { case <-ctx.Done(): t.Fatal("the test could not complete as the context timed out before the file watcher updated") default: // nothing } } }) } }) } func TestPasswordless(t *testing.T) { t.Parallel() ctx := context.Background() conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation(), configx.WithValue(config.ViperKeyWebAuthnPasswordless, true)) require.NoError(t, err) assert.True(t, conf.WebAuthnForPasswordless(ctx)) conf.MustSet(ctx, config.ViperKeyWebAuthnPasswordless, false) assert.False(t, conf.WebAuthnForPasswordless(ctx)) } func TestPasswordlessCode(t *testing.T) { t.Parallel() ctx := context.Background() conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation(), configx.WithValue(config.ViperKeySelfServiceStrategyConfig+".code", map[string]interface{}{ "passwordless_enabled": true, "passwordless_login_fallback_enabled": true, "config": map[string]interface{}{}, })) require.NoError(t, err) assert.True(t, conf.SelfServiceCodeStrategy(ctx).PasswordlessEnabled) } func TestChangeMinPasswordLength(t *testing.T) { t.Parallel() t.Run("case=must fail on minimum password length below enforced minimum", func(t *testing.T) { ctx := context.Background() _, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.yaml"), configx.WithValue(config.ViperKeyPasswordMinLength, 5)) assert.Error(t, err) }) t.Run("case=must not fail on minimum password length above enforced minimum", func(t *testing.T) { ctx := context.Background() _, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.yaml"), configx.WithValue(config.ViperKeyPasswordMinLength, 9)) assert.NoError(t, err) }) } func TestCourierEmailHTTP(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=configs set", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.courier.email.http.yaml"), configx.SkipValidation()) assert.Equal(t, "http", conf.CourierEmailStrategy(ctx)) snapshotx.SnapshotT(t, conf.CourierEmailRequestConfig(ctx)) }) t.Run("case=defaults", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation()) assert.Equal(t, "smtp", conf.CourierEmailStrategy(ctx)) }) } func TestCourierChannels(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=configs set", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.courier.channels.yaml"), configx.SkipValidation()) channelConfig, err := conf.CourierChannels(ctx) require.NoError(t, err) require.Len(t, channelConfig, 2) assert.Equal(t, channelConfig[0].ID, "phone") assert.NotEmpty(t, channelConfig[0].RequestConfig) assert.Equal(t, channelConfig[1].ID, "email") assert.NotEmpty(t, channelConfig[1].SMTPConfig) }) t.Run("case=defaults", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation()) channelConfig, err := conf.CourierChannels(ctx) require.NoError(t, err) assert.Len(t, channelConfig, 1) assert.Equal(t, channelConfig[0].ID, "email") assert.Equal(t, channelConfig[0].Type, "smtp") }) t.Run("smtp urls", func(t *testing.T) { for _, tc := range []string{ "smtp://a:basdasdasda%2Fc@email-smtp.eu-west-3.amazonaws.com:587/", "smtp://a:b$c@email-smtp.eu-west-3.amazonaws.com:587/", "smtp://a/a:bc@email-smtp.eu-west-3.amazonaws.com:587", "smtp://aa:b+c@email-smtp.eu-west-3.amazonaws.com:587/", "smtp://user?name:password@email-smtp.eu-west-3.amazonaws.com:587/", "smtp://username:pass%2Fword@email-smtp.eu-west-3.amazonaws.com:587/", } { t.Run("case="+tc, func(t *testing.T) { conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithValue(config.ViperKeyCourierSMTPURL, tc), configx.SkipValidation()) require.NoError(t, err) cs, err := conf.CourierChannels(ctx) require.NoError(t, err) require.Len(t, cs, 1) assert.Equal(t, tc, cs[0].SMTPConfig.ConnectionURI) }) } }) } func TestCourierMessageTTL(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=configs set", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.courier.message_retries.yaml"), configx.SkipValidation()) assert.Equal(t, conf.CourierMessageRetries(ctx), 10) }) t.Run("case=defaults", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation()) assert.Equal(t, conf.CourierMessageRetries(ctx), 5) }) } func TestTwoStep(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=nothing is set", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation()) assert.True(t, conf.SelfServiceFlowRegistrationTwoSteps(ctx)) }) t.Run("case=legacy config explicit off", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithValue(config.ViperKeySelfServiceRegistrationEnableLegacyOneStep, false), configx.SkipValidation(), ) assert.True(t, conf.SelfServiceFlowRegistrationTwoSteps(ctx)) }) t.Run("case=legacy config explicit on", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithValue(config.ViperKeySelfServiceRegistrationEnableLegacyOneStep, true), configx.SkipValidation(), ) assert.False(t, conf.SelfServiceFlowRegistrationTwoSteps(ctx)) }) t.Run("case=new config explicit on", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithValue(config.ViperKeySelfServiceRegistrationFlowStyle, "profile_first"), configx.SkipValidation(), ) assert.True(t, conf.SelfServiceFlowRegistrationTwoSteps(ctx)) }) t.Run("case=new config explicit off", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithValue(config.ViperKeySelfServiceRegistrationFlowStyle, "unified"), configx.SkipValidation(), ) assert.False(t, conf.SelfServiceFlowRegistrationTwoSteps(ctx)) }) t.Run("case=new config explicit on but legacy off", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithValue(config.ViperKeySelfServiceRegistrationFlowStyle, "profile_first"), configx.WithValue(config.ViperKeySelfServiceRegistrationEnableLegacyOneStep, true), configx.SkipValidation(), ) assert.False(t, conf.SelfServiceFlowRegistrationTwoSteps(ctx)) }) } func TestOAuth2Provider(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=configs set", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.oauth2_provider.yaml"), configx.SkipValidation()) assert.Equal(t, "https://oauth2_provider/", conf.OAuth2ProviderURL(ctx).String()) assert.Equal(t, http.Header{"Authorization": {"Basic"}}, conf.OAuth2ProviderHeader(ctx)) assert.True(t, conf.OAuth2ProviderOverrideReturnTo(ctx)) }) t.Run("case=defaults", func(t *testing.T) { conf, _ := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.SkipValidation()) assert.Empty(t, conf.OAuth2ProviderURL(ctx)) assert.Empty(t, conf.OAuth2ProviderHeader(ctx)) assert.False(t, conf.OAuth2ProviderOverrideReturnTo(ctx)) }) } func TestWebauthn(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=multiple origins", func(t *testing.T) { conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.webauthn.origins.yaml")) require.NoError(t, err) webAuthnConfig := conf.WebAuthnConfig(ctx) assert.Equal(t, "https://example.com/webauthn", webAuthnConfig.RPID) assert.EqualValues(t, []string{ "https://origin-a.example.com", "https://origin-b.example.com", "https://origin-c.example.com", }, webAuthnConfig.RPOrigins) }) t.Run("case=one origin", func(t *testing.T) { conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.webauthn.origin.yaml")) require.NoError(t, err) webAuthnConfig := conf.WebAuthnConfig(ctx) assert.Equal(t, "https://example.com/webauthn", webAuthnConfig.RPID) assert.EqualValues(t, []string{ "https://origin-a.example.com", }, webAuthnConfig.RPOrigins) }) t.Run("case=id as origin", func(t *testing.T) { conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.yaml")) require.NoError(t, err) webAuthnConfig := conf.WebAuthnConfig(ctx) assert.Equal(t, "example.com", webAuthnConfig.RPID) assert.EqualValues(t, []string{ "http://example.com", }, webAuthnConfig.RPOrigins) }) t.Run("case=invalid", func(t *testing.T) { _, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.webauthn.invalid.yaml")) assert.Error(t, err) }) } func TestCourierTemplatesConfig(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=partial template update allowed", func(t *testing.T) { _, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.courier.remote.partial.templates.yaml")) assert.NoError(t, err) }) t.Run("case=load remote template with fallback template overrides path", func(t *testing.T) { _, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.courier.remote.templates.yaml")) assert.NoError(t, err) }) t.Run("case=courier template helper", func(t *testing.T) { c, err := config.New(ctx, logrusx.New("", ""), os.Stderr, &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.courier.remote.templates.yaml")) assert.NoError(t, err) courierTemplateConfig := &config.CourierEmailTemplate{ Body: &config.CourierEmailBodyTemplate{ PlainText: "", HTML: "", }, Subject: "", } assert.Equal(t, courierTemplateConfig, c.CourierEmailTemplatesHelper(ctx, config.ViperKeyCourierTemplatesVerificationInvalidEmail)) assert.Equal(t, courierTemplateConfig, c.CourierEmailTemplatesHelper(ctx, config.ViperKeyCourierTemplatesVerificationValidEmail)) // this should return an empty courierEmailTemplate as the key does not exist assert.Equal(t, courierTemplateConfig, c.CourierEmailTemplatesHelper(ctx, "a_random_key")) courierTemplateConfig = &config.CourierEmailTemplate{ Body: &config.CourierEmailBodyTemplate{ PlainText: "base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu", HTML: "base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu", }, Subject: "base64://QWNjb3VudCBBY2Nlc3MgQXR0ZW1wdGVk", } assert.Equal(t, courierTemplateConfig, c.CourierEmailTemplatesHelper(ctx, config.ViperKeyCourierTemplatesRecoveryInvalidEmail)) courierTemplateConfig = &config.CourierEmailTemplate{ Body: &config.CourierEmailBodyTemplate{ PlainText: "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQp7eyAuUmVjb3ZlcnlVUkwgfX0K", HTML: "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4=", }, Subject: "base64://UmVjb3ZlciBhY2Nlc3MgdG8geW91ciBhY2NvdW50", } assert.Equal(t, courierTemplateConfig, c.CourierEmailTemplatesHelper(ctx, config.ViperKeyCourierTemplatesRecoveryValidEmail)) }) } func TestCleanup(t *testing.T) { t.Parallel() ctx := context.Background() p := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.WithConfigFiles("stub/.kratos.yaml")) t.Run("group=cleanup config", func(t *testing.T) { assert.Equal(t, p.DatabaseCleanupSleepTables(ctx), 1*time.Minute) p.MustSet(ctx, config.ViperKeyDatabaseCleanupSleepTables, "1s") assert.Equal(t, p.DatabaseCleanupSleepTables(ctx), time.Second) assert.Equal(t, p.DatabaseCleanupBatchSize(ctx), 100) p.MustSet(ctx, config.ViperKeyDatabaseCleanupBatchSize, "1") assert.Equal(t, p.DatabaseCleanupBatchSize(ctx), 1) }) } const ( keyPublicTLSCertBase64 = "serve.public.tls.cert.base64" keyPublicTLSKeyBase64 = "serve.public.tls.key.base64" keyPublicTLSCertPath = "serve.public.tls.cert.path" keyPublicTLSKeyPath = "serve.public.tls.key.path" keyAdminTLSCertBase64 = "serve.admin.tls.cert.base64" keyAdminTLSKeyBase64 = "serve.admin.tls.key.base64" keyAdminTLSCertPath = "serve.admin.tls.cert.path" keyAdminTLSKeyPath = "serve.admin.tls.key.path" ) ================================================ FILE: driver/config/handler.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package config import ( "crypto/sha256" "fmt" "net/http" "github.com/knadh/koanf/parsers/json" "github.com/ory/x/httprouterx" ) func RegisterConfigHashRoute(c Provider, router *httprouterx.RouterAdmin) { router.GET("/health/config", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") if revision := c.Config().GetProvider(r.Context()).String("revision"); len(revision) > 0 { _, _ = fmt.Fprintf(w, "%s", revision) } else { bytes, _ := c.Config().GetProvider(r.Context()).Marshal(json.Parser()) _, _ = fmt.Fprintf(w, "%x", sha256.Sum256(bytes)) } }) } ================================================ FILE: driver/config/handler_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package config_test import ( "context" "io" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/x/contextx" "github.com/ory/x/httprouterx" ) type configProvider struct { cfg *config.Config } func (c *configProvider) Config() *config.Config { return c.cfg } func TestNewConfigHashHandler(t *testing.T) { ctx := context.Background() cfg := pkg.NewConfigurationWithDefaults(t) router := httprouterx.NewTestRouterAdmin(t) config.RegisterConfigHashRoute(&configProvider{cfg: cfg}, router) ts := contextx.NewConfigurableTestServer(router) t.Cleanup(ts.Close) // first request, get baseline hash res, err := ts.Client(ctx).Get(ts.URL + "/health/config") require.NoError(t, err) defer func() { _ = res.Body.Close() }() require.Equal(t, 200, res.StatusCode) first, err := io.ReadAll(res.Body) require.NoError(t, err) // second request, no config change res, err = ts.Client(ctx).Get(ts.URL + "/health/config") require.NoError(t, err) defer func() { _ = res.Body.Close() }() require.Equal(t, 200, res.StatusCode) second, err := io.ReadAll(res.Body) require.NoError(t, err) assert.Equal(t, first, second) // third request, with config change res, err = ts.Client(contextx.WithConfigValue(ctx, config.ViperKeySessionDomain, "foobar")).Get(ts.URL + "/health/config") require.NoError(t, err) defer func() { _ = res.Body.Close() }() require.Equal(t, 200, res.StatusCode) third, err := io.ReadAll(res.Body) require.NoError(t, err) assert.NotEqual(t, first, third) // fourth request, no config change res, err = ts.Client(ctx).Get(ts.URL + "/health/config") require.NoError(t, err) defer func() { _ = res.Body.Close() }() require.Equal(t, 200, res.StatusCode) fourth, err := io.ReadAll(res.Body) require.NoError(t, err) assert.Equal(t, first, fourth) } ================================================ FILE: driver/config/stub/.defaults-password.yml ================================================ selfservice: methods: password: enabled: true flows: recovery: false ================================================ FILE: driver/config/stub/.defaults-verification.yml ================================================ selfservice: methods: password: enabled: true flows: settings: privileged_session_max_age: 1m after: hooks: profile: - hook: show_verification_ui verification: enabled: true lifespan: 5s after: default_browser_return_url: http://127.0.0.1:4455/ logout: after: default_browser_return_url: http://127.0.0.1:4455/auth/login identity: default_schema_id: default schemas: - id: default url: file://test/e2e/profiles/verification/identity.traits.schema.json ================================================ FILE: driver/config/stub/.defaults.yml ================================================ selfservice: ================================================ FILE: driver/config/stub/.identity.invalid.json ================================================ { "$id": "ory://identity-invalid-schema", "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdentityInvalidSchema", "type": "object", "properties": { "traits": { "type": "object", "properties": {}, "required": [ "other", "email" ], "additionalProperties": true } } } ================================================ FILE: driver/config/stub/.identity.other.json ================================================ { "$id": "ory://identity-other-schema", "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdentityOtherSchema", "type": "object", "properties": { "traits": { "type": "object", "properties": { "other": { "type": "string" }, "email": { "type": "string", "title": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } } }, "required": [ "other", "email" ], "additionalProperties": true } } } ================================================ FILE: driver/config/stub/.identity.test.json ================================================ { "$id": "ory://identity-test-schema", "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdentitySchema", "type": "object", "properties": { "traits": { "type": "object", "properties": { "name": { "type": "object", "properties": { "first": { "type": "string" }, "last": { "type": "string" } } } }, "required": [ "name" ], "additionalProperties": true } } } ================================================ FILE: driver/config/stub/.kratos.courier.channels.yaml ================================================ courier: smtp: connection_uri: smtp://username:password@smtp.example.com:587 channels: - id: phone request_config: url: https://ory.sh method: GET body: base64://ZnVuY3Rpb24oY3R4KSB7CkJvZHk6IGN0eC5ib2R5LApUbzogY3R4LnRvLEZyb206IGN0eC5mcm9tCn0= headers: Content-Type: application/x-www-form-urlencoded auth: type: basic_auth config: user: ABC password: DEF ================================================ FILE: driver/config/stub/.kratos.courier.email.http.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: https://example.com/return_to identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 courier: delivery_strategy: http http: request_config: url: https://example.com/email body: file://some.jsonnet header: 'Content-Type': 'application/json' auth: type: basic_auth config: user: YourUsername password: YourPass ================================================ FILE: driver/config/stub/.kratos.courier.message_retries.yaml ================================================ courier: message_retries: 10 ================================================ FILE: driver/config/stub/.kratos.courier.remote.invalid.subject.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 courier: smtp: connection_uri: smtp://stub-url templates: recovery: invalid: email: body: html: base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu # omit subject to verify it is validated valid: # omit template_root as it is not required on valid email: body: plaintext: base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQp7eyAuUmVjb3ZlcnlVUkwgfX0K html: base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4= subject: base64://UmVjb3ZlciBhY2Nlc3MgdG8geW91ciBhY2NvdW50 # omit verification here to test templates override fallback template_override_path: "../../courier/template/courier/builtin/templates" ================================================ FILE: driver/config/stub/.kratos.courier.remote.partial.templates.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 courier: smtp: connection_uri: smtp://stub-url templates: recovery: invalid: email: body: plaintext: base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu html: base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu verification: valid: email: subject: base64://VmVyaWZpY2F0aW9uIEVtYWls template_override_path: "../../courier/template/courier/builtin/templates" ================================================ FILE: driver/config/stub/.kratos.courier.remote.templates.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 courier: smtp: connection_uri: smtp://stub-url templates: recovery: invalid: email: body: plaintext: base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu html: base64://SGksCgp5b3UgKG9yIHNvbWVvbmUgZWxzZSkgZW50ZXJlZCB0aGlzIGVtYWlsIGFkZHJlc3Mgd2hlbiB0cnlpbmcgdG8gcmVjb3ZlciBhY2Nlc3MgdG8gYW4gYWNjb3VudC4KCkhvd2V2ZXIsIHRoaXMgZW1haWwgYWRkcmVzcyBpcyBub3Qgb24gb3VyIGRhdGFiYXNlIG9mIHJlZ2lzdGVyZWQgdXNlcnMgYW5kIHRoZXJlZm9yZSB0aGUgYXR0ZW1wdCBoYXMgZmFpbGVkLgoKSWYgdGhpcyB3YXMgeW91LCBjaGVjayBpZiB5b3Ugc2lnbmVkIHVwIHVzaW5nIGEgZGlmZmVyZW50IGFkZHJlc3MuCgpJZiB0aGlzIHdhcyBub3QgeW91LCBwbGVhc2UgaWdub3JlIHRoaXMgZW1haWwu subject: base64://QWNjb3VudCBBY2Nlc3MgQXR0ZW1wdGVk valid: email: body: plaintext: base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQp7eyAuUmVjb3ZlcnlVUkwgfX0K html: base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4= subject: base64://UmVjb3ZlciBhY2Nlc3MgdG8geW91ciBhY2NvdW50 # omit verification here to test templates override fallback template_override_path: "../../courier/template/courier/builtin/templates" ================================================ FILE: driver/config/stub/.kratos.courier.sms.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 courier: smtp: connection_uri: smtp://foo:bar@baz/ sms: enabled: true from: '+49123456789' request_config: url: https://api.twilio.com/2010-04-01/Accounts/YourAccountID/Messages.json method: POST body: base64://e30= header: 'Content-Type': 'application/x-www-form-urlencoded' auth: type: basic_auth config: user: YourUsername password: YourPass ================================================ FILE: driver/config/stub/.kratos.invalid.identities.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ identity: default_schema_id: other schemas: - id: other url: file://stub/.identity.invalid.json courier: smtp: connection_uri: smtp://foo:bar@baz/ ================================================ FILE: driver/config/stub/.kratos.mock.identities.yaml ================================================ dsn: sqlite://foo.db?mode=memory&_fk=true selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ identity: default_schema_id: default schemas: - id: default url: http://test.kratos.ory.sh/default-identity.schema.json - id: other url: http://test.kratos.ory.sh/other-identity.schema.json courier: smtp: connection_uri: smtp://foo:bar@baz/ ================================================ FILE: driver/config/stub/.kratos.notify-unknown-recipients.yml ================================================ selfservice: flows: recovery: notify_unknown_recipients: true verification: notify_unknown_recipients: true ================================================ FILE: driver/config/stub/.kratos.oauth2_provider.yaml ================================================ oauth2_provider: url: https://oauth2_provider/ headers: Authorization: Basic override_return_to: true ================================================ FILE: driver/config/stub/.kratos.webauthn.invalid.yaml ================================================ # serve controls the configuration for the http(s) daemon serve: admin: base_url: http://admin.kratos.ory.sh port: 1234 host: admin.kratos.ory.sh public: base_url: http://public.kratos.ory.sh port: 1235 host: public.kratos.ory.sh dsn: sqlite://foo.db?mode=memory&_fk=true log: level: debug courier: smtp: connection_uri: smtp://foo:bar@baz/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 - id: other url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktb3RoZXItc2NoZW1hIiwKICAiJHNjaGVtYSI6ICJodHRwOi8vanNvbi1zY2hlbWEub3JnL2RyYWZ0LTA3L3NjaGVtYSMiLAogICJ0aXRsZSI6ICJJZGVudGl0eU90aGVyU2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm90aGVyIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgIH0sCiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJ0aXRsZSI6ICJlbWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJvdGhlciIsCiAgICAgICAgImVtYWlsIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 hashers: argon2: memory: 1MB iterations: 2 parallelism: 4 salt_length: 16 key_length: 32 dedicated_memory: 1GB expected_duration: 500ms expected_deviation: 500ms secrets: cookie: - session-key-7f8a9b77-1 - session-key-7f8a9b77-2 cipher: - secret-thirty-two-character-long ciphers: algorithm: xchacha20-poly1305 selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ allowed_return_urls: - http://return-to-1-test.ory.sh/ - http://return-to-2-test.ory.sh/ - http://*.wildcards.ory.sh - http://*.sh - http://*.com - http://*.com.pl - http://* - /return-to-relative-test/ methods: totp: enabled: true config: issuer: issuer.ory.sh password: enabled: true oidc: enabled: true config: providers: - id: github provider: github client_id: a client_secret: b mapper_url: http://test.kratos.ory.sh/default-identity.schema.json webauthn: enabled: true config: rp: id: https://example.com/webauthn display_name: Webauthn origin: https://origin-a.example.com origins: - https://origin-a.example.com - https://origin-b.example.com - https://origin-c.example.com flows: error: ui_url: http://test.kratos.ory.sh/error logout: after: default_browser_return_url: http://test.kratos.ory.sh:4000/ recovery: enabled: true ui_url: http://test.kratos.ory.sh/recovery lifespan: 98m after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_recovery_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet verification: enabled: true lifespan: 97m ui_url: http://test.kratos.ory.sh/verification after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_verification_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet settings: ui_url: http://test.kratos.ory.sh/settings lifespan: 99m privileged_session_max_age: 5m after: default_browser_return_url: https://self-service/settings/return_to password: default_browser_return_url: https://self-service/settings/password/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet profile: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_profile_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet login: ui_url: http://test.kratos.ory.sh/login lifespan: 99m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_login_hook method: POST headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/login/return_to password: default_browser_return_url: https://self-service/login/password/return_to hooks: - hook: revoke_active_sessions - hook: require_verified_address - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: basic_auth config: user: test-user password: super-secret oidc: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: revoke_active_sessions hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet registration: enabled: true ui_url: http://test.kratos.ory.sh/register lifespan: 98m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_registration_hook method: GET headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/registration/return_to password: hooks: - hook: session - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: api_key config: name: My-Key value: My-Key-Value in: header oidc: default_browser_return_url: https://self-service/registration/oidc/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: session ================================================ FILE: driver/config/stub/.kratos.webauthn.origin.yaml ================================================ # serve controls the configuration for the http(s) daemon serve: admin: base_url: http://admin.kratos.ory.sh port: 1234 host: admin.kratos.ory.sh public: base_url: http://public.kratos.ory.sh port: 1235 host: public.kratos.ory.sh dsn: sqlite://foo.db?mode=memory&_fk=true log: level: debug courier: smtp: connection_uri: smtp://foo:bar@baz/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 - id: other url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktb3RoZXItc2NoZW1hIiwKICAiJHNjaGVtYSI6ICJodHRwOi8vanNvbi1zY2hlbWEub3JnL2RyYWZ0LTA3L3NjaGVtYSMiLAogICJ0aXRsZSI6ICJJZGVudGl0eU90aGVyU2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm90aGVyIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgIH0sCiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJ0aXRsZSI6ICJlbWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJvdGhlciIsCiAgICAgICAgImVtYWlsIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 hashers: argon2: memory: 1MB iterations: 2 parallelism: 4 salt_length: 16 key_length: 32 dedicated_memory: 1GB expected_duration: 500ms expected_deviation: 500ms secrets: cookie: - session-key-7f8a9b77-1 - session-key-7f8a9b77-2 cipher: - secret-thirty-two-character-long ciphers: algorithm: xchacha20-poly1305 selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ allowed_return_urls: - http://return-to-1-test.ory.sh/ - http://return-to-2-test.ory.sh/ - http://*.wildcards.ory.sh - http://*.sh - http://*.com - http://*.com.pl - http://* - /return-to-relative-test/ methods: totp: enabled: true config: issuer: issuer.ory.sh password: enabled: true oidc: enabled: true config: providers: - id: github provider: github client_id: a client_secret: b mapper_url: http://test.kratos.ory.sh/default-identity.schema.json webauthn: enabled: true config: rp: id: https://example.com/webauthn display_name: Webauthn origin: https://origin-a.example.com flows: error: ui_url: http://test.kratos.ory.sh/error logout: after: default_browser_return_url: http://test.kratos.ory.sh:4000/ recovery: enabled: true ui_url: http://test.kratos.ory.sh/recovery lifespan: 98m after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_recovery_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet verification: enabled: true lifespan: 97m ui_url: http://test.kratos.ory.sh/verification after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_verification_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet settings: ui_url: http://test.kratos.ory.sh/settings lifespan: 99m privileged_session_max_age: 5m after: default_browser_return_url: https://self-service/settings/return_to password: default_browser_return_url: https://self-service/settings/password/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet profile: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_profile_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet login: ui_url: http://test.kratos.ory.sh/login lifespan: 99m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_login_hook method: POST headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/login/return_to password: default_browser_return_url: https://self-service/login/password/return_to hooks: - hook: revoke_active_sessions - hook: require_verified_address - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: basic_auth config: user: test-user password: super-secret oidc: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: revoke_active_sessions hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet registration: enabled: true ui_url: http://test.kratos.ory.sh/register lifespan: 98m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_registration_hook method: GET headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/registration/return_to password: hooks: - hook: session - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: api_key config: name: My-Key value: My-Key-Value in: header oidc: default_browser_return_url: https://self-service/registration/oidc/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: session ================================================ FILE: driver/config/stub/.kratos.webauthn.origins.yaml ================================================ # serve controls the configuration for the http(s) daemon serve: admin: base_url: http://admin.kratos.ory.sh port: 1234 host: admin.kratos.ory.sh public: base_url: http://public.kratos.ory.sh port: 1235 host: public.kratos.ory.sh dsn: sqlite://foo.db?mode=memory&_fk=true log: level: debug courier: smtp: connection_uri: smtp://foo:bar@baz/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 - id: other url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktb3RoZXItc2NoZW1hIiwKICAiJHNjaGVtYSI6ICJodHRwOi8vanNvbi1zY2hlbWEub3JnL2RyYWZ0LTA3L3NjaGVtYSMiLAogICJ0aXRsZSI6ICJJZGVudGl0eU90aGVyU2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm90aGVyIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgIH0sCiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJ0aXRsZSI6ICJlbWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJvdGhlciIsCiAgICAgICAgImVtYWlsIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 hashers: argon2: memory: 1MB iterations: 2 parallelism: 4 salt_length: 16 key_length: 32 dedicated_memory: 1GB expected_duration: 500ms expected_deviation: 500ms secrets: cookie: - session-key-7f8a9b77-1 - session-key-7f8a9b77-2 cipher: - secret-thirty-two-character-long ciphers: algorithm: xchacha20-poly1305 selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ allowed_return_urls: - http://return-to-1-test.ory.sh/ - http://return-to-2-test.ory.sh/ - http://*.wildcards.ory.sh - http://*.sh - http://*.com - http://*.com.pl - http://* - /return-to-relative-test/ methods: totp: enabled: true config: issuer: issuer.ory.sh password: enabled: true oidc: enabled: true config: providers: - id: github provider: github client_id: a client_secret: b mapper_url: http://test.kratos.ory.sh/default-identity.schema.json webauthn: enabled: true config: rp: id: https://example.com/webauthn display_name: Webauthn origins: - https://origin-a.example.com - https://origin-b.example.com - https://origin-c.example.com flows: error: ui_url: http://test.kratos.ory.sh/error logout: after: default_browser_return_url: http://test.kratos.ory.sh:4000/ recovery: enabled: true ui_url: http://test.kratos.ory.sh/recovery lifespan: 98m after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_recovery_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet verification: enabled: true lifespan: 97m ui_url: http://test.kratos.ory.sh/verification after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_verification_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet settings: ui_url: http://test.kratos.ory.sh/settings lifespan: 99m privileged_session_max_age: 5m after: default_browser_return_url: https://self-service/settings/return_to password: default_browser_return_url: https://self-service/settings/password/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet profile: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_profile_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet login: ui_url: http://test.kratos.ory.sh/login lifespan: 99m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_login_hook method: POST headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/login/return_to password: default_browser_return_url: https://self-service/login/password/return_to hooks: - hook: revoke_active_sessions - hook: require_verified_address - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: basic_auth config: user: test-user password: super-secret oidc: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: revoke_active_sessions hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet registration: enabled: true ui_url: http://test.kratos.ory.sh/register lifespan: 98m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_registration_hook method: GET headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/registration/return_to password: hooks: - hook: session - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: api_key config: name: My-Key value: My-Key-Value in: header oidc: default_browser_return_url: https://self-service/registration/oidc/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: session ================================================ FILE: driver/config/stub/.kratos.yaml ================================================ # serve controls the configuration for the http(s) daemon serve: admin: base_url: http://admin.kratos.ory.sh port: 1234 host: admin.kratos.ory.sh public: base_url: http://public.kratos.ory.sh port: 1235 host: public.kratos.ory.sh dsn: sqlite://foo.db?mode=memory&_fk=true log: level: debug courier: smtp: connection_uri: smtp://foo:bar@baz/ identity: default_schema_id: default schemas: - id: default url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 - id: other url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktb3RoZXItc2NoZW1hIiwKICAiJHNjaGVtYSI6ICJodHRwOi8vanNvbi1zY2hlbWEub3JnL2RyYWZ0LTA3L3NjaGVtYSMiLAogICJ0aXRsZSI6ICJJZGVudGl0eU90aGVyU2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm90aGVyIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgIH0sCiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJ0aXRsZSI6ICJlbWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJvdGhlciIsCiAgICAgICAgImVtYWlsIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9 hashers: argon2: memory: 1MB iterations: 2 parallelism: 4 salt_length: 16 key_length: 32 dedicated_memory: 1GB expected_duration: 500ms expected_deviation: 500ms secrets: cookie: - session-key-7f8a9b77-1 - session-key-7f8a9b77-2 cipher: - secret-thirty-two-character-long ciphers: algorithm: xchacha20-poly1305 selfservice: default_browser_return_url: http://return-to-3-test.ory.sh/ allowed_return_urls: - http://return-to-1-test.ory.sh/ - http://return-to-2-test.ory.sh/ - http://*.wildcards.ory.sh - http://*.sh - http://*.com - http://*.com.pl - http://* - /return-to-relative-test/ methods: totp: enabled: true config: issuer: issuer.ory.sh password: enabled: true oidc: enabled: true config: providers: - id: github provider: github client_id: a client_secret: b mapper_url: http://test.kratos.ory.sh/default-identity.schema.json webauthn: enabled: true config: rp: id: example.com display_name: Webauthn flows: error: ui_url: http://test.kratos.ory.sh/error logout: after: default_browser_return_url: http://test.kratos.ory.sh:4000/ recovery: enabled: true ui_url: http://test.kratos.ory.sh/recovery lifespan: 98m after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_recovery_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet verification: enabled: true lifespan: 97m ui_url: http://test.kratos.ory.sh/verification after: default_browser_return_url: http://test.kratos.ory.sh/dashboard hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_verification_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet settings: ui_url: http://test.kratos.ory.sh/settings lifespan: 99m privileged_session_max_age: 5m after: default_browser_return_url: https://self-service/settings/return_to password: default_browser_return_url: https://self-service/settings/password/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet profile: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_profile_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_settings_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet login: ui_url: http://test.kratos.ory.sh/login lifespan: 99m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_login_hook method: POST headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/login/return_to password: default_browser_return_url: https://self-service/login/password/return_to hooks: - hook: revoke_active_sessions - hook: require_verified_address - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: basic_auth config: user: test-user password: super-secret oidc: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: revoke_active_sessions hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_login_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet registration: enabled: true ui_url: http://test.kratos.ory.sh/register lifespan: 98m before: hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/before_registration_hook method: GET headers: X-Custom-Header: test after: default_browser_return_url: https://self-service/registration/return_to password: hooks: - hook: session - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_password_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_global_hook method: POST headers: X-Custom-Header: test body: /path/to/template.jsonnet auth: type: api_key config: name: My-Key value: My-Key-Value in: header oidc: default_browser_return_url: https://self-service/registration/oidc/return_to hooks: - hook: web_hook config: url: https://test.kratos.ory.sh/after_registration_oidc_hook method: GET headers: X-Custom-Header: test body: /path/to/template.jsonnet - hook: session ================================================ FILE: driver/factory.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "io" "github.com/ory/x/servicelocatorx" "github.com/ory/kratos/driver/config" "github.com/ory/x/logrusx" ) func New(ctx context.Context, stdOutOrErr io.Writer, dOpts ...RegistryOption) (*RegistryDefault, error) { r, err := NewWithoutInit(ctx, stdOutOrErr, dOpts...) if err != nil { return nil, err } ctxter := r.Contextualizer() if err := r.Init(ctx, ctxter, dOpts...); err != nil { r.Logger().WithError(err).Error("Unable to initialize service registry.") return nil, err } return r, nil } func NewWithoutInit(ctx context.Context, stdOutOrErr io.Writer, dOpts ...RegistryOption) (*RegistryDefault, error) { opts := newOptions(dOpts) sl := servicelocatorx.NewOptions(opts.serviceLocatorOptions...) l := sl.Logger() if l == nil { l = logrusx.New("Ory Kratos", config.Version) } c := opts.config if c == nil { var err error c, err = config.New(ctx, l, stdOutOrErr, sl.Contextualizer(), opts.configOptions...) if err != nil { l.WithError(err).Error("Unable to instantiate configuration.") return nil, err } } r, err := NewRegistryFromDSN(ctx, c, l) if err != nil { l.WithError(err).Error("Unable to instantiate service registry.") return nil, err } r.slOptions = sl r.SetContextualizer(sl.Contextualizer()) return r, nil } ================================================ FILE: driver/factory_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver_test import ( "context" "os" "testing" "time" "github.com/gofrs/uuid" "github.com/ory/x/configx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" ) func TestDriverNew(t *testing.T) { ctx := context.Background() r, err := driver.New( context.Background(), os.Stderr, driver.WithConfigOptions( configx.WithValue(config.ViperKeyDSN, config.DefaultSQLiteMemoryDSN), configx.SkipValidation(), )) require.NoError(t, err) assert.EqualValues(t, config.DefaultSQLiteMemoryDSN, r.Config().DSN(ctx)) pingCtx, cancel := context.WithTimeout(ctx, 10*time.Second) t.Cleanup(cancel) require.NoError(t, r.Persister().Ping(pingCtx)) assert.NotEqual(t, uuid.Nil.String(), r.Persister().NetworkID(context.Background()).String()) n, err := r.Persister().DetermineNetwork(context.Background()) require.NoError(t, err) assert.Equal(t, r.Persister().NetworkID(context.Background()), n.ID) } ================================================ FILE: driver/registry.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "io/fs" "github.com/gorilla/sessions" "github.com/ory/x/httpx" "github.com/ory/kratos/cipher" "github.com/ory/kratos/continuity" "github.com/ory/kratos/courier" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hash" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence" "github.com/ory/kratos/schema" "github.com/ory/kratos/selfservice/errorx" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/logout" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/sessiontokenexchange" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/kratos/selfservice/strategy/link" password2 "github.com/ory/kratos/selfservice/strategy/password" "github.com/ory/kratos/session" "github.com/ory/kratos/x" "github.com/ory/kratos/x/nosurfx" "github.com/ory/nosurf" "github.com/ory/pop/v6" "github.com/ory/x/configx" "github.com/ory/x/contextx" "github.com/ory/x/healthx" "github.com/ory/x/httprouterx" "github.com/ory/x/jsonnetsecure" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/popx" "github.com/ory/x/servicelocatorx" ) type Registry interface { Init(ctx context.Context, ctxer contextx.Contextualizer, opts ...RegistryOption) error SetLogger(l *logrusx.Logger) SetJSONNetVMProvider(jsonnetsecure.VMProvider) WithCSRFHandler(c nosurf.Handler) WithCSRFTokenGenerator(cg nosurfx.CSRFToken) HealthHandler(ctx context.Context) *healthx.Handler CookieManager(ctx context.Context) sessions.StoreExact ContinuityCookieManager(ctx context.Context) sessions.StoreExact RegisterPublicRoutes(ctx context.Context, public *httprouterx.RouterPublic) RegisterAdminRoutes(ctx context.Context, admin *httprouterx.RouterAdmin) Tracer(context.Context) *otelx.Tracer SetTracer(*otelx.Tracer) config.Provider CourierConfig() config.CourierConfigs SetConfig(c *config.Config) SetContextualizer(ctxer contextx.Contextualizer) nosurfx.CSRFProvider httpx.WriterProvider logrusx.Provider httpx.ClientProvider jsonnetsecure.VMProvider continuity.ManagementProvider continuity.PersistenceProvider cipher.Provider courier.Provider persistence.Provider errorx.ManagementProvider errorx.HandlerProvider errorx.PersistenceProvider hash.HashProvider identity.HandlerProvider identity.ValidationProvider identity.PoolProvider identity.PrivilegedPoolProvider identity.ManagementProvider identity.ActiveCredentialsCounterStrategyProvider courier.HandlerProvider courier.PersistenceProvider schema.HandlerProvider schema.IdentitySchemaProvider password2.ValidationProvider session.HandlerProvider session.ManagementProvider session.PersistenceProvider session.TokenizerProvider settings.HandlerProvider settings.ErrorHandlerProvider settings.FlowPersistenceProvider settings.StrategyProvider login.FlowPersistenceProvider login.ErrorHandlerProvider login.HooksProvider login.HookExecutorProvider login.HandlerProvider login.StrategyProvider logout.HandlerProvider registration.FlowPersistenceProvider registration.ErrorHandlerProvider registration.HooksProvider registration.HookExecutorProvider registration.HandlerProvider registration.StrategyProvider verification.FlowPersistenceProvider verification.ErrorHandlerProvider verification.HandlerProvider verification.StrategyProvider sessiontokenexchange.PersistenceProvider link.SenderProvider link.VerificationTokenPersistenceProvider link.RecoveryTokenPersistenceProvider code.SenderProvider code.RecoveryCodePersistenceProvider recovery.FlowPersistenceProvider recovery.ErrorHandlerProvider recovery.HandlerProvider recovery.StrategyProvider nosurfx.CSRFTokenGeneratorProvider } func NewRegistryFromDSN(ctx context.Context, c *config.Config, l *logrusx.Logger) (*RegistryDefault, error) { reg := NewRegistryDefault() tracer, err := otelx.New("Ory Kratos", l, c.Tracing(ctx)) if err != nil { l.WithError(err).Fatalf("failed to initialize tracer") tracer = otelx.NewNoop() } reg.SetTracer(tracer) reg.SetLogger(l) reg.SetConfig(c) return reg, nil } type options struct { skipNetworkInit bool config *config.Config configOptions []configx.OptionModifier replaceTracer func(*otelx.Tracer) *otelx.Tracer replaceIdentitySchemaProvider func(Registry) schema.IdentitySchemaProvider inspect func(Registry) error extraMigrations []fs.FS extraGoMigrations popx.Migrations replacementStrategies []NewStrategy extraHooks map[string]func(config.SelfServiceHook) any extraHandlers []NewHandler disableMigrationLogging bool jsonnetPool jsonnetsecure.Pool serviceLocatorOptions []servicelocatorx.Option dbOpts []func(details *pop.ConnectionDetails) } type RegistryOption func(*options) // WithDBOptions adds database connection options that will be applied to the // underlying connection. func WithDBOptions(opts ...func(details *pop.ConnectionDetails)) RegistryOption { return func(o *options) { o.dbOpts = append(o.dbOpts, opts...) } } func SkipNetworkInit(o *options) { o.skipNetworkInit = true } func WithJsonnetPool(pool jsonnetsecure.Pool) RegistryOption { return func(o *options) { o.jsonnetPool = pool } } func WithConfig(config *config.Config) RegistryOption { return func(o *options) { o.config = config } } func WithConfigOptions(opts ...configx.OptionModifier) RegistryOption { return func(o *options) { o.configOptions = append(o.configOptions, opts...) } } func WithIdentitySchemaProvider(f func(r Registry) schema.IdentitySchemaProvider) RegistryOption { return func(o *options) { o.replaceIdentitySchemaProvider = f } } func ReplaceTracer(f func(*otelx.Tracer) *otelx.Tracer) RegistryOption { return func(o *options) { o.replaceTracer = f } } type NewStrategy func(deps any) any // WithReplaceStrategies adds a strategy to the registry. This is useful if you want to // add a custom strategy to the registry. Default strategies with the same // name/ID will be overwritten. func WithReplaceStrategies(s ...NewStrategy) RegistryOption { return func(o *options) { o.replacementStrategies = append(o.replacementStrategies, s...) } } func WithExtraHooks(hooks map[string]func(config.SelfServiceHook) any) RegistryOption { return func(o *options) { o.extraHooks = hooks } } type NewHandler func(deps any) x.Handler func WithExtraHandlers(handlers ...NewHandler) RegistryOption { return func(o *options) { o.extraHandlers = handlers } } func Inspect(f func(reg Registry) error) RegistryOption { return func(o *options) { o.inspect = f } } func WithExtraMigrations(m ...fs.FS) RegistryOption { return func(o *options) { o.extraMigrations = append(o.extraMigrations, m...) } } func WithExtraGoMigrations(m ...popx.Migration) RegistryOption { return func(o *options) { o.extraGoMigrations = append(o.extraGoMigrations, m...) } } func WithDisabledMigrationLogging() RegistryOption { return func(o *options) { o.disableMigrationLogging = true } } func WithServiceLocatorOptions(opts ...servicelocatorx.Option) RegistryOption { return func(o *options) { o.serviceLocatorOptions = append(o.serviceLocatorOptions, opts...) } } func newOptions(os []RegistryOption) *options { o := new(options) for _, f := range os { f(o) } return o } ================================================ FILE: driver/registry_default.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "crypto/sha256" "net/http" "strings" "sync" "testing" "time" "github.com/cenkalti/backoff" "github.com/dgraph-io/ristretto/v2" "github.com/gorilla/sessions" "github.com/hashicorp/go-retryablehttp" "github.com/lestrrat-go/jwx/jwk" "github.com/pkg/errors" "github.com/urfave/negroni" "github.com/ory/herodot" "github.com/ory/kratos/cipher" "github.com/ory/kratos/continuity" "github.com/ory/kratos/courier" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hash" "github.com/ory/kratos/hydra" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence" "github.com/ory/kratos/persistence/sql" "github.com/ory/kratos/schema" "github.com/ory/kratos/selfservice/errorx" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/logout" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/hook" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/kratos/selfservice/strategy/idfirst" "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/kratos/selfservice/strategy/lookup" "github.com/ory/kratos/selfservice/strategy/oidc" "github.com/ory/kratos/selfservice/strategy/passkey" "github.com/ory/kratos/selfservice/strategy/password" "github.com/ory/kratos/selfservice/strategy/profile" "github.com/ory/kratos/selfservice/strategy/totp" "github.com/ory/kratos/selfservice/strategy/webauthn" "github.com/ory/kratos/session" "github.com/ory/kratos/x" "github.com/ory/kratos/x/nosurfx" "github.com/ory/kratos/x/webauthnx" "github.com/ory/nosurf" "github.com/ory/pop/v6" "github.com/ory/x/contextx" "github.com/ory/x/dbal" "github.com/ory/x/healthx" "github.com/ory/x/httprouterx" "github.com/ory/x/httpx" "github.com/ory/x/jsonnetsecure" "github.com/ory/x/jwksx" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/popx" "github.com/ory/x/prometheusx" "github.com/ory/x/servicelocatorx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlxx" ) type RegistryDefault struct { l *logrusx.Logger c *config.Config ctxer contextx.Contextualizer injectedSelfserviceHooks map[string]func(config.SelfServiceHook) interface{} extraHandlerFactories []NewHandler extraHandlers []x.Handler slOptions *servicelocatorx.Options nosurf nosurf.Handler trc *otelx.Tracer writer herodot.Writer healthxHandler *healthx.Handler persister persistence.Persister migrationStatus popx.MigrationStatuses hookVerifier *hook.Verifier hookSessionIssuer *hook.SessionIssuer hookSessionDestroyer *hook.SessionDestroyer hookAddressVerifier *hook.AddressVerifier hookShowVerificationUI *hook.ShowVerificationUIHook identityHandler *identity.Handler identityValidator *identity.Validator identityManager *identity.Manager identitySchemaProvider schema.IdentitySchemaProvider courierHandler *courier.Handler continuityManager continuity.Manager schemaHandler *schema.Handler sessionHandler *session.Handler sessionManager session.Manager sessionTokenizer initOnce[*session.Tokenizer] passwordHasher initOnce[hash.Hasher] passwordValidator initOnce[password.Validator] crypter initOnce[cipher.Cipher] errorHandler *errorx.Handler errorManager *errorx.Manager selfserviceRegistrationExecutor *registration.HookExecutor selfserviceRegistrationHandler *registration.Handler seflserviceRegistrationErrorHandler *registration.ErrorHandler selfserviceRegistrationRequestErrorHandler *registration.ErrorHandler selfserviceLoginExecutor *login.HookExecutor selfserviceLoginHandler *login.Handler selfserviceLoginRequestErrorHandler *login.ErrorHandler selfserviceSettingsHandler *settings.Handler selfserviceSettingsErrorHandler *settings.ErrorHandler selfserviceSettingsExecutor *settings.HookExecutor selfserviceVerifyErrorHandler *verification.ErrorHandler selfserviceVerifyManager *identity.Manager selfserviceVerifyHandler *verification.Handler selfserviceVerificationExecutor *verification.HookExecutor selfserviceLinkSender *link.Sender selfserviceCodeSender *code.Sender selfserviceRecoveryErrorHandler *recovery.ErrorHandler selfserviceRecoveryHandler *recovery.Handler selfserviceRecoveryExecutor *recovery.HookExecutor selfserviceLogoutHandler *logout.Handler selfserviceStrategies []any replacementSelfserviceStrategies []NewStrategy hydra initOnce[hydra.Hydra] csrfTokenGenerator nosurfx.CSRFToken jsonnetVMProvider initOnce[jsonnetsecure.VMProvider] jsonnetPool jsonnetsecure.Pool jwkFetcher initOnce[*jwksx.FetcherNext] } func (m *RegistryDefault) JsonnetVM(ctx context.Context) (jsonnetsecure.VM, error) { return m.jsonnetVMProvider.Get(func() jsonnetsecure.VMProvider { return &jsonnetsecure.DefaultProvider{Subcommand: "jsonnet", Pool: m.jsonnetPool} }).JsonnetVM(ctx) } func (m *RegistryDefault) Audit() *logrusx.Logger { return m.Logger().WithField("audience", "audit") } func (m *RegistryDefault) RegisterPublicRoutes(ctx context.Context, router *httprouterx.RouterPublic) { for _, h := range m.ExtraHandlers() { h.RegisterPublicRoutes(router) } m.LoginHandler().RegisterPublicRoutes(router) m.RegistrationHandler().RegisterPublicRoutes(router) m.LogoutHandler().RegisterPublicRoutes(router) m.SettingsHandler().RegisterPublicRoutes(router) m.IdentityHandler().RegisterPublicRoutes(router) m.CourierHandler().RegisterPublicRoutes(router) m.SessionHandler().RegisterPublicRoutes(router) m.SelfServiceErrorHandler().RegisterPublicRoutes(router) m.SchemaHandler().RegisterPublicRoutes(router) m.RecoveryHandler().RegisterPublicRoutes(router) m.VerificationHandler().RegisterPublicRoutes(router) m.HealthHandler(ctx).SetHealthRoutes(router, false) webauthnx.RegisterWebauthnRoute(router) for _, s := range m.selfServiceStrategies() { s, ok := s.(x.PublicHandler) if ok { s.RegisterPublicRoutes(router) } } } func (m *RegistryDefault) RegisterAdminRoutes(ctx context.Context, router *httprouterx.RouterAdmin) { for _, h := range m.ExtraHandlers() { h.RegisterAdminRoutes(router) } m.RegistrationHandler().RegisterAdminRoutes(router) m.LoginHandler().RegisterAdminRoutes(router) m.LogoutHandler().RegisterAdminRoutes(router) m.SchemaHandler().RegisterAdminRoutes(router) m.SettingsHandler().RegisterAdminRoutes(router) m.IdentityHandler().RegisterAdminRoutes(router) m.CourierHandler().RegisterAdminRoutes(router) m.SelfServiceErrorHandler().RegisterAdminRoutes(router) m.RecoveryHandler().RegisterAdminRoutes(router) m.SessionHandler().RegisterAdminRoutes(router) m.VerificationHandler().RegisterAdminRoutes(router) m.HealthHandler(ctx).SetHealthRoutes(router, true) m.HealthHandler(ctx).SetVersionRoutes(router) prometheusx.SetMuxRoutes(router) config.RegisterConfigHashRoute(m, router) for _, s := range m.selfServiceStrategies() { s, ok := s.(x.AdminHandler) if ok { s.RegisterAdminRoutes(router) } } } func (m *RegistryDefault) HTTPMiddlewares() []negroni.Handler { return m.slOptions.HTTPMiddlewares() } func NewRegistryDefault() *RegistryDefault { r := &RegistryDefault{ trc: otelx.NewNoop(), slOptions: &servicelocatorx.Options{}, } r.initCheapMembers() return r } func (m *RegistryDefault) SetLogger(l *logrusx.Logger) { m.l = l } func (m *RegistryDefault) SetJSONNetVMProvider(p jsonnetsecure.VMProvider) { m.jsonnetVMProvider.Set(p) } func (m *RegistryDefault) LogoutHandler() *logout.Handler { if m.selfserviceLogoutHandler == nil { m.selfserviceLogoutHandler = logout.NewHandler(m) } return m.selfserviceLogoutHandler } func (m *RegistryDefault) HealthHandler(_ context.Context) *healthx.Handler { if m.healthxHandler == nil { m.healthxHandler = healthx.NewHandler(m.Writer(), config.Version, healthx.ReadyCheckers{ "database": func(r *http.Request) error { return m.PingContext(r.Context()) }, "migrations": func(r *http.Request) error { if m.migrationStatus != nil && !m.migrationStatus.HasPending() { return nil } status, err := m.Persister().MigrationStatus(r.Context()) if err != nil { return err } if status.HasPending() { return errors.Errorf("migrations have not yet been fully applied") } m.migrationStatus = status return nil }, }) } return m.healthxHandler } func (m *RegistryDefault) WithCSRFHandler(c nosurf.Handler) { m.nosurf = c } func (m *RegistryDefault) CSRFHandler() nosurf.Handler { if m.nosurf == nil { panic("csrf handler is not set") } return m.nosurf } func (m *RegistryDefault) Config() *config.Config { if m.c == nil { panic("configuration not set") } return m.c } func (m *RegistryDefault) CourierConfig() config.CourierConfigs { return m.Config() } func (m *RegistryDefault) selfServiceStrategies() []any { if len(m.selfserviceStrategies) == 0 { if m.replacementSelfserviceStrategies != nil { // Construct self-service strategies from the replacements for _, newStrategy := range m.replacementSelfserviceStrategies { m.selfserviceStrategies = append(m.selfserviceStrategies, newStrategy(m)) } } else { // Construct the default list of strategies m.selfserviceStrategies = []any{ profile.NewStrategy(m), // <- should remain first password.NewStrategy(m), oidc.NewStrategy(m), code.NewStrategy(m), link.NewStrategy(m), totp.NewStrategy(m), passkey.NewStrategy(m), webauthn.NewStrategy(m), lookup.NewStrategy(m), idfirst.NewStrategy(m), } } } return m.selfserviceStrategies } func (m *RegistryDefault) strategyRegistrationEnabled(ctx context.Context, id string) bool { if id == "profile" { return true } return m.Config().SelfServiceStrategy(ctx, id).Enabled } func (m *RegistryDefault) strategyLoginEnabled(ctx context.Context, id string) bool { return m.Config().SelfServiceStrategy(ctx, id).Enabled } func (m *RegistryDefault) RegistrationStrategies(ctx context.Context, filters ...registration.StrategyFilter) (registrationStrategies registration.Strategies) { nextStrategy: for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(registration.Strategy); ok { for _, filter := range filters { if !filter(s) { continue nextStrategy } } if m.strategyRegistrationEnabled(ctx, s.ID().String()) { registrationStrategies = append(registrationStrategies, s) } } } return } func (m *RegistryDefault) AllRegistrationStrategies() registration.Strategies { var registrationStrategies []registration.Strategy for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(registration.Strategy); ok { registrationStrategies = append(registrationStrategies, s) } } return registrationStrategies } func (m *RegistryDefault) LoginStrategies(ctx context.Context, filters ...login.StrategyFilter) (loginStrategies login.Strategies) { nextStrategy: for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(login.Strategy); ok { for _, filter := range filters { if !filter(s) { continue nextStrategy } } if m.strategyLoginEnabled(ctx, s.ID().String()) { loginStrategies = append(loginStrategies, s) } } } return } func (m *RegistryDefault) AllLoginStrategies() login.Strategies { var loginStrategies []login.Strategy for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(login.Strategy); ok { loginStrategies = append(loginStrategies, s) } } return loginStrategies } func (m *RegistryDefault) ActiveCredentialsCounterStrategies(_ context.Context) (activeCredentialsCounterStrategies []identity.ActiveCredentialsCounter) { for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(identity.ActiveCredentialsCounter); ok { activeCredentialsCounterStrategies = append(activeCredentialsCounterStrategies, s) } } return } func (m *RegistryDefault) IdentityValidator() *identity.Validator { return m.identityValidator } func (m *RegistryDefault) SetConfig(c *config.Config) { m.c = c } // WithSelfserviceStrategies is only available in testing and overrides the // selfservice strategies with the given ones. func (m *RegistryDefault) WithSelfserviceStrategies(t testing.TB, strategies []any) Registry { if t == nil { panic("Passing selfservice strategies is only supported in testing") } m.selfserviceStrategies = strategies return m } func (m *RegistryDefault) Writer() herodot.Writer { if m.writer == nil { h := herodot.NewJSONWriter(m.Logger()) m.writer = h } return m.writer } func (m *RegistryDefault) Logger() *logrusx.Logger { if m.l == nil { m.l = logrusx.New("Ory Kratos", config.Version) } return m.l } func (m *RegistryDefault) IdentityHandler() *identity.Handler { if m.identityHandler == nil { m.identityHandler = identity.NewHandler(m) } return m.identityHandler } func (m *RegistryDefault) CourierHandler() *courier.Handler { if m.courierHandler == nil { m.courierHandler = courier.NewHandler(m) } return m.courierHandler } func (m *RegistryDefault) SchemaHandler() *schema.Handler { if m.schemaHandler == nil { m.schemaHandler = schema.NewHandler(m) } return m.schemaHandler } func (m *RegistryDefault) SessionHandler() *session.Handler { if m.sessionHandler == nil { m.sessionHandler = session.NewHandler(m) } return m.sessionHandler } func (m *RegistryDefault) Cipher(ctx context.Context) cipher.Cipher { return m.crypter.Get(func() cipher.Cipher { switch m.c.CipherAlgorithm(ctx) { case "xchacha20-poly1305": return cipher.NewCryptChaCha20(m.Config()) case "aes": return cipher.NewCryptAES(m.Config()) default: m.l.Logger.Warning("No encryption configuration found. The default algorithm (noop) will be used, resulting in sensitive data being stored in plaintext") return cipher.NewNoop() } }) } func (m *RegistryDefault) Hasher(ctx context.Context) hash.Hasher { return m.passwordHasher.Get(func() hash.Hasher { if m.c.HasherPasswordHashingAlgorithm(ctx) == "bcrypt" { return hash.NewHasherBcrypt(m) } return hash.NewHasherArgon2(m) }) } func (m *RegistryDefault) PasswordValidator() password.Validator { m.passwordValidator.Get(func() password.Validator { val, err := password.NewDefaultPasswordValidatorStrategy(m) if err != nil { m.Logger().WithError(err).Fatal("could not initialize DefaultPasswordValidator") } return val }) return m.passwordValidator.value } func (m *RegistryDefault) SelfServiceErrorHandler() *errorx.Handler { if m.errorHandler == nil { m.errorHandler = errorx.NewHandler(m) } return m.errorHandler } func (m *RegistryDefault) CookieManager(ctx context.Context) sessions.StoreExact { var keys [][]byte for _, k := range m.Config().SecretsSession(ctx) { encrypt := sha256.Sum256(k) keys = append(keys, k, encrypt[:]) } cs := sessions.NewCookieStore(keys...) cs.Options.Secure = m.Config().SessionCookieSecure(ctx) cs.Options.HttpOnly = true if domain := m.Config().SessionDomain(ctx); domain != "" { cs.Options.Domain = domain } if path := m.Config().SessionPath(ctx); path != "" { cs.Options.Path = path } if sameSite := m.Config().SessionSameSiteMode(ctx); sameSite != 0 { cs.Options.SameSite = sameSite } cs.Options.MaxAge = 0 if m.Config().SessionPersistentCookie(ctx) { cs.Options.MaxAge = int(m.Config().SessionLifespan(ctx).Seconds()) cs.MaxAge(cs.Options.MaxAge) } return cs } func (m *RegistryDefault) ContinuityCookieManager(ctx context.Context) sessions.StoreExact { // To support hot reloading, this can not be instantiated only once. cs := sessions.NewCookieStore(m.Config().SecretsSession(ctx)...) cs.Options.Secure = m.Config().CookieSecure(ctx) cs.Options.HttpOnly = true cs.Options.SameSite = http.SameSiteLaxMode return cs } func (m *RegistryDefault) Tracer(context.Context) *otelx.Tracer { if m.trc == nil { return otelx.NewNoop() } return m.trc } func (m *RegistryDefault) SetTracer(t *otelx.Tracer) { m.trc = t } func (m *RegistryDefault) SessionManager() session.Manager { return m.sessionManager } func (m *RegistryDefault) Hydra() hydra.Hydra { return m.hydra.Get(func() hydra.Hydra { return hydra.NewDefaultHydra(m) }) } func (m *RegistryDefault) SetHydra(h hydra.Hydra) { m.hydra.Set(h) } func (m *RegistryDefault) SelfServiceErrorManager() *errorx.Manager { return m.errorManager } func (m *RegistryDefault) Init(ctx context.Context, ctxer contextx.Contextualizer, opts ...RegistryOption) error { if m.persister != nil { // The DSN connection can not be hot-reloaded! panic("RegistryDefault.Init() must not be called more than once.") } o := newOptions(opts) m.jsonnetPool = o.jsonnetPool if o.replaceTracer != nil { m.trc = o.replaceTracer(m.trc) } if o.replacementStrategies != nil { m.replacementSelfserviceStrategies = o.replacementStrategies } if o.extraHooks != nil { m.WithHooks(o.extraHooks) } if o.extraHandlers != nil { m.WithExtraHandlers(o.extraHandlers) } if o.replaceIdentitySchemaProvider != nil { m.identitySchemaProvider = o.replaceIdentitySchemaProvider(m) } bc := backoff.NewExponentialBackOff() bc.MaxElapsedTime = time.Minute * 5 bc.Reset() err := backoff.Retry(func() error { m.SetContextualizer(ctxer) pool, idlePool, connMaxLifetime, connMaxIdleTime, cleanedDSN := sqlcon.ParseConnectionOptions(m.l, m.Config().DSN(ctx)) dbOpts := &pop.ConnectionDetails{ URL: sqlcon.FinalizeDSN(m.l, cleanedDSN), IdlePool: idlePool, ConnMaxLifetime: connMaxLifetime, ConnMaxIdleTime: connMaxIdleTime, Pool: pool, TracerProvider: m.Tracer(ctx).Provider(), } for _, f := range o.dbOpts { f(dbOpts) } scheme, _, _ := sqlxx.ExtractSchemeFromDSN(dbOpts.URL) if !dbOpts.AllowMinPool && scheme == "postgres" && strings.Contains(dbOpts.URL, "pool_min_conns=") { err := errors.Errorf("attempting to use the option 'pool_min_conns' with Postgres, but the pgxpool connection pool is disabled, this will be rejected by the database: dsn=%s dbOpts.AllowMinPool=%v", dbOpts.URL, dbOpts.AllowMinPool) return backoff.Permanent(err) } if (scheme == "sqlite" || scheme == "mysql") && strings.Contains(dbOpts.URL, "pool_min_conns=") { err := errors.Errorf("attempting to use the option 'pool_min_conns' with %s, but connection pooling is not supported for this case: dsn=%s", scheme, dbOpts.URL) return backoff.Permanent(err) } m.Logger(). WithField("pool", pool). WithField("idlePool", idlePool). WithField("connMaxLifetime", connMaxLifetime). Debug("Connecting to SQL Database") c, err := pop.NewConnection(dbOpts) if err != nil { m.Logger().WithError(err).Warnf("Unable to connect to database, retrying.") return errors.WithStack(err) } if err := c.Open(); err != nil { m.Logger().WithError(err).Warnf("Unable to open database, retrying.") return errors.WithStack(err) } p, err := sql.NewPersister(m, c, sql.WithExtraMigrations(o.extraMigrations...), sql.WithExtraGoMigrations(o.extraGoMigrations...), sql.WithDisabledLogging(o.disableMigrationLogging)) if err != nil { m.Logger().WithError(err).Warnf("Unable to initialize persister, retrying.") return err } pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() if err := c.Store.SQLDB().PingContext(pingCtx); err != nil { m.Logger().WithError(err).Warnf("Unable to ping database, retrying.") return err } // if dsn is memory we have to run the migrations on every start if dbal.IsMemorySQLite(m.Config().DSN(ctx)) || m.Config().DSN(ctx) == "memory" { m.Logger().Infoln("Ory Kratos is running migrations on every startup as DSN is memory. This means your data is lost when Kratos terminates.") if err := p.MigrateUp(ctx); err != nil { m.Logger().WithError(err).Warnf("Unable to run migrations, retrying.") return err } } if o.skipNetworkInit { m.persister = p return nil } net, err := p.DetermineNetwork(ctx) if err != nil { m.Logger().WithError(err).Warnf("Unable to determine network, retrying.") return err } m.persister = p.WithNetworkID(net.ID) return nil }, bc) if err != nil { return err } if o.inspect != nil { if err := o.inspect(m); err != nil { return errors.WithStack(err) } } return nil } func (m *RegistryDefault) SetPersister(p persistence.Persister) { m.persister = p } func (m *RegistryDefault) Courier(ctx context.Context) (courier.Courier, error) { return courier.NewCourier(ctx, m) } func (m *RegistryDefault) ContinuityManager() continuity.Manager { return m.continuityManager } func (m *RegistryDefault) Persister() persistence.Persister { return m.persister } func (m *RegistryDefault) ContinuityPersister() continuity.Persister { return m.persister } func (m *RegistryDefault) IdentityPool() identity.Pool { return m.persister } func (m *RegistryDefault) PrivilegedIdentityPool() identity.PrivilegedPool { return m.persister } func (m *RegistryDefault) RegistrationFlowPersister() registration.FlowPersister { return m.persister } func (m *RegistryDefault) RecoveryFlowPersister() recovery.FlowPersister { return m.persister } func (m *RegistryDefault) LoginFlowPersister() login.FlowPersister { return m.persister } func (m *RegistryDefault) SettingsFlowPersister() settings.FlowPersister { return m.persister } func (m *RegistryDefault) SelfServiceErrorPersister() errorx.Persister { return m.persister } func (m *RegistryDefault) SessionPersister() session.Persister { return m.persister } func (m *RegistryDefault) CourierPersister() courier.Persister { return m.persister } func (m *RegistryDefault) RecoveryTokenPersister() link.RecoveryTokenPersister { return m.persister } func (m *RegistryDefault) RecoveryCodePersister() code.RecoveryCodePersister { return m.persister } func (m *RegistryDefault) LoginCodePersister() code.LoginCodePersister { return m.persister } func (m *RegistryDefault) VerificationTokenPersister() link.VerificationTokenPersister { return m.persister } func (m *RegistryDefault) VerificationCodePersister() code.VerificationCodePersister { return m.persister } func (m *RegistryDefault) RegistrationCodePersister() code.RegistrationCodePersister { return m.persister } func (m *RegistryDefault) TransactionalPersisterProvider() x.TransactionalPersister { return m.persister } func (m *RegistryDefault) PingContext(ctx context.Context) error { return m.persister.Ping(ctx) } func (m *RegistryDefault) Ping() error { return m.persister.Ping(context.Background()) } func (m *RegistryDefault) WithCSRFTokenGenerator(cg nosurfx.CSRFToken) { m.csrfTokenGenerator = cg } func (m *RegistryDefault) GenerateCSRFToken(r *http.Request) string { if m.csrfTokenGenerator == nil { m.csrfTokenGenerator = nosurfx.DefaultCSRFToken } return m.csrfTokenGenerator(r) } func (m *RegistryDefault) IdentityManager() *identity.Manager { return m.identityManager } func (m *RegistryDefault) HTTPClient(_ context.Context, opts ...httpx.ResilientOptions) *retryablehttp.Client { opts = append([]httpx.ResilientOptions{ httpx.ResilientClientWithLogger(m.Logger()), httpx.ResilientClientWithMaxRetry(2), httpx.ResilientClientWithConnectionTimeout(30 * time.Second), }, opts...) // One of the few exceptions, this usually should not be hot reloaded. if m.Config().ClientHTTPNoPrivateIPRanges(contextx.RootContext) { opts = append( opts, httpx.ResilientClientDisallowInternalIPs(), // One of the few exceptions, this usually should not be hot reloaded. httpx.ResilientClientAllowInternalIPRequestsTo(m.Config().ClientHTTPPrivateIPExceptionURLs(contextx.RootContext)...), ) } return httpx.NewResilientClient(opts...) } func (m *RegistryDefault) SetContextualizer(ctxer contextx.Contextualizer) { m.ctxer = ctxer } func (m *RegistryDefault) Contextualizer() contextx.Contextualizer { if m.ctxer == nil { panic("registry Contextualizer not set") } return m.ctxer } func (m *RegistryDefault) JWKSFetcher() *jwksx.FetcherNext { return m.jwkFetcher.Get(func() *jwksx.FetcherNext { maxItems := int64(10_000_000) cache, _ := ristretto.NewCache(&ristretto.Config[[]byte, jwk.Set]{ NumCounters: maxItems * 10, MaxCost: maxItems, BufferItems: 64, Metrics: true, IgnoreInternalCost: true, Cost: func(value jwk.Set) int64 { return 1 }, }) return jwksx.NewFetcherNext(cache) }) } func (m *RegistryDefault) SessionTokenizer() *session.Tokenizer { return m.sessionTokenizer.Get(func() *session.Tokenizer { return session.NewTokenizer(m) }) } func (m *RegistryDefault) ExtraHandlers() []x.Handler { if m.extraHandlers == nil { for _, newHandler := range m.extraHandlerFactories { m.extraHandlers = append(m.extraHandlers, newHandler(m)) } } return m.extraHandlers } // initCheapMembers initializes members that are cheap to initialize. func (m *RegistryDefault) initCheapMembers() { m.identityValidator = identity.NewValidator(m) m.identityManager = identity.NewManager(m) m.sessionManager = session.NewManagerHTTP(m) m.errorManager = errorx.NewManager(m) m.continuityManager = continuity.NewManagerCookie(m) } type initOnce[T any] struct { value T init sync.Once } // Set sets the value of the initOnce, marking it as initialized. func (o *initOnce[T]) Set(val T) { // We need to both set the value and run the init function to mark the value as // initialized. o.init.Do(func() { o.value = val }) o.value = val } // Get returns the value of the initOnce, initializing it with newT if it has not // been initialized yet. `newT` is only called once, even if Get is called // concurrently from multiple goroutines or with different `newT` functions. func (o *initOnce[T]) Get(newT func() T) T { o.init.Do(func() { o.value = newT() }) return o.value } ================================================ FILE: driver/registry_default_hooks.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "encoding/json" "fmt" "github.com/pkg/errors" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/request" "github.com/ory/kratos/selfservice/hook" ) func (m *RegistryDefault) HookVerifier() *hook.Verifier { if m.hookVerifier == nil { m.hookVerifier = hook.NewVerifier(m) } return m.hookVerifier } func (m *RegistryDefault) HookSessionIssuer() *hook.SessionIssuer { if m.hookSessionIssuer == nil { m.hookSessionIssuer = hook.NewSessionIssuer(m) } return m.hookSessionIssuer } func (m *RegistryDefault) HookSessionDestroyer() *hook.SessionDestroyer { if m.hookSessionDestroyer == nil { m.hookSessionDestroyer = hook.NewSessionDestroyer(m) } return m.hookSessionDestroyer } func (m *RegistryDefault) HookAddressVerifier() *hook.AddressVerifier { if m.hookAddressVerifier == nil { m.hookAddressVerifier = hook.NewAddressVerifier(m) } return m.hookAddressVerifier } func (m *RegistryDefault) HookShowVerificationUI() *hook.ShowVerificationUIHook { if m.hookShowVerificationUI == nil { m.hookShowVerificationUI = hook.NewShowVerificationUIHook(m) } return m.hookShowVerificationUI } func (m *RegistryDefault) WithHooks(hooks map[string]func(config.SelfServiceHook) interface{}) { m.injectedSelfserviceHooks = hooks } func (m *RegistryDefault) WithExtraHandlers(handlers []NewHandler) { m.extraHandlerFactories = handlers } func getHooks[T any](m *RegistryDefault, credentialsType string, configs []config.SelfServiceHook) ([]T, error) { hooks := make([]T, 0, len(configs)) var addSessionIssuer bool allHooksLoop: for _, h := range configs { switch h.Name { case hook.KeySessionIssuer: // The session issuer hook always needs to come last. addSessionIssuer = true case hook.KeySessionDestroyer: if h, ok := any(m.HookSessionDestroyer()).(T); ok { hooks = append(hooks, h) } case hook.KeyWebHook: cfg := request.Config{} if err := json.Unmarshal(h.Config, &cfg); err != nil { m.l.WithError(err).WithField("raw_config", string(h.Config)).Error("failed to unmarshal hook configuration, ignoring hook") return nil, errors.WithStack(fmt.Errorf("failed to unmarshal webhook configuration for %s: %w", credentialsType, err)) } if h, ok := any(hook.NewWebHook(m, &cfg)).(T); ok { hooks = append(hooks, h) } case hook.KeyRequireVerifiedAddress: if h, ok := any(m.HookAddressVerifier()).(T); ok { hooks = append(hooks, h) } case hook.KeyVerificationUI: if h, ok := any(m.HookShowVerificationUI()).(T); ok { hooks = append(hooks, h) } case hook.KeyVerifier: if h, ok := any(m.HookVerifier()).(T); ok { hooks = append(hooks, h) } default: for name, m := range m.injectedSelfserviceHooks { if name == h.Name { if h, ok := m(h).(T); ok { hooks = append(hooks, h) } continue allHooksLoop } } m.l. WithField("for", credentialsType). WithField("hook", h.Name). Warn("A configuration for a non-existing hook was found and will be ignored.") } } if addSessionIssuer { if h, ok := any(m.HookSessionIssuer()).(T); ok { hooks = append(hooks, h) } } return hooks, nil } ================================================ FILE: driver/registry_default_login.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow/login" ) func (m *RegistryDefault) LoginHookExecutor() *login.HookExecutor { if m.selfserviceLoginExecutor == nil { m.selfserviceLoginExecutor = login.NewHookExecutor(m) } return m.selfserviceLoginExecutor } func (m *RegistryDefault) PreLoginHooks(ctx context.Context) ([]login.PreHookExecutor, error) { return getHooks[login.PreHookExecutor](m, "", m.Config().SelfServiceFlowLoginBeforeHooks(ctx)) } func (m *RegistryDefault) PostLoginHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]login.PostHookExecutor, error) { hooks, err := getHooks[login.PostHookExecutor](m, string(credentialsType), m.Config().SelfServiceFlowLoginAfterHooks(ctx, string(credentialsType))) if err != nil { return nil, err } if len(hooks) > 0 { return hooks, nil } // since we don't want merging hooks defined in a specific strategy and global hooks // global hooks are added only if no strategy specific hooks are defined return getHooks[login.PostHookExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowLoginAfterHooks(ctx, config.HookGlobal)) } func (m *RegistryDefault) LoginHandler() *login.Handler { if m.selfserviceLoginHandler == nil { m.selfserviceLoginHandler = login.NewHandler(m) } return m.selfserviceLoginHandler } func (m *RegistryDefault) LoginFlowErrorHandler() *login.ErrorHandler { if m.selfserviceLoginRequestErrorHandler == nil { m.selfserviceLoginRequestErrorHandler = login.NewFlowErrorHandler(m) } return m.selfserviceLoginRequestErrorHandler } ================================================ FILE: driver/registry_default_recovery.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/strategy/code" ) func (m *RegistryDefault) RecoveryFlowErrorHandler() *recovery.ErrorHandler { if m.selfserviceRecoveryErrorHandler == nil { m.selfserviceRecoveryErrorHandler = recovery.NewErrorHandler(m) } return m.selfserviceRecoveryErrorHandler } func (m *RegistryDefault) RecoveryHandler() *recovery.Handler { if m.selfserviceRecoveryHandler == nil { m.selfserviceRecoveryHandler = recovery.NewHandler(m) } return m.selfserviceRecoveryHandler } func (m *RegistryDefault) RecoveryStrategies(ctx context.Context) (recoveryStrategies recovery.Strategies) { for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(recovery.Strategy); ok { if m.Config().SelfServiceStrategy(ctx, s.RecoveryStrategyID()).Enabled { recoveryStrategies = append(recoveryStrategies, s) } } } return } // GetActiveRecoveryStrategies returns the currently active recovery strategies. // It returns a list of all strategies and the specific primary strategy. // If no primary recovery strategy has been set, an error is returned. func (m *RegistryDefault) GetActiveRecoveryStrategies(ctx context.Context) (active recovery.Strategies, primary recovery.Strategy, err error) { as := m.Config().SelfServiceFlowRecoveryUse(ctx) s, ps, err := m.RecoveryStrategies(ctx).ActiveStrategies(as) if err != nil { return nil, ps, errors.WithStack(herodot.ErrBadRequest. WithReasonf("You attempted recovery using %s, which is not enabled or does not exist. An administrator needs to enable this recovery method.", as)) } return s, ps, nil } func (m *RegistryDefault) AllRecoveryStrategies() (recoveryStrategies recovery.Strategies) { for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(recovery.Strategy); ok { recoveryStrategies = append(recoveryStrategies, s) } } return } func (m *RegistryDefault) RecoveryExecutor() *recovery.HookExecutor { if m.selfserviceRecoveryExecutor == nil { m.selfserviceRecoveryExecutor = recovery.NewHookExecutor(m) } return m.selfserviceRecoveryExecutor } func (m *RegistryDefault) PreRecoveryHooks(ctx context.Context) ([]recovery.PreHookExecutor, error) { return getHooks[recovery.PreHookExecutor](m, "", m.Config().SelfServiceFlowRecoveryBeforeHooks(ctx)) } func (m *RegistryDefault) PostRecoveryHooks(ctx context.Context) ([]recovery.PostHookExecutor, error) { return getHooks[recovery.PostHookExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowRecoveryAfterHooks(ctx, config.HookGlobal)) } func (m *RegistryDefault) CodeSender() *code.Sender { if m.selfserviceCodeSender == nil { m.selfserviceCodeSender = code.NewSender(m) } return m.selfserviceCodeSender } ================================================ FILE: driver/registry_default_registration.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "slices" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow/registration" ) func (m *RegistryDefault) PostRegistrationPrePersistHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]registration.PostHookPrePersistExecutor, error) { hooks, err := getHooks[registration.PostHookPrePersistExecutor](m, string(credentialsType), m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, string(credentialsType))) if err != nil { return nil, err } return hooks, nil } func (m *RegistryDefault) PostRegistrationPostPersistHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]registration.PostHookPostPersistExecutor, error) { hooks, err := getHooks[registration.PostHookPostPersistExecutor](m, string(credentialsType), m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, string(credentialsType))) if err != nil { return nil, err } if len(hooks) == 0 { // since we don't want merging hooks defined in a specific strategy and // global hooks are added only if no strategy specific hooks are defined hooks, err = getHooks[registration.PostHookPostPersistExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, config.HookGlobal)) if err != nil { return nil, err } } // WARNING - If you remove this, no verification emails / sms will be sent post-registration. if m.Config().SelfServiceFlowVerificationEnabled(ctx) { hooks = slices.Insert(hooks, 0, registration.PostHookPostPersistExecutor(m.HookVerifier())) } return hooks, nil } func (m *RegistryDefault) PreRegistrationHooks(ctx context.Context) ([]registration.PreHookExecutor, error) { return getHooks[registration.PreHookExecutor](m, "", m.Config().SelfServiceFlowRegistrationBeforeHooks(ctx)) } func (m *RegistryDefault) RegistrationExecutor() *registration.HookExecutor { if m.selfserviceRegistrationExecutor == nil { m.selfserviceRegistrationExecutor = registration.NewHookExecutor(m) } return m.selfserviceRegistrationExecutor } func (m *RegistryDefault) RegistrationHookExecutor() *registration.HookExecutor { if m.selfserviceRegistrationExecutor == nil { m.selfserviceRegistrationExecutor = registration.NewHookExecutor(m) } return m.selfserviceRegistrationExecutor } func (m *RegistryDefault) RegistrationErrorHandler() *registration.ErrorHandler { if m.seflserviceRegistrationErrorHandler == nil { m.seflserviceRegistrationErrorHandler = registration.NewErrorHandler(m) } return m.seflserviceRegistrationErrorHandler } func (m *RegistryDefault) RegistrationHandler() *registration.Handler { if m.selfserviceRegistrationHandler == nil { m.selfserviceRegistrationHandler = registration.NewHandler(m) } return m.selfserviceRegistrationHandler } func (m *RegistryDefault) RegistrationFlowErrorHandler() *registration.ErrorHandler { if m.selfserviceRegistrationRequestErrorHandler == nil { m.selfserviceRegistrationRequestErrorHandler = registration.NewErrorHandler(m) } return m.selfserviceRegistrationRequestErrorHandler } ================================================ FILE: driver/registry_default_schemas.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "github.com/ory/kratos/schema" ) func (m *RegistryDefault) IdentityTraitsSchemas(ctx context.Context) (schema.IdentitySchemaList, error) { if m.identitySchemaProvider == nil { m.identitySchemaProvider = schema.NewDefaultIdentityTraitsProvider(m) } return m.identitySchemaProvider.IdentityTraitsSchemas(ctx) } ================================================ FILE: driver/registry_default_schemas_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver_test import ( "context" "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/pkg" "github.com/ory/kratos/schema" "github.com/ory/x/urlx" ) func TestRegistryDefault_IdentityTraitsSchemas(t *testing.T) { ctx := context.Background() conf, reg := pkg.NewFastRegistryWithMocks(t) defaultSchema := schema.Schema{ ID: "default", URL: urlx.ParseOrPanic("file://default.schema.json"), RawURL: "file://default.schema.json", } altSchema := schema.Schema{ ID: "alt", URL: urlx.ParseOrPanic("file://other.schema.json"), RawURL: "file://other.schema.json", } conf.MustSet(ctx, config.ViperKeyIdentitySchemas, []config.Schema{ {ID: altSchema.ID, URL: altSchema.RawURL}, {ID: defaultSchema.ID, URL: defaultSchema.RawURL}, }) ss, err := reg.IdentityTraitsSchemas(context.Background()) require.NoError(t, err) assert.Equal(t, 2, ss.Total()) assert.Contains(t, ss, defaultSchema) assert.Contains(t, ss, altSchema) } ================================================ FILE: driver/registry_default_sessiontokenexchange.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import "github.com/ory/kratos/selfservice/sessiontokenexchange" func (m *RegistryDefault) SessionTokenExchangePersister() sessiontokenexchange.Persister { return m.Persister() } ================================================ FILE: driver/registry_default_settings.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "slices" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/selfservice/flow/settings" ) func (m *RegistryDefault) PostSettingsPrePersistHooks(ctx context.Context, settingsType string) ([]settings.PostHookPrePersistExecutor, error) { return getHooks[settings.PostHookPrePersistExecutor](m, settingsType, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, settingsType)) } func (m *RegistryDefault) PreSettingsHooks(ctx context.Context) ([]settings.PreHookExecutor, error) { return getHooks[settings.PreHookExecutor](m, "", m.Config().SelfServiceFlowSettingsBeforeHooks(ctx)) } func (m *RegistryDefault) PostSettingsPostPersistHooks(ctx context.Context, settingsType string) ([]settings.PostHookPostPersistExecutor, error) { hooks, err := getHooks[settings.PostHookPostPersistExecutor](m, settingsType, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, settingsType)) if err != nil { return nil, err } if len(hooks) == 0 { // since we don't want merging hooks defined in a specific strategy and // global hooks are added only if no strategy specific hooks are defined hooks, err = getHooks[settings.PostHookPostPersistExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, config.HookGlobal)) if err != nil { return nil, err } } if m.Config().SelfServiceFlowVerificationEnabled(ctx) { hooks = slices.Insert(hooks, 0, settings.PostHookPostPersistExecutor(m.HookVerifier())) } return hooks, nil } func (m *RegistryDefault) SettingsHookExecutor() *settings.HookExecutor { if m.selfserviceSettingsExecutor == nil { m.selfserviceSettingsExecutor = settings.NewHookExecutor(m) } return m.selfserviceSettingsExecutor } func (m *RegistryDefault) SettingsHandler() *settings.Handler { if m.selfserviceSettingsHandler == nil { m.selfserviceSettingsHandler = settings.NewHandler(m) } return m.selfserviceSettingsHandler } func (m *RegistryDefault) SettingsFlowErrorHandler() *settings.ErrorHandler { if m.selfserviceSettingsErrorHandler == nil { m.selfserviceSettingsErrorHandler = settings.NewErrorHandler(m) } return m.selfserviceSettingsErrorHandler } func (m *RegistryDefault) SettingsStrategies(ctx context.Context) (profileStrategies settings.Strategies) { for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(settings.Strategy); ok { if m.Config().SelfServiceStrategy(ctx, s.SettingsStrategyID()).Enabled { profileStrategies = append(profileStrategies, s) } } } return } func (m *RegistryDefault) AllSettingsStrategies() settings.Strategies { var profileStrategies []settings.Strategy for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(settings.Strategy); ok { profileStrategies = append(profileStrategies, s) } } return profileStrategies } ================================================ FILE: driver/registry_default_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver_test import ( "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/x/configx" "github.com/ory/x/contextx" "github.com/ory/x/logrusx" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/pkg" "github.com/ory/kratos/request" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/hook" ) func TestDriverDefault_Hooks(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("type=verification", func(t *testing.T) { t.Parallel() // BEFORE hooks _, reg := pkg.NewVeryFastRegistryWithoutDB(t) for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []verification.PreHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []verification.PreHookExecutor { return []verification.PreHookExecutor{} }, }, { uc: "Two web_hooks are configured", config: map[string]any{ config.ViperKeySelfServiceVerificationBeforeHooks: []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []verification.PreHookExecutor { return []verification.PreHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("before/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) h, err := reg.PreVerificationHooks(ctx) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } // AFTER hooks for _, tc := range []struct { uc string prep func(conf *config.Config) config map[string]any expect func(reg *driver.RegistryDefault) []verification.PostHookExecutor }{ { uc: "No hooks configured", prep: func(conf *config.Config) {}, expect: func(reg *driver.RegistryDefault) []verification.PostHookExecutor { return []verification.PostHookExecutor{} }, }, { uc: "Multiple web_hooks configured", config: map[string]any{ config.ViperKeySelfServiceVerificationAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []verification.PostHookExecutor { return []verification.PostHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("after/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) h, err := reg.PostVerificationHooks(ctx) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } }) t.Run("type=recovery", func(t *testing.T) { t.Parallel() // BEFORE hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []recovery.PreHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []recovery.PreHookExecutor { return []recovery.PreHookExecutor{} }, }, { uc: "Two web_hooks are configured", config: map[string]any{ config.ViperKeySelfServiceRecoveryBeforeHooks: []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []recovery.PreHookExecutor { return []recovery.PreHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("before/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PreRecoveryHooks(ctx) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } // AFTER hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []recovery.PostHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []recovery.PostHookExecutor { return []recovery.PostHookExecutor{} }, }, { uc: "Multiple web_hooks configured", config: map[string]any{ config.ViperKeySelfServiceRecoveryAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []recovery.PostHookExecutor { return []recovery.PostHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("after/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PostRecoveryHooks(ctx) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } }) t.Run("type=registration", func(t *testing.T) { t.Parallel() // BEFORE hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []registration.PreHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []registration.PreHookExecutor { return []registration.PreHookExecutor{} }, }, { uc: "Two web_hooks are configured", config: map[string]any{ config.ViperKeySelfServiceRegistrationBeforeHooks: []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []registration.PreHookExecutor { return []registration.PreHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("before/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PreRegistrationHooks(ctx) require.NoError(t, err) assert.EqualValues(t, tc.expect(reg), h) }) } // AFTER hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return []registration.PostHookPostPersistExecutor{} }, }, { uc: "Only session hook configured for password strategy", config: map[string]any{ config.ViperKeySelfServiceVerificationEnabled: true, config.ViperKeySelfServiceRegistrationAfter + ".password.hooks": []map[string]any{ {"hook": "session"}, }, }, expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return []registration.PostHookPostPersistExecutor{ hook.NewVerifier(reg), hook.NewSessionIssuer(reg), } }, }, { uc: "A session hook and a web_hook are configured for password strategy", config: map[string]any{ config.ViperKeySelfServiceVerificationEnabled: true, config.ViperKeySelfServiceRegistrationAfter + ".password.hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"headers": map[string]string{"X-Custom-Header": "test"}, "url": "foo", "method": "POST", "body": "bar"}}, {"hook": "session"}, }, }, expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return []registration.PostHookPostPersistExecutor{ hook.NewVerifier(reg), hook.NewWebHook(reg, &request.Config{URL: "foo", Method: "POST", TemplateURI: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewSessionIssuer(reg), } }, }, { uc: "Two web_hooks are configured on a global level", config: map[string]any{ config.ViperKeySelfServiceRegistrationAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return []registration.PostHookPostPersistExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, { uc: "Hooks are configured on a global level, as well as on a strategy level", config: map[string]any{ config.ViperKeySelfServiceRegistrationAfter + ".password.hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "session"}, }, config.ViperKeySelfServiceRegistrationAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, config.ViperKeySelfServiceVerificationEnabled: true, }, expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return []registration.PostHookPostPersistExecutor{ hook.NewVerifier(reg), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewSessionIssuer(reg), } }, }, { uc: "show_verification_ui is configured", config: map[string]any{ config.ViperKeySelfServiceRegistrationAfter + ".hooks": []map[string]any{ {"hook": "show_verification_ui"}, }, }, expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return []registration.PostHookPostPersistExecutor{ hook.NewShowVerificationUIHook(reg), } }, }, } { t.Run(fmt.Sprintf("after/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PostRegistrationPostPersistHooks(ctx, identity.CredentialsTypePassword) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } }) t.Run("type=login", func(t *testing.T) { t.Parallel() // BEFORE hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []login.PreHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []login.PreHookExecutor { return []login.PreHookExecutor{} }, }, { uc: "Two web_hooks are configured", config: map[string]any{ config.ViperKeySelfServiceLoginBeforeHooks: []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []login.PreHookExecutor { return []login.PreHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("before/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PreLoginHooks(ctx) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } // AFTER hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []login.PostHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{} }, }, { uc: "Only revoke_active_sessions hook configured for password strategy", config: map[string]any{ config.ViperKeySelfServiceLoginAfter + ".password.hooks": []map[string]any{ {"hook": "revoke_active_sessions"}, }, }, expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{ hook.NewSessionDestroyer(reg), } }, }, { uc: "Only require_verified_address hook configured for password strategy", config: map[string]any{ config.ViperKeySelfServiceLoginAfter + ".password.hooks": []map[string]any{ {"hook": "require_verified_address"}, }, }, expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{ hook.NewAddressVerifier(reg), } }, }, { uc: "A revoke_active_sessions hook, require_verified_address hook and a web_hook are configured for password strategy", config: map[string]any{ config.ViperKeySelfServiceLoginAfter + ".password.hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"headers": map[string]string{"X-Custom-Header": "test"}, "url": "foo", "method": "POST", "body": "bar"}}, {"hook": "require_verified_address"}, {"hook": "revoke_active_sessions"}, }, }, expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{ hook.NewWebHook(reg, &request.Config{TemplateURI: "bar", Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewAddressVerifier(reg), hook.NewSessionDestroyer(reg), } }, }, { uc: "Two web_hooks are configured on a global level", config: map[string]any{ config.ViperKeySelfServiceLoginAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, { uc: "Hooks are configured on a global level, as well as on a strategy level", config: map[string]any{ config.ViperKeySelfServiceLoginAfter + ".password.hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "revoke_active_sessions"}, {"hook": "require_verified_address"}, }, config.ViperKeySelfServiceLoginAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewSessionDestroyer(reg), hook.NewAddressVerifier(reg), } }, }, } { t.Run(fmt.Sprintf("after/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PostLoginHooks(ctx, identity.CredentialsTypePassword) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } }) t.Run("type=settings", func(t *testing.T) { t.Parallel() // BEFORE hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []settings.PreHookExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []settings.PreHookExecutor { return []settings.PreHookExecutor{} }, }, { uc: "Two web_hooks are configured", config: map[string]any{ config.ViperKeySelfServiceSettingsBeforeHooks: []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []settings.PreHookExecutor { return []settings.PreHookExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("before/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PreSettingsHooks(ctx) require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } // AFTER hooks for _, tc := range []struct { uc string config map[string]any expect func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor }{ { uc: "No hooks configured", expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor { return []settings.PostHookPostPersistExecutor{} }, }, { uc: "Only verify hook configured for the strategy", config: map[string]any{ config.ViperKeySelfServiceVerificationEnabled: true, // I think this is a bug as there is a hook named verify defined for both profile and password // strategies. Instead of using it, the code makes use of the property used above and which // is defined in an entirely different flow (verification). }, expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor { return []settings.PostHookPostPersistExecutor{ hook.NewVerifier(reg), } }, }, { uc: "A verify hook and a web_hook are configured for profile strategy", config: map[string]any{ config.ViperKeySelfServiceSettingsAfter + ".profile.hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"headers": map[string]string{"X-Custom-Header": "test"}, "url": "foo", "method": "POST", "body": "bar"}}, }, config.ViperKeySelfServiceVerificationEnabled: true, }, expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor { return []settings.PostHookPostPersistExecutor{ hook.NewVerifier(reg), hook.NewWebHook(reg, &request.Config{TemplateURI: "bar", Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, { uc: "Two web_hooks are configured on a global level", config: map[string]any{ config.ViperKeySelfServiceSettingsAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, {"hook": "web_hook", "config": map[string]any{"url": "bar", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor { return []settings.PostHookPostPersistExecutor{ hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, { uc: "Hooks are configured on a global level, as well as on a strategy level", config: map[string]any{ config.ViperKeySelfServiceVerificationEnabled: true, config.ViperKeySelfServiceSettingsAfter + ".profile.hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "GET", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, config.ViperKeySelfServiceSettingsAfter + ".hooks": []map[string]any{ {"hook": "web_hook", "config": map[string]any{"url": "foo", "method": "POST", "headers": map[string]string{"X-Custom-Header": "test"}}}, }, }, expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor { return []settings.PostHookPostPersistExecutor{ hook.NewVerifier(reg), hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}), } }, }, } { t.Run(fmt.Sprintf("after/uc=%s", tc.uc), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h, err := reg.PostSettingsPostPersistHooks(ctx, "profile") require.NoError(t, err) assert.Equal(t, tc.expect(reg), h) }) } }) } func TestDriverDefault_Strategies(t *testing.T) { t.Parallel() ctx := context.Background() t.Run("case=registration", func(t *testing.T) { t.Parallel() for _, tc := range []struct { name string config map[string]any expect []string }{ { name: "no strategies", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"profile"}, }, { name: "only password", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"profile", "password"}, }, { name: "oidc and password", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".oidc.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"profile", "password", "oidc"}, }, { name: "oidc, password and totp", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".oidc.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".totp.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"profile", "password", "oidc"}, }, { name: "password and code", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": true, }, expect: []string{"profile", "password", "code"}, }, } { t.Run(fmt.Sprintf("subcase=%s", tc.name), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) s := reg.RegistrationStrategies(ctx) require.Len(t, s, len(tc.expect)) for k, e := range tc.expect { assert.Equal(t, e, s[k].ID().String()) } }) } }) t.Run("case=login", func(t *testing.T) { t.Parallel() for _, tc := range []struct { name string config map[string]any expect []string }{ { name: "no strategies", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, }, { name: "only password", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"password"}, }, { name: "oidc and password", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".oidc.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"password", "oidc"}, }, { name: "oidc, password and totp", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".oidc.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".totp.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, }, expect: []string{"password", "oidc", "totp"}, }, { name: "password and code", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": true, }, expect: []string{"password", "code"}, }, { name: "code is enabled if passwordless_enabled is true", config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".password.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".code.passwordless_enabled": true, }, expect: []string{"code"}, }, } { t.Run(fmt.Sprintf("run=%s", tc.name), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) s := reg.LoginStrategies(ctx) require.Len(t, s, len(tc.expect)) for k, e := range tc.expect { assert.Equal(t, e, s[k].ID().String()) } }) } }) t.Run("case=recovery", func(t *testing.T) { t.Parallel() for k, tc := range []struct { config map[string]any expect []string }{ { config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".code.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".link.enabled": false, }, }, { config: map[string]any{ config.ViperKeySelfServiceStrategyConfig + ".code.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".link.enabled": true, }, expect: []string{"code", "link"}, }, } { t.Run(fmt.Sprintf("run=%d", k), func(t *testing.T) { t.Parallel() ctx := contextx.WithConfigValues(ctx, tc.config) _, reg := pkg.NewVeryFastRegistryWithoutDB(t) s := reg.RecoveryStrategies(ctx) require.Len(t, s, len(tc.expect)) for k, e := range tc.expect { assert.Equal(t, e, s[k].RecoveryStrategyID()) } }) } }) t.Run("case=settings", func(t *testing.T) { t.Parallel() l := logrusx.New("", "") for k, tc := range []struct { configOptions []configx.OptionModifier expect []string }{ { configOptions: []configx.OptionModifier{configx.WithValues(map[string]any{ config.ViperKeyDSN: config.DefaultSQLiteMemoryDSN, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".oidc.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".profile.enabled": false, })}, }, { configOptions: []configx.OptionModifier{configx.WithValues(map[string]any{ config.ViperKeyDSN: config.DefaultSQLiteMemoryDSN, config.ViperKeySelfServiceStrategyConfig + ".profile.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": false, })}, expect: []string{"profile"}, }, { configOptions: []configx.OptionModifier{configx.WithValues(map[string]any{ config.ViperKeyDSN: config.DefaultSQLiteMemoryDSN, config.ViperKeySelfServiceStrategyConfig + ".profile.enabled": true, config.ViperKeySelfServiceStrategyConfig + ".password.enabled": false, config.ViperKeySelfServiceStrategyConfig + ".totp.enabled": true, })}, expect: []string{"profile", "totp"}, }, { configOptions: []configx.OptionModifier{configx.WithValues(map[string]any{ config.ViperKeyDSN: config.DefaultSQLiteMemoryDSN, })}, expect: []string{"profile", "password"}, }, { configOptions: []configx.OptionModifier{ configx.WithConfigFiles("../test/e2e/profiles/verification/.kratos.yml"), configx.WithValue(config.ViperKeyDSN, config.DefaultSQLiteMemoryDSN), }, expect: []string{"profile", "password"}, }, } { t.Run(fmt.Sprintf("run=%d", k), func(t *testing.T) { conf := config.MustNew(t, l, &contextx.Default{}, append(tc.configOptions, configx.SkipValidation())...) reg, err := driver.NewRegistryFromDSN(ctx, conf, l) require.NoError(t, err) s := reg.SettingsStrategies(ctx) require.Len(t, s, len(tc.expect)) for k, e := range tc.expect { assert.Equal(t, e, s[k].SettingsStrategyID()) } }) } }) } func TestDefaultRegistry_AllStrategies(t *testing.T) { t.Parallel() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) t.Run("case=all login strategies", func(t *testing.T) { expects := []string{"password", "oidc", "code", "totp", "passkey", "webauthn", "lookup_secret", "identifier_first"} s := reg.AllLoginStrategies() require.Len(t, s, len(expects)) for k, e := range expects { assert.Equal(t, e, s[k].ID().String()) } }) t.Run("case=all registration strategies", func(t *testing.T) { expects := []string{"profile", "password", "oidc", "code", "passkey", "webauthn"} s := reg.AllRegistrationStrategies() require.Len(t, s, len(expects)) for k, e := range expects { assert.Equal(t, e, s[k].ID().String()) } }) t.Run("case=all settings strategies", func(t *testing.T) { expects := []string{"profile", "password", "oidc", "totp", "passkey", "webauthn", "lookup_secret"} s := reg.AllSettingsStrategies() require.Len(t, s, len(expects)) for k, e := range expects { assert.Equal(t, e, s[k].SettingsStrategyID()) } }) t.Run("case=all recovery strategies", func(t *testing.T) { expects := []string{"code", "link"} s := reg.AllRecoveryStrategies() require.Len(t, s, len(expects)) for k, e := range expects { assert.Equal(t, e, s[k].RecoveryStrategyID()) } }) } func TestGetActiveRecoveryStrategy(t *testing.T) { t.Parallel() ctx := context.Background() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) t.Run("returns error if active strategy is disabled", func(t *testing.T) { ctx := contextx.WithConfigValues(ctx, map[string]any{ "selfservice.methods.code.enabled": false, config.ViperKeySelfServiceRecoveryUse: "code", }) _, _, err := reg.GetActiveRecoveryStrategies(ctx) require.Error(t, err) }) t.Run("returns active strategies", func(t *testing.T) { for _, sID := range []string{ "code", "link", } { t.Run(fmt.Sprintf("strategy=%s", sID), func(t *testing.T) { ctx := contextx.WithConfigValues(ctx, map[string]any{ fmt.Sprintf("selfservice.methods.%s.enabled", sID): true, config.ViperKeySelfServiceRecoveryUse: sID, }) s, ps, err := reg.GetActiveRecoveryStrategies(ctx) require.NoError(t, err) require.Len(t, s, 1) require.Equal(t, sID, ps.RecoveryStrategyID()) }) } }) } func TestGetActiveVerificationStrategy(t *testing.T) { t.Parallel() ctx := context.Background() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) t.Run("returns error if active strategy is disabled", func(t *testing.T) { ctx := contextx.WithConfigValues(ctx, map[string]any{ "selfservice.methods.code.enabled": false, config.ViperKeySelfServiceVerificationUse: "code", }) _, _, err := reg.GetActiveVerificationStrategies(ctx) require.Error(t, err) }) t.Run("returns active strategy", func(t *testing.T) { for _, sID := range []string{ "code", "link", } { t.Run(fmt.Sprintf("strategy=%s", sID), func(t *testing.T) { ctx := contextx.WithConfigValues(ctx, map[string]any{ fmt.Sprintf("selfservice.methods.%s.enabled", sID): true, config.ViperKeySelfServiceVerificationUse: sID, }) _, s, err := reg.GetActiveVerificationStrategies(ctx) require.NoError(t, err) require.Equal(t, sID, s.VerificationStrategyID()) }) } }) } ================================================ FILE: driver/registry_default_verification.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package driver import ( "context" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/strategy/link" ) func (m *RegistryDefault) VerificationFlowPersister() verification.FlowPersister { return m.persister } func (m *RegistryDefault) VerificationFlowErrorHandler() *verification.ErrorHandler { if m.selfserviceVerifyErrorHandler == nil { m.selfserviceVerifyErrorHandler = verification.NewErrorHandler(m) } return m.selfserviceVerifyErrorHandler } func (m *RegistryDefault) VerificationManager() *identity.Manager { if m.selfserviceVerifyManager == nil { m.selfserviceVerifyManager = identity.NewManager(m) } return m.selfserviceVerifyManager } func (m *RegistryDefault) VerificationHandler() *verification.Handler { if m.selfserviceVerifyHandler == nil { m.selfserviceVerifyHandler = verification.NewHandler(m) } return m.selfserviceVerifyHandler } func (m *RegistryDefault) LinkSender() *link.Sender { if m.selfserviceLinkSender == nil { m.selfserviceLinkSender = link.NewSender(m) } return m.selfserviceLinkSender } // GetActiveVerificationStrategies returns the currently active verification strategies. // It returns a list of all strategies and the specific primary strategy. // If no primary verification strategy has been set, an error is returned. func (m *RegistryDefault) GetActiveVerificationStrategies(ctx context.Context) (active verification.Strategies, primary verification.PrimaryStrategy, err error) { as := m.Config().SelfServiceFlowVerificationUse(ctx) s, ps, err := m.VerificationStrategies(ctx).ActiveStrategies(as) if err != nil { return nil, ps, errors.WithStack(herodot.ErrBadRequest. WithReasonf("The active verification strategy %s is not enabled. Please enable it in the configuration.", as)) } return s, ps, nil } func (m *RegistryDefault) VerificationStrategies(ctx context.Context) (verificationStrategies verification.Strategies) { for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(verification.Strategy); ok { if m.Config().SelfServiceStrategy(ctx, s.VerificationStrategyID()).Enabled { verificationStrategies = append(verificationStrategies, s) } } } return } func (m *RegistryDefault) AllVerificationStrategies() (verificationStrategies verification.Strategies) { for _, strategy := range m.selfServiceStrategies() { if s, ok := strategy.(verification.Strategy); ok { verificationStrategies = append(verificationStrategies, s) } } return } func (m *RegistryDefault) VerificationExecutor() *verification.HookExecutor { if m.selfserviceVerificationExecutor == nil { m.selfserviceVerificationExecutor = verification.NewHookExecutor(m) } return m.selfserviceVerificationExecutor } func (m *RegistryDefault) PreVerificationHooks(ctx context.Context) ([]verification.PreHookExecutor, error) { return getHooks[verification.PreHookExecutor](m, "", m.Config().SelfServiceFlowVerificationBeforeHooks(ctx)) } func (m *RegistryDefault) PostVerificationHooks(ctx context.Context) ([]verification.PostHookExecutor, error) { return getHooks[verification.PostHookExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowVerificationAfterHooks(ctx, config.HookGlobal)) } ================================================ FILE: embedx/config.schema.json ================================================ { "$id": "https://github.com/ory/kratos/embedx/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Ory Kratos Configuration", "type": "object", "definitions": { "baseUrl": { "title": "Base URL", "description": "The URL where the endpoint is exposed at. This domain is used to generate redirects, form URLs, and more.", "type": "string", "format": "uri-reference", "examples": [ "https://my-app.com/", "https://my-app.com/.ory/kratos/public" ] }, "socket": { "type": "object", "additionalProperties": false, "description": "Sets the permissions of the unix socket", "properties": { "owner": { "type": "string", "description": "Owner of unix socket. If empty, the owner will be the user running Kratos.", "default": "" }, "group": { "type": "string", "description": "Group of unix socket. If empty, the group will be the primary group of the user running Kratos.", "default": "" }, "mode": { "type": "integer", "description": "Mode of unix socket in numeric form", "default": 493, "minimum": 0, "maximum": 511 } } }, "defaultReturnTo": { "title": "Redirect browsers to set URL per default", "description": "Ory Kratos redirects to this URL per default on completion of self-service flows and other browser interaction. Read this [article for more information on browser redirects](https://www.ory.sh/kratos/docs/concepts/browser-redirect-flow-completion).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/dashboard", "/dashboard"] }, "selfServiceSessionRevokerHook": { "type": "object", "properties": { "hook": { "const": "revoke_active_sessions" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceSessionIssuerHook": { "type": "object", "properties": { "hook": { "const": "session" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceRequireVerifiedAddressHook": { "type": "object", "properties": { "hook": { "const": "require_verified_address" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceVerificationHook": { "type": "object", "properties": { "hook": { "const": "verification" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceShowVerificationUIHook": { "type": "object", "properties": { "hook": { "const": "show_verification_ui" } }, "additionalProperties": false, "required": ["hook"] }, "b2bSSOHook": { "type": "object", "properties": { "hook": { "enum": ["b2b_sso", "organization"] }, "config": { "type": "object", "additionalProperties": true } }, "additionalProperties": false, "required": ["hook", "config"] }, "webHookAuthBasicAuthProperties": { "properties": { "type": { "const": "basic_auth" }, "config": { "type": "object", "properties": { "user": { "type": "string", "description": "user name for basic auth" }, "password": { "type": "string", "description": "password for basic auth" } }, "additionalProperties": false, "required": ["user", "password"] } }, "additionalProperties": false, "required": ["type", "config"] }, "httpRequestConfig": { "type": "object", "properties": { "url": { "title": "HTTP address of API endpoint", "description": "This URL will be used to send the emails to.", "examples": ["https://example.com/api/v1/email"], "type": "string", "pattern": "^https?://" }, "method": { "type": "string", "description": "The HTTP method to use (GET, POST, etc). Defaults to POST.", "default": "POST" }, "headers": { "type": "object", "description": "The HTTP headers that must be applied to request", "additionalProperties": { "type": "string" } }, "body": { "type": "string", "format": "uri", "pattern": "^(http|https|file|base64)://", "description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods which support HTTP body payloads", "default": "base64://ZnVuY3Rpb24oY3R4KSB7CiAgcmVjaXBpZW50OiBjdHgucmVjaXBpZW50LAogIHRlbXBsYXRlX3R5cGU6IGN0eC50ZW1wbGF0ZV90eXBlLAogIHRvOiBpZiAidGVtcGxhdGVfZGF0YSIgaW4gY3R4ICYmICJ0byIgaW4gY3R4LnRlbXBsYXRlX2RhdGEgdGhlbiBjdHgudGVtcGxhdGVfZGF0YS50byBlbHNlIG51bGwsCiAgcmVjb3ZlcnlfY29kZTogaWYgInRlbXBsYXRlX2RhdGEiIGluIGN0eCAmJiAicmVjb3ZlcnlfY29kZSIgaW4gY3R4LnRlbXBsYXRlX2RhdGEgdGhlbiBjdHgudGVtcGxhdGVfZGF0YS5yZWNvdmVyeV9jb2RlIGVsc2UgbnVsbCwKICByZWNvdmVyeV91cmw6IGlmICJ0ZW1wbGF0ZV9kYXRhIiBpbiBjdHggJiYgInJlY292ZXJ5X3VybCIgaW4gY3R4LnRlbXBsYXRlX2RhdGEgdGhlbiBjdHgudGVtcGxhdGVfZGF0YS5yZWNvdmVyeV91cmwgZWxzZSBudWxsLAogIHZlcmlmaWNhdGlvbl91cmw6IGlmICJ0ZW1wbGF0ZV9kYXRhIiBpbiBjdHggJiYgInZlcmlmaWNhdGlvbl91cmwiIGluIGN0eC50ZW1wbGF0ZV9kYXRhIHRoZW4gY3R4LnRlbXBsYXRlX2RhdGEudmVyaWZpY2F0aW9uX3VybCBlbHNlIG51bGwsCiAgdmVyaWZpY2F0aW9uX2NvZGU6IGlmICJ0ZW1wbGF0ZV9kYXRhIiBpbiBjdHggJiYgInZlcmlmaWNhdGlvbl9jb2RlIiBpbiBjdHgudGVtcGxhdGVfZGF0YSB0aGVuIGN0eC50ZW1wbGF0ZV9kYXRhLnZlcmlmaWNhdGlvbl9jb2RlIGVsc2UgbnVsbCwKICBzdWJqZWN0OiBjdHguc3ViamVjdCwKICBib2R5OiBjdHguYm9keQp9Cg==", "examples": [ "file:///path/to/body.jsonnet", "file://./body.jsonnet", "base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=", "https://oryapis.com/default_body.jsonnet" ] }, "auth": { "type": "object", "title": "Auth mechanisms", "description": "Define which auth mechanism to use for auth with the HTTP email provider", "oneOf": [ { "$ref": "#/definitions/webHookAuthApiKeyProperties" }, { "$ref": "#/definitions/webHookAuthBasicAuthProperties" } ] }, "additionalProperties": false }, "additionalProperties": false }, "webHookAuthApiKeyProperties": { "properties": { "type": { "const": "api_key" }, "config": { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the api key" }, "value": { "type": "string", "description": "The value of the api key" }, "in": { "type": "string", "description": "How the api key should be transferred", "enum": ["header", "cookie"] } }, "additionalProperties": false, "required": ["name", "value", "in"] } }, "additionalProperties": false, "required": ["type", "config"] }, "selfServiceWebHook": { "type": "object", "properties": { "hook": { "const": "web_hook" }, "config": { "type": "object", "title": "Web-Hook Configuration", "description": "Define what the hook should do", "properties": { "id": { "type": "string", "description": "The ID of the hook. Used to identify the hook in logs and errors. For debugging purposes only." }, "response": { "title": "Response Handling", "description": "How the web hook should handle the response", "type": "object", "additionalProperties": false, "properties": { "ignore": { "type": "boolean", "description": "Ignore the response from the web hook. If enabled the request will be made asynchronously which can be useful if you only wish to notify another system but do not parse the response.", "default": false }, "parse": { "type": "boolean", "default": false, "description": "If enabled parses the response before saving the flow result. Set this value to true if you would like to modify the identity, for example identity metadata, before saving it during registration. When enabled, you may also abort the registration, verification, login or settings flow due to, for example, a validation flow. Head over to the [web hook documentation](https://www.ory.sh/docs/kratos/hooks/configure-hooks) for more information." } }, "not": { "properties": { "ignore": { "const": true }, "parse": { "const": true } }, "required": ["ignore", "parse"] } }, "url": { "type": "string", "description": "The URL the Web-Hook should call", "format": "uri" }, "method": { "type": "string", "description": "The HTTP method to use (GET, POST, etc)." }, "headers": { "type": "object", "description": "The HTTP headers that must be applied to the Web-Hook", "additionalProperties": { "type": "string" } }, "body": { "type": "string", "oneOf": [ { "format": "uri", "pattern": "^(http|https|file|base64)://", "description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods, which support HTTP body payloads", "examples": [ "file:///path/to/body.jsonnet", "file://./body.jsonnet", "base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=", "https://oryapis.com/default_body.jsonnet" ] }, { "description": "DEPRECATED: please use a URI instead (i.e. prefix your filepath with 'file://')", "not": { "pattern": "^(http|https|file|base64)://" } } ] }, "can_interrupt": { "type": "boolean", "default": false, "description": "Deprecated, please use `response.parse` instead. If enabled allows the web hook to interrupt / abort the self-service flow. It only applies to certain flows (registration/verification/login/settings) and requires a valid response format." }, "emit_analytics_event": { "type": "boolean", "default": true, "description": "Emit tracing events for this webhook on delivery or error" }, "auth": { "type": "object", "title": "Auth mechanisms", "description": "Define which auth mechanism the Web-Hook should use", "oneOf": [ { "$ref": "#/definitions/webHookAuthApiKeyProperties" }, { "$ref": "#/definitions/webHookAuthBasicAuthProperties" } ] }, "additionalProperties": false }, "anyOf": [ { "not": { "properties": { "response": { "properties": { "ignore": { "const": true } }, "required": ["ignore"] } }, "required": ["response"] } }, { "properties": { "can_interrupt": { "const": false } }, "require": ["can_interrupt"] } ], "additionalProperties": false, "required": ["url", "method"] } }, "additionalProperties": false, "required": ["hook", "config"] }, "OIDCClaims": { "title": "OpenID Connect claims", "description": "The OpenID Connect claims and optionally their properties which should be included in the id_token or returned from the UserInfo Endpoint.", "type": "object", "examples": [ { "id_token": { "email": null, "email_verified": null } }, { "userinfo": { "given_name": { "essential": true }, "nickname": null, "email": { "essential": true }, "email_verified": { "essential": true }, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": { "essential": true }, "acr": { "values": ["urn:mace:incommon:iap:silver"] } } } ], "patternProperties": { "^userinfo$|^id_token$": { "type": "object", "additionalProperties": false, "patternProperties": { ".*": { "oneOf": [ { "const": null, "description": "Indicates that this Claim is being requested in the default manner." }, { "type": "object", "additionalProperties": false, "properties": { "essential": { "description": "Indicates whether the Claim being requested is an Essential Claim.", "type": "boolean" }, "value": { "description": "Requests that the Claim be returned with a particular value.", "$comment": "There seem to be no constrains on value" }, "values": { "description": "Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.", "type": "array", "items": { "$comment": "There seem to be no constrains on individual items" } } } } ] } } } } }, "selfServiceOIDCProvider": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon, amazon.", "type": "string", "enum": [ "github", "github-app", "gitlab", "generic", "google", "microsoft", "discord", "salesforce", "slack", "facebook", "auth0", "vk", "yandex", "apple", "spotify", "netid", "dingtalk", "patreon", "line", "linkedin", "linkedin_v2", "lark", "x", "fedcm-test", "amazon", "uaepass" ], "examples": ["google"] }, "label": { "title": "Optional string which will be used when generating labels for UI buttons.", "type": "string" }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to Ory Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "microsoft_tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] }, "subject_source": { "title": "Microsoft subject source", "description": "Controls which source the subject identifier is taken from by microsoft provider. If set to `userinfo` (the default) then the identifier is taken from the `sub` field of OIDC ID token or data received from `/userinfo` standard OIDC endpoint. If set to `me` then the `id` field of data structure received from `https://graph.microsoft.com/v1.0/me` is taken as an identifier. If the value is `oid` then the the oid (Object ID) is taken to identify users across different services.", "type": "string", "enum": ["userinfo", "me", "oid"], "default": "userinfo", "examples": ["userinfo"] }, "apple_team_id": { "title": "Apple Developer Team ID", "description": "Apple Developer Team ID needed for generating a JWT token for client secret", "type": "string", "examples": ["KP76DQS54M"] }, "apple_private_key_id": { "title": "Apple Private Key Identifier", "description": "Sign In with Apple Private Key Identifier needed for generating a JWT token for client secret", "type": "string", "examples": ["UX56C66723"] }, "apple_private_key": { "title": "Apple Private Key", "description": "Sign In with Apple Private Key needed for generating a JWT token for client secret", "type": "string", "examples": [ "-----BEGIN PRIVATE KEY-----\n........\n-----END PRIVATE KEY-----" ] }, "requested_claims": { "$ref": "#/definitions/OIDCClaims" }, "organization_id": { "title": "Organization ID", "description": "The ID of the organization that this provider belongs to. Only effective in the Ory Network.", "type": "string", "examples": ["12345678-1234-1234-1234-123456789012"] }, "additional_id_token_audiences": { "title": "Additional client ids allowed when using ID token submission", "type": "array", "items": { "type": "string", "examples": ["12345678-1234-1234-1234-123456789012"] } }, "claims_source": { "title": "Claims source", "description": "Can be either `userinfo` (calls the userinfo endpoint to get the claims) or `id_token` (takes the claims from the id token). It defaults to `id_token`", "type": "string", "enum": ["id_token", "userinfo"], "default": "id_token", "examples": ["id_token", "userinfo"] }, "pkce": { "title": "Proof Key for Code Exchange", "description": "PKCE controls if the OpenID Connect OAuth2 flow should use PKCE (Proof Key for Code Exchange). IMPORTANT: If you set this to `force`, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. Instead of /self-service/methods/oidc/callback/, you must use /self-service/methods/oidc/callback", "type": "string", "enum": ["auto", "never", "force"], "default": "auto" }, "fedcm_config_url": { "title": "Federation Configuration URL", "description": "The URL where the FedCM IdP configuration is located for the provider. This is only effective in the Ory Network.", "type": "string", "format": "uri", "examples": ["https://example.com/config.json"] }, "net_id_token_origin_header": { "title": "NetID Token Origin Header", "description": "Contains the orgin header to be used when exchanging a NetID FedCM token for an ID token", "type": "string", "examples": ["https://example.com"] }, "account_linking_mode": { "title": "Account linking mode", "description": "Controls how account conflicts are resolved for this provider. `confirm_with_existing_credential` (default) requires the user to verify their identity with an existing credential. `automatic` silently links accounts if the provider verifies email ownership. This is only effective in the Ory Network.", "type": "string", "enum": ["confirm_with_existing_credential", "automatic"], "default": "confirm_with_existing_credential" } }, "additionalProperties": false, "required": ["id", "provider", "client_id", "mapper_url"], "allOf": [ { "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["microsoft_tenant"] }, "else": { "not": { "properties": { "microsoft_tenant": {} }, "required": ["microsoft_tenant"] } } }, { "if": { "properties": { "provider": { "const": "apple" } }, "required": ["provider"] }, "then": { "not": { "properties": { "client_secret": { "type": "string", "minLength": 1 } }, "required": ["client_secret"] }, "required": [ "apple_private_key_id", "apple_private_key", "apple_team_id" ] }, "else": { "required": ["client_secret"], "allOf": [ { "not": { "properties": { "apple_team_id": { "type": "string", "minLength": 1 } }, "required": ["apple_team_id"] } }, { "not": { "properties": { "apple_private_key_id": { "type": "string", "minLength": 1 } }, "required": ["apple_private_key_id"] } }, { "not": { "properties": { "apple_private_key": { "type": "string", "minLength": 1 } }, "required": ["apple_private_key"] } } ] } } ] }, "selfServiceHooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false }, "selfServiceAfterRecoveryHooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false }, "selfServiceAfterSettingsProfileMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceShowVerificationUIHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterSettingsAuthMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterDefaultLoginMethodHooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" }, { "$ref": "#/definitions/selfServiceRequireVerifiedAddressHook" }, { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceVerificationHook" }, { "$ref": "#/definitions/selfServiceShowVerificationUIHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false }, "selfServiceAfterDefaultLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethodHooks" } } }, "selfServiceAfterOIDCLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" }, { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceRequireVerifiedAddressHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterRegistrationMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionIssuerHook" }, { "$ref": "#/definitions/selfServiceWebHook" }, { "$ref": "#/definitions/selfServiceShowVerificationUIHook" }, { "$ref": "#/definitions/b2bSSOHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "featureRequiredAal": { "title": "Required Authenticator Assurance Level", "description": "Sets what Authenticator Assurance Level (used for 2FA) is required to access this feature. If set to `highest_available` then this endpoint requires the highest AAL the identity has set up. If set to `aal1` then the identity can access this feature without 2FA.", "type": "string", "enum": ["aal1", "highest_available"], "default": "highest_available" }, "selfServiceAfterSettings": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "totp": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "webauthn": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "passkey": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "lookup_secret": { "$ref": "#/definitions/selfServiceAfterSettingsAuthMethod" }, "profile": { "$ref": "#/definitions/selfServiceAfterSettingsProfileMethod" }, "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeLogin": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterLogin": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "webauthn": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "passkey": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterOIDCLoginMethod" }, "code": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "totp": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "lookup_secret": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethod" }, "hooks": { "$ref": "#/definitions/selfServiceAfterDefaultLoginMethodHooks" } } }, "selfServiceBeforeRegistration": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeSettings": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeRecovery": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceBeforeVerification": { "type": "object", "additionalProperties": false, "properties": { "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterRegistration": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "webauthn": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "passkey": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "code": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterVerification": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "$ref": "#/definitions/selfServiceHooks" } } }, "selfServiceAfterRecovery": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "$ref": "#/definitions/selfServiceAfterRecoveryHooks" } }, "additionalProperties": false }, "tlsxSource": { "type": "object", "additionalProperties": false, "properties": { "path": { "title": "Path to PEM-encoded Fle", "type": "string", "examples": ["path/to/file.pem"] }, "base64": { "title": "Base64 Encoded Inline", "description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "type": "string", "examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ] } } }, "tlsx": { "title": "HTTPS", "description": "Configure HTTP over TLS (HTTPS). All options can also be set using environment variables by replacing dots (`.`) with underscores (`_`) and uppercasing the key. For example, `some.prefix.tls.key.path` becomes `export SOME_PREFIX_TLS_KEY_PATH`. If all keys are left undefined, TLS will be disabled.", "type": "object", "additionalProperties": false, "properties": { "key": { "title": "Private Key (PEM)", "allOf": [ { "$ref": "#/definitions/tlsxSource" } ] }, "cert": { "title": "TLS Certificate (PEM)", "allOf": [ { "$ref": "#/definitions/tlsxSource" } ] } } }, "courierTemplates": { "additionalProperties": false, "type": "object", "properties": { "invalid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" } }, "required": ["email"] }, "valid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" }, "sms": { "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] } } }, "smsCourierTemplate": { "additionalProperties": false, "type": "object", "properties": { "body": { "additionalProperties": false, "type": "object", "properties": { "plaintext": { "type": "string", "description": "A template send to the SMS provider.", "format": "uri", "examples": [ "file://path/to/body.plaintext.gotmpl", "https://foo.bar.com/path/to/body.plaintext.gotmpl" ] } } } } }, "emailCourierTemplate": { "additionalProperties": false, "type": "object", "properties": { "body": { "additionalProperties": false, "type": "object", "properties": { "plaintext": { "type": "string", "description": "The fallback template for email clients that do not support html.", "format": "uri", "examples": [ "file://path/to/body.plaintext.gotmpl", "https://foo.bar.com/path/to/body.plaintext.gotmpl", "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQp7eyAuUmVjb3ZlcnlVUkwgfX0K" ] }, "html": { "type": "string", "description": "The default template used for sending out emails. The template can contain HTML ", "format": "uri", "examples": [ "file://path/to/body.html.gotmpl", "https://foo.bar.com/path/to/body.html.gotmpl", "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4" ] } } }, "subject": { "type": "string", "format": "uri", "examples": [ "file://path/to/subject.gotmpl", "https://foo.bar.com/path/to/subject.gotmpl", "base64://e3sgZGVmaW5lIGFmLVpBIH19CkhhbGxvLAoKSGVyc3RlbCBqb3UgcmVrZW5pbmcgZGV1ciBoaWVyZGllIHNrYWtlbCB0ZSB2b2xnOgp7ey0gZW5kIC19fQoKe3sgZGVmaW5lIGVuLVVTIH19CkhpLAoKcGxlYXNlIHJlY292ZXIgYWNjZXNzIHRvIHlvdXIgYWNjb3VudCBieSBjbGlja2luZyB0aGUgZm9sbG93aW5nIGxpbms6Cnt7LSBlbmQgLX19Cgp7ey0gaWYgZXEgLmxhbmcgImFmLVpBIiAtfX0KCnt7IHRlbXBsYXRlICJhZi1aQSIgLiB9fQoKe3stIGVsc2UgLX19Cgp7eyB0ZW1wbGF0ZSAiZW4tVVMiIH19Cgp7ey0gZW5kIC19fQo8YSBocmVmPSJ7eyAuUmVjb3ZlcnlVUkwgfX0iPnt7IC5SZWNvdmVyeVVSTCB9fTwvYT4" ] } } } }, "properties": { "selfservice": { "type": "object", "additionalProperties": false, "required": ["default_browser_return_url"], "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "allowed_return_urls": { "title": "Allowed Return To URLs", "description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "examples": [ [ "https://app.my-app.com/dashboard", "/dashboard", "https://www.my-app.com/", "https://*.my-app.com/" ] ] }, "flows": { "type": "object", "additionalProperties": false, "properties": { "settings": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "URL of the Settings page.", "description": "URL where the Settings UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/user/settings"], "default": "https://www.ory.sh/kratos/docs/fallback/settings" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "privileged_session_max_age": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "required_aal": { "$ref": "#/definitions/featureRequiredAal" }, "after": { "$ref": "#/definitions/selfServiceAfterSettings" }, "before": { "$ref": "#/definitions/selfServiceBeforeSettings" } } }, "logout": { "type": "object", "additionalProperties": false, "properties": { "after": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } } } } }, "registration": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable User Registration", "description": "If set to true will enable [User Registration](https://www.ory.sh/kratos/docs/self-service/flows/user-registration/).", "default": true }, "login_hints": { "type": "boolean", "title": "Provide Login Hints on Failed Registration", "description": "When registration fails because an account with the given credentials or addresses previously signed up, provide login hints about available methods to sign in to the user.", "default": false }, "ui_url": { "title": "Registration UI URL", "description": "URL where the Registration UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/signup"], "default": "https://www.ory.sh/kratos/docs/fallback/registration" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "before": { "$ref": "#/definitions/selfServiceBeforeRegistration" }, "after": { "$ref": "#/definitions/selfServiceAfterRegistration" }, "enable_legacy_one_step": { "type": "boolean", "title": "Disable two-step registration", "description": "Deprecated, please use `style` instead.", "deprecationMessage": "Deprecated, please use `style` instead.", "default": false }, "style": { "title": "Registration Flow Style", "description": "The style of the registration flow. If set to `unified` the login flow will be a one-step process. If set to `profile_first` the registration flow will first ask for the profile information first, and then the credentials.", "type": "string", "enum": ["unified", "profile_first"], "default": "profile_first" } } }, "login": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Login UI URL", "description": "URL where the Login UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/login"], "default": "https://www.ory.sh/kratos/docs/fallback/login" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "style": { "title": "Login Flow Style", "description": "The style of the login flow. If set to `unified` the login flow will be a one-step process. If set to `identifier_first` (experimental!) the login flow will first ask for the identifier and then the credentials.", "type": "string", "enum": ["unified", "identifier_first"], "default": "unified" }, "before": { "$ref": "#/definitions/selfServiceBeforeLogin" }, "after": { "$ref": "#/definitions/selfServiceAfterLogin" } } }, "verification": { "title": "Email and Phone Verification and Account Activation Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Email/Phone Verification", "description": "If set to true will enable [Email and Phone Verification and Account Activation](https://www.ory.sh/kratos/docs/self-service/flows/verify-email-account-activation/).", "default": false }, "ui_url": { "title": "Verify UI URL", "description": "URL where the Ory Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/verification" }, "after": { "$ref": "#/definitions/selfServiceAfterVerification" }, "lifespan": { "title": "Self-Service Verification Request Lifespan", "description": "Sets how long the verification request (for the UI interaction) is valid.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "before": { "$ref": "#/definitions/selfServiceBeforeVerification" }, "use": { "title": "Verification Strategy", "description": "The strategy to use for verification requests", "type": "string", "enum": ["link", "code"], "default": "code" }, "notify_unknown_recipients": { "title": "Notify unknown recipients", "description": "Whether to notify recipients, if verification was requested for their address.", "type": "boolean", "default": false } } }, "recovery": { "title": "Account Recovery Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Account Recovery", "description": "If set to true will enable [Account Recovery](https://www.ory.sh/kratos/docs/self-service/flows/password-reset-account-recovery/).", "default": false }, "ui_url": { "title": "Recovery UI URL", "description": "URL where the Ory Recovery UI is hosted. This is the page where users request and complete account recovery. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/recovery" }, "after": { "$ref": "#/definitions/selfServiceAfterRecovery" }, "lifespan": { "title": "Self-Service Recovery Request Lifespan", "description": "Sets how long the recovery request is valid. If expired, the user has to redo the flow.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "before": { "$ref": "#/definitions/selfServiceBeforeRecovery" }, "use": { "title": "Recovery Strategy", "description": "The strategy to use for recovery requests", "type": "string", "enum": ["link", "code"], "default": "code" }, "notify_unknown_recipients": { "title": "Notify unknown recipients", "description": "Whether to notify recipients, if recovery was requested for their account.", "type": "boolean", "default": false } } }, "error": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Ory Kratos Error UI URL", "description": "URL where the Ory Kratos Error UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/kratos-error"], "default": "https://www.ory.sh/kratos/docs/fallback/error" } } } } }, "methods": { "type": "object", "additionalProperties": false, "properties": { "b2b": { "title": "Single Sign-On for B2B", "description": "Single Sign-On for B2B allows your customers to bring their own (workforce) identity server (e.g. OneLogin). This feature is not available in the open source licensed code.", "type": "object", "properties": { "config": { "type": "object", "additionalProperties": false, "properties": { "organizations": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "The ID of the organization.", "format": "uuid", "examples": ["00000000-0000-0000-0000-000000000000"] }, "label": { "type": "string", "description": "The label of the organization.", "examples": ["ACME SSO"] }, "domains": { "type": "array", "items": { "type": "string", "format": "hostname", "examples": ["my-app.com"], "description": "If this domain matches the email's domain, this provider is shown." } } } } } } } }, "additionalProperties": false }, "profile": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Profile Management Method", "default": true } } }, "link": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Link Method", "default": false }, "config": { "type": "object", "title": "Link Configuration", "description": "Additional configuration for the link strategy.", "properties": { "base_url": { "title": "Override the base URL which should be used as the base for recovery and verification links.", "type": "string", "deprecationMessage": "This option has no effect, because the request URL is now used as the base URL.", "examples": ["https://my-app.com"] }, "lifespan": { "title": "How long a link is valid for", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] } } } } }, "code": { "type": "object", "additionalProperties": true, "anyOf": [ { "properties": { "passwordless_enabled": { "const": true }, "mfa_enabled": { "const": false } } }, { "properties": { "mfa_enabled": { "const": true }, "passwordless_enabled": { "const": false } } }, { "properties": { "mfa_enabled": { "const": false }, "passwordless_enabled": { "const": false } } } ], "properties": { "passwordless_enabled": { "type": "boolean", "title": "Enables login and registration with the code method.", "description": "If set to true, code.enabled will be set to true as well.", "default": false }, "mfa_enabled": { "type": "boolean", "title": "Enables login flows code method to fulfil MFA requests", "default": false }, "enabled": { "type": "boolean", "title": "Enables Code Method", "default": true }, "config": { "type": "object", "title": "Code Configuration", "description": "Additional configuration for the code strategy.", "properties": { "lifespan": { "title": "How long a code is valid for", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "max_submissions": { "type": "integer", "title": "Maximum number of times the code can be submitted before a flow is invalidated", "minimum": 1, "maximum": 255, "default": 5 }, "missing_credential_fallback_enabled": { "type": "boolean", "title": "Enable Code OTP as a Fallback", "description": "Enabling this allows users to sign in with the code method, even if their identity schema or their credentials are not set up to use the code method. If enabled, a verified address (such as an email) will be used to send the code to the user. Use with caution and only if actually needed.", "default": false } } } } }, "password": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Username/Email and Password Method", "default": true }, "config": { "type": "object", "title": "Password Configuration", "description": "Define how passwords are validated.", "properties": { "haveibeenpwned_host": { "title": "Custom haveibeenpwned host", "description": "Allows changing the default HIBP host to a self hosted version.", "type": "string", "default": "api.pwnedpasswords.com" }, "haveibeenpwned_enabled": { "title": "Enable the HaveIBeenPwned API", "description": "If set to false the password validation does not utilize the Have I Been Pwnd API.", "type": "boolean", "default": true }, "max_breaches": { "title": "Allow Password Breaches", "description": "Defines how often a password may have been breached before it is rejected.", "type": "integer", "minimum": 0, "maximum": 100, "default": 0 }, "ignore_network_errors": { "title": "Ignore Lookup Network Errors", "description": "If set to false the password validation fails when the network or the Have I Been Pwnd API is down.", "type": "boolean", "default": true }, "min_password_length": { "title": "Minimum Password Length", "description": "Defines the minimum length of the password.", "type": "integer", "default": 8, "minimum": 6 }, "identifier_similarity_check_enabled": { "title": "Enable password-identifier similarity check", "description": "If set to false the password validation does not check for similarity between the password and the user identifier.", "type": "boolean", "default": true }, "migrate_hook": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Password Migration", "description": "If set to true will enable password migration.", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "url": { "type": "string", "description": "The URL the password migration hook should call", "format": "uri" }, "method": { "type": "string", "description": "The HTTP method to use (GET, POST, etc).", "const": "POST", "default": "POST" }, "headers": { "type": "object", "description": "The HTTP headers that must be applied to the password migration hook.", "additionalProperties": { "type": "string" } }, "emit_analytics_event": { "type": "boolean", "default": true, "description": "Emit tracing events for this hook on delivery or error" }, "auth": { "type": "object", "title": "Auth mechanisms", "description": "Define which auth mechanism the Web-Hook should use", "oneOf": [ { "$ref": "#/definitions/webHookAuthApiKeyProperties" }, { "$ref": "#/definitions/webHookAuthBasicAuthProperties" } ] }, "body": { "type": "string", "format": "uri", "pattern": "^(http|https|file|base64)://", "description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods, which support HTTP body payloads", "examples": [ "file:///path/to/body.jsonnet", "file://./body.jsonnet", "base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=", "https://oryapis.com/default_body.jsonnet" ] }, "additionalProperties": false } } } } }, "additionalProperties": false } } }, "totp": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the TOTP method", "default": false }, "config": { "type": "object", "title": "TOTP Configuration", "properties": { "issuer": { "title": "TOTP Issuer", "description": "The issuer (e.g. a domain name) will be shown in the TOTP app (e.g. Google Authenticator). It helps the user differentiate between different codes.", "type": "string" } }, "additionalProperties": false } } }, "lookup_secret": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the lookup secret method", "default": false } } }, "webauthn": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the WebAuthn method", "default": false }, "config": { "type": "object", "title": "WebAuthn Configuration", "properties": { "passwordless": { "type": "boolean", "title": "Use For Passwordless Flows", "description": "If enabled will have the effect that WebAuthn is used for passwordless flows (as a first factor) and not for multi-factor set ups. With this set to true, users will see an option to sign up with WebAuthn on the registration screen." }, "rp": { "title": "Relying Party (RP) Config", "properties": { "display_name": { "type": "string", "title": "Relying Party Display Name", "description": "An name to help the user identify this RP.", "examples": ["Ory Foundation"] }, "id": { "type": "string", "title": "Relying Party Identifier", "description": "The id must be a subset of the domain currently in the browser.", "examples": ["ory.sh"] }, "origin": { "type": "string", "title": "Relying Party Origin", "description": "An explicit RP origin. If left empty, this defaults to `id`, prepended with the current protocol schema (HTTP or HTTPS).", "format": "uri", "deprecationMessage": "This field is deprecated. Use `origins` instead.", "examples": ["https://www.ory.sh"] }, "origins": { "type": "array", "title": "Relying Party Origins", "description": "A list of explicit RP origins. If left empty, this defaults to either `origin` or `id`, prepended with the current protocol schema (HTTP or HTTPS).", "items": { "type": "string", "format": "uri", "examples": [ "https://www.ory.sh", "https://auth.ory.sh" ] } }, "icon": { "type": "string", "title": "Relying Party Icon", "description": "An icon to help the user identify this RP.", "format": "uri", "deprecationMessage": "This field is deprecated and ignored due to security considerations.", "examples": ["https://www.ory.sh/an-icon.png"] } }, "type": "object", "oneOf": [ { "required": ["id", "display_name"], "properties": { "origin": { "not": {} }, "origins": { "not": {} } } }, { "required": ["id", "display_name", "origin"], "properties": { "origin": { "type": "string" }, "origins": { "not": {} } } }, { "required": ["id", "display_name", "origins"], "properties": { "origin": { "not": {} }, "origins": { "type": "array", "items": { "type": "string" } } } } ] } }, "additionalProperties": false } }, "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] }, "then": { "required": ["config"] } }, "passkey": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables the Passkey method", "default": false }, "config": { "type": "object", "title": "Passkey Configuration", "properties": { "rp": { "title": "Relying Party (RP) Config", "properties": { "display_name": { "type": "string", "title": "Relying Party Display Name", "description": "A name to help the user identify this RP.", "examples": ["Ory Foundation"] }, "id": { "type": "string", "title": "Relying Party Identifier", "description": "The id must be a subset of the domain currently in the browser.", "examples": ["ory.sh"] }, "origins": { "type": "array", "title": "Relying Party Origins", "description": "A list of explicit RP origins. If left empty, this defaults to either `origin` or `id`, prepended with the current protocol schema (HTTP or HTTPS).", "items": { "type": "string", "examples": [ "https://www.ory.sh", "https://auth.ory.sh" ] } } }, "type": "object", "required": ["display_name", "id"] } }, "additionalProperties": false } }, "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] }, "then": { "required": ["config"] } }, "oidc": { "type": "object", "title": "Specify OpenID Connect and OAuth2 Configuration", "showEnvVarBlockForObject": true, "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables OpenID Connect Method", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "base_redirect_uri": { "type": "string", "title": "Base URL for OAuth2 Redirect URIs", "description": "Can be used to modify the base URL for OAuth2 Redirect URLs. If unset, the Public Base URL will be used.", "format": "uri", "examples": ["https://auth.myexample.org/"] }, "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers Ory Kratos should integrate with.", "type": "array", "items": { "$ref": "#/definitions/selfServiceOIDCProvider" } } } } } } } } } }, "database": { "type": "object", "title": "Database related configuration", "description": "Miscellaneous settings used in database related tasks (cleanup, etc.)", "properties": { "cleanup": { "type": "object", "title": "Database cleanup settings", "description": "Settings that controls how the database cleanup process is configured (delays, batch size, etc.)", "properties": { "batch_size": { "type": "integer", "title": "Number of records to clean in one iteration", "description": "Controls how many records should be purged from one table during database cleanup task", "minimum": 1, "default": 100 }, "sleep": { "type": "object", "title": "Delays between various database cleanup phases", "description": "Configures delays between each step of the cleanup process. It is useful to tune the process so it will be efficient and performant.", "properties": { "tables": { "type": "string", "title": "Delay between each table cleanups", "description": "Controls the delay time between cleaning each table in one cleanup iteration", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1m" } } }, "older_than": { "type": "string", "title": "Remove records older than", "description": "Controls how old records do we want to leave", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "0s" } } } }, "additionalProperties": false }, "dsn": { "type": "string", "title": "Data Source Name", "description": "DSN is used to specify the database credentials as a connection URI.", "examples": [ "postgres://user: password@postgresd:5432/database?sslmode=disable&max_conns=20&max_idle_conns=4", "mysql://user:secret@tcp(mysqld:3306)/database?max_conns=20&max_idle_conns=4", "cockroach://user@cockroachdb:26257/database?sslmode=disable&max_conns=20&max_idle_conns=4", "sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc" ] }, "courier": { "type": "object", "title": "Courier configuration", "description": "The courier is responsible for sending and delivering messages over email, sms, and other means.", "properties": { "templates": { "additionalProperties": false, "type": "object", "properties": { "recovery": { "$ref": "#/definitions/courierTemplates" }, "recovery_code": { "$ref": "#/definitions/courierTemplates" }, "verification": { "$ref": "#/definitions/courierTemplates" }, "verification_code": { "$ref": "#/definitions/courierTemplates" }, "registration_code": { "additionalProperties": false, "type": "object", "properties": { "valid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" }, "sms": { "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] } } }, "login_code": { "additionalProperties": false, "type": "object", "properties": { "valid": { "additionalProperties": false, "type": "object", "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" }, "sms": { "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] } } } } }, "template_override_path": { "type": "string", "title": "Override message templates", "description": "You can override certain or all message templates by pointing this key to the path where the templates are located.", "examples": ["/conf/courier-templates"] }, "message_retries": { "description": "Defines the maximum number of times the sending of a message is retried after it failed before it is marked as abandoned", "type": "integer", "default": 5, "examples": [10, 60] }, "worker": { "description": "Configures the dispatch worker.", "type": "object", "properties": { "pull_count": { "description": "Defines how many messages are pulled from the queue at once.", "type": "integer", "default": 10 }, "pull_wait": { "description": "Defines how long the worker waits before pulling messages from the queue again.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1s" } } }, "delivery_strategy": { "title": "Delivery Strategy", "description": "Defines how emails will be sent, either through SMTP (default) or HTTP.", "type": "string", "enum": ["smtp", "http"], "default": "smtp" }, "http": { "title": "HTTP Configuration", "description": "Configures outgoing emails using HTTP.", "type": "object", "properties": { "request_config": { "$ref": "#/definitions/httpRequestConfig" } }, "additionalProperties": false }, "smtp": { "title": "SMTP Configuration", "description": "Configures outgoing emails using the SMTP protocol.", "type": "object", "properties": { "connection_uri": { "title": "SMTP connection string", "description": "This URI will be used to connect to the SMTP server. Use the scheme smtps for implicit TLS sessions or smtp for explicit StartTLS/cleartext sessions. Please note that TLS is always enforced with certificate trust verification by default for security reasons on both schemes. With the smtp scheme you can use the query parameter (`?disable_starttls=true`) to allow cleartext sessions or (`?disable_starttls=false`) to enforce StartTLS (default behaviour). Additionally, use the query parameter to allow (`?skip_ssl_verify=true`) or disallow (`?skip_ssl_verify=false`) self-signed TLS certificates (default behaviour) on both implicit and explicit TLS sessions.", "examples": [ "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=false", "smtp://foo:bar@my-mailserver:1234/?disable_starttls=true (NOT RECOMMENDED: Cleartext smtp for devel and legacy infrastructure only)", "smtp://foo:bar@my-mailserver:1234/ (Explicit StartTLS with certificate trust verification)", "smtp://foo:bar@my-mailserver:1234/?skip_ssl_verify=true (NOT RECOMMENDED: Explicit StartTLS without certificate trust verification)", "smtps://foo:bar@my-mailserver:1234/ (Implicit TLS with certificate trust verification)", "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=true (NOT RECOMMENDED: Implicit TLS without certificate trust verification)", "smtps://subdomain.my-mailserver:1234/?server_name=my-mailserver (allows TLS to work if the server is hosted on a sudomain that uses a non-wildcard domain certificate)" ], "type": "string", "pattern": "^smtps?://.*" }, "client_cert_path": { "title": "SMTP Client certificate path", "description": "Path of the client X.509 certificate, in case of certificate based client authentication to the SMTP server.", "type": "string", "default": "" }, "client_key_path": { "title": "SMTP Client private key path", "description": "Path of the client certificate private key, in case of certificate based client authentication to the SMTP server", "type": "string", "default": "" }, "from_address": { "title": "SMTP Sender Address", "description": "The recipient of an email will see this as the sender address.", "type": "string", "format": "email", "default": "no-reply@ory.kratos.sh" }, "from_name": { "title": "SMTP Sender Name", "description": "The recipient of an email will see this as the sender name.", "type": "string", "examples": ["Bob"] }, "headers": { "title": "SMTP Headers", "description": "These headers will be passed in the SMTP conversation -- e.g. when using the AWS SES SMTP interface for cross-account sending.", "type": "object", "additionalProperties": { "type": "string" }, "examples": [ { "X-SES-SOURCE-ARN": "arn:aws:ses:us-west-2:123456789012:identity/example.com", "X-SES-FROM-ARN": "arn:aws:ses:us-west-2:123456789012:identity/example.com", "X-SES-RETURN-PATH-ARN": "arn:aws:ses:us-west-2:123456789012:identity/example.com" } ] }, "local_name": { "title": "SMTP HELO/EHLO name", "description": "Identifier used in the SMTP HELO/EHLO command. Some SMTP relays require a unique identifier.", "type": "string", "default": "localhost" } }, "additionalProperties": false }, "channels": { "type": "array", "items": { "title": "Courier channel configuration", "type": "object", "properties": { "id": { "type": "string", "title": "Channel id", "description": "The channel id. Corresponds to the .via property of the identity schema for recovery, verification, etc. Currently only sms is supported.", "maxLength": 32, "enum": ["sms"] }, "type": { "type": "string", "title": "Channel type", "description": "The channel type. Currently only http is supported.", "enum": ["http"] }, "request_config": { "$ref": "#/definitions/httpRequestConfig" } }, "required": ["id", "request_config"], "additionalProperties": false } } }, "additionalProperties": false }, "oauth2_provider": { "title": "OAuth2 Provider Configuration", "type": "object", "properties": { "url": { "title": "OAuth 2.0 Provider URL.", "description": "If set, the login and registration flows will handle the Ory OAuth 2.0 & OpenID `login_challenge` query parameter to serve as an OpenID Connect Provider. This URL should point to Ory Hydra when you are not running on the Ory Network and be left untouched otherwise.", "type": "string", "format": "uri", "examples": [ "https://some-slug.projects.oryapis.com", "https://domain-of-ory-hydra:4445" ] }, "headers": { "title": "HTTP Request Headers", "description": "These headers will be passed in HTTP request to the OAuth2 Provider.", "type": "object", "additionalProperties": { "type": "string" }, "examples": [ { "Authorization": "Bearer some-token" } ] }, "override_return_to": { "title": "Persist OAuth2 request between flows", "type": "boolean", "default": false, "description": "Override the return_to query parameter with the OAuth2 provider request URL when perfoming an OAuth2 login flow." } }, "additionalProperties": false }, "preview": { "title": "Configure Preview Features", "type": "object", "properties": { "default_read_consistency_level": { "type": "string", "title": "Default Read Consistency Level", "description": "The default consistency level to use when reading from the database. Defaults to `strong` to not break existing API contracts. Only set this to `eventual` if you can accept that other read APIs will suddenly return eventually consistent results. It is only effective in Ory Network.", "enum": ["strong", "eventual"], "default": "strong" } } }, "serve": { "type": "object", "properties": { "admin": { "type": "object", "properties": { "request_log": { "type": "object", "properties": { "disable_for_health": { "title": "Disable health endpoints request logging", "description": "Disable request logging for /health/alive and /health/ready endpoints", "type": "boolean", "default": false } }, "additionalProperties": false }, "base_url": { "title": "Admin Base URL", "description": "The URL where the admin endpoint is exposed at.", "type": "string", "format": "uri", "examples": ["https://kratos.private-network:4434/"] }, "host": { "title": "Admin Host", "description": "The host (interface) kratos' admin endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Admin Port", "description": "The port kratos' admin endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4434], "default": 4434 }, "socket": { "$ref": "#/definitions/socket" }, "tls": { "$ref": "#/definitions/tlsx" } }, "additionalProperties": false }, "public": { "type": "object", "properties": { "request_log": { "type": "object", "properties": { "disable_for_health": { "title": "Disable health endpoints request logging", "description": "Disable request logging for /health/alive and /health/ready endpoints", "type": "boolean", "default": false } }, "additionalProperties": false }, "cors": { "type": "object", "additionalProperties": false, "description": "Configures Cross Origin Resource Sharing for public endpoints.", "properties": { "enabled": { "type": "boolean", "description": "Sets whether CORS is enabled.", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "does match all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "type": "string", "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "default": ["*"], "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "default": [ "Authorization", "Content-Type", "Max-Age", "X-Session-Token", "X-XSRF-TOKEN", "X-CSRF-TOKEN" ], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "default": ["Content-Type"], "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug server side CORS issues.", "default": false } } }, "base_url": { "$ref": "#/definitions/baseUrl" }, "host": { "title": "Public Host", "description": "The host (interface) kratos' public endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Public Port", "description": "The port kratos' public endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4433], "default": 4433 }, "socket": { "$ref": "#/definitions/socket" }, "tls": { "$ref": "#/definitions/tlsx" } }, "additionalProperties": false } }, "additionalProperties": false }, "tracing": { "$ref": "ory://tracing-config" }, "log": { "title": "Log", "description": "Configure logging using the following options. Logging will always be sent to stdout and stderr.", "type": "object", "properties": { "level": { "description": "Debug enables stack traces on errors. Can also be set using environment variable LOG_LEVEL.", "type": "string", "default": "info", "enum": [ "trace", "debug", "info", "warning", "error", "fatal", "panic" ] }, "leak_sensitive_values": { "type": "boolean", "title": "Leak Sensitive Log Values", "description": "If set will leak sensitive values (e.g. emails) in the logs." }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "format": { "description": "The log format can either be text or JSON.", "type": "string", "enum": ["json", "text"] } }, "additionalProperties": false }, "identity": { "type": "object", "properties": { "default_schema_id": { "title": "The default Identity Schema", "description": "This Identity Schema will be used as the default for self-service flows. Its ID needs to exist in the \"schemas\" list.", "type": "string", "default": "default" }, "schemas": { "type": "array", "title": "All JSON Schemas for Identity Traits", "description": "Note that identities that used the \"default_schema_url\" field in older kratos versions will be corrupted unless you specify their schema url with the id \"default\" in this list.", "examples": [ [ { "id": "customer", "url": "base64://ewogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInR5cGUiOiAib2JqZWN0IiwKICAicHJvcGVydGllcyI6IHsKICAgICJiYXIiOiB7CiAgICAgICJ0eXBlIjogInN0cmluZyIKICAgIH0KICB9LAogICJyZXF1aXJlZCI6IFsKICAgICJiYXIiCiAgXQp9" }, { "id": "employee", "url": "https://foo.bar.com/path/to/employee.traits.schema.json" }, { "id": "employee-v2", "url": "file://path/to/employee.v2.traits.schema.json" } ] ], "minItems": 1, "items": { "type": "object", "properties": { "id": { "title": "The schema's ID.", "type": "string", "examples": ["employee"] }, "url": { "type": "string", "title": "JSON Schema URL for identity traits schema", "description": "URL for JSON Schema which describes a identity's traits. Can be a file path, a https URL, or a base64 encoded string.", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json", "base64://ewogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInR5cGUiOiAib2JqZWN0IiwKICAicHJvcGVydGllcyI6IHsKICAgICJiYXIiOiB7CiAgICAgICJ0eXBlIjogInN0cmluZyIKICAgIH0KICB9LAogICJyZXF1aXJlZCI6IFsKICAgICJiYXIiCiAgXQp9" ] }, "selfservice_selectable": { "type": "boolean", "title": "Is the schema enabled in self-service flows", "description": "If set to true, this schema can be used explicity in self-service flows by setting `identity_schema` query parameter to the schema's ID.", "default": false } }, "required": ["id", "url"] } } }, "required": ["schemas"], "additionalProperties": false }, "secrets": { "type": "object", "properties": { "default": { "type": "array", "title": "Default Encryption Signing Secrets", "description": "The first secret in the array is used for signing and encrypting things while all other keys are used to verify and decrypt older things that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "cookie": { "type": "array", "title": "Signing Keys for Cookies", "description": "The first secret in the array is used for encrypting cookies while all other keys are used to decrypt older cookies that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "pagination": { "type": "array", "title": "Secrets to encrypt the pagination token", "description": "To avoid clients reverse-engineering and relying on the implementation details of the pagination token, it is encrypted with these keys", "items": { "type": "string", "minLength": 16 }, "minItems": 1, "examples": [ [ "secret used for encryption", "old secret kept for decryption", "another old secret kept for decryption" ] ] }, "cipher": { "type": "array", "title": "Secrets to use for encryption by cipher", "description": "The first secret in the array is used for encryption data while all other keys are used to decrypt older data that were signed with.", "items": { "type": "string", "minLength": 32, "maxLength": 32 }, "minItems": 1 } }, "additionalProperties": false }, "hashers": { "title": "Hashing Algorithm Configuration", "type": "object", "properties": { "algorithm": { "title": "Password hashing algorithm", "description": "One of the values: argon2, bcrypt.\nAny other hashes will be migrated to the set algorithm once an identity authenticates using their password.", "type": "string", "default": "bcrypt", "enum": ["argon2", "bcrypt"] }, "argon2": { "title": "Configuration for the Argon2id hasher.", "type": "object", "properties": { "memory": { "type": "string", "pattern": "^[0-9]+(B|KB|MB|GB|TB|PB|EB)", "default": "128MB" }, "iterations": { "type": "integer", "minimum": 1, "default": 1 }, "parallelism": { "type": "integer", "minimum": 1, "description": "Number of parallel workers, defaults to 2*runtime.NumCPU()." }, "salt_length": { "type": "integer", "minimum": 16, "default": 16 }, "key_length": { "type": "integer", "minimum": 16, "default": 32 }, "expected_duration": { "description": "The time a hashing operation (~login latency) should take.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "500ms" }, "expected_deviation": { "description": "The standard deviation expected for hashing operations. If this value is exceeded you will be warned in the logs to adjust the parameters.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "500ms" }, "dedicated_memory": { "description": "The memory dedicated for Kratos. As password hashing is very resource intense, Kratos will monitor the memory consumption and warn about high values.", "type": "string", "pattern": "^[0-9]+(B|KB|MB|GB|TB|PB|EB)", "default": "1GB" } }, "additionalProperties": false }, "bcrypt": { "title": "Configuration for the Bcrypt hasher. Minimum is 4 when --dev flag is used and 12 otherwise.", "type": "object", "additionalProperties": false, "required": ["cost"], "properties": { "cost": { "type": "integer", "minimum": 4, "maximum": 31, "default": 12 } } } }, "additionalProperties": false }, "ciphers": { "title": "Cipher Algorithm Configuration", "type": "object", "properties": { "algorithm": { "title": "ciphering algorithm", "description": "One of the values: noop, aes, xchacha20-poly1305", "type": "string", "default": "noop", "enum": ["noop", "aes", "xchacha20-poly1305"] } } }, "cookies": { "type": "object", "title": "HTTP Cookie Configuration", "description": "Configure the HTTP Cookies. Applies to both CSRF and session cookies.", "properties": { "domain": { "title": "HTTP Cookie Domain", "description": "Sets the cookie domain for session and CSRF cookies. Useful when dealing with subdomains. Use with care!", "type": "string" }, "path": { "title": "HTTP Cookie Path", "description": "Sets the session and CSRF cookie path. Use with care!", "type": "string", "default": "/" }, "secure": { "title": "Session Cookie Secure Flag", "description": "Sets the session secure flag. If unset, defaults to !dev mode.", "type": "string" }, "same_site": { "title": "HTTP Cookie Same Site Configuration", "description": "Sets the session and CSRF cookie SameSite.", "type": "string", "enum": ["Strict", "Lax", "None"], "default": "Lax" } }, "additionalProperties": false }, "session": { "type": "object", "additionalProperties": false, "properties": { "whoami": { "title": "WhoAmI / ToSession Settings", "description": "Control how the `/sessions/whoami` endpoint is behaving.", "type": "object", "properties": { "required_aal": { "$ref": "#/definitions/featureRequiredAal" }, "tokenizer": { "title": "Tokenizer configuration", "description": "Configure the tokenizer, responsible for converting a session into a token format such as JWT.", "type": "object", "properties": { "templates": { "title": "Tokenizer templates", "description": "A list of different templates that govern how a session is converted to a token format.", "type": "object", "patternProperties": { "[a-zA-Z0-9-_.]+": { "type": "object", "required": ["jwks_url"], "properties": { "ttl": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1m", "title": "Token time to live" }, "claims_mapper_url": { "type": "string", "format": "uri", "title": "Jsonnet mapper URL" }, "jwks_url": { "type": "string", "format": "uri", "title": "JSON Web Key Set URL" }, "subject_source": { "type": "string", "title": "Subject source", "description": "The source of the subject claim in the token. Can be one of: `id`, or `external_id`.", "enum": ["id", "external_id"], "default": "id" } } } } } } } }, "additionalProperties": false }, "lifespan": { "title": "Session Lifespan", "description": "Defines how long a session is active. Once that lifespan has been reached, the user needs to sign in again.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "24h", "examples": ["1h", "1m", "1s"] }, "cookie": { "type": "object", "properties": { "domain": { "title": "Session Cookie Domain", "description": "Sets the session cookie domain. Useful when dealing with subdomains. Use with care! Overrides `cookies.domain`.", "type": "string" }, "name": { "title": "Session Cookie Name", "description": "Sets the session cookie name. Use with care!", "type": "string", "default": "ory_kratos_session" }, "persistent": { "title": "Make Session Cookie Persistent", "description": "If set to true will persist the cookie in the end-user's browser using the `max-age` parameter which is set to the `session.lifespan` value. Persistent cookies are not deleted when the browser is closed (e.g. on reboot or alt+f4). This option affects the Ory OAuth2 and OpenID Provider's remember feature as well.", "type": "boolean", "default": true }, "path": { "title": "Session Cookie Path", "description": "Sets the session cookie path. Use with care! Overrides `cookies.path`.", "type": "string" }, "secure": { "title": "Session Cookie Secure Flag", "description": "Sets the session secure flag. If unset, defaults to !dev mode.", "type": "string" }, "same_site": { "title": "Session Cookie SameSite Configuration", "description": "Sets the session cookie SameSite. Overrides `cookies.same_site`.", "type": "string", "enum": ["Strict", "Lax", "None"] } }, "additionalProperties": false }, "earliest_possible_extend": { "title": "Earliest Possible Session Extension", "description": "Sets when a session can be extended. Settings this value to `24h` will prevent the session from being extended before until 24 hours before it expires. This setting prevents excessive writes to the database. We highly recommend setting this value.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "examples": ["1h", "1m", "1s"] } } }, "security": { "type": "object", "properties": { "account_enumeration": { "type": "object", "properties": { "mitigate": { "type": "boolean", "default": false, "description": "Mitigate account enumeration by making it harder to figure out if an identifier (email, phone number) exists or not. Enabling this setting degrades user experience. This setting does not mitigate all possible attack vectors yet." } } } } }, "version": { "title": "The kratos version this config is written for.", "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "type": "string", "pattern": "^(v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|$", "examples": ["v0.5.0-alpha.1"] }, "dev": { "type": "boolean" }, "help": { "type": "boolean" }, "sqa-opt-out": { "type": "boolean", "default": false, "description": "This is a CLI flag and environment variable and can not be set using the config file." }, "watch-courier": { "type": "boolean", "default": false, "description": "This is a CLI flag and environment variable and can not be set using the config file." }, "expose-metrics-port": { "title": "Metrics port", "description": "The port the courier's metrics endpoint listens on (0/disabled by default). This is a CLI flag and environment variable and can not be set using the config file.", "type": "integer", "minimum": 0, "maximum": 65535, "examples": [4434], "default": 0 }, "config": { "type": "array", "items": { "type": "string" }, "description": "This is a CLI flag and environment variable and can not be set using the config file." }, "clients": { "title": "Global outgoing network settings", "description": "Configure how outgoing network calls behave.", "type": "object", "properties": { "http": { "title": "Global HTTP client configuration", "description": "Configure how outgoing HTTP calls behave.", "type": "object", "properties": { "disallow_private_ip_ranges": { "title": "Disallow private IP ranges", "description": "Disallow all outgoing HTTP calls to private IP ranges. This feature can help protect against SSRF attacks.", "type": "boolean", "default": false }, "private_ip_exception_urls": { "title": "Add exempt URLs to private IP ranges", "description": "Allows the given URLs to be called despite them being in the private IP range. URLs need to have an exact and case-sensitive match to be excempt.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "default": [] } } }, "web_hook": { "title": "Global web_hook HTTP client configuration", "description": "Configure the global HTTP client of the web_hook action.", "type": "object", "properties": { "header_allowlist": { "title": "Allowed request headers", "description": "List of request headers that are forwarded to the web hook target in canonical form.", "type": "array", "items": { "type": "string" }, "default": [ "Accept", "Accept-Encoding", "Accept-Language", "Content-Length", "Content-Type", "Origin", "Priority", "Referer", "Sec-Ch-Ua", "Sec-Ch-Ua-Mobile", "Sec-Ch-Ua-Platform", "Sec-Fetch-Dest", "Sec-Fetch-Mode", "Sec-Fetch-Site", "Sec-Fetch-User", "True-Client-Ip", "User-Agent" ] } } } } }, "feature_flags": { "title": "Feature flags", "properties": { "cacheable_sessions": { "type": "boolean", "title": "Enable Ory Sessions caching", "description": "If enabled allows Ory Sessions to be cached. Only effective in the Ory Network.", "default": false }, "cacheable_sessions_max_age": { "title": "Set Ory Session Edge Caching maximum age", "description": "Set how long Ory Sessions are cached on the edge. If unset, the session expiry will be used. Only effective in the Ory Network.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$" }, "use_continue_with_transitions": { "type": "boolean", "title": "Enable new flow transitions using `continue_with` items", "description": "If enabled allows new flow transitions using `continue_with` items.", "default": false }, "choose_recovery_address": { "type": "boolean", "title": "Enable new recovery screens to pick which address to send a recovery code/link to", "description": "If enabled, enable new recovery screens to pick which address to send a recovery code to, and can send a code via SMS. It is safe to toggle it back and forth, existing recovery flows will be handled with their respective logic. That is because it is decided at creation time whether a recovery flow is V1 or V2 and this cannot be changed afterwards. Thus, if a recovery flow is created with this flag enabled, it will be created as a recovery v2 flow. If this flag is disabled while this flow is still active, this flow will still be handled with the correct logic (v2).", "default": false }, "legacy_continue_with_verification_ui": { "type": "boolean", "title": "Always include show_verification_ui in continue_with", "description": "If true, restores the legacy behavior of always including `show_verification_ui` in the registration flow's `continue_with` when verification is enabled. If set to false, `show_verification_ui` is only set in `continue_with` if the `show_verification_ui` hook is used. This flag will be removed in the future.", "deprecationMessage": "This behavior is deprecated and will be removed in the future. Use the `show_verification_hook` in the post-registration hook instead.", "default": false }, "legacy_require_verified_login_error": { "type": "boolean", "title": "Return a form error if the login identifier is not verified", "description": "If true, the login flow will return a form error if the login identifier is not verified, which restores legacy behavior. If this value is false, the `continue_with` array will contain a `show_verification_ui` hook instead.", "deprecationMessage": "This behavior is deprecated and will be removed in the future. Please upgrade your SDKs.", "default": false }, "faster_session_extend": { "type": "boolean", "title": "Enable faster session extension", "description": "If enabled allows faster session extension by skipping the session lookup. Disabling this feature will be deprecated in the future.", "default": false }, "password_profile_registration_node_group": { "title": "Registration node group", "description": "The node group to use for registration flows. Previously, the node group for the password method's profile fields was `password`. Going forward, it will be `default`. This switch can toggle between those two for backwards compatibility.", "enum": ["password", "default"], "default": "default" }, "legacy_oidc_registration_node_group": { "title": "Registration node group for OIDC", "description": "The node group to use for registration flows. Previously, the node group for the oidc method's profile fields was `oidc`. Going forward, it will be `default`. This switch can toggle between those two for backwards compatibility and will be removed in the future.", "default": false, "type": "boolean" } }, "additionalProperties": false }, "organizations": { "title": "Organizations", "description": "Please use selfservice.methods.b2b instead. This key will be removed. Only effective in the Ory Network.", "type": "array", "default": [] }, "enterprise": { "title": "Enterprise features", "description": "Specifies enterprise features. Only effective in the Ory Network or with a valid license.", "type": "object", "properties": { "identity_schema_fallback_url_template": { "type": "string", "title": "Fallback URL template for identity schemas", "description": "A fallback URL template used when looking up identity schemas." } }, "additionalProperties": false }, "revision": { "title": "Config revision", "description": "Set a recognizable revision. This could be the commit time or a random value. This value is exposed at the `/health/config` endpoint and allows you to ensure that the correct config is loaded.", "type": "string" } }, "allOf": [ { "if": { "properties": { "selfservice": { "properties": { "flows": { "oneOf": [ { "properties": { "verification": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["verification"] }, { "properties": { "recovery": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["recovery"] } ] } }, "required": ["flows"] } }, "required": ["selfservice"] }, "then": { "required": ["courier"] } }, { "if": { "properties": { "ciphers": { "properties": { "algorithm": { "oneOf": [ { "const": "aes" }, { "const": "xchacha20-poly1305" } ] } }, "required": ["algorithm"] } }, "required": ["ciphers"] }, "then": { "required": ["secrets"], "properties": { "secrets": { "required": ["cipher"] } } } } ], "required": ["identity", "dsn", "selfservice"], "additionalProperties": false } ================================================ FILE: embedx/embedx.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package embedx import ( "bytes" _ "embed" "io" "github.com/pkg/errors" "github.com/tidwall/gjson" "github.com/ory/x/configx" "github.com/ory/x/otelx" ) //go:embed config.schema.json var ConfigSchema []byte //go:embed identity_meta.schema.json var IdentityMetaSchema []byte //go:embed identity_extension.schema.json var IdentityExtensionSchema []byte type SchemaType int const ( Config SchemaType = iota + 1 IdentityMeta IdentityExtension ) type Schema struct { id string data []byte dependencies []*Schema } var ( identityExt = &Schema{ id: gjson.GetBytes(IdentityExtensionSchema, "$id").Str, data: IdentityExtensionSchema, dependencies: nil, } schemas = map[SchemaType]*Schema{ Config: { id: gjson.GetBytes(ConfigSchema, "$id").Str, data: ConfigSchema, dependencies: nil, }, IdentityMeta: { id: gjson.GetBytes(IdentityMetaSchema, "$id").Str, data: IdentityMetaSchema, dependencies: []*Schema{ identityExt, }, }, IdentityExtension: identityExt, } ) func getSchema(schema SchemaType) (*Schema, error) { if val, ok := schemas[schema]; ok { return val, nil } return nil, errors.Errorf("the specified schema type (%d) is not supported", int(schema)) } func (s SchemaType) GetSchemaID() string { return schemas[s].id } // AddSchemaResources adds the specified schemas including their dependencies to the compiler. // The interface is specified instead of `jsonschema.Compiler` to allow the use of any jsonschema library fork or version. func AddSchemaResources(c interface { AddResource(url string, r io.Reader) error }, opts ...SchemaType) error { var sc []*Schema for _, o := range opts { s, err := getSchema(o) if err != nil { return err } sc = append(sc, s) } if err := addSchemaResources(c, sc); err != nil { return err } if err := otelx.AddConfigSchema(c); err != nil { return err } return configx.AddSchemaResources(c) } func addSchemaResources(c interface { AddResource(url string, r io.Reader) error }, schemas []*Schema) error { for _, s := range schemas { if err := c.AddResource(s.id, bytes.NewReader(s.data)); err != nil { return errors.WithStack(err) } if s.dependencies != nil { if err := addSchemaResources(c, s.dependencies); err != nil { return err } } } return nil } ================================================ FILE: embedx/embedx_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package embedx import ( "context" "embed" "io/fs" "strings" "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" "github.com/ory/jsonschema/v3" ) func TestAddSchemaResources(t *testing.T) { for _, tc := range []struct { description string dependencies []SchemaType extraDependencies []SchemaType mustFail bool failMessage string }{ { description: "config schema", dependencies: []SchemaType{Config}, extraDependencies: nil, }, { description: "identity schema with dependencies", dependencies: []SchemaType{IdentityMeta}, extraDependencies: []SchemaType{IdentityExtension}, }, { description: "multiple schemas", dependencies: []SchemaType{Config, IdentityMeta, IdentityExtension}, extraDependencies: nil, }, { description: "verify dependencies are also loaded", dependencies: []SchemaType{IdentityMeta}, extraDependencies: []SchemaType{IdentityExtension}, }, { description: "must fail on unsupported schema types", dependencies: []SchemaType{4, 10}, mustFail: true, failMessage: "the specified schema type (4) is not supported", }, } { t.Run("case="+tc.description, func(t *testing.T) { c := jsonschema.NewCompiler() err := AddSchemaResources(c, tc.dependencies...) if tc.mustFail { assert.Errorf(t, err, "an error must be thrown on `%s`", tc.description) assert.EqualError(t, err, tc.failMessage) } else { assert.NoError(t, err) } if !tc.mustFail { for _, s := range append(tc.dependencies, tc.extraDependencies...) { _, err := c.Compile(context.Background(), s.GetSchemaID()) assert.NoError(t, err) } } }) } } //go:embed testdata/identity_meta.* var identityMetaTestCases embed.FS func TestIdentityMetaSchema(t *testing.T) { c := jsonschema.NewCompiler() err := AddSchemaResources(c, IdentityMeta, IdentityExtension) require.NoError(t, err) schema, err := c.Compile(context.Background(), IdentityMeta.GetSchemaID()) require.NoError(t, err) require.NoError(t, fs.WalkDir(identityMetaTestCases, ".", func(path string, d fs.DirEntry, err error) error { if d.IsDir() { return nil } t.Run("case="+path, func(t *testing.T) { f, err := identityMetaTestCases.Open(path) require.NoError(t, err) err = schema.Validate(f) if strings.HasSuffix(path, "invalid.schema.json") { assert.Error(t, err) } else { assert.NoError(t, err) } }) return nil })) } ================================================ FILE: embedx/identity_extension.schema.json ================================================ { "$id": "ory://identity-extension", "$schema": "http://json-schema.org/draft-07/schema#", "allOf": [ { "properties": { "ory.sh/kratos": { "type": "object", "additionalProperties": false, "properties": { "credentials": { "type": "object", "additionalProperties": false, "properties": { "password": { "type": "object", "additionalProperties": false, "properties": { "identifier": { "type": "boolean" } } }, "webauthn": { "type": "object", "additionalProperties": false, "properties": { "identifier": { "type": "boolean" } } }, "passkey": { "type": "object", "additionalProperties": false, "properties": { "display_name": { "type": "boolean" } } }, "totp": { "type": "object", "additionalProperties": false, "properties": { "account_name": { "type": "boolean" } } }, "code": { "type": "object", "additionalProperties": false, "properties": { "identifier": { "type": "boolean" }, "via": { "type": "string", "enum": ["email", "sms"] } } } } }, "verification": { "type": "object", "additionalProperties": false, "properties": { "via": { "type": "string", "enum": ["email", "sms"] } } }, "recovery": { "type": "object", "additionalProperties": false, "properties": { "via": { "type": "string", "enum": ["email", "sms"] } } }, "organizations": { "type": "object", "additionalProperties": false, "properties": { "matcher": { "type": "string", "enum": ["email_domain"] } } } } } } }, { "patternProperties": { ".*": { "$ref": "#" } } } ] } ================================================ FILE: embedx/identity_meta.schema.json ================================================ { "$id": "ory://identity-meta", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "allOf": [ { "$ref": "http://json-schema.org/draft-07/schema#" }, { "properties": { "properties": { "type": "object", "required": ["traits"], "properties": { "traits": { "type": "object", "required": ["properties"], "properties": { "type": { "const": "object" }, "properties": { "type": "object", "minProperties": 1, "patternProperties": { ".*": { "type": "object", "if": { "properties": { "ory.sh/kratos": { "type": "object", "properties": { "verification": {} }, "required": ["verification"] } }, "required": ["ory.sh/kratos"] }, "then": { "properties": { "format": { "enum": [ "email", "tel", "date", "time", "date-time", "no-validate" ] } } }, "allOf": [ { "$ref": "ory://identity-extension" } ] } } } } } } } }, "required": ["properties"] } ] } ================================================ FILE: embedx/testdata/identity_meta.no_traits.invalid.schema.json ================================================ {} ================================================ FILE: embedx/testdata/identity_meta.simple.valid.schema.json ================================================ { "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" } } } } } } } ================================================ FILE: embedx/testdata/identity_meta.verification_format.invalid.schema.json ================================================ { "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "unknown", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" } } } } } } } ================================================ FILE: examples/go/identity/create/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" ory "github.com/ory/client-go" "github.com/ory/kratos/examples/go/pkg" "github.com/ory/kratos/x" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func createIdentity() *ory.Identity { ctx := context.Background() identity, res, err := client.IdentityAPI.CreateIdentity(ctx). CreateIdentityBody(ory.CreateIdentityBody{ SchemaId: "default", Traits: map[string]interface{}{ "email": "dev+" + x.NewUUID().String() + "@ory.sh", }, }).Execute() pkg.SDKExitOnError(err, res) return identity } func main() { pkg.PrintJSONPretty(createIdentity()) } ================================================ FILE: examples/go/identity/create/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/stretchr/testify/require" "github.com/ory/kratos/examples/go/pkg" ) func TestFunc(t *testing.T) { client = pkg.TestClient(t) require.NotEmpty(t, createIdentity().Id) } ================================================ FILE: examples/go/identity/delete/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "github.com/ory/kratos/examples/go/pkg" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func deleteIdentity() { ctx := context.Background() identity := pkg.CreateIdentity(client) res, err := client.IdentityAPI.DeleteIdentity(ctx, identity.Id).Execute() pkg.SDKExitOnError(err, res) } func main() { deleteIdentity() fmt.Println("Success") } ================================================ FILE: examples/go/identity/delete/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) deleteIdentity() } ================================================ FILE: examples/go/identity/get/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" ory "github.com/ory/client-go" "github.com/ory/kratos/examples/go/pkg" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func getIdentity() *ory.Identity { ctx := context.Background() created := pkg.CreateIdentity(client) identity, res, err := client.IdentityAPI.GetIdentity(ctx, created.Id).IncludeCredential([]string{"password"}).Execute() pkg.SDKExitOnError(err, res) return identity } func main() { pkg.PrintJSONPretty(getIdentity()) } ================================================ FILE: examples/go/identity/get/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/require" ) func TestFunc(t *testing.T) { client = pkg.TestClient(t) require.NotEmpty(t, getIdentity().Id) } ================================================ FILE: examples/go/identity/update/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" ory "github.com/ory/client-go" "github.com/ory/kratos/examples/go/pkg" "github.com/ory/kratos/x" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func updateIdentity() *ory.Identity { ctx := context.Background() toUpdate := pkg.CreateIdentity(client) identity, res, err := client.IdentityAPI.UpdateIdentity(ctx, toUpdate.Id).UpdateIdentityBody(ory.UpdateIdentityBody{ Traits: map[string]interface{}{ "email": "dev+not-" + x.NewUUID().String() + "@ory.sh", }, }).Execute() pkg.SDKExitOnError(err, res) return identity } func main() { pkg.PrintJSONPretty(updateIdentity()) } ================================================ FILE: examples/go/identity/update/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) identity := updateIdentity() require.NotEmpty(t, identity.Id) assert.Contains(t, identity.Traits.(map[string]interface{})["email"].(string), "dev+not") } ================================================ FILE: examples/go/pkg/common.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pkg import ( "encoding/json" "fmt" "net/http" "net/http/cookiejar" "os" "testing" ory "github.com/ory/client-go" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/x" ) func PrintJSONPretty(v interface{}) { out, _ := json.MarshalIndent(v, "", " ") fmt.Println(string(out)) } func TestClient(t *testing.T) *ory.APIClient { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) return NewSDKForSelfHosted(publicURL) } func NewSDK(project string) *ory.APIClient { return NewSDKForSelfHosted(fmt.Sprintf("https://%s.projects.oryapis.com/api/kratos/public", project)) } func NewSDKForSelfHosted(endpoint string) *ory.APIClient { conf := ory.NewConfiguration() conf.Servers = ory.ServerConfigurations{{URL: endpoint}} cj, _ := cookiejar.New(nil) conf.HTTPClient = &http.Client{Jar: cj} return ory.NewAPIClient(conf) } func ExitOnError(err error) { if err == nil { return } out, _ := json.MarshalIndent(err, "", " ") fmt.Printf("%s\n\nAn error occurred: %+v\n", out, err) os.Exit(1) } func SDKExitOnError(err error, res *http.Response) { if err == nil { return } var body []byte if res != nil { body, _ = json.MarshalIndent(json.RawMessage(x.MustReadAll(res.Body)), "", " ") } out, _ := json.MarshalIndent(err, "", " ") fmt.Printf("%s\n\nAn error occurred: %+v\nbody: %s\n", out, err, body) os.Exit(1) } ================================================ FILE: examples/go/pkg/resources.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pkg import ( "context" "log" "strings" "github.com/gofrs/uuid" ory "github.com/ory/client-go" ) func RandomCredentials() (email, password string) { email = "dev+" + uuid.Must(uuid.NewV4()).String() + "@ory.sh" password = strings.ReplaceAll(uuid.Must(uuid.NewV4()).String(), "-", "") return } // CreateIdentityWithSession creates an identity and an Ory Session Token for it. func CreateIdentityWithSession(c *ory.APIClient, email, password string) (*ory.Session, string) { ctx := context.Background() if email == "" { email, _ = RandomCredentials() } if password == "" { _, password = RandomCredentials() } // Initialize a registration flow flow, _, err := c.FrontendAPI.CreateNativeRegistrationFlow(ctx).Execute() ExitOnError(err) // Submit the registration flow result, res, err := c.FrontendAPI.UpdateRegistrationFlow(ctx).Flow(flow.Id).UpdateRegistrationFlowBody( ory.UpdateRegistrationFlowWithPasswordMethodAsUpdateRegistrationFlowBody(&ory.UpdateRegistrationFlowWithPasswordMethod{ Method: "password", Password: password, Traits: map[string]interface{}{"email": email}, }), ).Execute() SDKExitOnError(err, res) if result.Session == nil { log.Fatalf("The server is expected to create sessions for new registrations.") } return result.Session, *result.SessionToken } func CreateIdentity(c *ory.APIClient) *ory.Identity { ctx := context.Background() email, _ := RandomCredentials() identity, _, err := c.IdentityAPI.CreateIdentity(ctx).CreateIdentityBody(ory.CreateIdentityBody{ SchemaId: "default", Traits: map[string]interface{}{ "email": email, }}).Execute() ExitOnError(err) return identity } ================================================ FILE: examples/go/pkg/stub/identity.schema.json ================================================ { "$id": "identity.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } }, "required": [ "email" ], "additionalProperties": false } } } ================================================ FILE: examples/go/pkg/stub/kratos.yaml ================================================ version: v0.6.3-alpha.1 dsn: memory serve: public: cors: enabled: true selfservice: default_browser_return_url: http://127.0.0.1:4455/ allowed_return_urls: - http://127.0.0.1:4455 methods: password: enabled: true link: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings privileged_session_max_age: 15m recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery use: link verification: enabled: true ui_url: http://127.0.0.1:4455/verify use: link after: default_browser_return_url: http://127.0.0.1:4455/ logout: after: default_browser_return_url: http://127.0.0.1:4455/auth/login login: ui_url: http://127.0.0.1:4455/auth/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/auth/registration after: password: hooks: - hook: session log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE cipher: - 32-LONG-SECRET-NOT-SECURE-AT-ALL ciphers: algorithm: xchacha20-poly1305 hashers: argon2: parallelism: 1 memory: 128MB iterations: 2 salt_length: 16 key_length: 16 identity: default_schema_id: default schemas: - id: default url: file://../../pkg/stub/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: examples/go/selfservice/error/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func getError() *ory.FlowError { e, res, err := client.FrontendAPI.GetFlowError(context.Background()).Id("stub:500").Execute() pkg.SDKExitOnError(err, res) return e } func main() { pkg.PrintJSONPretty( getError(), ) } ================================================ FILE: examples/go/selfservice/error/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/require" ) func TestError(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) e := getError() require.NotEmpty(t, e.Id) } ================================================ FILE: examples/go/selfservice/login/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func performLogin() *ory.SuccessfulNativeLogin { ctx := context.Background() // Create a temporary user email, password := pkg.RandomCredentials() _, _ = pkg.CreateIdentityWithSession(client, email, password) // Initialize the flow flow, res, err := client.FrontendAPI.CreateNativeLoginFlow(ctx).Execute() pkg.SDKExitOnError(err, res) // If you want, print the flow here: // // pkg.PrintJSONPretty(flow) // Submit the form result, res, err := client.FrontendAPI.UpdateLoginFlow(ctx).Flow(flow.Id).UpdateLoginFlowBody( ory.UpdateLoginFlowWithPasswordMethodAsUpdateLoginFlowBody(&ory.UpdateLoginFlowWithPasswordMethod{ Method: "password", Password: password, PasswordIdentifier: &email, }), ).Execute() pkg.SDKExitOnError(err, res) return result } func main() { pkg.PrintJSONPretty( performLogin(), ) } ================================================ FILE: examples/go/selfservice/login/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/require" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) result := performLogin() require.NotEmpty(t, result.Session) } ================================================ FILE: examples/go/selfservice/logout/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func performLogout() { // Create a temporary user email, password := pkg.RandomCredentials() _, sessionToken := pkg.CreateIdentityWithSession(client, email, password) // Log out using session token res, err := client.FrontendAPI.PerformNativeLogout(context.Background()). PerformNativeLogoutBody(ory.PerformNativeLogoutBody{SessionToken: sessionToken}).Execute() pkg.SDKExitOnError(err, res) } func main() { performLogout() fmt.Println("Logout successful!") } ================================================ FILE: examples/go/selfservice/logout/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) performLogout() } ================================================ FILE: examples/go/selfservice/recovery/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func performRecovery(email string) *ory.RecoveryFlow { ctx := context.Background() // Initialize the flow flow, res, err := client.FrontendAPI.CreateNativeRecoveryFlow(ctx).Execute() pkg.SDKExitOnError(err, res) // If you want, print the flow here: // // pkg.PrintJSONPretty(flow) // Submit the form afterSubmit, res, err := client.FrontendAPI.UpdateRecoveryFlow(ctx).Flow(flow.Id). UpdateRecoveryFlowBody(ory.UpdateRecoveryFlowWithLinkMethodAsUpdateRecoveryFlowBody(&ory.UpdateRecoveryFlowWithLinkMethod{ Email: email, Method: "link", })).Execute() pkg.SDKExitOnError(err, res) return afterSubmit } func main() { pkg.PrintJSONPretty( performRecovery("someone@foobar.com"), ) } ================================================ FILE: examples/go/selfservice/recovery/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/kratos/pkg/httpclient" "github.com/ory/kratos/pkg/testhelpers" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) flow := performRecovery("dev+" + uuid.Must(uuid.NewV4()).String() + "@ory.sh") require.NotEmpty(t, flow.Id) assert.EqualValues(t, ory.RECOVERYFLOWSTATE_SENT_EMAIL, flow.State) } ================================================ FILE: examples/go/selfservice/registration/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func initRegistration() *ory.SuccessfulNativeRegistration { ctx := context.Background() // Initialize the flow flow, res, err := client.FrontendAPI.CreateNativeRegistrationFlow(ctx).Execute() pkg.SDKExitOnError(err, res) // If you want, print the flow here: // // pkg.PrintJSONPretty(flow) email, password := pkg.RandomCredentials() // Submit the registration flow result, res, err := client.FrontendAPI.UpdateRegistrationFlow(ctx).Flow(flow.Id).UpdateRegistrationFlowBody( ory.UpdateRegistrationFlowWithPasswordMethodAsUpdateRegistrationFlowBody(&ory.UpdateRegistrationFlowWithPasswordMethod{ Method: "password", Password: password, Traits: map[string]interface{}{"email": email}, }), ).Execute() pkg.SDKExitOnError(err, res) return result } func main() { pkg.PrintJSONPretty( initRegistration(), ) } ================================================ FILE: examples/go/selfservice/registration/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/require" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) result := initRegistration() require.NotEmpty(t, result.Identity.Id) } ================================================ FILE: examples/go/selfservice/settings/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") var ctx = context.Background() func initFlow(email, password string) (string, *ory.SettingsFlow) { // Create a temporary user _, sessionToken := pkg.CreateIdentityWithSession(client, email, password) flow, res, err := client.FrontendAPI.CreateNativeSettingsFlow(context.Background()).XSessionToken(sessionToken).Execute() pkg.SDKExitOnError(err, res) // If you want, print the flow here: // // pkg.PrintJSONPretty(flow) return sessionToken, flow } func changePassword(email, password string) *ory.SettingsFlow { sessionToken, flow := initFlow(email, password) // Submit the form result, res, err := client.FrontendAPI.UpdateSettingsFlow(ctx).Flow(flow.Id).XSessionToken(sessionToken).UpdateSettingsFlowBody( ory.UpdateSettingsFlowWithPasswordMethodAsUpdateSettingsFlowBody(&ory.UpdateSettingsFlowWithPasswordMethod{ Method: "password", Password: "not-" + password, }), ).Execute() pkg.SDKExitOnError(err, res) return result } func changeTraits(email, password string) *ory.SettingsFlow { sessionToken, flow := initFlow(email, password) // Submit the form result, res, err := client.FrontendAPI.UpdateSettingsFlow(ctx).Flow(flow.Id).XSessionToken(sessionToken).UpdateSettingsFlowBody( ory.UpdateSettingsFlowWithProfileMethodAsUpdateSettingsFlowBody(&ory.UpdateSettingsFlowWithProfileMethod{ Method: "profile", Traits: map[string]interface{}{ "email": "not-" + email, }, }), ).Execute() pkg.SDKExitOnError(err, res) return result } func main() { pkg.PrintJSONPretty( changePassword(pkg.RandomCredentials()), ) pkg.PrintJSONPretty( changeTraits(pkg.RandomCredentials()), ) } ================================================ FILE: examples/go/selfservice/settings/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" ory "github.com/ory/kratos/pkg/httpclient" "github.com/stretchr/testify/assert" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/require" ) func TestSettings(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) email, password := pkg.RandomCredentials() result := changePassword(email, password) require.NotEmpty(t, result.Id) assert.EqualValues(t, ory.SETTINGSFLOWSTATE_SUCCESS, result.State) email, password = pkg.RandomCredentials() result = changeTraits(email, password) require.NotEmpty(t, result.Id) assert.EqualValues(t, ory.SETTINGSFLOWSTATE_SUCCESS, result.State) assert.Equal(t, "not-"+email, result.Identity.Traits.(map[string]interface{})["email"].(string)) } ================================================ FILE: examples/go/selfservice/verification/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func performVerification(email string) *ory.VerificationFlow { ctx := context.Background() // Initialize the flow flow, res, err := client.FrontendAPI.CreateNativeVerificationFlow(ctx).Execute() pkg.SDKExitOnError(err, res) // If you want, print the flow here: // // pkg.PrintJSONPretty(flow) // Submit the form afterSubmit, res, err := client.FrontendAPI.UpdateVerificationFlow(ctx).Flow(flow.Id). UpdateVerificationFlowBody(ory.UpdateVerificationFlowWithLinkMethodAsUpdateVerificationFlowBody(&ory.UpdateVerificationFlowWithLinkMethod{ Email: email, Method: "link", })).Execute() pkg.SDKExitOnError(err, res) return afterSubmit } func main() { pkg.PrintJSONPretty( performVerification("someone@foobar.com"), ) } ================================================ FILE: examples/go/selfservice/verification/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/kratos/pkg/httpclient" "github.com/ory/kratos/pkg/testhelpers" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) flow := performVerification("dev+" + uuid.Must(uuid.NewV4()).String() + "@ory.sh") require.NotEmpty(t, flow.Id) assert.EqualValues(t, ory.VERIFICATIONFLOWSTATE_SENT_EMAIL, flow.State) } ================================================ FILE: examples/go/session/tosession/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "github.com/ory/kratos/examples/go/pkg" ory "github.com/ory/client-go" ) // If you use Open Source this would be: // // var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433") var client = pkg.NewSDK("playground") func toSession() *ory.Session { // Create a temporary user email, password := pkg.RandomCredentials() _, sessionToken := pkg.CreateIdentityWithSession(client, email, password) session, res, err := client.FrontendAPI.ToSessionExecute(ory.FrontendAPIToSessionRequest{}. XSessionToken(sessionToken)) pkg.SDKExitOnError(err, res) return session } func main() { session := toSession() pkg.PrintJSONPretty(session) } ================================================ FILE: examples/go/session/tosession/main_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "testing" "github.com/stretchr/testify/assert" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/examples/go/pkg" "github.com/stretchr/testify/require" ) func TestFunc(t *testing.T) { publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml", nil) client = pkg.NewSDKForSelfHosted(publicURL) session := toSession() require.NotEmpty(t, session.Id) assert.Contains(t, session.Identity.Traits.(map[string]interface{})["email"].(string), "dev+") } ================================================ FILE: gen/oidc/v1/state.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc (unknown) // source: oidc/v1/state.proto package oidcv1 import ( reflect "reflect" sync "sync" unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type FlowKind int32 const ( FlowKind_FLOW_KIND_UNSPECIFIED FlowKind = 0 FlowKind_FLOW_KIND_LOGIN FlowKind = 1 FlowKind_FLOW_KIND_REGISTRATION FlowKind = 2 FlowKind_FLOW_KIND_SETTINGS FlowKind = 3 ) // Enum value maps for FlowKind. var ( FlowKind_name = map[int32]string{ 0: "FLOW_KIND_UNSPECIFIED", 1: "FLOW_KIND_LOGIN", 2: "FLOW_KIND_REGISTRATION", 3: "FLOW_KIND_SETTINGS", } FlowKind_value = map[string]int32{ "FLOW_KIND_UNSPECIFIED": 0, "FLOW_KIND_LOGIN": 1, "FLOW_KIND_REGISTRATION": 2, "FLOW_KIND_SETTINGS": 3, } ) func (x FlowKind) Enum() *FlowKind { p := new(FlowKind) *p = x return p } func (x FlowKind) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (FlowKind) Descriptor() protoreflect.EnumDescriptor { return file_oidc_v1_state_proto_enumTypes[0].Descriptor() } func (FlowKind) Type() protoreflect.EnumType { return &file_oidc_v1_state_proto_enumTypes[0] } func (x FlowKind) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use FlowKind.Descriptor instead. func (FlowKind) EnumDescriptor() ([]byte, []int) { return file_oidc_v1_state_proto_rawDescGZIP(), []int{0} } type State struct { state protoimpl.MessageState `protogen:"open.v1"` FlowId []byte `protobuf:"bytes,1,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"` SessionTokenExchangeCodeSha512 []byte `protobuf:"bytes,2,opt,name=session_token_exchange_code_sha512,json=sessionTokenExchangeCodeSha512,proto3" json:"session_token_exchange_code_sha512,omitempty"` ProviderId string `protobuf:"bytes,3,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` PkceVerifier string `protobuf:"bytes,4,opt,name=pkce_verifier,json=pkceVerifier,proto3" json:"pkce_verifier,omitempty"` FlowKind FlowKind `protobuf:"varint,5,opt,name=flow_kind,json=flowKind,proto3,enum=oidc.v1.FlowKind" json:"flow_kind,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *State) Reset() { *x = State{} mi := &file_oidc_v1_state_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *State) String() string { return protoimpl.X.MessageStringOf(x) } func (*State) ProtoMessage() {} func (x *State) ProtoReflect() protoreflect.Message { mi := &file_oidc_v1_state_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use State.ProtoReflect.Descriptor instead. func (*State) Descriptor() ([]byte, []int) { return file_oidc_v1_state_proto_rawDescGZIP(), []int{0} } func (x *State) GetFlowId() []byte { if x != nil { return x.FlowId } return nil } func (x *State) GetSessionTokenExchangeCodeSha512() []byte { if x != nil { return x.SessionTokenExchangeCodeSha512 } return nil } func (x *State) GetProviderId() string { if x != nil { return x.ProviderId } return "" } func (x *State) GetPkceVerifier() string { if x != nil { return x.PkceVerifier } return "" } func (x *State) GetFlowKind() FlowKind { if x != nil { return x.FlowKind } return FlowKind_FLOW_KIND_UNSPECIFIED } var File_oidc_v1_state_proto protoreflect.FileDescriptor const file_oidc_v1_state_proto_rawDesc = "" + "\n" + "\x13oidc/v1/state.proto\x12\aoidc.v1\"\xe2\x01\n" + "\x05State\x12\x17\n" + "\aflow_id\x18\x01 \x01(\fR\x06flowId\x12J\n" + "\"session_token_exchange_code_sha512\x18\x02 \x01(\fR\x1esessionTokenExchangeCodeSha512\x12\x1f\n" + "\vprovider_id\x18\x03 \x01(\tR\n" + "providerId\x12#\n" + "\rpkce_verifier\x18\x04 \x01(\tR\fpkceVerifier\x12.\n" + "\tflow_kind\x18\x05 \x01(\x0e2\x11.oidc.v1.FlowKindR\bflowKind*n\n" + "\bFlowKind\x12\x19\n" + "\x15FLOW_KIND_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fFLOW_KIND_LOGIN\x10\x01\x12\x1a\n" + "\x16FLOW_KIND_REGISTRATION\x10\x02\x12\x16\n" + "\x12FLOW_KIND_SETTINGS\x10\x03B|\n" + "\vcom.oidc.v1B\n" + "StateProtoP\x01Z$github.com/ory/kratos/oidc/v1;oidcv1\xa2\x02\x03OXX\xaa\x02\aOidc.V1\xca\x02\aOidc\\V1\xe2\x02\x13Oidc\\V1\\GPBMetadata\xea\x02\bOidc::V1b\x06proto3" var ( file_oidc_v1_state_proto_rawDescOnce sync.Once file_oidc_v1_state_proto_rawDescData []byte ) func file_oidc_v1_state_proto_rawDescGZIP() []byte { file_oidc_v1_state_proto_rawDescOnce.Do(func() { file_oidc_v1_state_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_oidc_v1_state_proto_rawDesc), len(file_oidc_v1_state_proto_rawDesc))) }) return file_oidc_v1_state_proto_rawDescData } var file_oidc_v1_state_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_oidc_v1_state_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_oidc_v1_state_proto_goTypes = []any{ (FlowKind)(0), // 0: oidc.v1.FlowKind (*State)(nil), // 1: oidc.v1.State } var file_oidc_v1_state_proto_depIdxs = []int32{ 0, // 0: oidc.v1.State.flow_kind:type_name -> oidc.v1.FlowKind 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_oidc_v1_state_proto_init() } func file_oidc_v1_state_proto_init() { if File_oidc_v1_state_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_oidc_v1_state_proto_rawDesc), len(file_oidc_v1_state_proto_rawDesc)), NumEnums: 1, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_oidc_v1_state_proto_goTypes, DependencyIndexes: file_oidc_v1_state_proto_depIdxs, EnumInfos: file_oidc_v1_state_proto_enumTypes, MessageInfos: file_oidc_v1_state_proto_msgTypes, }.Build() File_oidc_v1_state_proto = out.File file_oidc_v1_state_proto_goTypes = nil file_oidc_v1_state_proto_depIdxs = nil } ================================================ FILE: go.mod ================================================ module github.com/ory/kratos go 1.26 replace ( github.com/coreos/go-oidc/v3 => github.com/ory/go-oidc/v3 v3.0.0-20250124100243-69986dfaf891 github.com/go-swagger/go-swagger => github.com/aeneasr/go-swagger v0.19.1-0.20241013070044-bccef3a12e26 // See https://github.com/go-swagger/go-swagger/issues/3131 // github.com/go-swagger/go-swagger => ../../go-swagger/go-swagger github.com/gorilla/sessions => github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2 github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.14.28 // Use the internal httpclient which can be generated in this codebase but mark it as the // official SDK, allowing for the Ory CLI to consume Ory Kratos' CLI commands. github.com/ory/client-go => ./pkg/client-go github.com/ory/x => ./oryx ) require ( dario.cat/mergo v1.0.2 github.com/Masterminds/sprig/v3 v3.3.0 github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 github.com/bwmarrin/discordgo v0.28.1 github.com/coreos/go-oidc/v3 v3.11.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/dghubble/oauth1 v0.7.3 github.com/dgraph-io/ristretto/v2 v2.2.0 github.com/fatih/color v1.18.0 github.com/ghodss/yaml v1.0.0 github.com/go-crypt/crypt v0.2.25 github.com/go-faker/faker/v4 v4.4.2 github.com/go-openapi/strfmt v0.23.0 github.com/go-webauthn/webauthn v0.11.2 github.com/gobuffalo/httptest v1.5.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/golang-jwt/jwt/v4 v4.5.2 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2 github.com/golang/mock v1.6.0 github.com/google/go-github/v38 v38.1.0 github.com/google/go-jsonnet v0.21.0 github.com/gorilla/sessions v1.3.0 github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/jarcoal/httpmock v1.3.1 github.com/jmoiron/sqlx v1.4.0 github.com/knadh/koanf/parsers/json v0.1.0 github.com/laher/mergefs v0.1.2-0.20230223191438-d16611b2f4e7 // indirect github.com/lestrrat-go/jwx/v2 v2.1.1 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/montanaflynn/stats v0.7.1 github.com/ory/analytics-go/v5 v5.0.1 github.com/ory/client-go v0.0.0-00010101000000-000000000000 github.com/ory/graceful v0.1.4-0.20230301144740-e222150c51d0 github.com/ory/herodot v0.10.8 github.com/ory/hydra-client-go/v2 v2.2.1 github.com/ory/jsonschema/v3 v3.0.9-0.20250317235931-280c5fc7bf0e github.com/ory/mail/v3 v3.0.0 github.com/ory/nosurf v1.2.7 github.com/ory/pop/v6 v6.3.2-0.20251203152233-a32233875f7e github.com/ory/x v0.0.0-00010101000000-000000000000 github.com/peterhellberg/link v1.2.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 github.com/rakutentech/jwk-go v1.2.0 github.com/rs/cors v1.11.1 github.com/samber/lo v1.46.0 github.com/sirupsen/logrus v1.9.3 github.com/slack-go/slack v0.13.1 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/urfave/negroni v1.0.0 github.com/wI2L/jsondiff v0.6.0 github.com/zmb3/spotify/v2 v2.4.2 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 go.opentelemetry.io/otel v1.40.0 go.opentelemetry.io/otel/sdk v1.40.0 go.opentelemetry.io/otel/trace v1.40.0 golang.org/x/crypto v0.46.0 golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect golang.org/x/net v0.48.0 golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.19.0 golang.org/x/text v0.32.0 google.golang.org/grpc v1.79.3 ) require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/gorilla/pat v1.0.2 github.com/ian-kent/go-log v0.0.0-20160113211217-5731446c36ab github.com/mailhog/MailHog-Server v1.0.1 github.com/mailhog/data v1.0.1 github.com/mailhog/storage v1.0.1 github.com/moby/moby/api v1.54.0 github.com/ory/dockertest/v4 v4.0.0-beta.4 ) require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/XSAM/otelsql v0.39.0 // indirect github.com/a8m/envsubst v1.4.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/cortesi/modd v0.8.1 // indirect github.com/cortesi/moddwatch v0.1.0 // indirect github.com/cortesi/termlog v0.0.0-20210222042314-a1eec763abec // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/elliotchance/orderedmap v1.7.1 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/inflect v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect github.com/go-swagger/go-swagger v0.31.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobuffalo/plush/v5 v5.0.7 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/ian-kent/envconf v0.0.0-20141026121121-c19809918c02 // indirect github.com/ian-kent/goose v0.0.0-20141221090059-c3541ea826ad // indirect github.com/ian-kent/linkio v0.0.0-20170807205755-97566b872887 // indirect github.com/jackc/pgx/v5 v5.7.5 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect github.com/jessevdk/go-flags v1.6.1 // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailhog/MailHog v1.0.1 // indirect github.com/mailhog/MailHog-UI v1.0.1 // indirect github.com/mailhog/http v1.0.1 // indirect github.com/mailhog/mhsendmail v0.2.0 // indirect github.com/mailhog/smtp v1.0.1 // indirect github.com/mikefarah/yq/v4 v4.45.1 // indirect github.com/moby/moby/client v0.3.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ogier/pflag v0.0.1 // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/rjeczalik/notify v0.9.3 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/ssoready/hyrumtoken v1.0.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/t-k/fluent-logger-golang v1.0.0 // indirect github.com/tinylib/msgp v1.2.5 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect golang.org/x/term v0.38.0 // indirect golang.org/x/tools v0.39.0 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 // indirect mvdan.cc/sh/v3 v3.6.0 // indirect ) require ( code.dny.dev/ssrf v0.2.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/avast/retry-go/v4 v4.6.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/cockroach-go/v2 v2.4.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.3.3+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-crypt/x v0.2.18 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/errors v0.22.2 // indirect github.com/go-openapi/jsonpointer v0.21.2 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-webauthn/x v0.1.14 // indirect github.com/gobuffalo/envy v1.10.2 // indirect github.com/gobuffalo/fizz v1.14.4 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gobuffalo/github_flavored_markdown v1.1.4 // indirect github.com/gobuffalo/helpers v0.6.10 // indirect github.com/gobuffalo/nulls v0.4.2 // indirect github.com/gobuffalo/plush/v4 v4.1.22 // indirect github.com/gobuffalo/tags/v3 v3.1.4 // indirect github.com/gobuffalo/validate/v3 v3.3.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-tpm v0.9.1 // indirect github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/parsers/toml v0.1.0 // indirect github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect github.com/knadh/koanf/providers/posflag v0.1.0 // indirect github.com/knadh/koanf/v2 v2.2.2 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/jwx v1.2.31 github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.9 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/nyaruka/phonenumbers v1.6.5 github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/profile v1.7.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/backo-go v1.1.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect github.com/spf13/cast v1.9.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.37.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.31.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect; / indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect; / indirect go.opentelemetry.io/otel/exporters/zipkin v1.37.0 // indirect; / indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/sys v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) tool ( github.com/cortesi/modd/cmd/modd github.com/go-swagger/go-swagger/cmd/swagger github.com/mailhog/MailHog github.com/mikefarah/yq/v4 golang.org/x/tools/cmd/goimports ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= code.dny.dev/ssrf v0.2.0 h1:wCBP990rQQ1CYfRpW+YK1+8xhwUjv189AQ3WMo1jQaI= code.dny.dev/ssrf v0.2.0/go.mod h1:B+91l25OnyaLIeCx0WRJN5qfJ/4/ZTZxRXgm0lj/2w8= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/XSAM/otelsql v0.39.0 h1:4o374mEIMweaeevL7fd8Q3C710Xi2Jh/c8G4Qy9bvCY= github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0= github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg= github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY= github.com/aeneasr/go-swagger v0.19.1-0.20241013070044-bccef3a12e26 h1:rwCKVbnpzxQ0F/AhO9FkXnrKqRmqej4epjhe1CpNkB0= github.com/aeneasr/go-swagger v0.19.1-0.20241013070044-bccef3a12e26/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po= github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk= github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/cockroach-go/v2 v2.4.1 h1:ACVT/zXsuK6waRPVYtDQpsM8pPA7IA/3fkgA02RR/Gw= github.com/cockroachdb/cockroach-go/v2 v2.4.1/go.mod h1:9U179XbCx4qFWtNhc7BiWLPfuyMVQ7qdAhfrwLz1vH0= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cortesi/modd v0.8.1 h1:0s8e10CJ6pxc6NQHYFrmUZOLP0X6v63ry+3na6Gq2Ow= github.com/cortesi/modd v0.8.1/go.mod h1:GDJFkhHnnW+SD1C+wHBlKe5Yh2IqiOb6Lu5t2/fjnS4= github.com/cortesi/moddwatch v0.1.0 h1:+TSMuplhKlKEPKsdUXNHd67aCqew+et15dJvRCxMd1M= github.com/cortesi/moddwatch v0.1.0/go.mod h1:PFkhcmmwsRMQ76IMjKbaIIMcGQt7BSMtFOp+pA0B2eo= github.com/cortesi/termlog v0.0.0-20210222042314-a1eec763abec h1:v7D8uHsIKsyjfyhhNdY4qivqN558Ejiq+CDXiUljZ+4= github.com/cortesi/termlog v0.0.0-20210222042314-a1eec763abec/go.mod h1:10Fm2kasJmcKf1FSMQGSWb976sfR29hejNtfS9AydB4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dghubble/oauth1 v0.7.3 h1:EkEM/zMDMp3zOsX2DC/ZQ2vnEX3ELK0/l9kb+vs4ptE= github.com/dghubble/oauth1 v0.7.3/go.mod h1:oxTe+az9NSMIucDPDCCtzJGsPhciJV33xocHfcR2sVY= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elliotchance/orderedmap v1.7.1 h1:8SR2DB391dw0HVI9572ElrY+KU0Q89OCXYwWZx7aAZc= github.com/elliotchance/orderedmap v1.7.1/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-crypt/crypt v0.2.25 h1:uW/3n4/9zYSOOgY0Md9dMxGSqrjaMyLo1/IFrm2L5yw= github.com/go-crypt/crypt v0.2.25/go.mod h1:ny8BOunn+/kr99iq2LYSKA0MAsxNaxZUmKKL42vV1io= github.com/go-crypt/x v0.2.18 h1:KdUGj4D/PdzcIkOQhK36QHzH2YD5GWrsVQ7JgO73Q8Y= github.com/go-crypt/x v0.2.18/go.mod h1:z48dMLdgMGPPjrzni9ETtg2foPez3EytjCL43Ak4QZ8= github.com/go-faker/faker/v4 v4.4.2 h1:96WeU9QKEqRUVYdjHquY2/5bAqmVM0IfGKHV5mbfqmQ= github.com/go-faker/faker/v4 v4.4.2/go.mod h1:4K3v4AbKXYNHMQNaREMc9/kRB9j5JJzpFo6KHRvrcIw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg= github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0= github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc= github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= github.com/gobuffalo/fizz v1.14.4 h1:8uume7joF6niTNWN582IQ2jhGTUoa9g1fiV/tIoGdBs= github.com/gobuffalo/fizz v1.14.4/go.mod h1:9/2fGNXNeIFOXEEgTPJwiK63e44RjG+Nc4hfMm1ArGM= github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= github.com/gobuffalo/github_flavored_markdown v1.1.4 h1:WacrEGPXUDX+BpU1GM/Y0ADgMzESKNWls9hOTG1MHVs= github.com/gobuffalo/github_flavored_markdown v1.1.4/go.mod h1:Vl9686qrVVQou4GrHRK/KOG3jCZOKLUqV8MMOAYtlso= github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= github.com/gobuffalo/helpers v0.6.10 h1:puKDCOrJ0EIq5ScnTRgKyvEZ05xQa+gwRGCpgoh6Ek8= github.com/gobuffalo/helpers v0.6.10/go.mod h1:r52L6VSnByLJFOmURp1irvzgSakk7RodChi1YbGwk8I= github.com/gobuffalo/httptest v1.5.2 h1:GpGy520SfY1QEmyPvaqmznTpG4gEQqQ82HtHqyNEreM= github.com/gobuffalo/httptest v1.5.2/go.mod h1:FA23yjsWLGj92mVV74Qtc8eqluc11VqcWr8/C1vxt4g= github.com/gobuffalo/nulls v0.4.2 h1:GAqBR29R3oPY+WCC7JL9KKk9erchaNuV6unsOSZGQkw= github.com/gobuffalo/nulls v0.4.2/go.mod h1:EElw2zmBYafU2R9W4Ii1ByIj177wA/pc0JdjtD0EsH8= github.com/gobuffalo/plush/v4 v4.1.16/go.mod h1:6t7swVsarJ8qSLw1qyAH/KbrcSTwdun2ASEQkOznakg= github.com/gobuffalo/plush/v4 v4.1.22 h1:bPQr5PsiTg54UGMsfvnIAvFmUfxzD/ri+wbpu7PlmTM= github.com/gobuffalo/plush/v4 v4.1.22/go.mod h1:WiKHJx3qBvfaDVlrv8zT7NCd3dEMaVR/fVxW4wqV17M= github.com/gobuffalo/plush/v5 v5.0.7 h1:nI8sIt5tZAN2tCZHeaXkH7HAvxvvk3sJHG2TtrKeSHM= github.com/gobuffalo/plush/v5 v5.0.7/go.mod h1:C08u/VEqzzPBXFF/yqs40P/5Cvc/zlZsMzhCxXyWJmU= github.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM= github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4= github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2 h1:xisWqjiKEff2B0KfFYGpCqc3M3zdTz+OHQHRc09FeYk= github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v38 v38.1.0 h1:C6h1FkaITcBFK7gAmq4eFzt6gbhEhk7L5z6R3Uva+po= github.com/google/go-github/v38 v38.1.0/go.mod h1:cStvrz/7nFr0FoENgG6GLbp53WaelXucT+BBz/3VKx4= github.com/google/go-jsonnet v0.21.0 h1:43Bk3K4zMRP/aAZm9Po2uSEjY6ALCkYUVIcz9HLGMvA= github.com/google/go-jsonnet v0.21.0/go.mod h1:tCGAu8cpUpEZcdGMmdOu37nh8bGgqubhI5v2iSk3KJQ= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM= github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/pat v1.0.2 h1:TDh/RulbnPxMQACcwbgMF5Bf00jaGoeYBNu+XUFuwtE= github.com/gorilla/pat v1.0.2/go.mod h1:ioQ7dFQ2KXmOmWLJs6vZAfRikcm2D2JyuLrL9b5wVCg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69 h1:7xsUJsB2NrdcttQPa7JLEaGzvdbk7KvfrjgHZXOQRo0= github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69/go.mod h1:YLEMZOtU+AZ7dhN9T/IpGhXVGly2bvkJQ+zxj3WeVQo= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ian-kent/envconf v0.0.0-20141026121121-c19809918c02 h1:dU8zq210pt1b71X8xh9GOxC7uBHNtQ9BYC+Lb6SA/mA= github.com/ian-kent/envconf v0.0.0-20141026121121-c19809918c02/go.mod h1:1m5fo3aKG2moYtGHC4I2nFkXmG97+vCeaEIWC+mXTSI= github.com/ian-kent/go-log v0.0.0-20160113211217-5731446c36ab h1:OgrFrYWlVzY7Tc8rq7Y4ErlKo28igc70gbfJGTVWTJk= github.com/ian-kent/go-log v0.0.0-20160113211217-5731446c36ab/go.mod h1:6HitiSDIbT2r0dab4CoKoMAtR7tb0ORQ3OmjkjCZ+zk= github.com/ian-kent/goose v0.0.0-20141221090059-c3541ea826ad h1:5UZIY1lPvsBrRQRgyt00lJ1J6HH6CwWAVQB6azyAA1c= github.com/ian-kent/goose v0.0.0-20141221090059-c3541ea826ad/go.mod h1:VHyJj0/IJFmpYvVqWFIN2HgjCatXujj7XaLLyOMC23M= github.com/ian-kent/linkio v0.0.0-20170807205755-97566b872887 h1:LPaZmcRJS13h+igi07S26uKy0qxCa76u1+pArD+JGrY= github.com/ian-kent/linkio v0.0.0-20170807205755-97566b872887/go.mod h1:aE63iKqF9rMrshaEiYZroUYFZLaYoTuA7pBMsg3lJoY= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY= github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI= github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18= github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U= github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A= github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/laher/mergefs v0.1.2-0.20230223191438-d16611b2f4e7 h1:PDeBswTUsSIT4QSrzLvlqKlGrANYa7TrXUwdBN9myU8= github.com/laher/mergefs v0.1.2-0.20230223191438-d16611b2f4e7/go.mod h1:FSY1hYy94on4Tz60waRMGdO1awwS23BacqJlqf9lJ9Q= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= github.com/lestrrat-go/jwx/v2 v2.1.1 h1:Y2ltVl8J6izLYFs54BVcpXLv5msSW4o8eXwnzZLI32E= github.com/lestrrat-go/jwx/v2 v2.1.1/go.mod h1:4LvZg7oxu6Q5VJwn7Mk/UwooNRnTHUpXBj2C4j3HNx0= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailhog/MailHog v1.0.1 h1:NDExFIj+JGzXT3kmG31r7Okrn78Sk/5p9lP/TV8OE4E= github.com/mailhog/MailHog v1.0.1/go.mod h1:QlN3aQB5Kx2ZoQy439EWkjWHyJrgqNDsjfknyQOBCOI= github.com/mailhog/MailHog-Server v1.0.1 h1:mK9inUHV2p6pO55cHZTCdZ8D4aXzd+M9wvqtU0XmWcM= github.com/mailhog/MailHog-Server v1.0.1/go.mod h1:ScCrImbapPxdrQ85qoxkMygsSiY74ohIj1knLzuN8J0= github.com/mailhog/MailHog-UI v1.0.1 h1:B3mVLiVLd4amNVQtiklr+srI3eMKiMzYSXXqDGa7JzA= github.com/mailhog/MailHog-UI v1.0.1/go.mod h1:zLEw2DaBMXpL6nmpdB8S5U1Y3MMSATlTcjUYSTAB7HQ= github.com/mailhog/data v1.0.1 h1:7I+opBvVdi4EMJaihXavM98jp/ovt4o6mz47446RAW8= github.com/mailhog/data v1.0.1/go.mod h1:tjR/iXRhbSUKHzAAMd99RygVaDB5rIDC/bmWc363MzU= github.com/mailhog/http v1.0.1 h1:i3sxAt7/WcdRXdKJZgiDRkWIAYScnrqqHQTZSxXkM0I= github.com/mailhog/http v1.0.1/go.mod h1:91oqUCI9ZoSDY2cTj4pWDJVBHCK1U762V2a4if4KlOw= github.com/mailhog/mhsendmail v0.2.0 h1:C5HUC4obHfXIkttLfGBUopYbsJmh+bnExGWHBpWQ8IA= github.com/mailhog/mhsendmail v0.2.0/go.mod h1:B0778+OoPEc5aEFqatEnSO4ZWl9FCTxvaY+c7OOQadM= github.com/mailhog/smtp v1.0.1 h1:igL3N/L+pWuGCqUaje21HX3VIVnqHoVlqWO0t+wJEYE= github.com/mailhog/smtp v1.0.1/go.mod h1:GMrAdv1hXro38xj5dsWPAk5ZiXJHFx9t7W9Yqsk0XUM= github.com/mailhog/storage v1.0.1 h1:uut2nlG5hIxbsl6f8DGznPAHwQLf3/7Na2t4gmrIais= github.com/mailhog/storage v1.0.1/go.mod h1:4EAUf5xaEVd7c/OhvSxOOwQ66jT6q2er+BDBQ0EVrew= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mikefarah/yq/v4 v4.45.1 h1:EW+HjKEVa55pUYFJseEHEHdQ0+ulunY+q42zF3M7ZaQ= github.com/mikefarah/yq/v4 v4.45.1/go.mod h1:djgN2vD749hpjVNGYTShr5Kmv5LYljhCG3lUTuEe3LM= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/moby/api v1.54.0 h1:7kbUgyiKcoBhm0UrWbdrMs7RX8dnwzURKVbZGy2GnL0= github.com/moby/moby/api v1.54.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= github.com/moby/moby/client v0.3.0 h1:UUGL5okry+Aomj3WhGt9Aigl3ZOxZGqR7XPo+RLPlKs= github.com/moby/moby/client v0.3.0/go.mod h1:HJgFbJRvogDQjbM8fqc1MCEm4mIAGMLjXbgwoZp6jCQ= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nyaruka/phonenumbers v1.6.5 h1:aBCaUhfpRA7hU6fsXk+p7KF1aNx4nQlq9hGeo2qdFg8= github.com/nyaruka/phonenumbers v1.6.5/go.mod h1:7gjs+Lchqm49adhAKB5cdcng5ZXgt6x7Jgvi0ZorUtU= github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750= github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/ory/analytics-go/v5 v5.0.1 h1:LX8T5B9FN8KZXOtxgN+R3I4THRRVB6+28IKgKBpXmAM= github.com/ory/analytics-go/v5 v5.0.1/go.mod h1:lWCiCjAaJkKfgR/BN5DCLMol8BjKS1x+4jxBxff/FF0= github.com/ory/dockertest/v4 v4.0.0-beta.4 h1:QcrNrobOP+5IjSDmS4//EuBtwiFuznQhi5xTe8oFSoM= github.com/ory/dockertest/v4 v4.0.0-beta.4/go.mod h1:p9kfE14tzK8+WU4F9YbIZlzhCzQ2pH7H1KIfBKrF3DM= github.com/ory/go-oidc/v3 v3.0.0-20250124100243-69986dfaf891 h1:HjpfYsY85wpheyMwR9EEk3347I0QsCllRMJShods3jc= github.com/ory/go-oidc/v3 v3.0.0-20250124100243-69986dfaf891/go.mod h1:Jxfv2TPRvdJuLfmkvokss8dkguhMmer2UvARU6SWy0Y= github.com/ory/graceful v0.1.4-0.20230301144740-e222150c51d0 h1:VMUeLRfQD14fOMvhpYZIIT4vtAqxYh+f3KnSqCeJ13o= github.com/ory/graceful v0.1.4-0.20230301144740-e222150c51d0/go.mod h1:hg2iCy+LCWOXahBZ+NQa4dk8J2govyQD79rrqrgMyY8= github.com/ory/herodot v0.10.8 h1:uUPsXd4FKTsNHHU+OS5gYrRNEMraU36st0kBeWiXsno= github.com/ory/herodot v0.10.8/go.mod h1:j6i246U6iX8TStYNKIVQxb2waweQvtOLi+b/9q+OULg= github.com/ory/hydra-client-go/v2 v2.2.1 h1:m1821pIX6ybG/3oSAn2wtrbBKNwe9q5A8fLljYuLpBk= github.com/ory/hydra-client-go/v2 v2.2.1/go.mod h1:K83R+iK40+5uF2uQ34yRUrf9izRvFsza9pG2Se5qMmk= github.com/ory/jsonschema/v3 v3.0.9-0.20250317235931-280c5fc7bf0e h1:4tUrC7x4YWRVMFp+c64KACNSGchW1zXo4l6Pa9/1hA8= github.com/ory/jsonschema/v3 v3.0.9-0.20250317235931-280c5fc7bf0e/go.mod h1:XWLxVK4un/iuIcrw+6lCeanbF3NZwO5k6RdLeu/loQk= github.com/ory/mail v2.3.1+incompatible/go.mod h1:87D9/1gB6ewElQoN0lXJ0ayfqcj3cW3qCTXh+5E9mfU= github.com/ory/mail/v3 v3.0.0 h1:8LFMRj473vGahFD/ntiotWEd4S80FKYFtiZTDfOQ+sM= github.com/ory/mail/v3 v3.0.0/go.mod h1:JGAVeZF8YAlxbaFDUHqRZAKBCSeW2w1vuxf28hFbZAw= github.com/ory/nosurf v1.2.7 h1:YrHrbSensQyU6r6HT/V5+HPdVEgrOTMJiLoJABSBOp4= github.com/ory/nosurf v1.2.7/go.mod h1:d4L3ZBa7Amv55bqxCBtCs63wSlyaiCkWVl4vKf3OUxA= github.com/ory/pop/v6 v6.3.2-0.20251203152233-a32233875f7e h1:gsbAteu8HZYnkIF4WVBaxklvF/s5IbcxYcCi6qX93ms= github.com/ory/pop/v6 v6.3.2-0.20251203152233-a32233875f7e/go.mod h1:PEqjxMcIV87rBhlyDDha76I7/w2W/FHenSq3V3X1A/A= github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2 h1:zm6sDvHy/U9XrGpixwHiuAwpp0Ock6khSVHkrv6lQQU= github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rakutentech/jwk-go v1.2.0 h1:vNJwedPkRR+32V5WGNj0JP4COes93BGERvzQLBjLy4c= github.com/rakutentech/jwk-go v1.2.0/go.mod h1:pI0bYVntqaJ27RCpaC75MTUacheW0Rk4+8XzWWe1OWM= github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 h1:0b8DF5kR0PhRoRXDiEEdzrgBc8UqVY4JWLkQJCRsLME= github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761/go.mod h1:/THDZYi7F/BsVEcYzYPqdcWFQ+1C2InkawTKfLOAnzg= github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/segmentio/backo-go v1.1.0 h1:cJIfHQUdmLsd8t9IXqf5J8SdrOMn9vMa7cIvOavHAhc= github.com/segmentio/backo-go v1.1.0/go.mod h1:ckenwdf+v/qbyhVdNPWHnqh2YdJBED1O9cidYyM5J18= github.com/segmentio/conf v1.2.0/go.mod h1:Y3B9O/PqqWqjyxyWWseyj/quPEtMu1zDp/kVbSWWaB0= github.com/segmentio/go-snakecase v1.1.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.13.1 h1:6UkM3U1OnbhPsYeb1IMkQ6HSNOSikWluwOncJt4Tz/o= github.com/slack-go/slack v0.13.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/ssoready/hyrumtoken v1.0.0 h1:N/JPJDOuYS7qPSnOvZpPxNVXwtlT3kfzAMEcPrH8ywQ= github.com/ssoready/hyrumtoken v1.0.0/go.mod h1:h8q768r5Uv6iJKOwsNENIWWUP9kvmLykQox5m3SCpqc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/t-k/fluent-logger-golang v1.0.0 h1:4IQzY+/l66Zkkhk9eB3LwF9vPkgKHJ1rpYdrRiap0EI= github.com/t-k/fluent-logger-golang v1.0.0/go.mod h1:6vC3Vzp9Kva0l5J9+YDY5/ROePwkAqwLK+KneCjSm4w= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/wI2L/jsondiff v0.6.0 h1:zrsH3FbfVa3JO9llxrcDy/XLkYPLgoMX6Mz3T2PP2AI= github.com/wI2L/jsondiff v0.6.0/go.mod h1:D6aQ5gKgPF9g17j+E9N7aasmU1O+XvfmWm1y8UMmNpw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/zmb3/spotify/v2 v2.4.2 h1:j3yNN5lKVEMZQItJF4MHCSZbfNWmXO+KaC+3RFaLlLc= github.com/zmb3/spotify/v2 v2.4.2/go.mod h1:XOV7BrThayFYB9AAfB+L0Q0wyxBuLCARk4fI/ZXCBW8= go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 h1:wCeciVlAfb5DC8MQl/DlmAv/FVPNpQgFvI/71+hatuc= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0/go.mod h1:WfEApdZDMlLUAev/0QQpr8EJ/z0VWDKYZ5tF5RH5T1U= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/b3 v1.37.0 h1:0aGKdIuVhy5l4GClAjl72ntkZJhijf2wg1S7b5oLoYA= go.opentelemetry.io/contrib/propagators/b3 v1.37.0/go.mod h1:nhyrxEJEOQdwR15zXrCKI6+cJK60PXAkJ/jRyfhr2mg= go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 h1:pW+qDVo0jB0rLsNeaP85xLuz20cvsECUcN7TE+D8YTM= go.opentelemetry.io/contrib/propagators/jaeger v1.37.0/go.mod h1:x7bd+t034hxLTve1hF9Yn9qQJlO/pP8H5pWIt7+gsFM= go.opentelemetry.io/contrib/samplers/jaegerremote v0.31.0 h1:l8XCsDh7L6Z7PB+vlw1s4ufNab+ayT2RMNdvDE/UyPc= go.opentelemetry.io/contrib/samplers/jaegerremote v0.31.0/go.mod h1:XAOSk4bqj5vtoiY08bexeiafzxdXeLlxKFnwscvn8Fc= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= go.opentelemetry.io/otel/exporters/zipkin v1.37.0 h1:Z2apuaRnHEjzDAkpbWNPiksz1R0/FCIrJSjiMA43zwI= go.opentelemetry.io/otel/exporters/zipkin v1.37.0/go.mod h1:ofGu/7fG+bpmjZoiPUUmYDJ4vXWxMT57HmGoegx49uw= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/mold.v2 v2.2.0/go.mod h1:XMyyRsGtakkDPbxXbrA5VODo6bUXyvoDjLd5l3T0XoA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= mvdan.cc/sh/v3 v3.6.0 h1:gtva4EXJ0dFNvl5bHjcUEvws+KRcDslT8VKheTYkbGU= mvdan.cc/sh/v3 v3.6.0/go.mod h1:U4mhtBLZ32iWhif5/lD+ygy1zrgaQhUu+XFy7C8+TTA= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= ================================================ FILE: hash/hash_comparator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash import ( "bytes" "context" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/md5" //nolint:all // System compatibility for imported passwords "crypto/sha1" //nolint:all // System compatibility for imported passwords "crypto/sha256" "crypto/sha512" "crypto/subtle" "encoding/base64" "encoding/hex" "fmt" "hash" "regexp" "strings" "github.com/go-crypt/crypt" "github.com/go-crypt/crypt/algorithm/md5crypt" "github.com/go-crypt/crypt/algorithm/shacrypt" "github.com/pkg/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" //nolint:staticcheck //lint:ignore SA1019 compatibility for imported passwords "golang.org/x/crypto/md4" //nolint:gosec // disable G115 G501 -- compatibility for imported passwords "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" "github.com/ory/kratos/driver/config" "github.com/ory/x/otelx" ) var ErrUnknownHashAlgorithm = errors.New("unknown hash algorithm") func NewCryptDecoder() *crypt.Decoder { decoder := crypt.NewDecoder() // The register function only returns an error if the decoder is nil or the algorithm is already registered. // This is never the case here, if it is, we did something horribly wrong. if err := md5crypt.RegisterDecoderCommon(decoder); err != nil { panic(err) } if err := shacrypt.RegisterDecoder(decoder); err != nil { panic(err) } return decoder } var CryptDecoder = NewCryptDecoder() type SupportedHasher struct { Comparator func(ctx context.Context, password, hash []byte) error Name string Is func(hash []byte) bool } func AddSupportedHasher(s SupportedHasher) { supportedHashers = append(supportedHashers, s) } var supportedHashers = []SupportedHasher{ { Comparator: CompareMD5Crypt, Name: "md5crypt", Is: IsMD5CryptHash, }, { Comparator: CompareBcrypt, Name: "bcrypt", Is: IsBcryptHash, }, { Comparator: CompareSHA256Crypt, Name: "sha256crypt", Is: IsSHA256CryptHash, }, { Comparator: CompareSHA512Crypt, Name: "sha512crypt", Is: IsSHA512CryptHash, }, { Comparator: CompareArgon2id, Name: "argon2id", Is: IsArgon2idHash, }, { Comparator: CompareArgon2i, Name: "argon2i", Is: IsArgon2iHash, }, { Comparator: ComparePbkdf2, Name: "pbkdf2", Is: IsPbkdf2Hash, }, { Comparator: CompareScrypt, Name: "scrypt", Is: IsScryptHash, }, { Comparator: CompareSSHA, Name: "ssha", Is: IsSSHAHash, }, { Comparator: CompareSHA, Name: "sha", Is: IsSHAHash, }, { Comparator: CompareFirebaseScrypt, Name: "firebasescrypt", Is: IsFirebaseScryptHash, }, { Comparator: CompareMD5, Name: "md5", Is: IsMD5Hash, }, { Comparator: CompareHMAC, Name: "hmac", Is: IsHMACHash, }, } func Compare(ctx context.Context, password, hash []byte) (err error) { ctx, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Compare") defer otelx.End(span, &err) for _, h := range supportedHashers { if h.Is(hash) { span.SetAttributes(attribute.String("hash.type", h.Name)) return h.Comparator(ctx, password, hash) } } span.SetAttributes(attribute.String("hash.type", "unknown")) return errors.WithStack(ErrUnknownHashAlgorithm) } func CompareMD5Crypt(_ context.Context, password, hash []byte) error { // the password has successfully been validated (has prefix `$md5-crypt`), // the decoder expect the module crypt identifier instead (`$1`), which means we need to replace the prefix // before decoding hash = bytes.TrimPrefix(hash, []byte("$md5-crypt")) hash = append([]byte("$1"), hash...) return compareCryptHelper(password, string(hash)) } func CompareBcrypt(ctx context.Context, password, hash []byte) error { if err := validateBcryptPasswordLength(password); err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } err := bcrypt.CompareHashAndPassword(hash, password) if err != nil { if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { return errors.WithStack(ErrMismatchedHashAndPassword) } return err } return nil } func CompareSHA256Crypt(_ context.Context, password, hash []byte) error { hash = bytes.TrimPrefix(hash, []byte("$sha256-crypt")) hash = append([]byte("$5"), hash...) return compareCryptHelper(password, string(hash)) } func CompareSHA512Crypt(_ context.Context, password, hash []byte) error { hash = bytes.TrimPrefix(hash, []byte("$sha512-crypt")) hash = append([]byte("$6"), hash...) return compareCryptHelper(password, string(hash)) } func CompareArgon2id(ctx context.Context, password, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodeArgon2idHash(string(hash)) if err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } // Derive the key from the other password using the same parameters. //nolint:gosec // disable G115 otherHash := argon2.IDKey(password, salt, p.Iterations, uint32(p.Memory), p.Parallelism, p.KeyLength) return comparePasswordHashConstantTime(hash, otherHash) } func CompareArgon2i(ctx context.Context, password, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodeArgon2idHash(string(hash)) if err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } // Derive the key from the other password using the same parameters. otherHash := argon2.Key(password, salt, p.Iterations, uint32(p.Memory), p.Parallelism, p.KeyLength) // #nosec G115 -- memory (KiB) would need to be 2^32 kibibyte to overflow uint32 return comparePasswordHashConstantTime(hash, otherHash) } func ComparePbkdf2(ctx context.Context, password, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodePbkdf2Hash(string(hash)) if err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } // Derive the key from the other password using the same parameters. otherHash := pbkdf2.Key(password, salt, int(p.Iterations), int(p.KeyLength), getPseudorandomFunctionForPbkdf2(p.Algorithm)) return comparePasswordHashConstantTime(hash, otherHash) } func CompareScrypt(ctx context.Context, password, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodeScryptHash(string(hash)) if err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } // Derive the key from the other password using the same parameters. otherHash, err := scrypt.Key(password, salt, int(p.Cost), int(p.Block), int(p.Parrellization), int(p.KeyLength)) if err != nil { return errors.WithStack(err) } return comparePasswordHashConstantTime(hash, otherHash) } func CompareSSHA(ctx context.Context, password, hash []byte) error { hasher, salt, hash, err := decodeSSHAHash(string(hash)) if err != nil { return err } raw := append(password[:], salt[:]...) // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } return CompareSHAHelper(hasher, raw, hash) } func CompareSHA(ctx context.Context, password, hash []byte) error { hasher, pf, salt, hash, err := decodeSHAHash(string(hash)) if err != nil { return err } r := strings.NewReplacer("{SALT}", string(salt), "{PASSWORD}", string(password)) raw := []byte(r.Replace(string(pf))) // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } return CompareSHAHelper(hasher, raw, hash) } func CompareFirebaseScrypt(ctx context.Context, password, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, saltSeparator, hash, signerKey, err := decodeFirebaseScryptHash(string(hash)) if err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } // Derive the key from the other password using the same parameters. // FirebaseScript algorithm implementation from https://github.com/Aoang/firebase-scrypt ck, err := scrypt.Key(password, append(salt, saltSeparator...), int(p.Cost), int(p.Block), int(p.Parrellization), 32) if err != nil { return errors.WithStack(err) } var block cipher.Block if block, err = aes.NewCipher(ck); err != nil { return errors.WithStack(err) } cipherText := make([]byte, aes.BlockSize+len(signerKey)) stream := cipher.NewCTR(block, cipherText[:aes.BlockSize]) stream.XORKeyStream(cipherText[aes.BlockSize:], signerKey) otherHash := cipherText[aes.BlockSize:] return comparePasswordHashConstantTime(hash, otherHash) } func CompareMD5(ctx context.Context, password, hash []byte) error { // Extract the hash from the encoded password pf, salt, hash, err := decodeMD5Hash(string(hash)) if err != nil { return err } arg := password if salt != nil { r := strings.NewReplacer("{SALT}", string(salt), "{PASSWORD}", string(password)) arg = []byte(r.Replace(string(pf))) } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } //#nosec G401 -- compatibility for imported passwords otherHash := md5.Sum(arg) return comparePasswordHashConstantTime(hash, otherHash[:]) } func CompareHMAC(ctx context.Context, password, hash []byte) error { // Extract the hash from the encoded password hasher, hash, key, err := decodeHMACHash(string(hash)) if err != nil { return err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return ctx.Err() } mac := hmac.New(hasher, key) _, err = mac.Write(password) if err != nil { return err } otherHash := []byte(hex.EncodeToString(mac.Sum(nil))) return comparePasswordHashConstantTime(hash, otherHash) } var ( isMD5CryptHash = regexp.MustCompile(`^\$md5-crypt\$`) isBcryptHash = regexp.MustCompile(`^\$2[abzy]?\$`) isSHA256CryptHash = regexp.MustCompile(`^\$sha256-crypt\$`) isSHA512CryptHash = regexp.MustCompile(`^\$sha512-crypt\$`) isArgon2idHash = regexp.MustCompile(`^\$argon2id\$`) isArgon2iHash = regexp.MustCompile(`^\$argon2i\$`) isPbkdf2Hash = regexp.MustCompile(`^\$pbkdf2-sha[0-9]{1,3}\$`) isScryptHash = regexp.MustCompile(`^\$scrypt\$`) isSSHAHash = regexp.MustCompile(`^{SSHA(256|512)?}.*`) isSHAHash = regexp.MustCompile(`^\$sha(1|256|512)\$`) isFirebaseScryptHash = regexp.MustCompile(`^\$firescrypt\$`) isMD5Hash = regexp.MustCompile(`^\$md5\$`) isHMACHash = regexp.MustCompile(`^\$hmac-(md4|md5|sha1|sha224|sha256|sha384|sha512)\$`) ) func IsMD5CryptHash(hash []byte) bool { return isMD5CryptHash.Match(hash) } func IsBcryptHash(hash []byte) bool { return isBcryptHash.Match(hash) } func IsSHA256CryptHash(hash []byte) bool { return isSHA256CryptHash.Match(hash) } func IsSHA512CryptHash(hash []byte) bool { return isSHA512CryptHash.Match(hash) } func IsArgon2idHash(hash []byte) bool { return isArgon2idHash.Match(hash) } func IsArgon2iHash(hash []byte) bool { return isArgon2iHash.Match(hash) } func IsPbkdf2Hash(hash []byte) bool { return isPbkdf2Hash.Match(hash) } func IsScryptHash(hash []byte) bool { return isScryptHash.Match(hash) } func IsSSHAHash(hash []byte) bool { return isSSHAHash.Match(hash) } func IsSHAHash(hash []byte) bool { return isSHAHash.Match(hash) } func IsFirebaseScryptHash(hash []byte) bool { return isFirebaseScryptHash.Match(hash) } func IsMD5Hash(hash []byte) bool { return isMD5Hash.Match(hash) } func IsHMACHash(hash []byte) bool { return isHMACHash.Match(hash) } func IsValidHashFormat(hash []byte) bool { for _, h := range supportedHashers { if h.Is(hash) { return true } } return false } func decodeArgon2idHash(encodedHash string) (p *config.Argon2, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 6 { return nil, nil, nil, ErrInvalidHash } var version int _, err = fmt.Sscanf(parts[2], "v=%d", &version) if err != nil { return nil, nil, nil, err } if version != argon2.Version { return nil, nil, nil, ErrIncompatibleVersion } p = new(config.Argon2) _, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &p.Memory, &p.Iterations, &p.Parallelism) if err != nil { return nil, nil, nil, err } salt, err = base64.RawStdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, err } p.SaltLength = uint32(len(salt)) // #nosec G115 -- salt would need to be 2^32 bytes long to overflow uint32 hash, err = base64.RawStdEncoding.Strict().DecodeString(parts[5]) if err != nil { return nil, nil, nil, err } p.KeyLength = uint32(len(hash)) // #nosec G115 -- hash would need to be 2^32 bytes long to overflow uint32 return p, salt, hash, nil } // decodePbkdf2Hash decodes PBKDF2 encoded password hash. // format: $pbkdf2-$i=,l=$$ func decodePbkdf2Hash(encodedHash string) (p *Pbkdf2, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 5 { return nil, nil, nil, ErrInvalidHash } p = new(Pbkdf2) digestParts := strings.SplitN(parts[1], "-", 2) if len(digestParts) != 2 { return nil, nil, nil, ErrInvalidHash } p.Algorithm = digestParts[1] _, err = fmt.Sscanf(parts[2], "i=%d,l=%d", &p.Iterations, &p.KeyLength) if err != nil { return nil, nil, nil, err } salt, err = base64.RawStdEncoding.Strict().DecodeString(parts[3]) if err != nil { return nil, nil, nil, err } p.SaltLength = uint32(len(salt)) // #nosec G115 -- salt would need to be 2^32 bytes long to overflow uint32 hash, err = base64.RawStdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, err } p.KeyLength = uint32(len(hash)) // #nosec G115 -- hash would need to be 2^32 bytes long to overflow uint32 return p, salt, hash, nil } // decodeScryptHash decodes Scrypt encoded password hash. // format: $scrypt$ln=,r=,p=$$ func decodeScryptHash(encodedHash string) (p *Scrypt, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 5 { return nil, nil, nil, ErrInvalidHash } p = new(Scrypt) _, err = fmt.Sscanf(parts[2], "ln=%d,r=%d,p=%d", &p.Cost, &p.Block, &p.Parrellization) if err != nil { return nil, nil, nil, err } salt, err = base64.StdEncoding.Strict().DecodeString(parts[3]) if err != nil { return nil, nil, nil, err } p.SaltLength = uint32(len(salt)) // #nosec G115 -- salt would need to be 2^32 bytes long to overflow uint32 hash, err = base64.StdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, err } p.KeyLength = uint32(len(hash)) // #nosec G115 -- hash would need to be 2^32 bytes long to overflow uint32 return p, salt, hash, nil } // decodeSHAHash decodes SHA[1|256|512] encoded password hash in custom PHC format. // format: $sha1$pf=$$ func decodeSHAHash(encodedHash string) (hasher string, pf, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 5 { return "", nil, nil, nil, ErrInvalidHash } hasher = parts[1] _, err = fmt.Sscanf(parts[2], "pf=%s", &pf) if err != nil { return "", nil, nil, nil, err } pf, err = base64.StdEncoding.Strict().DecodeString(string(pf)) if err != nil { return "", nil, nil, nil, err } salt, err = base64.StdEncoding.Strict().DecodeString(parts[3]) if err != nil { return "", nil, nil, nil, err } hash, err = base64.StdEncoding.Strict().DecodeString(parts[4]) if err != nil { return "", nil, nil, nil, err } return hasher, pf, salt, hash, nil } // CompareSHAHelper compares the raw password with the hash using the given hasher. func CompareSHAHelper(hasher string, raw []byte, hash []byte) error { var sha []byte switch hasher { case "sha1": sum := sha1.Sum(raw) //#nosec G401 -- compatibility for imported passwords sha = sum[:] case "sha256": sum := sha256.Sum256(raw) sha = sum[:] case "sha512": sum := sha512.Sum512(raw) sha = sum[:] default: return errors.WithStack(ErrMismatchedHashAndPassword) } encodedHash := []byte(base64.StdEncoding.EncodeToString(hash)) newEncodedHash := []byte(base64.StdEncoding.EncodeToString(sha)) return comparePasswordHashConstantTime(encodedHash, newEncodedHash) } func compareCryptHelper(password []byte, hash string) error { digest, err := CryptDecoder.Decode(hash) if err != nil { return err } if digest.MatchBytes(password) { return nil } return errors.WithStack(ErrMismatchedHashAndPassword) } var regexSSHA = regexp.MustCompile(`\{([^}]*)\}`) // decodeSSHAHash decodes SSHA[1|256|512] encoded password hash in usual {SSHA...} format. func decodeSSHAHash(encodedHash string) (hasher string, salt, hash []byte, err error) { match := regexSSHA.FindStringSubmatch(string(encodedHash)) var index_of_salt_begin int var index_of_hash_begin int switch match[1] { case "SSHA": hasher = "sha1" index_of_hash_begin = 6 index_of_salt_begin = 20 case "SSHA256": hasher = "sha256" index_of_hash_begin = 9 index_of_salt_begin = 32 case "SSHA512": hasher = "sha512" index_of_hash_begin = 9 index_of_salt_begin = 64 default: return "", nil, nil, ErrInvalidHash } decoded, err := base64.StdEncoding.DecodeString(string(encodedHash[index_of_hash_begin:])) if err != nil { return "", nil, nil, ErrInvalidHash } if len(decoded) < index_of_salt_begin+1 { return "", nil, nil, ErrInvalidHash } salt = decoded[index_of_salt_begin:] hash = decoded[:index_of_salt_begin] return hasher, salt, hash, nil } // decodeFirebaseScryptHash decodes Firebase Scrypt encoded password hash. // format: $firescrypt$ln=,r=,p=$$$$ func decodeFirebaseScryptHash(encodedHash string) (p *Scrypt, salt, saltSeparator, hash, signerKey []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 7 { return nil, nil, nil, nil, nil, ErrInvalidHash } p = new(Scrypt) _, err = fmt.Sscanf(parts[2], "ln=%d,r=%d,p=%d", &p.Cost, &p.Block, &p.Parrellization) if err != nil { return nil, nil, nil, nil, nil, err } // convert from firebase config "mem_cost" to // scrypt CPU/memory cost parameter, which must be a power of two greater than 1. p.Cost = 1 << p.Cost salt, err = base64.StdEncoding.Strict().DecodeString(parts[3]) if err != nil { return nil, nil, nil, nil, nil, err } p.SaltLength = uint32(len(salt)) // #nosec G115 -- salt would need to be 2^32 bytes long to overflow uint32 hash, err = base64.StdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, nil, nil, err } // Are all firebase script hashes of length 32? p.KeyLength = 32 saltSeparator, err = base64.StdEncoding.Strict().DecodeString(parts[5]) if err != nil { return nil, nil, nil, nil, nil, err } signerKey, err = base64.StdEncoding.Strict().DecodeString(parts[6]) if err != nil { return nil, nil, nil, nil, nil, err } return p, salt, saltSeparator, hash, signerKey, nil } // decodeMD5Hash decodes MD5 encoded password hash. // format without salt: $md5$ // format with salt $md5$pf=$$ func decodeMD5Hash(encodedHash string) (pf, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") switch len(parts) { case 3: hash, err := base64.StdEncoding.Strict().DecodeString(parts[2]) return nil, nil, hash, err case 5: _, err = fmt.Sscanf(parts[2], "pf=%s", &pf) if err != nil { return nil, nil, nil, err } pf, err := base64.StdEncoding.Strict().DecodeString(string(pf)) if err != nil { return nil, nil, nil, err } salt, err = base64.StdEncoding.Strict().DecodeString(parts[3]) if err != nil { return nil, nil, nil, err } hash, err = base64.StdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, err } return pf, salt, hash, nil default: return nil, nil, nil, ErrInvalidHash } } // decodeHMACHash decodes HMAC encoded password hash. // format : $hmac-$$ func decodeHMACHash(encodedHash string) (hasher func() hash.Hash, hash, key []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 4 { return nil, nil, nil, ErrInvalidHash } hashMatch := isHMACHash.FindStringSubmatch(encodedHash) if len(hashMatch) != 2 { return nil, nil, nil, errors.WithStack(ErrUnknownHashAlgorithm) } switch hashMatch[1] { case "md4": hasher = md4.New //#nosec G401 -- compatibility for imported passwords case "md5": hasher = md5.New //#nosec G401 -- compatibility for imported passwords case "sha1": hasher = sha1.New //#nosec G401 -- compatibility for imported passwords case "sha224": hasher = sha256.New224 case "sha256": hasher = sha256.New case "sha384": hasher = sha512.New384 case "sha512": hasher = sha512.New default: return nil, nil, nil, errors.WithStack(ErrUnknownHashAlgorithm) } hash, err = base64.StdEncoding.Strict().DecodeString(parts[2]) if err != nil { return nil, nil, nil, err } key, err = base64.StdEncoding.Strict().DecodeString(parts[3]) if err != nil { return nil, nil, nil, err } return hasher, hash, key, nil } func comparePasswordHashConstantTime(hash, otherHash []byte) error { // use subtle.ConstantTimeCompare() to prevent timing attacks. if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return nil } return errors.WithStack(ErrMismatchedHashAndPassword) } ================================================ FILE: hash/hasher.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash import ( "context" ) // Hasher provides methods for generating and comparing password hashes. type Hasher interface { // Generate returns a hash derived from the password or an error if the hash method failed. Generate(ctx context.Context, password []byte) ([]byte, error) // Understands returns whether the given hash can be understood by this hasher. Understands(hash []byte) bool } type HashProvider interface { Hasher(ctx context.Context) Hasher } const tracingComponent = "github.com/ory/kratos/hash" ================================================ FILE: hash/hasher_argon2.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash import ( "bytes" "context" "crypto/rand" "encoding/base64" "fmt" "github.com/inhies/go-bytesize" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "github.com/pkg/errors" "golang.org/x/crypto/argon2" "github.com/ory/kratos/driver/config" "github.com/ory/x/otelx" ) var ( ErrInvalidHash = errors.New("the encoded hash is not in the correct format") ErrIncompatibleVersion = errors.New("incompatible version of argon2") ErrMismatchedHashAndPassword = errors.New("passwords do not match") ) type Argon2 struct { c Argon2Configuration } type Argon2Configuration interface { config.Provider } func NewHasherArgon2(c Argon2Configuration) *Argon2 { return &Argon2{c: c} } func toKB(mem bytesize.ByteSize) uint32 { //nolint:gosec // disable G115 return uint32(mem / bytesize.KB) } func (h *Argon2) Generate(ctx context.Context, password []byte) (_ []byte, err error) { conf := h.c.Config().HasherArgon2(ctx) _, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Generate", trace.WithAttributes( attribute.String("hash.type", "argon2id"), attribute.String("hash.config", fmt.Sprintf("%#v", conf)), )) defer otelx.End(span, &err) salt := make([]byte, conf.SaltLength) if _, err := rand.Read(salt); err != nil { return nil, err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return nil, ctx.Err() } // Pass the plaintext password, salt and parameters to the argon2.IDKey // function. This will generate a hash of the password using the Argon2id // variant. hash := argon2.IDKey(password, salt, conf.Iterations, toKB(conf.Memory), conf.Parallelism, conf.KeyLength) var b bytes.Buffer if _, err := fmt.Fprintf( &b, "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, toKB(conf.Memory), conf.Iterations, conf.Parallelism, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash), ); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return nil, errors.WithStack(err) } return b.Bytes(), nil } func (h *Argon2) Understands(hash []byte) bool { return IsArgon2idHash(hash) } ================================================ FILE: hash/hasher_bcrypt.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash import ( "context" "fmt" "github.com/ory/kratos/text" "github.com/ory/x/otelx" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/schema" "golang.org/x/crypto/bcrypt" "github.com/ory/kratos/driver/config" ) type Bcrypt struct { c BcryptConfiguration } type BcryptConfiguration interface { config.Provider } func NewHasherBcrypt(c BcryptConfiguration) *Bcrypt { return &Bcrypt{c: c} } func (h *Bcrypt) Generate(ctx context.Context, password []byte) (_ []byte, err error) { conf := h.c.Config().HasherBcrypt(ctx) _, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Generate", trace.WithAttributes( attribute.String("hash.type", "bcrypt"), attribute.String("hash.config", fmt.Sprintf("%#v", conf)), )) defer otelx.End(span, &err) if err := validateBcryptPasswordLength(password); err != nil { return nil, err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return nil, ctx.Err() } hash, err := bcrypt.GenerateFromPassword(password, int(conf.Cost)) if err != nil { return nil, err } return hash, nil } func validateBcryptPasswordLength(password []byte) error { // Bcrypt truncates the password to the first 72 bytes, following the OpenBSD implementation, // so if password is longer than 72 bytes, function returns an error // See https://en.wikipedia.org/wiki/Bcrypt#User_input if len(password) > 72 { return schema.NewPasswordPolicyViolationError( "#/password", text.NewErrorValidationPasswordMaxLength(72, len(password)), ) } return nil } func (h *Bcrypt) Understands(hash []byte) bool { return IsBcryptHash(hash) } ================================================ FILE: hash/hasher_pbkdf2.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash import ( "bytes" "context" "crypto/rand" "crypto/sha1" //#nosec G505 -- compatibility for imported passwords "crypto/sha256" "crypto/sha512" "encoding/base64" "fmt" "hash" "github.com/pkg/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/sha3" "github.com/ory/x/otelx" ) type Pbkdf2 struct { Algorithm string Iterations uint32 SaltLength uint32 KeyLength uint32 } func (h *Pbkdf2) Generate(ctx context.Context, password []byte) (_ []byte, err error) { _, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Generate", trace.WithAttributes( attribute.String("hash.type", "pbkdf2"), attribute.String("hash.config", fmt.Sprintf("%#v", h)), )) defer otelx.End(span, &err) salt := make([]byte, h.SaltLength) if _, err := rand.Read(salt); err != nil { return nil, err } // ensure that the context is not canceled before doing the heavy lifting if ctx.Err() != nil { return nil, ctx.Err() } key := pbkdf2.Key(password, salt, int(h.Iterations), int(h.KeyLength), getPseudorandomFunctionForPbkdf2(h.Algorithm)) var b bytes.Buffer if _, err := fmt.Fprintf( &b, "$pbkdf2-%s$i=%d,l=%d$%s$%s", h.Algorithm, h.Iterations, h.KeyLength, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(key), ); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return nil, errors.WithStack(err) } return b.Bytes(), nil } func (h *Pbkdf2) Understands(hash []byte) bool { return IsPbkdf2Hash(hash) } func getPseudorandomFunctionForPbkdf2(alg string) func() hash.Hash { switch alg { case "sha1": return sha1.New case "sha224": return sha3.New224 case "sha256": return sha256.New case "sha384": return sha3.New384 case "sha512": return sha512.New default: return sha256.New } } ================================================ FILE: hash/hasher_scrypt.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash type Scrypt struct { Cost uint32 Block uint32 Parrellization uint32 SaltLength uint32 KeyLength uint32 } ================================================ FILE: hash/hasher_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hash_test import ( "crypto/rand" "encoding/base64" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/hash" "github.com/ory/kratos/pkg" ) func mkpw(t *testing.T, length int) []byte { pw := make([]byte, length) _, err := rand.Read(pw) require.NoError(t, err) return pw } func TestArgonHasher(t *testing.T) { t.Parallel() ctx := t.Context() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) h := hash.NewHasherArgon2(reg) for _, pwLength := range []int{ 8, 16, 32, 64, 128, } { pwLength := pwLength t.Run(fmt.Sprintf("length=%dchars", pwLength), func(t *testing.T) { t.Parallel() pw := mkpw(t, pwLength) hs, err := h.Generate(ctx, pw) require.NoError(t, err) assert.NotEqual(t, pw, hs) t.Logf("hash: %s", hs) require.NoError(t, hash.CompareArgon2id(ctx, pw, hs)) mod := make([]byte, len(pw)) copy(mod, pw) mod[len(pw)-1] = ^pw[len(pw)-1] require.Error(t, hash.CompareArgon2id(ctx, mod, hs)) }) } } func TestBcryptHasherGeneratesErrorWhenPasswordIsLong(t *testing.T) { t.Parallel() ctx := t.Context() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) hasher := hash.NewHasherBcrypt(reg) password := mkpw(t, 73) res, err := hasher.Generate(ctx, password) assert.Error(t, err, "password is too long") assert.Nil(t, res) } func TestBcryptHasherGeneratesHash(t *testing.T) { t.Parallel() ctx := t.Context() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) hasher := hash.NewHasherBcrypt(reg) for _, pwLength := range []int{ 8, 16, 32, 64, 72, } { pwLength := pwLength t.Run(fmt.Sprintf("length=%dchars", pwLength), func(t *testing.T) { t.Parallel() pw := mkpw(t, pwLength) hs, err := hasher.Generate(ctx, pw) assert.Nil(t, err) assert.True(t, hasher.Understands(hs)) // Valid format: $2a$12$[22 character salt][31 character hash] assert.Equal(t, 60, len(string(hs)), "invalid bcrypt hash length") assert.Equal(t, "$2a$12$", string(hs)[:7], "invalid bcrypt identifier") }) } } func TestComparatorBcryptFailsWhenPasswordIsTooLong(t *testing.T) { t.Parallel() ctx := t.Context() password := mkpw(t, 73) err := hash.CompareBcrypt(ctx, password, []byte("hash")) assert.Error(t, err, "password is too long") } func TestComparatorBcryptSuccess(t *testing.T) { t.Parallel() ctx := t.Context() _, reg := pkg.NewVeryFastRegistryWithoutDB(t) hasher := hash.NewHasherBcrypt(reg) for _, pwLength := range []int{ 8, 16, 32, 64, 72, } { pwLength := pwLength t.Run(fmt.Sprintf("length=%dchars", pwLength), func(t *testing.T) { t.Parallel() pw := mkpw(t, pwLength) hs, err := hasher.Generate(ctx, pw) assert.Nil(t, err) assert.True(t, hasher.Understands(hs)) err = hash.CompareBcrypt(ctx, pw, hs) assert.Nil(t, err, "hash validation fails") }) } } func TestComparatorBcryptFail(t *testing.T) { t.Parallel() ctx := t.Context() for _, pwLength := range []int{ 8, 16, 32, 64, 72, } { pwLength := pwLength t.Run(fmt.Sprintf("length=%dchars", pwLength), func(t *testing.T) { t.Parallel() pw := mkpw(t, pwLength) mod := make([]byte, len(pw)) copy(mod, pw) mod[len(pw)-1] = ^pw[len(pw)-1] err := hash.CompareBcrypt(ctx, pw, mod) assert.Error(t, err) }) } } func TestPbkdf2Hasher(t *testing.T) { t.Parallel() ctx := t.Context() for _, pwLength := range []int{ 8, 16, 32, 64, 128, } { pwLength := pwLength t.Run(fmt.Sprintf("length=%dchars", pwLength), func(t *testing.T) { t.Parallel() for _, algorithm := range []string{ "sha1", "sha224", "sha256", "sha384", "sha512", } { algorithm := algorithm t.Run(fmt.Sprintf("algorithm=%s", algorithm), func(t *testing.T) { t.Parallel() hasher := &hash.Pbkdf2{ Algorithm: algorithm, Iterations: 100_000, SaltLength: 32, KeyLength: 32, } pw := mkpw(t, pwLength) t.Logf("%d", pwLength) hs, err := hasher.Generate(ctx, pw) require.NoError(t, err) assert.NotEqual(t, pw, hs) t.Logf("hash: %s", hs) require.NoError(t, hash.ComparePbkdf2(ctx, pw, hs)) assert.True(t, hasher.Understands(hs)) mod := make([]byte, len(pw)) copy(mod, pw) mod[len(pw)-1] = ^pw[len(pw)-1] require.Error(t, hash.ComparePbkdf2(ctx, mod, hs)) }) } }) } } func TestCompare(t *testing.T) { t.Parallel() ctx := t.Context() t.Run("unknown", func(t *testing.T) { t.Parallel() assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$unknown$12$o6hx.Wog/wvFSkT/Bp/6DOxCtLRTDj7lm9on9suF/WaCGNVHbkfL6"))) }) t.Run("bcrypt", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$2a$12$o6hx.Wog/wvFSkT/Bp/6DOxCtLRTDj7lm9on9suF/WaCGNVHbkfL6"))) assert.Nil(t, hash.CompareBcrypt(ctx, []byte("test"), []byte("$2a$12$o6hx.Wog/wvFSkT/Bp/6DOxCtLRTDj7lm9on9suF/WaCGNVHbkfL6"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$2a$12$o6hx.Wog/wvFSkT/Bp/6DOxCtLRTDj7lm9on9suF/WaCGNVHbkfL7"))) assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$2a$15$GRvRO2nrpYTEuPQX6AieaOlZ4.7nMGsXpt.QWMev1zrP86JNspZbO"))) assert.Nil(t, hash.CompareBcrypt(ctx, []byte("test"), []byte("$2a$15$GRvRO2nrpYTEuPQX6AieaOlZ4.7nMGsXpt.QWMev1zrP86JNspZbO"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$2a$15$GRvRO2nrpYTEuPQX6AieaOlZ4.7nMGsXpt.QWMev1zrP86JNspZb1"))) }) t.Run("Argon2", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"))) assert.Nil(t, hash.CompareArgon2id(ctx, []byte("test"), []byte("$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRN2"))) assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$argon2i$v=19$m=65536,t=3,p=4$kk51rW/vxIVCYn+EG4kTSg$NyT88uraJ6im6dyha/M5jhXvpqlEdlS/9fEm7ScMb8c"))) assert.Nil(t, hash.CompareArgon2i(ctx, []byte("test"), []byte("$argon2i$v=19$m=65536,t=3,p=4$kk51rW/vxIVCYn+EG4kTSg$NyT88uraJ6im6dyha/M5jhXvpqlEdlS/9fEm7ScMb8c"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$argon2i$v=19$m=65536,t=3,p=4$pZ+27D6B0bCi0DwSmANF1w$4RNCUu4Uyu7eTIvzIdSuKz+I9idJlX/ykn6J10/W0EU"))) assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$argon2id$v=19$m=32,t=5,p=4$cm94YnRVOW5jZzFzcVE4bQ$fBxypOL0nP/zdPE71JtAV71i487LbX3fJI5PoTN6Lp4"))) assert.Nil(t, hash.CompareArgon2id(ctx, []byte("test"), []byte("$argon2id$v=19$m=32,t=5,p=4$cm94YnRVOW5jZzFzcVE4bQ$fBxypOL0nP/zdPE71JtAV71i487LbX3fJI5PoTN6Lp4"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$argon2id$v=19$m=32,t=5,p=4$cm94YnRVOW5jZzFzcVE4bQ$fBxypOL0nP/zdPE71JtAV71i487LbX3fJI5PoTN6Lp5"))) }) t.Run("pbkdf2", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha256$i=100000,l=32$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpI"))) assert.Nil(t, hash.ComparePbkdf2(ctx, []byte("test"), []byte("$pbkdf2-sha256$i=100000,l=32$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpI"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha256$i=100000,l=32$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpp"))) assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha512$i=100000,l=32$bdHBpn7OWOivJMVJypy2UqR0UnaD5prQXRZevj/05YU$+wArTfv1a+bNGO1iZrmEdVjhA+lL11wF4/IxpgYfPwc"))) assert.Nil(t, hash.ComparePbkdf2(ctx, []byte("test"), []byte("$pbkdf2-sha512$i=100000,l=32$bdHBpn7OWOivJMVJypy2UqR0UnaD5prQXRZevj/05YU$+wArTfv1a+bNGO1iZrmEdVjhA+lL11wF4/IxpgYfPwc"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha512$i=100000,l=32$bdHBpn7OWOivJMVJypy2UqR0UnaD5prQXRZevj/05YU$+wArTfv1a+bNGO1iZrmEdVjhA+lL11wF4/IxpgYfPww"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha256$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpI"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha256$aaaa$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpI"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha256$i=100000,l=32$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXcc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpI"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha256$i=100000,l=32$1jP+5Zxpxgtee/iPxGgOz0RfE9/KJuDElP1ley4VxXc$QJxzfvdbHYBpydCbHoFg3GJEqMFULwskiuqiJctoYpII"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$pbkdf2-sha512$I=100000,l=32$bdHBpn7OWOivJMVJypy2UqR0UnaD5prQXRZevj/05YU$+wArTfv1a+bNGO1iZrmEdVjhA+lL11wF4/IxpgYfPwc"))) }) t.Run("scrypt", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$scrypt$ln=16384,r=8,p=1$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Nil(t, hash.CompareScrypt(ctx, []byte("test"), []byte("$scrypt$ln=16384,r=8,p=1$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$scrypt$ln=16384,r=8,p=1$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYF="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$scrypt$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$scrypt$ln=16384,r=8,p=1$(2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$scrypt$ln=16384,r=8,p=1$(2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$(MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$scrypt$ln=16385,r=8,p=1$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Error(t, hash.Compare(ctx, []byte("tesu"), []byte("$scrypt$ln=16384,r=8,p=1$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) assert.Error(t, hash.Compare(ctx, []byte("tesu"), []byte("$scrypt$ln=abc,r=8,p=1$2npRo7P03Mt8keSoMbyD/tKFWyUzjiQf2svUaNDSrhA=$MiCzNcIplSMqSBrm4HckjYqYhaVPPjTARTzwB1cVNYE="))) }) t.Run("firescrypt", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("8x4WjoDbSxJZdR"), []byte("$firescrypt$ln=14,r=8,p=1$sPtDhWcd1MfdAw==$xbSou7FOl6mChCyzpCPIQ7tku7nsQMTFtyOZSXXd7tjBa4NtimOx7v42Gv2SfzPQu1oxM2/k4SsbOu73wlKe1A==$Bw==$YE0dO4bwD4JnJafh6lZZfkp1MtKzuKAXQcDCJNJNyeCHairWHKENOkbh3dzwaCdizzOspwr/FITUVlnOAwPKyw=="))) assert.Nil(t, hash.CompareFirebaseScrypt(ctx, []byte("8x4WjoDbSxJZdR"), []byte("$firescrypt$ln=14,r=8,p=1$sPtDhWcd1MfdAw==$xbSou7FOl6mChCyzpCPIQ7tku7nsQMTFtyOZSXXd7tjBa4NtimOx7v42Gv2SfzPQu1oxM2/k4SsbOu73wlKe1A==$Bw==$YE0dO4bwD4JnJafh6lZZfkp1MtKzuKAXQcDCJNJNyeCHairWHKENOkbh3dzwaCdizzOspwr/FITUVlnOAwPKyw=="))) assert.Error(t, hash.Compare(ctx, []byte("8x4WjoDbSxJZdR"), []byte("$firescrypt$ln=14,r=8,p=1$sPtDhWcd1MfdAw==$xbSou7FOl6mChCyzpCPIQ7tku7nsQMTFtyOZSXXd7tjBa4NtimOx7v42Gv2SfzPQu1oxM2/k4SsbOu73wlKe1A==$Bw==$YE0dO4bwD4JnJafh6lZZfkp1MtKzuKAXQcDCJNJNyeCHairWHKENOkbh3dzwaCdizzOspwr/FITUVlnOAwPKyc=="))) }) t.Run("SSHA", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test123"), []byte("{SSHA}JFZFs0oHzxbMwkSJmYVeI8MnTDy/276a"))) assert.Nil(t, hash.CompareSSHA(ctx, []byte("test123"), []byte("{SSHA}JFZFs0oHzxbMwkSJmYVeI8MnTDy/276a"))) assert.Error(t, hash.CompareSSHA(ctx, []byte("badtest"), []byte("{SSHA}JFZFs0oHzxbMwkSJmYVeI8MnTDy/276a"))) assert.Error(t, hash.Compare(ctx, []byte(""), []byte("{SSHA}tooshort"))) assert.Nil(t, hash.Compare(ctx, []byte("test123"), []byte("{SSHA256}czO44OTV17PcF1cRxWrLZLy9xHd7CWyVYplr1rOhuMlx/7IK"))) assert.Nil(t, hash.CompareSSHA(ctx, []byte("test123"), []byte("{SSHA256}czO44OTV17PcF1cRxWrLZLy9xHd7CWyVYplr1rOhuMlx/7IK"))) assert.Error(t, hash.CompareSSHA(ctx, []byte("badtest"), []byte("{SSHA256}czO44OTV17PcF1cRxWrLZLy9xHd7CWyVYplr1rOhuMlx/7IK"))) assert.Nil(t, hash.Compare(ctx, []byte("test123"), []byte("{SSHA512}xPUl/px+1cG55rUH4rzcwxdOIPSB2TingLpiJJumN2xyDWN4Ix1WQG3ihnvHaWUE8MYNkvMi5rf0C9NYixHsE6Yh59M="))) assert.Nil(t, hash.CompareSSHA(ctx, []byte("test123"), []byte("{SSHA512}xPUl/px+1cG55rUH4rzcwxdOIPSB2TingLpiJJumN2xyDWN4Ix1WQG3ihnvHaWUE8MYNkvMi5rf0C9NYixHsE6Yh59M="))) assert.Error(t, hash.CompareSSHA(ctx, []byte("badtest"), []byte("{SSHA512}xPUl/px+1cG55rUH4rzcwxdOIPSB2TingLpiJJumN2xyDWN4Ix1WQG3ihnvHaWUE8MYNkvMi5rf0C9NYixHsE6Yh59M="))) assert.Error(t, hash.CompareSSHA(ctx, []byte("test123"), []byte("{SSHAnotExistent}xPUl/px+1cG55rUH4rzcwxdOIPSB2TingLpiJJumN2xyDWN4Ix1WQG3ihnvHaWUE8MYNkvMi5rf0C9NYixHsE6Yh59M="))) }) t.Run("sha1", func(t *testing.T) { t.Parallel() //pf: {SALT}{PASSWORD} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) assert.Error(t, hash.Compare(ctx, []byte("wrongpass"), []byte("$sha1$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) assert.Error(t, hash.Compare(ctx, []byte("tset"), []byte("$sha1$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) // wrong salt assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=e1NBTFR9e1BBU1NXT1JEfQ==$cDJvb3ZrZGJ6cQ==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) // salt not encoded assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=e1NBTFR9e1BBU1NXT1JEfQ==$5opmkgz03r$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) assert.Nil(t, hash.Compare(ctx, []byte("BwS^514g^cv@Z"), []byte("$sha1$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$99h9net4BXl7qdTRaiGUobLROxM="))) // no format string assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=$NW9wbWtnejAzcg==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$$NW9wbWtnejAzcg==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) // wrong number of parameters assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$NW9wbWtnejAzcg==$2qU2SGWP8viTM1md3FiI3+rjWXQ="))) // pf: ??staticPrefix??{SALT}{PASSWORD} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=Pz9zdGF0aWNQcmVmaXg/P3tTQUxUfXtQQVNTV09SRH0=$NW9wbWtnejAzcg==$SAAxMUn7jxckQXkBmsVF0nHwqso="))) // pf: {PASSWORD}%%{SALT} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=e1BBU1NXT1JEfSUle1NBTFR9$NW9wbWtnejAzcg==$YX0AW8/MW5ojUlnzTaR43ucHCog="))) // pf: ${PASSWORD}${SALT}$ assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha1$pf=JHtQQVNTV09SRH0ke1NBTFR9JA==$NW9wbWtnejAzcg==$iE5n1yjX3oAdxRHwZ4u57I4LpQo="))) }) t.Run("sha256", func(t *testing.T) { t.Parallel() //pf: {SALT}{PASSWORD} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha256$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$0gfRVLCvtBCk20udLDEY5vNhujWx7RGjwRIS1ebMsLY="))) assert.Nil(t, hash.CompareSHA(ctx, []byte("test"), []byte("$sha256$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$0gfRVLCvtBCk20udLDEY5vNhujWx7RGjwRIS1ebMsLY="))) assert.Error(t, hash.Compare(ctx, []byte("wrongpass"), []byte("$sha256$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$0gfRVLCvtBCk20udLDEY5vNhujWx7RGjwRIS1ebMsLY="))) //pf: {SALT}$${PASSWORD} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha256$pf=e1NBTFR9JCR7UEFTU1dPUkR9$NW9wbWtnejAzcg==$HokCOi9OtiZaZRvnkgemV3B4UUHpI7kA8zq/EZWH2NY="))) }) t.Run("sha512", func(t *testing.T) { t.Parallel() //pf: {SALT}{PASSWORD} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha512$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$6ctpVuApMNp0CgBXcdHw/GC562eFEFGr4gpgANX8ZYsX+j5B19IkdmOY2Fytsz3QUwSWdGcUjbqwgJGTH0UYvw=="))) assert.Nil(t, hash.CompareSHA(ctx, []byte("test"), []byte("$sha512$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$6ctpVuApMNp0CgBXcdHw/GC562eFEFGr4gpgANX8ZYsX+j5B19IkdmOY2Fytsz3QUwSWdGcUjbqwgJGTH0UYvw=="))) assert.Error(t, hash.Compare(ctx, []byte("wrongpass"), []byte("$sha512$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$6ctpVuApMNp0CgBXcdHw/GC562eFEFGr4gpgANX8ZYsX+j5B19IkdmOY2Fytsz3QUwSWdGcUjbqwgJGTH0UYvw=="))) //pf: {SALT}$${PASSWORD} assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha512$pf=e1NBTFR9JCR7UEFTU1dPUkR9$NW9wbWtnejAzcg==$1F9BPW8UtdJkZ9Dhlf+D4X4dJ9xfuH8y04EfuCP2k4aGPPq/aWxU9/xe3LydHmYW1/K3zu3NFO9ETVrZettz3w=="))) }) t.Run("sha unknown", func(t *testing.T) { t.Parallel() assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$shaNotExistent$pf=e1NBTFR9e1BBU1NXT1JEfQ==$NW9wbWtnejAzcg==$6ctpVuApMNp0CgBXcdHw/GC562eFEFGr4gpgANX8ZYsX+j5B19IkdmOY2Fytsz3QUwSWdGcUjbqwgJGTH0UYvw=="))) }) t.Run("md5", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$md5$CY9rzUYh03PK3k6DJie09g=="))) assert.Nil(t, hash.CompareMD5(ctx, []byte("test"), []byte("$md5$CY9rzUYh03PK3k6DJie09g=="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$md5$WhBei51A4TKXgNYuoiZdig=="))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$md5$Dk/E5LQLsx4yt8QbUbvpdg=="))) assert.Nil(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$ptoWyof5SobW+pbZu2QXoQ=="))) assert.Nil(t, hash.CompareMD5(ctx, []byte("ory"), []byte("$md5$ptoWyof5SobW+pbZu2QXoQ=="))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$4skj967KRHFsnPFoL5dMMw=="))) assert.ErrorIs(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$$")), hash.ErrInvalidHash) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$$$"))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$pf=$$"))) assert.ErrorIs(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$pf=MTIz$Z$")), base64.CorruptInputError(0)) assert.ErrorIs(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$pf=MTIz$Z$")), base64.CorruptInputError(0)) assert.ErrorIs(t, hash.Compare(ctx, []byte("ory"), []byte("$md5$pf=MTIz$MTIz$Z")), base64.CorruptInputError(0)) assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$md5$pf=e1NBTFR9e1BBU1NXT1JEfQ==$MTIz$q+RdKCgc+ipCAcm5ChQwlQ=="))) // pf={SALT}{PASSWORD} salt=123 assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$md5$pf=e1NBTFR9e1BBU1NXT1JEfQ==$MTIz$hh8ZTp1hGPPZQqcr4+UXSQ=="))) assert.Nil(t, hash.CompareMD5(ctx, []byte("test"), []byte("$md5$pf=e1NBTFR9JCR7UEFTU1dPUkR9$MTIzNA==$ud392Z8rfZ+Ou7ZFXYLKbA=="))) // pf={SALT}$${PASSWORD} salt=1234 assert.Error(t, hash.CompareMD5(ctx, []byte("test1"), []byte("$md5$pf=e1NBTFR9JCR7UEFTU1dPUkR9$MTIzNA==$ud392Z8rfZ+Ou7ZFXYLKbA=="))) assert.Nil(t, hash.CompareMD5(ctx, []byte("ory"), []byte("$md5$pf=e1BBU1NXT1JEfXtTQUxUfSQ/$MTIzNDU2Nzg5$8PhwWanVRnpJAFK4NUjR0w=="))) // pf={PASSWORD}{SALT}$? salt=123456789 assert.Error(t, hash.CompareMD5(ctx, []byte("ory1"), []byte("$md5$pf=e1BBU1NXT1JEfXtTQUxUfSQ/$MTIzNDU2Nzg5$8PhwWanVRnpJAFK4NUjR0w=="))) }) t.Run("md5-crypt", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$md5-crypt$TVEiiKNb$SN6/pUaRQS/E8Jh46As2C/"))) assert.Nil(t, hash.CompareMD5Crypt(ctx, []byte("test"), []byte("$md5-crypt$TVEiiKNb$SN6/pUaRQS/E8Jh46As2C/"))) assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$md5-crypt$$whuMjZj.HMFoaTaZRRtkO0"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$md5-crypt$xWMlm2eL$GGTOpgZu4p2k6ORprAu3b."))) assert.Nil(t, hash.Compare(ctx, []byte("ory"), []byte("$md5-crypt$xWMlm2eL$GGTOpgZu4p2k6ORprAu3b."))) assert.Nil(t, hash.CompareMD5Crypt(ctx, []byte("ory"), []byte("$md5-crypt$xWMlm2eL$GGTOpgZu4p2k6ORprAu3b."))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$md5-crypt$E7zjruqF$RTglYR1CzBHwwiTk9nVzx1"))) assert.ErrorIs(t, hash.Compare(ctx, []byte("ory"), []byte("$md5-crypt$$")), hash.ErrMismatchedHashAndPassword) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$md5-crypt$$$"))) // per crypt(5), `md5crypt` can be run without a salt, but the salt section must still be present assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$md5-crypt$whuMjZj.HMFoaTaZRRtkO0")), "md5crypt decode error: provided encoded hash has an invalid format") }) t.Run("sha256-crypt", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha256-crypt$rounds=535000$05R.9KB6UC2kLI3w$Q/zslzx./JjkAVPTwp6th7nW5l7JU91Gte/UmIh.U78"))) assert.Nil(t, hash.CompareSHA256Crypt(ctx, []byte("test"), []byte("$sha256-crypt$rounds=535000$05R.9KB6UC2kLI3w$Q/zslzx./JjkAVPTwp6th7nW5l7JU91Gte/UmIh.U78"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$sha256-crypt$rounds=535000$awpcR7lDlnK/S7WE$vHU7KkQwyjfGz6u4MUi7.lH9htK/l63HloTsX1ZMz.3"))) assert.Nil(t, hash.Compare(ctx, []byte("ory"), []byte("$sha256-crypt$rounds=535000$awpcR7lDlnK/S7WE$vHU7KkQwyjfGz6u4MUi7.lH9htK/l63HloTsX1ZMz.3"))) assert.Nil(t, hash.CompareSHA256Crypt(ctx, []byte("ory"), []byte("$sha256-crypt$rounds=535000$awpcR7lDlnK/S7WE$vHU7KkQwyjfGz6u4MUi7.lH9htK/l63HloTsX1ZMz.3"))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$sha256-crypt$rounds=535000$T95kH8e37IGVdxzJ$gLeaNa6qRog.bx4Bzqp63ceWItH6nSAal6c3WmT5GHB"))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$sha256-crypt$$")), "shacrypt decode error: provided encoded hash has an invalid format") assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$sha256-crypt$$$"))) }) t.Run("sha512-crypt", func(t *testing.T) { t.Parallel() assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$sha512-crypt$rounds=656000$3LVbIAVxR//cRajw$uuNasMW.RYxlGzIRFU1Was70BPSa933AjxhZIGJdJBOlqJAHlgqa0yuiuq5JHF/ryNGryJkj87G9i3G2HPSXg1"))) assert.Nil(t, hash.CompareSHA512Crypt(ctx, []byte("test"), []byte("$sha512-crypt$rounds=656000$3LVbIAVxR//cRajw$uuNasMW.RYxlGzIRFU1Was70BPSa933AjxhZIGJdJBOlqJAHlgqa0yuiuq5JHF/ryNGryJkj87G9i3G2HPSXg1"))) assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$5$rounds=535000$awpcR7lDlnK/S7WE$vHU7KkQwyjfGz6u4MUi7.lH9htK/l63HloTsX1ZMz.3"))) assert.Nil(t, hash.Compare(ctx, []byte("ory"), []byte("$sha512-crypt$rounds=656000$0baQbxBrfpKqvizk$Q9cYk1MeNAlECPgpG3jjfNI2DumLqd0yHbxzLdxiX6nsSD5i9n0awcbiCf8R5DzpIYxeBPznPcb1wtzlgUKtH0"))) assert.Nil(t, hash.CompareSHA512Crypt(ctx, []byte("ory"), []byte("$sha512-crypt$rounds=656000$0baQbxBrfpKqvizk$Q9cYk1MeNAlECPgpG3jjfNI2DumLqd0yHbxzLdxiX6nsSD5i9n0awcbiCf8R5DzpIYxeBPznPcb1wtzlgUKtH0"))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$sha512-crypt$rounds=656000$hNcDLFO63bkYVDZf$Mt9dhH0xqfxWZ6Pu8zXw.Ku5f15IRTweuaDcUc.ObXWGn7B1h8YIWLmArZd8psd2mrUVswCXLAVptmISr.8iI/"))) assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$sha512-crypt$$")), "shacrypt decode error: provided encoded hash has an invalid format") assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$sha512-crypt$$$"))) }) t.Run("hmac errors", func(t *testing.T) { t.Parallel() //Missing Key assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=")), hash.ErrInvalidHash) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk="))) assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$")), hash.ErrMismatchedHashAndPassword) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$"))) //Missing Password Hash assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$MTIzNDU=")), hash.ErrInvalidHash) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md5$MTIzNDU="))) assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$$MTIzNDU=")), hash.ErrMismatchedHashAndPassword) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md5$$MTIzNDU="))) //Missing Password Hash and Key assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$")), hash.ErrInvalidHash) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md5$"))) //Missing Hash Algorithm assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU=")), hash.ErrUnknownHashAlgorithm) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU="))) //Missing Invalid Hash Algorithm assert.ErrorIs(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-invalid$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU=")), hash.ErrUnknownHashAlgorithm) assert.Error(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-invalid$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU="))) }) t.Run("hmac-md4", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md4$MWQ5ZTI4Nzc2Zjg4YmE2MTQ5YjQ0OTMyOGE4NWU4YjA=$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md4$MWQ5ZTI4Nzc2Zjg4YmE2MTQ5YjQ0OTMyOGE4NWU4YjA=$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md4$MWQ5ZTI4Nzc2Zjg4YmE2MTQ5YjQ0OTMyOGE4NWU4YjA=$MTIzNA==")), hash.ErrMismatchedHashAndPassword) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-md4$MWQ5ZTI4Nzc2Zjg4YmE2MTQ5YjQ0OTMyOGE4NWU4YjA=$MTIzNDU="))) }) t.Run("hmac-md5", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNA=="))) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-md5$ZmU4Njk3Zjc0MmQwODA0MDVkMTI3MGU2MTYzMzE2Zjk=$MTIzNDU="))) }) t.Run("hmac-sha1", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha1$NDMyNjcxZTUyY2Y2YTBmYjZjZDE2NjQxYjAwNjFiZjAwOGEzNWM5MA==$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-sha1$NDMyNjcxZTUyY2Y2YTBmYjZjZDE2NjQxYjAwNjFiZjAwOGEzNWM5MA==$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha1$NDMyNjcxZTUyY2Y2YTBmYjZjZDE2NjQxYjAwNjFiZjAwOGEzNWM5MA==$MTIzNA=="))) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-sha1$NDMyNjcxZTUyY2Y2YTBmYjZjZDE2NjQxYjAwNjFiZjAwOGEzNWM5MA==$MTIzNDU="))) }) t.Run("hmac-sha224", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha224$YmUwYmYzM2EwNGRlNDE0YjQzNjBhNmIyOThmNmIyYzI4OWQyMzk3MDUwZDFjMzliYjVmMDMyOTQ=$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-sha224$YmUwYmYzM2EwNGRlNDE0YjQzNjBhNmIyOThmNmIyYzI4OWQyMzk3MDUwZDFjMzliYjVmMDMyOTQ=$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha224$YmUwYmYzM2EwNGRlNDE0YjQzNjBhNmIyOThmNmIyYzI4OWQyMzk3MDUwZDFjMzliYjVmMDMyOTQ=$MTIzNA=="))) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-sha224$YmUwYmYzM2EwNGRlNDE0YjQzNjBhNmIyOThmNmIyYzI4OWQyMzk3MDUwZDFjMzliYjVmMDMyOTQ=$MTIzNDU="))) }) t.Run("hmac-sha256", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha256$ZTAzMWJhMWMyOTM4YjFkMjgzZjkxOWExZGY5YWM2NmMxOTJhN2RkNzQ0MzJkNWZkNGFkYTI5OTk0MWJhMTA5Zg==$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-sha256$ZTAzMWJhMWMyOTM4YjFkMjgzZjkxOWExZGY5YWM2NmMxOTJhN2RkNzQ0MzJkNWZkNGFkYTI5OTk0MWJhMTA5Zg==$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha256$ZTAzMWJhMWMyOTM4YjFkMjgzZjkxOWExZGY5YWM2NmMxOTJhN2RkNzQ0MzJkNWZkNGFkYTI5OTk0MWJhMTA5Zg==$MTIzNA=="))) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-sha256$ZTAzMWJhMWMyOTM4YjFkMjgzZjkxOWExZGY5YWM2NmMxOTJhN2RkNzQ0MzJkNWZkNGFkYTI5OTk0MWJhMTA5Zg==$MTIzNDU="))) }) t.Run("hmac-sha384", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha384$ZWEyMGM3NGE4Y2UzMTljNTdjZTlhZGQyYTZjNDE0MGQ4YjMwYWIwOWM4OTRiNWQ4MmZjODlhMzBhMmQzNGE5NmQ0NDY1NWRhYjQ2ZjhiYjBkNTRmYjk5YWZkZTA1MGY1$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-sha384$ZWEyMGM3NGE4Y2UzMTljNTdjZTlhZGQyYTZjNDE0MGQ4YjMwYWIwOWM4OTRiNWQ4MmZjODlhMzBhMmQzNGE5NmQ0NDY1NWRhYjQ2ZjhiYjBkNTRmYjk5YWZkZTA1MGY1$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha384$ZWEyMGM3NGE4Y2UzMTljNTdjZTlhZGQyYTZjNDE0MGQ4YjMwYWIwOWM4OTRiNWQ4MmZjODlhMzBhMmQzNGE5NmQ0NDY1NWRhYjQ2ZjhiYjBkNTRmYjk5YWZkZTA1MGY1$MTIzNA=="))) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-sha384$ZWEyMGM3NGE4Y2UzMTljNTdjZTlhZGQyYTZjNDE0MGQ4YjMwYWIwOWM4OTRiNWQ4MmZjODlhMzBhMmQzNGE5NmQ0NDY1NWRhYjQ2ZjhiYjBkNTRmYjk5YWZkZTA1MGY1$MTIzNDU="))) }) t.Run("hmac-sha512", func(t *testing.T) { t.Parallel() //Valid assert.Nil(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha512$OTFmODY0ZTI1NmU0ZjVhYjhiMDViZGFmNGVmNGZmMGVlNTY4ODYwNWJhYTk4MTk2OTgyMzc3NzI1YTc4MzcxMTMzNzZmY2YxYTk5MGMxM2RiZDk2MGFmMmQ1YzRmODdlMGMwYTNkYjcyNjY0NjM4NGE4YzQ2MjNhZDZkN2UxZTE=$MTIzNDU="))) assert.Nil(t, hash.CompareHMAC(ctx, []byte("test"), []byte("$hmac-sha512$OTFmODY0ZTI1NmU0ZjVhYjhiMDViZGFmNGVmNGZmMGVlNTY4ODYwNWJhYTk4MTk2OTgyMzc3NzI1YTc4MzcxMTMzNzZmY2YxYTk5MGMxM2RiZDk2MGFmMmQ1YzRmODdlMGMwYTNkYjcyNjY0NjM4NGE4YzQ2MjNhZDZkN2UxZTE=$MTIzNDU="))) //Wrong Key assert.Error(t, hash.Compare(ctx, []byte("test"), []byte("$hmac-sha512$OTFmODY0ZTI1NmU0ZjVhYjhiMDViZGFmNGVmNGZmMGVlNTY4ODYwNWJhYTk4MTk2OTgyMzc3NzI1YTc4MzcxMTMzNzZmY2YxYTk5MGMxM2RiZDk2MGFmMmQ1YzRmODdlMGMwYTNkYjcyNjY0NjM4NGE4YzQ2MjNhZDZkN2UxZTE=$MTIzNA=="))) //Different password assert.Error(t, hash.Compare(ctx, []byte("ory"), []byte("$hmac-sha512$OTFmODY0ZTI1NmU0ZjVhYjhiMDViZGFmNGVmNGZmMGVlNTY4ODYwNWJhYTk4MTk2OTgyMzc3NzI1YTc4MzcxMTMzNzZmY2YxYTk5MGMxM2RiZDk2MGFmMmQ1YzRmODdlMGMwYTNkYjcyNjY0NjM4NGE4YzQ2MjNhZDZkN2UxZTE=$MTIzNDU="))) }) } ================================================ FILE: hydra/fake.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hydra import ( "context" "errors" "github.com/ory/herodot" hydraclientgo "github.com/ory/hydra-client-go/v2" ) const ( FakeInvalidLoginChallenge = "2e98454e-031b-4870-9ad6-8517df1ce604" FakeValidLoginChallenge = "5ff59a39-ecc5-467e-bb10-26644c0700ee" FakePostLoginURL = "https://www.example.com/fake-post-login" ) var ErrFakeAcceptLoginRequestFailed = errors.New("failed to accept login request") type FakeHydra struct { Skip bool RequestURL string } var _ Hydra = &FakeHydra{} func NewFake() *FakeHydra { return &FakeHydra{ RequestURL: "https://www.ory.sh", } } func (h *FakeHydra) AcceptLoginRequest(_ context.Context, params AcceptLoginRequestParams) (string, error) { if params.SessionID == "" { return "", errors.New("session id must not be empty") } switch params.LoginChallenge { case FakeInvalidLoginChallenge: return "", ErrFakeAcceptLoginRequestFailed case FakeValidLoginChallenge: return FakePostLoginURL, nil default: panic("unknown fake login_challenge " + params.LoginChallenge) } } func (h *FakeHydra) GetLoginRequest(_ context.Context, loginChallenge string) (*hydraclientgo.OAuth2LoginRequest, error) { switch loginChallenge { case FakeInvalidLoginChallenge: return nil, herodot.ErrBadRequest.WithReasonf("Unable to get OAuth 2.0 Login Challenge.") case FakeValidLoginChallenge: return &hydraclientgo.OAuth2LoginRequest{ RequestUrl: h.RequestURL, Skip: h.Skip, }, nil default: panic("unknown fake login_challenge " + loginChallenge) } } ================================================ FILE: hydra/hydra.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hydra import ( "context" "net/http" "time" "github.com/ory/x/httpx" "github.com/ory/x/sqlxx" "github.com/pkg/errors" "github.com/ory/herodot" hydraclientgo "github.com/ory/hydra-client-go/v2" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/session" ) type ( hydraDependencies interface { config.Provider httpx.ClientProvider } Provider interface { Hydra() Hydra } AcceptLoginRequestParams struct { LoginChallenge string IdentityID string SessionID string AuthenticationMethods session.AuthenticationMethods } Hydra interface { AcceptLoginRequest(ctx context.Context, params AcceptLoginRequestParams) (string, error) GetLoginRequest(ctx context.Context, loginChallenge string) (*hydraclientgo.OAuth2LoginRequest, error) } DefaultHydra struct { d hydraDependencies } ) func NewDefaultHydra(d hydraDependencies) *DefaultHydra { return &DefaultHydra{ d: d, } } func GetLoginChallengeID(conf *config.Config, r *http.Request) (sqlxx.NullString, error) { if !r.URL.Query().Has("login_challenge") { return "", nil } else if conf.OAuth2ProviderURL(r.Context()) == nil { return "", errors.WithStack(herodot.ErrMisconfiguration.WithReason("refusing to parse login_challenge query parameter because " + config.ViperKeyOAuth2ProviderURL + " is invalid or unset")) } loginChallenge := r.URL.Query().Get("login_challenge") if loginChallenge == "" { return "", errors.WithStack(herodot.ErrBadRequest.WithReason("the login_challenge parameter is present but empty")) } return sqlxx.NullString(loginChallenge), nil } func (h *DefaultHydra) getAdminURL(ctx context.Context) (string, error) { u := h.d.Config().OAuth2ProviderURL(ctx) if u == nil { return "", errors.WithStack(herodot.ErrMisconfiguration.WithReason(config.ViperKeyOAuth2ProviderURL + " is not configured")) } return u.String(), nil } func (h *DefaultHydra) getAdminAPIClient(ctx context.Context) (hydraclientgo.OAuth2API, error) { url, err := h.getAdminURL(ctx) if err != nil { return nil, err } configuration := hydraclientgo.NewConfiguration() configuration.Servers = hydraclientgo.ServerConfigurations{{URL: url}} client := h.d.HTTPClient(ctx).StandardClient() if header := h.d.Config().OAuth2ProviderHeader(ctx); header != nil { client.Transport = httpx.WrapTransportWithHeader(client.Transport, header) } configuration.HTTPClient = client return hydraclientgo.NewAPIClient(configuration).OAuth2API, nil } func (h *DefaultHydra) AcceptLoginRequest(ctx context.Context, params AcceptLoginRequestParams) (string, error) { remember := h.d.Config().SessionPersistentCookie(ctx) rememberFor := int64(h.d.Config().SessionLifespan(ctx) / time.Second) alr := hydraclientgo.NewAcceptOAuth2LoginRequest(params.IdentityID) alr.IdentityProviderSessionId = ¶ms.SessionID alr.Remember = &remember alr.RememberFor = &rememberFor alr.Amr = []string{} for _, r := range params.AuthenticationMethods { alr.Amr = append(alr.Amr, string(r.Method)) } aa, err := h.getAdminAPIClient(ctx) if err != nil { return "", err } resp, r, err := aa.AcceptOAuth2LoginRequest(ctx).LoginChallenge(params.LoginChallenge).AcceptOAuth2LoginRequest(*alr).Execute() if err != nil { innerErr := herodot.ErrInternalServerError.WithWrap(err).WithReasonf("Unable to accept OAuth 2.0 Login Challenge.") if r != nil { innerErr = innerErr. WithDetail("status_code", r.StatusCode). WithDebug(err.Error()) } if openApiErr := new(hydraclientgo.GenericOpenAPIError); errors.As(err, &openApiErr) { switch oauth2Err := openApiErr.Model().(type) { case hydraclientgo.ErrorOAuth2: innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint()) case *hydraclientgo.ErrorOAuth2: innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint()) } } return "", errors.WithStack(innerErr) } return resp.RedirectTo, nil } func (h *DefaultHydra) GetLoginRequest(ctx context.Context, loginChallenge string) (*hydraclientgo.OAuth2LoginRequest, error) { if loginChallenge == "" { return nil, errors.WithStack(herodot.ErrBadRequest.WithReason("invalid login_challenge")) } aa, err := h.getAdminAPIClient(ctx) if err != nil { return nil, err } hlr, r, err := aa.GetOAuth2LoginRequest(ctx).LoginChallenge(loginChallenge).Execute() if err != nil { var innerErr *herodot.DefaultError if r == nil || r.StatusCode >= 500 { innerErr = &herodot.ErrInternalServerError } else { innerErr = &herodot.ErrBadRequest } innerErr = innerErr.WithReasonf("Unable to get OAuth 2.0 Login Challenge.") if r != nil { innerErr = innerErr. WithDetail("status_code", r.StatusCode). WithDebug(err.Error()) } if openApiErr := new(hydraclientgo.GenericOpenAPIError); errors.As(err, &openApiErr) { switch oauth2Err := openApiErr.Model().(type) { case hydraclientgo.ErrorOAuth2: innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint()) case *hydraclientgo.ErrorOAuth2: innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint()) } } return nil, errors.WithStack(innerErr) } return hlr, nil } ================================================ FILE: hydra/hydra_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hydra_test import ( "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hydra" "github.com/ory/x/configx" "github.com/ory/x/contextx" "github.com/ory/x/logrusx" "github.com/ory/x/sqlxx" "github.com/ory/x/urlx" ) func requestFromChallenge(s string) *http.Request { return &http.Request{URL: urlx.ParseOrPanic("https://hydra?login_challenge=" + s)} } func TestGetLoginChallengeID(t *testing.T) { uuidChallenge := "b346a452-e8fb-4828-8ef8-a4dbc98dc23a" blobChallenge := "1337deadbeefcafe" defaultConfig := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation()) configWithHydra := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation(), configx.WithValues(map[string]interface{}{ config.ViperKeyOAuth2ProviderURL: "https://hydra", })) type args struct { conf *config.Config r *http.Request } tests := []struct { name string args args want string assertErr assert.ErrorAssertionFunc }{ { name: "no login challenge; hydra is not configured", args: args{ conf: defaultConfig, r: &http.Request{URL: urlx.ParseOrPanic("https://hydra")}, }, want: "", assertErr: assert.NoError, }, { name: "no login challenge; hydra is configured", args: args{ conf: configWithHydra, r: &http.Request{URL: urlx.ParseOrPanic("https://hydra")}, }, want: "", assertErr: assert.NoError, }, { name: "empty login challenge; hydra is configured", args: args{ conf: configWithHydra, r: requestFromChallenge(""), }, want: "", assertErr: assert.Error, }, { name: "login_challenge is present; Hydra is not configured", args: args{ conf: defaultConfig, r: requestFromChallenge(uuidChallenge), }, want: "", assertErr: assert.Error, }, { name: "login_challenge is present; hydra is configured", args: args{ conf: configWithHydra, r: requestFromChallenge(uuidChallenge), }, want: uuidChallenge, assertErr: assert.NoError, }, { name: "login_challenge is present & non-uuid; hydra is configured", args: args{ conf: configWithHydra, r: requestFromChallenge(blobChallenge), }, want: blobChallenge, assertErr: assert.NoError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := hydra.GetLoginChallengeID(tt.args.conf, tt.args.r) tt.assertErr(t, err) assert.Equal(t, sqlxx.NullString(tt.want), got) }) } } ================================================ FILE: identity/.snapshots/TestHandler-case=PATCH_should_allow_to_update_credential_password-endpoint=admin.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "c6eaf1da-4c4e-5da0-aa91-77299464d869@ory.sh" ], "config": { "hashed_password": "foo", "some-random-key": " some-random-value" }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "c6eaf1da-4c4e-5da0-aa91-77299464d869@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=PATCH_should_allow_to_update_credential_password-endpoint=public.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "c6eaf1da-4c4e-5da0-aa91-77299464d869@ory.sh" ], "config": { "hashed_password": "foo", "some-random-key": " some-random-value" }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "c6eaf1da-4c4e-5da0-aa91-77299464d869@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=PATCH_should_allow_to_update_credential_password.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "c6eaf1da-4c4e-5da0-aa91-77299464d869@ory.sh" ], "config": { "hashed_password": "secret", "some-random-key": " some-random-value" }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "c6eaf1da-4c4e-5da0-aa91-77299464d869@ory.sh" }, "metadata_public": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_cleartext_password_and_oidc_credentials.json ================================================ { "credentials": { "oidc": { "type": "oidc", "config": { "providers": [ { "subject": "import-2", "provider": "google", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "import-2", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0 }, "password": { "type": "password", "identifiers": [ "import-2@ory.sh" ], "config": { }, "version": 0 }, "saml": { "type": "saml", "identifiers": [ "okta:import-saml-2", "onelogin:import-saml-2" ], "config": { "providers": [ { "subject": "import-saml-2", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "import-saml-2", "provider": "onelogin", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-2@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=SSHA.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-6@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-6@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=SSHA256.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-7@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-7@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=SSHA512.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-8@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-8@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=argon2i.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-2@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-2@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=argon2id.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-3@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-3@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=bcrypt2.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-1@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-1@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=hmac.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-9@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-9@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=md5.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-5@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-5@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=pkbdf2.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-0@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-0@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_hashed_passwords-hash=scrypt.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-hash-4@ory.sh" ], "config": { }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-hash-4@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_not-normalized_email.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "uppercased@ory.sh" ], "config": {}, "version": 0 } }, "schema_id": "customer", "state": "active", "traits": { "email": "UpperCased@ory.sh" }, "verifiable_addresses": [ { "value": "uppercased@ory.sh", "verified": true, "via": "email", "status": "completed" } ], "recovery_addresses": [ { "value": "uppercased@ory.sh", "via": "email" } ], "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_organization_oidc_and_saml_credentials.json ================================================ { "credentials": { "oidc": { "type": "oidc", "config": { "providers": [ { "subject": "import-org-3", "provider": "google", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "ad6a7dac-4eef-4f09-8e58-c099c14b6c36" }, { "subject": "import-org-3", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "ad6a7dac-4eef-4f09-8e58-c099c14b6c36" } ] }, "version": 0 }, "password": { "type": "password", "identifiers": [ "import-3@ory.sh" ], "config": {}, "version": 0 }, "saml": { "type": "saml", "identifiers": [ "okta:import-saml-org-3", "onelogin:import-saml-org-3" ], "config": { "providers": [ { "subject": "import-saml-org-3", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "ad6a7dac-4eef-4f09-8e58-c099c14b6c36" }, { "subject": "import-saml-org-3", "provider": "onelogin", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "ad6a7dac-4eef-4f09-8e58-c099c14b6c36" } ] }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-3@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-with_password_migration_hook_enabled.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "pw-migration-hook@ory.sh" ], "config": { "use_password_migration_hook": true }, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "pw-migration-hook@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-without_any_credentials.json ================================================ { "credentials": { "password": { "type": "password", "identifiers": [ "import-1@ory.sh" ], "config": {}, "version": 0 } }, "schema_id": "default", "state": "active", "traits": { "email": "import-1@ory.sh" }, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_be_able_to_import_users-without_traits.json ================================================ { "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_and_multiple_fido_mfa_type-admin.json ================================================ { "credentials": { "webauthn": { "type": "webauthn", "identifiers": [], "config": { "credentials": [ { "public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=", "attestation_type": "none", "authenticator": { "aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09", "sign_count": 0, "clone_warning": false }, "display_name": "test", "added_at": "2022-12-16T14:11:55Z", "is_passwordless": true }, { "public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=", "attestation_type": "none", "authenticator": { "aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09", "sign_count": 0, "clone_warning": false }, "display_name": "test", "added_at": "2022-12-16T14:11:55Z", "is_passwordless": true } ], "user_handle": "RWY1SmlNcE1Sd3V6YXVXcy85SjBnUT09" }, "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_and_multiple_fido_mfa_type-public.json ================================================ { "credentials": { "webauthn": { "type": "webauthn", "identifiers": [], "config": { "credentials": [ { "public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=", "attestation_type": "none", "authenticator": { "aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09", "sign_count": 0, "clone_warning": false }, "display_name": "test", "added_at": "2022-12-16T14:11:55Z", "is_passwordless": true }, { "public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=", "attestation_type": "none", "authenticator": { "aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09", "sign_count": 0, "clone_warning": false }, "display_name": "test", "added_at": "2022-12-16T14:11:55Z", "is_passwordless": true } ], "user_handle": "RWY1SmlNcE1Sd3V6YXVXcy85SjBnUT09" }, "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_type-admin.json ================================================ { "credentials": { "webauthn": { "type": "webauthn", "identifiers": [], "config": { "credentials": [ { "added_at": "2022-12-16T14:11:55Z", "public_key": "pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU=", "display_name": "test", "authenticator": { "aaguid": "rc4AAjW8xgpkiwsl8fBVAw==", "sign_count": 0, "clone_warning": false }, "is_passwordless": true, "attestation_type": "none" } ], "user_handle": "Ef5JiMpMRwuzauWs/9J0gQ==" }, "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_delete_credential_of_a_specific_user_and_no_longer_be_able_to_retrieve_it-type=remove_webauthn_passwordless_type-public.json ================================================ { "credentials": { "webauthn": { "type": "webauthn", "identifiers": [], "config": { "credentials": [ { "added_at": "2022-12-16T14:11:55Z", "public_key": "pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU=", "display_name": "test", "authenticator": { "aaguid": "rc4AAjW8xgpkiwsl8fBVAw==", "sign_count": 0, "clone_warning": false }, "is_passwordless": true, "attestation_type": "none" } ], "user_handle": "Ef5JiMpMRwuzauWs/9J0gQ==" }, "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-case=should_list_all_identities_with_credentials-include_credential=oidc_should_include_OIDC_credentials_config.json ================================================ "{\"providers\":[{\"initial_id_token\":\"id_token0\",\"initial_access_token\":\"access_token0\",\"initial_refresh_token\":\"refresh_token0\",\"subject\":\"foo\",\"provider\":\"bar\"},{\"initial_id_token\":\"id_token1\",\"initial_access_token\":\"access_token1\",\"initial_refresh_token\":\"refresh_token1\",\"subject\":\"baz\",\"provider\":\"zab\"}]}" ================================================ FILE: identity/.snapshots/TestHandler-suite=PATCH_identities-case=success-assert=identity_0.json ================================================ { "credentials": { "password": { "type": "password", "version": 0 } }, "external_id": "external-id-Batch-Import-0", "metadata_admin": { "admin-0": "admin" }, "metadata_public": { "public-0": "public" }, "organization_id": null, "schema_id": "multiple_emails", "state": "active", "traits": { "emails": [ "Batch-Import-0-0@ory.sh", "Batch-Import-0-1@ory.sh", "Batch-Import-0-2@ory.sh", "Batch-Import-0-3@ory.sh" ], "username": "Batch-Import-0-0@ory.sh" } } ================================================ FILE: identity/.snapshots/TestHandler-suite=PATCH_identities-case=success-assert=identity_1.json ================================================ { "credentials": { "password": { "type": "password", "version": 0 } }, "metadata_admin": { "admin-1": "admin" }, "metadata_public": { "public-1": "public" }, "organization_id": null, "schema_id": "multiple_emails", "state": "active", "traits": { "emails": [ "batch-import-1-0@ory.sh", "batch-import-1-1@ory.sh", "batch-import-1-2@ory.sh", "batch-import-1-3@ory.sh" ], "username": "batch-import-1-0@ory.sh" } } ================================================ FILE: identity/.snapshots/TestHandler-suite=PATCH_identities-case=success-assert=identity_2.json ================================================ { "credentials": { "password": { "type": "password", "version": 0 } }, "external_id": "external-id-batch-import-2", "metadata_admin": { "admin-2": "admin" }, "metadata_public": { "public-2": "public" }, "organization_id": null, "schema_id": "multiple_emails", "state": "active", "traits": { "emails": [ "batch-import-2-0@ory.sh", "batch-import-2-1@ory.sh", "batch-import-2-2@ory.sh", "batch-import-2-3@ory.sh" ], "username": "batch-import-2-0@ory.sh" } } ================================================ FILE: identity/.snapshots/TestHandler-suite=PATCH_identities-case=success-assert=identity_3.json ================================================ { "credentials": { "password": { "type": "password", "version": 0 } }, "metadata_admin": { "admin-3": "admin" }, "metadata_public": { "public-3": "public" }, "organization_id": null, "schema_id": "multiple_emails", "state": "active", "traits": { "emails": [ "batch-import-3-0@ory.sh", "batch-import-3-1@ory.sh", "batch-import-3-2@ory.sh", "batch-import-3-3@ory.sh" ], "username": "batch-import-3-0@ory.sh" } } ================================================ FILE: identity/.snapshots/TestHandler-suite=create_and_update-case=should_get_identity_with_credentials-case=should_get_identity_with_password_and_webauthn_credentials_included.json ================================================ { "credentials": { "oidc": { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0 }, "password": { "type": "password", "identifiers": [ "bar", "zab" ], "config": { "some": "secret" }, "version": 0 }, "webauthn": { "type": "webauthn", "identifiers": [ "bar", "foo" ], "config": { "some": "secret", "user_handle": "rVIFaWRcTTuQLkXFmQWpgA==" }, "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-suite=create_and_update-case=should_get_identity_with_credentials-case=should_get_identity_with_password_credentials_included.json ================================================ { "credentials": { "oidc": { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0 }, "password": { "type": "password", "identifiers": [ "bar", "zab" ], "config": { "some": "secret" }, "version": 0 }, "webauthn": { "type": "webauthn", "identifiers": [ "bar", "foo" ], "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestHandler-suite=create_and_update-case=should_get_identity_with_credentials-case=should_get_identity_without_credentials_included.json ================================================ { "credentials": { "oidc": { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0 }, "password": { "type": "password", "identifiers": [ "bar", "zab" ], "version": 0 }, "webauthn": { "type": "webauthn", "identifiers": [ "bar", "foo" ], "version": 1 } }, "schema_id": "default", "state": "active", "traits": {}, "metadata_public": null, "metadata_admin": null, "organization_id": null } ================================================ FILE: identity/.snapshots/TestImportCredentials-OIDC_new_credential_with_organization.json ================================================ { "type": "oidc", "identifiers": [ "github:12345" ], "config": { "providers": [ { "subject": "12345", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "e7e3cbae-04cc-45f3-ae52-ea749a2ffaff" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-OIDC_new_credential_without_organization.json ================================================ { "type": "oidc", "identifiers": [ "github:12345" ], "config": { "providers": [ { "subject": "12345", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-OIDC_update_credential_with_organization.json ================================================ { "type": "oidc", "identifiers": [ "google:67890", "github:12345" ], "config": { "providers": [ { "subject": "67890", "provider": "google", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "12345", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "e7e3cbae-04cc-45f3-ae52-ea749a2ffaff" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-OIDC_update_credential_without_organization.json ================================================ { "type": "oidc", "identifiers": [ "google:67890", "github:12345" ], "config": { "providers": [ { "subject": "67890", "provider": "google", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "12345", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-OIDC_update_with_multiple_providers.json ================================================ { "type": "oidc", "identifiers": [ "google:67890", "github:12345", "gitlab:abcdef" ], "config": { "providers": [ { "subject": "67890", "provider": "google", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "12345", "provider": "github", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "e7e3cbae-04cc-45f3-ae52-ea749a2ffaff" }, { "subject": "abcdef", "provider": "gitlab", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-SAML_new_credential_with_organization.json ================================================ { "type": "saml", "identifiers": [ "okta:user123" ], "config": { "providers": [ { "subject": "user123", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "e7e3cbae-04cc-45f3-ae52-ea749a2ffaff" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-SAML_new_credential_without_organization.json ================================================ { "type": "saml", "identifiers": [ "okta:user123" ], "config": { "providers": [ { "subject": "user123", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-SAML_update_credential_with_organization.json ================================================ { "type": "saml", "identifiers": [ "onelogin:user456", "okta:user123" ], "config": { "providers": [ { "subject": "user456", "provider": "onelogin", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "user123", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "e7e3cbae-04cc-45f3-ae52-ea749a2ffaff" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-SAML_update_credential_without_organization.json ================================================ { "type": "saml", "identifiers": [ "onelogin:user456", "okta:user123" ], "config": { "providers": [ { "subject": "user456", "provider": "onelogin", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "user123", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestImportCredentials-SAML_update_with_multiple_providers.json ================================================ { "type": "saml", "identifiers": [ "onelogin:user456", "okta:user123", "auth0:user789" ], "config": { "providers": [ { "subject": "user456", "provider": "onelogin", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" }, { "subject": "user123", "provider": "okta", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "", "organization": "e7e3cbae-04cc-45f3-ae52-ea749a2ffaff" }, { "subject": "user789", "provider": "auth0", "initial_id_token": "", "initial_access_token": "", "initial_refresh_token": "" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestMarshalIdentityWithAll.json ================================================ { "password": { "type": "password", "identifiers": null, "config": { "some": "secret" }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=0.json ================================================ { "type": "password", "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=1.json ================================================ { "type": "password", "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=10.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "sms", "address": "+4917667111638" }, { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=11.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "sms", "address": "+4917667111638" }, { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=12.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "sms", "address": "+4917667111638" }, { "channel": "email", "address": "bar@ory.sh" }, { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=13.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "email", "address": "bar@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=2.json ================================================ { "type": "webauthn", "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=3.json ================================================ { "type": "password", "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=4.json ================================================ { "type": "webauthn", "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=5.json ================================================ { "type": "webauthn", "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=6.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=7.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=8.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestSchemaExtensionCredentials-case=9.json ================================================ { "type": "code", "config": { "addresses": [ { "channel": "sms", "address": "+4917667111638" }, { "channel": "email", "address": "foo@ory.sh" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestToNode.json ================================================ { "type": "text", "group": "lookup_secret", "attributes": { "text": { "id": 1050015, "text": "used, bar, baz, used", "type": "info", "context": { "secrets": [ { "id": 1050014, "text": "Secret was used at 2021-08-17 11:32:38 +0000 UTC", "type": "info", "context": { "used_at": "2021-08-17T11:32:38Z", "used_at_unix": 1629199958 } }, { "id": 1050009, "text": "bar", "type": "info", "context": { "secret": "bar" } }, { "id": 1050009, "text": "baz", "type": "info", "context": { "secret": "baz" } }, { "id": 1050014, "text": "Secret was used at 2021-08-17 11:32:48 +0000 UTC", "type": "info", "context": { "used_at": "2021-08-17T11:32:48Z", "used_at_unix": 1629199968 } } ] } }, "id": "lookup_secret_codes", "node_type": "text" }, "messages": [], "meta": { "label": { "id": 1050010, "text": "These are your back up recovery codes. Please keep them in a safe place!", "type": "info" } } } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-empty_credentials.json ================================================ { "id": "00000000-0000-0000-0000-000000000000", "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=code-from=v0_with_correct_value.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "code": { "type": "code", "identifiers": [ "hi@example.org" ], "config": { "addresses": [ { "channel": "email", "address": "hi@example.org" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=code-from=v0_with_email_empty_space_value-with_one_identifier.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "code": { "type": "code", "identifiers": [ "hi@example.org" ], "config": { "addresses": [ { "channel": "email", "address": "hi@example.org" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=code-from=v0_with_email_empty_space_value-with_two_identifiers.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "code": { "type": "code", "identifiers": [ "foo@example.org", "bar@example.org" ], "config": { "addresses": [ { "channel": "email", "address": "foo@example.org" }, { "channel": "email", "address": "bar@example.org" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=code-from=v0_with_empty_value.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "code": { "type": "code", "identifiers": [ "hi@example.org" ], "config": { "addresses": [ { "channel": "email", "address": "hi@example.org" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=code-from=v0_with_unknown_value.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "code": { "type": "code", "identifiers": [ "hi@example.org" ], "config": { "addresses": [ { "channel": "email", "address": "hi@example.org" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=code-from=v2_with_empty_value.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "code": { "type": "code", "identifiers": [ "foo@example.org", "+12341234" ], "config": { "addresses": [ { "address": "foo@example.org", "channel": "email" }, { "address": "+12341234", "channel": "sms" } ] }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=webauthn-from=v0.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "webauthn": { "type": "webauthn", "identifiers": [ "4d64fa08-20fc-450d-bebd-ebd7c7b6e249" ], "config": { "credentials": [ { "id": "HQ4LaIJ9NiqS1r0CQpWY+K0gMvhOq4yk5BHuO/YlitcurSpBK7weDXOvBcuN4lvn6DAmjGfmj/J/6bpOmtdT8Q==", "public_key": "pQECAyYgASFYILAYFLoH1T8bQMSbPrNBCMMS5U7OFWRwv2U+GkAoiBADIlggBv+8ni7XVZYBB8ufMbP/d9fDxbmOkVVHOgcJifnoOR4=", "attestation_type": "none", "authenticator": { "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==", "sign_count": 4, "clone_warning": false }, "display_name": "asdf", "added_at": "2022-02-28T16:40:39Z", "is_passwordless": false }, { "id": "1Q4LaIJ9NiqS1r0CQpWY+K0gMvhOq4yk5BHuO/YlitcurSpBK7weDXOvBcuN4lvn6DAmjGfmj/J/6bpOmtdT8Q==", "public_key": "pQECAyYgASFYILAYFLoH1T8bQMSbPrNBCMMS5U7OFWRwv2U+GkAoiBADIlggBv+8ni7XVZYBB8ufMbP/d9fDxbmOkVVHOgcJifnoOR4=", "attestation_type": "none", "authenticator": { "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==", "sign_count": 4, "clone_warning": false }, "display_name": "asdf", "added_at": "2022-02-28T16:40:39Z", "is_passwordless": false } ], "user_handle": "TWT6CCD8RQ2+vevXx7biSQ==" }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestUpgradeCredentials-type=webauthn-from=v1.json ================================================ { "id": "4d64fa08-20fc-450d-bebd-ebd7c7b6e249", "credentials": { "webauthn": { "type": "webauthn", "identifiers": [], "config": { "credentials": [ { "id": "HQ4LaIJ9NiqS1r0CQpWY+K0gMvhOq4yk5BHuO/YlitcurSpBK7weDXOvBcuN4lvn6DAmjGfmj/J/6bpOmtdT8Q==", "public_key": "pQECAyYgASFYILAYFLoH1T8bQMSbPrNBCMMS5U7OFWRwv2U+GkAoiBADIlggBv+8ni7XVZYBB8ufMbP/d9fDxbmOkVVHOgcJifnoOR4=", "attestation_type": "none", "authenticator": { "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==", "sign_count": 4, "clone_warning": false }, "display_name": "asdf", "added_at": "2022-02-28T16:40:39Z", "is_passwordless": true } ], "user_handle": "2gZaSs9fTEeGmsBlC4gfgg==" }, "version": 1, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } }, "schema_id": "", "schema_url": "", "state": "", "traits": null, "metadata_public": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z", "organization_id": null } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-multi-credential=oidc.json ================================================ { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-multi-credential=password.json ================================================ { "type": "password", "identifiers": [ "zab", "bar" ], "config": { "some": "secret" }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-multi-credential=saml.json ================================================ { "type": "saml", "identifiers": [ "qux", "quz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-multi-credential=webauthn.json ================================================ { "type": "webauthn", "identifiers": [ "foo", "bar" ], "config": { "some": "secret" }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-webauthn-credential=oidc.json ================================================ { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-webauthn-credential=password.json ================================================ { "type": "password", "identifiers": [ "zab", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-webauthn-credential=saml.json ================================================ { "type": "saml", "identifiers": [ "qux", "quz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=include-webauthn-credential=webauthn.json ================================================ { "type": "webauthn", "identifiers": [ "foo", "bar" ], "config": { "some": "secret" }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=no-include-credential=oidc.json ================================================ { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=no-include-credential=password.json ================================================ { "type": "password", "identifiers": [ "zab", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=no-include-credential=saml.json ================================================ { "type": "saml", "identifiers": [ "qux", "quz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=no-include-credential=webauthn.json ================================================ { "type": "webauthn", "identifiers": [ "foo", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=oidc-credential=oidc.json ================================================ { "type": "oidc", "identifiers": [ "bar", "baz" ], "config": { "providers": [ { "initial_id_token": "foo", "initial_access_token": "", "initial_refresh_token": "", "subject": "bar", "provider": "oidc1" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=oidc-credential=password.json ================================================ { "type": "password", "identifiers": [ "zab", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=oidc-credential=saml.json ================================================ { "type": "saml", "identifiers": [ "qux", "quz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=oidc-credential=webauthn.json ================================================ { "type": "webauthn", "identifiers": [ "foo", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=saml-credential=oidc.json ================================================ { "type": "oidc", "identifiers": [ "bar", "baz" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=saml-credential=password.json ================================================ { "type": "password", "identifiers": [ "zab", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=saml-credential=saml.json ================================================ { "type": "saml", "identifiers": [ "qux", "quz" ], "config": { "providers": [ { "subject": "qux", "provider": "saml1" } ] }, "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/.snapshots/TestWithDeclassifiedCredentials-case=saml-credential=webauthn.json ================================================ { "type": "webauthn", "identifiers": [ "foo", "bar" ], "version": 0, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ================================================ FILE: identity/address.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity const ( AddressTypeEmail = "email" AddressTypeSMS = "sms" ) ================================================ FILE: identity/credentials.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "database/sql" "encoding/json" "fmt" "reflect" "slices" "strings" "time" "github.com/gofrs/uuid" "github.com/wI2L/jsondiff" "github.com/ory/kratos/ui/node" "github.com/ory/x/sqlxx" ) // In nearly all cases, credential types have a constant id. // To make it potentially faster, and not need any data in the database, // we define this constant map. In 99.999% of the cases, a look up in this map is a hit and it's fast. // In the unlikely event that this is a miss, e.g. for very old self-hosted deployments from a time where // credential type ids used to be dynamic, we do a database look-up. var ConstantCredentialsTypeToId = map[CredentialsType]string{ CredentialsTypePassword: "78c1b41d-8341-4507-aa60-aff1d4369670", // gitleaks:allow CredentialsTypeOIDC: "6fa5e2e0-bfce-4631-b62b-cf2b0252b289", // gitleaks:allow CredentialsTypeTOTP: "5e29b036-aa47-457f-9fe6-aa8b854a752b", // gitleaks:allow CredentialsTypeLookup: "567a0730-7f48-4dd7-a13d-df87a51c245f", // gitleaks:allow CredentialsTypeWebAuthn: "6b213fa0-e6ad-46cb-8878-b088d2ce2e3c", // gitleaks:allow CredentialsTypeCodeAuth: "14f3b7e2-8725-4068-be39-8a796485fd97", // gitleaks:allow CredentialsTypePasskey: "8d0ca544-9bf6-45d3-bd75-0bbb3aeba3c7", // gitleaks:allow CredentialsTypeSAML: "7bddcf6c-f50e-4a18-9b0f-429114c33419", // gitleaks:allow } // Authenticator Assurance Level (AAL) // // The authenticator assurance level can be one of "aal1", "aal2", or "aal3". A higher number means that it is harder // for an attacker to compromise the account. // // Generally, "aal1" implies that one authentication factor was used while AAL2 implies that two factors (e.g. // password + TOTP) have been used. // // To learn more about these levels please head over to: https://www.ory.sh/kratos/docs/concepts/credentials // // swagger:model authenticatorAssuranceLevel type AuthenticatorAssuranceLevel string const ( NoAuthenticatorAssuranceLevel AuthenticatorAssuranceLevel = "aal0" AuthenticatorAssuranceLevel1 AuthenticatorAssuranceLevel = "aal1" AuthenticatorAssuranceLevel2 AuthenticatorAssuranceLevel = "aal2" ) type NullableAuthenticatorAssuranceLevel struct { sql.NullString } // NewNullableAuthenticatorAssuranceLevel returns a new NullableAuthenticatorAssuranceLevel func NewNullableAuthenticatorAssuranceLevel(aal AuthenticatorAssuranceLevel) NullableAuthenticatorAssuranceLevel { switch aal { case NoAuthenticatorAssuranceLevel: fallthrough case AuthenticatorAssuranceLevel1: fallthrough case AuthenticatorAssuranceLevel2: return NullableAuthenticatorAssuranceLevel{sql.NullString{ String: string(aal), Valid: true, }} default: return NullableAuthenticatorAssuranceLevel{sql.NullString{}} } } // ToAAL returns the AuthenticatorAssuranceLevel value of the given NullableAuthenticatorAssuranceLevel. func (n NullableAuthenticatorAssuranceLevel) ToAAL() (AuthenticatorAssuranceLevel, bool) { if !n.Valid { return "", false } switch n.String { case string(NoAuthenticatorAssuranceLevel): return NoAuthenticatorAssuranceLevel, true case string(AuthenticatorAssuranceLevel1): return AuthenticatorAssuranceLevel1, true case string(AuthenticatorAssuranceLevel2): return AuthenticatorAssuranceLevel2, true default: return "", false } } // CredentialsType represents several different credential types, like password credentials, passwordless credentials, // and so on. // // swagger:enum CredentialsType type CredentialsType string // Please make sure to add all of these values to the test that ensures they are created during migration const ( CredentialsTypePassword CredentialsType = "password" CredentialsTypeOIDC CredentialsType = "oidc" CredentialsTypeTOTP CredentialsType = "totp" CredentialsTypeLookup CredentialsType = "lookup_secret" CredentialsTypeWebAuthn CredentialsType = "webauthn" CredentialsTypeCodeAuth CredentialsType = "code" CredentialsTypePasskey CredentialsType = "passkey" CredentialsTypeProfile CredentialsType = "profile" CredentialsTypeSAML CredentialsType = "saml" ) func (c CredentialsType) String() string { return string(c) } func (c CredentialsType) ToUiNodeGroup() node.UiNodeGroup { switch c { case CredentialsTypePassword: return node.PasswordGroup case CredentialsTypeOIDC, CredentialsTypeSAML: return node.OpenIDConnectGroup case CredentialsTypeTOTP: return node.TOTPGroup case CredentialsTypeWebAuthn: return node.WebAuthnGroup case CredentialsTypeLookup: return node.LookupGroup case CredentialsTypeCodeAuth: return node.CodeGroup case CredentialsTypePasskey: return node.PasskeyGroup default: return node.DefaultGroup } } var AllCredentialTypes = []CredentialsType{ CredentialsTypePassword, CredentialsTypeOIDC, // CredentialsTypeSAML, placeholder for the OEL version CredentialsTypeTOTP, CredentialsTypeLookup, CredentialsTypeWebAuthn, CredentialsTypeCodeAuth, CredentialsTypePasskey, } const ( // CredentialsTypeRecoveryLink is a special credential type linked to the link strategy (recovery flow). // It is not used within the credentials object itself. CredentialsTypeRecoveryLink CredentialsType = "link_recovery" CredentialsTypeRecoveryCode CredentialsType = "code_recovery" ) // ParseCredentialsType parses a string into a CredentialsType or returns false as the second argument. func ParseCredentialsType(in string) (CredentialsType, bool) { switch t := CredentialsType(in); t { case CredentialsTypePassword, CredentialsTypeOIDC, CredentialsTypeSAML, CredentialsTypeTOTP, CredentialsTypeLookup, CredentialsTypeWebAuthn, CredentialsTypeCodeAuth, CredentialsTypeRecoveryLink, CredentialsTypeRecoveryCode, CredentialsTypePasskey: return t, true } return "", false } // Credentials represents a specific credential type // // swagger:model identityCredentials type Credentials struct { ID uuid.UUID `json:"-" db:"id"` // Type discriminates between different types of credentials. Type CredentialsType `json:"type" db:"-"` IdentityCredentialTypeID uuid.UUID `json:"-" db:"identity_credential_type_id"` // Identifiers represent a list of unique identifiers this credential type matches. Identifiers []string `json:"identifiers" db:"-"` // Config contains the concrete credential payload. This might contain the bcrypt-hashed password, the email // for passwordless authentication or access_token and refresh tokens from OpenID Connect flows. Config sqlxx.JSONRawMessage `json:"config,omitempty" db:"config"` // Version refers to the version of the credential. Useful when changing the config schema. Version int `json:"version" db:"version"` IdentityID uuid.UUID `json:"-" faker:"-" db:"identity_id"` // CreatedAt is a helper struct field for gobuffalo.pop. CreatedAt time.Time `json:"created_at" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. UpdatedAt time.Time `json:"updated_at" db:"updated_at"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` } func (c Credentials) TableName(context.Context) string { return "identity_credentials" } func (c Credentials) GetID() uuid.UUID { return c.ID } // Signature returns a unique string signature for the credential. func (c Credentials) Signature() string { sortedIdentifiers := slices.Clone(c.Identifiers) slices.Sort(sortedIdentifiers) identifiersStr := strings.Join(sortedIdentifiers, ",") // Normalize JSON config to remove whitespace and key ordering differences var normalizedConfig any if len(c.Config) > 0 { if err := json.Unmarshal(c.Config, &normalizedConfig); err != nil { // there is not much we can do when unmarshal fails except use the raw value normalizedConfig = c.Config } } return fmt.Sprintf("%v|%v|%d|%+v|%v|%v", c.Type, identifiersStr, c.Version, normalizedConfig, c.IdentityID, c.NID) } type ( // swagger:ignore CredentialIdentifier struct { ID uuid.UUID `db:"id"` Identifier string `db:"identifier"` IdentityID *uuid.UUID `json:"-" db:"identity_id"` IdentityCredentialsID uuid.UUID `json:"-" db:"identity_credential_id"` IdentityCredentialsTypeID uuid.UUID `json:"-" db:"identity_credential_type_id"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` } // swagger:ignore CredentialsTypeTable struct { ID uuid.UUID `json:"-" db:"id"` Name CredentialsType `json:"-" db:"name"` } // swagger:ignore ActiveCredentialsCounter interface { ID() CredentialsType CountActiveFirstFactorCredentials(context.Context, map[CredentialsType]Credentials) (int, error) CountActiveMultiFactorCredentials(context.Context, map[CredentialsType]Credentials) (int, error) } // swagger:ignore ActiveCredentialsCounterStrategyProvider interface { ActiveCredentialsCounterStrategies(context.Context) []ActiveCredentialsCounter } ) func (c CredentialsTypeTable) TableName(context.Context) string { return "identity_credential_types" } func (c CredentialIdentifier) TableName(context.Context) string { return "identity_credential_identifiers" } func CredentialsEqual(a, b map[CredentialsType]Credentials) bool { if len(a) != len(b) { return false } if len(a) == 0 && len(b) == 0 { return true } for k, expect := range b { actual, found := a[k] if !found { return false } // Try to normalize configs (remove spaces etc). patch, err := jsondiff.CompareJSON(actual.Config, expect.Config) if err != nil { return false } if len(patch) > 0 { return false } expectIdentifiers, actualIdentifiers := make(map[string]struct{}, len(expect.Identifiers)), make(map[string]struct{}, len(actual.Identifiers)) for _, i := range expect.Identifiers { expectIdentifiers[i] = struct{}{} } for _, i := range actual.Identifiers { actualIdentifiers[i] = struct{}{} } if !reflect.DeepEqual(expectIdentifiers, actualIdentifiers) { return false } } return true } ================================================ FILE: identity/credentials_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "encoding/json" "strings" "github.com/ory/herodot" "github.com/pkg/errors" "github.com/ory/x/stringsx" ) type CodeChannel string // Scan implements the sql.Scanner interface for CodeChannel // to support proper scanning from database values while removing // any trailing whitespace that might be present in // PostgreSQL and CockroachDB CHAR fields. func (c *CodeChannel) Scan(src interface{}) error { if src == nil { *c = "" return nil } switch s := src.(type) { case string: *c = CodeChannel(strings.TrimSpace(s)) return nil case []byte: *c = CodeChannel(strings.TrimSpace(string(s))) return nil default: return errors.Errorf("cannot scan %T into CodeChannel", src) } } const ( CodeChannelEmail CodeChannel = AddressTypeEmail CodeChannelSMS CodeChannel = AddressTypeSMS ) func NewCodeChannel(value string) (CodeChannel, error) { switch f := stringsx.SwitchExact(value); { case f.AddCase(string(CodeChannelEmail)): return CodeChannelEmail, nil case f.AddCase(string(CodeChannelSMS)): return CodeChannelSMS, nil default: return "", errors.Wrap(ErrInvalidCodeAddressType, f.ToUnknownCaseErr().Error()) } } // CredentialsCode represents a one time login/registration code // // swagger:model identityCredentialsCode type CredentialsCode struct { Addresses []CredentialsCodeAddress `json:"addresses"` } // swagger:model identityCredentialsCodeAddress type CredentialsCodeAddress struct { // The type of the address for this code Channel CodeChannel `json:"channel"` // The address for this code Address string `json:"address"` } var ErrInvalidCodeAddressType = herodot.ErrMisconfiguration.WithReasonf("The address type for sending OTP codes is not supported.") func (c *CredentialsCodeAddress) UnmarshalJSON(data []byte) (err error) { type alias CredentialsCodeAddress var ac alias if err := json.Unmarshal(data, &ac); err != nil { return err } ac.Channel, err = NewCodeChannel(string(ac.Channel)) if err != nil { return err } *c = CredentialsCodeAddress(ac) return nil } ================================================ FILE: identity/credentials_code_test.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" ) func TestCredentialsCodeAddressUnmarshalJSON(t *testing.T) { tests := []struct { name string input string want CredentialsCodeAddress wantErr bool }{ { name: "valid email address", input: `{"channel": "email", "address": "user@example.com"}`, want: CredentialsCodeAddress{ Channel: CodeChannelEmail, Address: "user@example.com", }, wantErr: false, }, { name: "valid SMS address", input: `{"channel": "sms", "address": "+1234567890"}`, want: CredentialsCodeAddress{ Channel: CodeChannelSMS, Address: "+1234567890", }, wantErr: false, }, { name: "invalid address type", input: `{"channel": "invalid", "address": "user@example.com"}`, want: CredentialsCodeAddress{}, wantErr: true, }, { name: "missing channel field", input: `{"address": "user@example.com"}`, want: CredentialsCodeAddress{}, wantErr: true, }, { name: "invalid JSON structure", input: `{"channel": "email", "address": "user@example.com"`, want: CredentialsCodeAddress{}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got CredentialsCodeAddress err := json.Unmarshal([]byte(tt.input), &got) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) } } func TestNewCodeAddressType(t *testing.T) { tests := []struct { name string input string want CodeChannel wantErr bool }{ { name: "valid email address type", input: "email", want: CodeChannelEmail, wantErr: false, }, { name: "valid SMS address type", input: "sms", want: CodeChannelSMS, wantErr: false, }, { name: "invalid address type", input: "invalid", want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewCodeChannel(tt.input) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) } } ================================================ FILE: identity/credentials_lookup.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "time" "github.com/ory/kratos/text" "github.com/ory/kratos/ui/node" "github.com/ory/x/sqlxx" ) // CredentialsConfig is the struct that is being used as part of the identity credentials. type CredentialsLookupConfig struct { // List of recovery codes RecoveryCodes []RecoveryCode `json:"recovery_codes"` } func (c *CredentialsLookupConfig) ToNode() *node.Node { messages := make([]text.Message, len(c.RecoveryCodes)) formatted := make([]string, len(c.RecoveryCodes)) for k, code := range c.RecoveryCodes { if time.Time(code.UsedAt).IsZero() { messages[k] = *text.NewInfoSelfServiceSettingsLookupSecret(code.Code) formatted[k] = code.Code } else { messages[k] = *text.NewInfoSelfServiceSettingsLookupSecretUsed(time.Time(code.UsedAt).UTC()) formatted[k] = "used" } } return node.NewTextField(node.LookupCodes, text.NewInfoSelfServiceSettingsLookupSecretList(formatted, messages), node.LookupGroup). WithMetaLabel(text.NewInfoSelfServiceSettingsLookupSecretsLabel()) } type RecoveryCode struct { // A recovery code Code string `json:"code"` // UsedAt indicates whether and when a recovery code was used. UsedAt sqlxx.NullTime `json:"used_at,omitempty"` } ================================================ FILE: identity/credentials_lookup_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity_test import ( _ "embed" "testing" "time" "github.com/ory/kratos/identity" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/x/sqlxx" ) func TestToNode(t *testing.T) { c := identity.CredentialsLookupConfig{RecoveryCodes: []identity.RecoveryCode{ {Code: "foo", UsedAt: sqlxx.NullTime(time.Unix(1629199958, 0).UTC())}, {Code: "bar"}, {Code: "baz"}, {Code: "oof", UsedAt: sqlxx.NullTime(time.Unix(1629199968, 0).UTC())}, }} testhelpers.SnapshotTExcept(t, c.ToNode(), []string{}) } ================================================ FILE: identity/credentials_migrate.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "encoding/json" "fmt" "strings" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "github.com/pkg/errors" ) func UpgradeWebAuthnCredentials(i *Identity, c *Credentials) (err error) { if c.Type != CredentialsTypeWebAuthn { return nil } version := c.Version if version == 0 { if gjson.GetBytes(c.Config, "user_handle").String() == "" { id, err := json.Marshal(i.ID[:]) if err != nil { return errors.WithStack(err) } c.Config, err = sjson.SetRawBytes(c.Config, "user_handle", id) if err != nil { return errors.WithStack(err) } } var index = -1 var err error gjson.GetBytes(c.Config, "credentials").ForEach(func(_, value gjson.Result) bool { index++ if value.Get("is_passwordless").Exists() { return true } c.Config, err = sjson.SetBytes(c.Config, fmt.Sprintf("credentials.%d.is_passwordless", index), false) return err == nil }) if err != nil { return errors.WithStack(err) } c.Version = 1 } return nil } // UpgradeCredentials migrates a set of older WebAuthn credentials to newer ones. func UpgradeCredentials(i *Identity) error { for k := range i.Credentials { c := i.Credentials[k] if err := UpgradeWebAuthnCredentials(i, &c); err != nil { return errors.WithStack(err) } if err := UpgradeCodeCredentials(&c); err != nil { return errors.WithStack(err) } i.Credentials[k] = c } return nil } func UpgradeCodeCredentials(c *Credentials) (err error) { if c.Type != CredentialsTypeCodeAuth { return nil } version := c.Version if version == 0 { addressType := strings.ToLower(strings.TrimSpace(gjson.GetBytes(c.Config, "address_type").String())) channel, err := NewCodeChannel(addressType) if err != nil { // We know that in some cases the address type can be empty. In this case, we default to email // as sms is a new addition to the address_type introduced in this PR. channel = CodeChannelEmail } c.Config, err = sjson.DeleteBytes(c.Config, "used_at") if err != nil { return errors.WithStack(err) } c.Config, err = sjson.DeleteBytes(c.Config, "address_type") if err != nil { return errors.WithStack(err) } for _, id := range c.Identifiers { if id == "" { continue } c.Config, err = sjson.SetBytes(c.Config, "addresses.-1", &CredentialsCodeAddress{ Address: id, Channel: channel, }) if err != nil { return errors.WithStack(err) } } // This is needed because sjson adds spaces which can trip string comparisons. c.Config, err = json.Marshal(json.RawMessage(c.Config)) if err != nil { return errors.WithStack(err) } c.Version = 1 } return nil } ================================================ FILE: identity/credentials_migrate_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( _ "embed" "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/x/snapshotx" ) //go:embed stub/webauthn/v0.json var webAuthnV0 []byte //go:embed stub/webauthn/v1.json var webAuthnV1 []byte func TestUpgradeCredentials(t *testing.T) { t.Run("empty credentials", func(t *testing.T) { i := &Identity{} err := UpgradeCredentials(i) require.NoError(t, err) wc := WithCredentialsAndAdminMetadataInJSON(*i) snapshotx.SnapshotTExcept(t, &wc, nil) }) run := func(t *testing.T, identifiers []string, config string, version int, credentialsType CredentialsType, expectedVersion int) { if identifiers == nil { identifiers = []string{"hi@example.org"} } i := &Identity{ ID: uuid.FromStringOrNil("4d64fa08-20fc-450d-bebd-ebd7c7b6e249"), Credentials: map[CredentialsType]Credentials{ credentialsType: { Identifiers: identifiers, Type: credentialsType, Version: version, Config: []byte(config), }}, } require.NoError(t, UpgradeCredentials(i)) wc := WithCredentialsAndAdminMetadataInJSON(*i) snapshotx.SnapshotT(t, &wc) assert.Equal(t, expectedVersion, i.Credentials[credentialsType].Version) } t.Run("type=code", func(t *testing.T) { t.Run("from=v0 with email empty space value", func(t *testing.T) { t.Run("with one identifier", func(t *testing.T) { run(t, nil, `{"address_type": "email ", "used_at": {"Time": "0001-01-01T00:00:00Z", "Valid": false}}`, 0, CredentialsTypeCodeAuth, 1) }) t.Run("with two identifiers", func(t *testing.T) { run(t, []string{"foo@example.org", "bar@example.org"}, `{"address_type": "email ", "used_at": {"Time": "0001-01-01T00:00:00Z", "Valid": false}}`, 0, CredentialsTypeCodeAuth, 1) }) }) t.Run("from=v0 with empty value", func(t *testing.T) { run(t, nil, `{"address_type": "", "used_at": {"Time": "0001-01-01T00:00:00Z", "Valid": false}}`, 0, CredentialsTypeCodeAuth, 1) }) t.Run("from=v0 with correct value", func(t *testing.T) { run(t, nil, `{"address_type": "email", "used_at": {"Time": "0001-01-01T00:00:00Z", "Valid": false}}`, 0, CredentialsTypeCodeAuth, 1) }) t.Run("from=v0 with unknown value", func(t *testing.T) { run(t, nil, `{"address_type": "other", "used_at": {"Time": "0001-01-01T00:00:00Z", "Valid": false}}`, 0, CredentialsTypeCodeAuth, 1) }) t.Run("from=v2 with empty value", func(t *testing.T) { run(t, []string{"foo@example.org", "+12341234"}, `{"addresses": [{"address":"foo@example.org","channel":"email"},{"address":"+12341234","channel":"sms"}]}`, 1, CredentialsTypeCodeAuth, 1) }) }) t.Run("type=webauthn", func(t *testing.T) { t.Run("from=v0", func(t *testing.T) { run(t, []string{"4d64fa08-20fc-450d-bebd-ebd7c7b6e249"}, string(webAuthnV0), 0, CredentialsTypeWebAuthn, 1) }) t.Run("from=v1", func(t *testing.T) { run(t, []string{}, string(webAuthnV1), 1, CredentialsTypeWebAuthn, 1) }) }) } ================================================ FILE: identity/credentials_oidc.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "bytes" "encoding/json" "fmt" "github.com/pkg/errors" "github.com/ory/kratos/x" ) // CredentialsOIDC is contains the configuration for credentials of the type oidc. // // swagger:model identityCredentialsOidc type CredentialsOIDC struct { Providers []CredentialsOIDCProvider `json:"providers"` } // CredentialsOIDCProvider is contains a specific OpenID COnnect credential for a particular connection (e.g. Google). // // swagger:model identityCredentialsOidcProvider type CredentialsOIDCProvider struct { Subject string `json:"subject"` Provider string `json:"provider"` InitialIDToken string `json:"initial_id_token"` InitialAccessToken string `json:"initial_access_token"` InitialRefreshToken string `json:"initial_refresh_token"` Organization string `json:"organization,omitempty"` UseAutoLink bool `json:"use_auto_link,omitzero"` } // swagger:ignore type CredentialsOIDCEncryptedTokens struct { RefreshToken string `json:"refresh_token,omitempty"` IDToken string `json:"id_token,omitempty"` AccessToken string `json:"access_token,omitempty"` } func (c *CredentialsOIDCEncryptedTokens) GetRefreshToken() string { if c == nil { return "" } return c.RefreshToken } func (c *CredentialsOIDCEncryptedTokens) GetAccessToken() string { if c == nil { return "" } return c.AccessToken } func (c *CredentialsOIDCEncryptedTokens) GetIDToken() string { if c == nil { return "" } return c.IDToken } // NewCredentialsOIDC creates a new OIDC credential. func NewCredentialsOIDC(tokens *CredentialsOIDCEncryptedTokens, provider, subject, organization string) (*Credentials, error) { return NewOIDCLikeCredentials(tokens, CredentialsTypeOIDC, provider, subject, organization) } // NewOIDCLikeCredentials creates a new OIDC-like credential. func NewOIDCLikeCredentials(tokens *CredentialsOIDCEncryptedTokens, t CredentialsType, provider, subject, organization string) (*Credentials, error) { if provider == "" { return nil, errors.New("received empty provider in oidc credentials") } if subject == "" { return nil, errors.New("received empty provider in oidc credentials") } var b bytes.Buffer if err := json.NewEncoder(&b).Encode(CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Subject: subject, Provider: provider, InitialIDToken: tokens.GetIDToken(), InitialAccessToken: tokens.GetAccessToken(), InitialRefreshToken: tokens.GetRefreshToken(), Organization: organization, }, }, }); err != nil { return nil, errors.WithStack(x.PseudoPanic. WithDebugf("Unable to encode password options to JSON: %s", err)) } return &Credentials{ Type: t, Identifiers: []string{OIDCUniqueID(provider, subject)}, Config: b.Bytes(), }, nil } func (c *CredentialsOIDCProvider) GetTokens() *CredentialsOIDCEncryptedTokens { return &CredentialsOIDCEncryptedTokens{ RefreshToken: c.InitialRefreshToken, IDToken: c.InitialIDToken, AccessToken: c.InitialAccessToken, } } func OIDCUniqueID(provider, subject string) string { return fmt.Sprintf("%s:%s", provider, subject) } func (c *CredentialsOIDC) Organization() string { for _, p := range c.Providers { if p.Organization != "" { return p.Organization } } return "" } ================================================ FILE: identity/credentials_oidc_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "testing" "github.com/stretchr/testify/require" ) func TestNewCredentialsOIDC(t *testing.T) { _, err := NewCredentialsOIDC(new(CredentialsOIDCEncryptedTokens), "", "not-empty", "") require.Error(t, err) _, err = NewCredentialsOIDC(new(CredentialsOIDCEncryptedTokens), "not-empty", "", "") require.Error(t, err) _, err = NewCredentialsOIDC(new(CredentialsOIDCEncryptedTokens), "not-empty", "not-empty", "") require.NoError(t, err) } ================================================ FILE: identity/credentials_password.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity // CredentialsPassword is contains the configuration for credentials of the type password. // // swagger:model identityCredentialsPassword type CredentialsPassword struct { // HashedPassword is a hash-representation of the password. HashedPassword string `json:"hashed_password"` // UsePasswordMigrationHook is set to true if the password should be migrated // using the password migration hook. If set, and the HashedPassword is empty, a // webhook will be called during login to migrate the password. UsePasswordMigrationHook bool `json:"use_password_migration_hook,omitempty"` } func (cp *CredentialsPassword) ShouldUsePasswordMigrationHook() bool { return cp != nil && cp.HashedPassword == "" && cp.UsePasswordMigrationHook } ================================================ FILE: identity/credentials_password_test.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "testing" "github.com/stretchr/testify/assert" ) func TestCredentialsPassword_ShouldUsePasswordMigrationHook(t *testing.T) { tests := []struct { name string cp *CredentialsPassword want bool }{{ name: "pw set", cp: &CredentialsPassword{ HashedPassword: "pw", UsePasswordMigrationHook: true, }, want: false, }, { name: "pw not set", cp: &CredentialsPassword{ HashedPassword: "", UsePasswordMigrationHook: true, }, want: true, }, { name: "nil", want: false, }, { name: "pw not set, hook not set", cp: &CredentialsPassword{ HashedPassword: "", }, want: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equalf(t, tt.want, tt.cp.ShouldUsePasswordMigrationHook(), "ShouldUsePasswordMigrationHook()") }) } } ================================================ FILE: identity/credentials_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "testing" "github.com/gofrs/uuid" "github.com/mohae/deepcopy" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/x/sqlxx" ) func TestCredentialsEqual(t *testing.T) { original := map[CredentialsType]Credentials{ "foo": {Type: "foo", Identifiers: []string{"bar"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`)}, } derived := deepcopy.Copy(original).(map[CredentialsType]Credentials) assert.EqualValues(t, original, derived) derived["foo"].Identifiers[0] = "baz" assert.NotEqual(t, original, derived) } func TestAALOrder(t *testing.T) { assert.True(t, NoAuthenticatorAssuranceLevel < AuthenticatorAssuranceLevel1) assert.True(t, AuthenticatorAssuranceLevel1 < AuthenticatorAssuranceLevel2) } func TestParseCredentialsType(t *testing.T) { for _, tc := range []struct { input string expected CredentialsType }{ {"password", CredentialsTypePassword}, {"oidc", CredentialsTypeOIDC}, {"totp", CredentialsTypeTOTP}, {"webauthn", CredentialsTypeWebAuthn}, {"lookup_secret", CredentialsTypeLookup}, {"link_recovery", CredentialsTypeRecoveryLink}, {"code_recovery", CredentialsTypeRecoveryCode}, } { t.Run("case="+tc.input, func(t *testing.T) { actual, ok := ParseCredentialsType(tc.input) require.True(t, ok) assert.Equal(t, tc.expected, actual) }) } t.Run("case=unknown", func(t *testing.T) { _, ok := ParseCredentialsType("unknown") require.False(t, ok) }) } func TestCredentials_Hash(t *testing.T) { baseID := uuid.Must(uuid.NewV4()) baseNID := uuid.Must(uuid.NewV4()) for _, tc := range []struct { name string cred1 Credentials cred2 Credentials expectEqual bool description string }{ { name: "same json with different whitespace", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar","baz":"qux"}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{ "foo": "bar", "baz": "qux" }`), Version: 1, }, expectEqual: true, description: "hashes should be equal for same JSON with different whitespace", }, { name: "same json with different key order", cred1: Credentials{ Type: CredentialsTypeCodeAuth, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"address":"test@example.com","channel":"email"}]}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeCodeAuth, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"test@example.com"}]}`), Version: 1, }, expectEqual: true, description: "hashes should be equal for same JSON with different key order", }, { name: "nested json with different key order", cred1: Credentials{ Type: CredentialsTypeWebAuthn, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"credentials":[{"id":"abc","public_key":"xyz","type":"webauthn"}]}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeWebAuthn, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"credentials":[{"type":"webauthn","public_key":"xyz","id":"abc"}]}`), Version: 1, }, expectEqual: true, description: "hashes should be equal for nested JSON with different key order", }, { name: "same identifiers in different order", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"a@example.com", "b@example.com", "c@example.com"}, Config: sqlxx.JSONRawMessage(`{}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"c@example.com", "a@example.com", "b@example.com"}, Config: sqlxx.JSONRawMessage(`{}`), Version: 1, }, expectEqual: true, description: "hashes should be equal for same identifiers in different order", }, { name: "different json config", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"different"}`), Version: 1, }, expectEqual: false, description: "hashes should be different for different JSON content", }, { name: "different types", cred1: Credentials{ Type: CredentialsTypePassword, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, }, expectEqual: false, description: "hashes should be different for different types", }, { name: "different identifiers", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test1@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test2@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, }, expectEqual: false, description: "hashes should be different for different identifiers", }, { name: "different versions", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 2, }, expectEqual: false, description: "hashes should be different for different versions", }, { name: "different identity IDs", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, IdentityID: baseID, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, IdentityID: uuid.Must(uuid.NewV4()), }, expectEqual: false, description: "hashes should be different for different identity IDs", }, { name: "different NIDs", cred1: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, NID: baseNID, }, cred2: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"test@example.com"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), Version: 1, NID: uuid.Must(uuid.NewV4()), }, expectEqual: false, description: "hashes should be different for different NIDs", }, } { t.Run("case="+tc.name, func(t *testing.T) { hash1 := tc.cred1.Signature() hash2 := tc.cred2.Signature() if tc.expectEqual { assert.Equal(t, hash1, hash2, tc.description) } else { assert.NotEqual(t, hash1, hash2, tc.description) } }) } } ================================================ FILE: identity/credentials_totp.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity // CredentialsConfig is the struct that is being used as part of the identity credentials. type CredentialsTOTPConfig struct { // TOTPURL is the TOTP URL // // For more details see: https://github.com/google/google-authenticator/wiki/Key-Uri-Format TOTPURL string `json:"totp_url"` } ================================================ FILE: identity/credentials_webauthn.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "time" "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "github.com/ory/kratos/x/webauthnx/aaguid" ) // CredentialsWebAuthnConfig is the struct that is being used as part of the identity credentials. type CredentialsWebAuthnConfig struct { // List of webauthn credentials. Credentials CredentialsWebAuthn `json:"credentials"` UserHandle []byte `json:"user_handle"` } type CredentialsWebAuthn []CredentialWebAuthn func CredentialFromWebAuthn(credential *webauthn.Credential, isPasswordless bool) *CredentialWebAuthn { cred := &CredentialWebAuthn{ ID: credential.ID, PublicKey: credential.PublicKey, IsPasswordless: isPasswordless, AttestationType: credential.AttestationType, AddedAt: time.Now().UTC().Round(time.Second), Authenticator: &AuthenticatorWebAuthn{ AAGUID: credential.Authenticator.AAGUID, SignCount: credential.Authenticator.SignCount, CloneWarning: credential.Authenticator.CloneWarning, }, Flags: &CredentialWebAuthnFlags{ UserPresent: credential.Flags.UserPresent, UserVerified: credential.Flags.UserVerified, BackupEligible: credential.Flags.BackupEligible, BackupState: credential.Flags.BackupState, }, } id := aaguid.Lookup(credential.Authenticator.AAGUID) if id != nil { cred.DisplayName = id.Name } return cred } func (c CredentialsWebAuthn) ToWebAuthn() (result []webauthn.Credential) { for k := range c { result = append(result, *c[k].ToWebAuthn()) } return result } // PasswordlessOnly returns only passwordless credentials. func (c CredentialsWebAuthn) PasswordlessOnly(authenticatorResponseFlags *protocol.AuthenticatorFlags) (result []webauthn.Credential) { for k, cc := range c { // Upgrade path for legacy webauthn credentials. Only possible if we are handling a response from an authenticator. if c[k].Flags == nil && authenticatorResponseFlags != nil { c[k].Flags = &CredentialWebAuthnFlags{ BackupEligible: authenticatorResponseFlags.HasBackupEligible(), BackupState: authenticatorResponseFlags.HasBackupState(), } } if cc.IsPasswordless { result = append(result, *c[k].ToWebAuthn()) } } return result } // ToWebAuthnFiltered returns only the appropriate credentials for the requested // AAL. For AAL1, only passwordless credentials are returned, for AAL2, only // non-passwordless credentials are returned. // // authenticatorResponseFlags should be passed if the response is from an authenticator. It will be used to // upgrade legacy webauthn credentials' BackupEligible and BackupState flags. func (c CredentialsWebAuthn) ToWebAuthnFiltered(aal AuthenticatorAssuranceLevel, authenticatorResponseFlags *protocol.AuthenticatorFlags) (result []webauthn.Credential) { for k, cc := range c { // Upgrade path for legacy webauthn credentials. Only possible if we are handling a response from an authenticator. if c[k].Flags == nil && authenticatorResponseFlags != nil { c[k].Flags = &CredentialWebAuthnFlags{ BackupEligible: authenticatorResponseFlags.HasBackupEligible(), BackupState: authenticatorResponseFlags.HasBackupState(), } } if (aal == AuthenticatorAssuranceLevel1 && cc.IsPasswordless) || (aal == AuthenticatorAssuranceLevel2 && !cc.IsPasswordless) { result = append(result, *c[k].ToWebAuthn()) } } return result } func (c *CredentialWebAuthn) ToWebAuthn() *webauthn.Credential { wc := &webauthn.Credential{ ID: c.ID, PublicKey: c.PublicKey, AttestationType: c.AttestationType, Transport: c.Transport, } if c.Authenticator != nil { wc.Authenticator = webauthn.Authenticator{ AAGUID: c.Authenticator.AAGUID, SignCount: c.Authenticator.SignCount, CloneWarning: c.Authenticator.CloneWarning, } } if c.Flags != nil { wc.Flags = webauthn.CredentialFlags{ UserPresent: c.Flags.UserPresent, UserVerified: c.Flags.UserVerified, BackupEligible: c.Flags.BackupEligible, BackupState: c.Flags.BackupState, } } if c.Attestation != nil { wc.Attestation = webauthn.CredentialAttestation{ ClientDataJSON: c.Attestation.ClientDataJSON, ClientDataHash: c.Attestation.ClientDataHash, AuthenticatorData: c.Attestation.AuthenticatorData, PublicKeyAlgorithm: c.Attestation.PublicKeyAlgorithm, Object: c.Attestation.Object, } } return wc } type CredentialWebAuthn struct { ID []byte `json:"id"` PublicKey []byte `json:"public_key"` AttestationType string `json:"attestation_type"` Authenticator *AuthenticatorWebAuthn `json:"authenticator,omitempty"` DisplayName string `json:"display_name"` AddedAt time.Time `json:"added_at"` IsPasswordless bool `json:"is_passwordless"` Flags *CredentialWebAuthnFlags `json:"flags,omitempty"` Transport []protocol.AuthenticatorTransport `json:"transport,omitempty"` Attestation *CredentialWebAuthnAttestation `json:"attestation,omitempty"` } type CredentialWebAuthnFlags struct { UserPresent bool `json:"user_present"` UserVerified bool `json:"user_verified"` BackupEligible bool `json:"backup_eligible"` BackupState bool `json:"backup_state"` } type CredentialWebAuthnAttestation struct { ClientDataJSON []byte `json:"client_dataJSON"` ClientDataHash []byte `json:"client_data_hash"` AuthenticatorData []byte `json:"authenticator_data"` PublicKeyAlgorithm int64 `json:"public_key_algorithm"` Object []byte `json:"object"` } type AuthenticatorWebAuthn struct { AAGUID []byte `json:"aaguid"` SignCount uint32 `json:"sign_count"` CloneWarning bool `json:"clone_warning"` } ================================================ FILE: identity/credentials_webauthn_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "testing" "github.com/stretchr/testify/require" "github.com/go-webauthn/webauthn/webauthn" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" ) func TestCredentialConversion(t *testing.T) { expected := &webauthn.Credential{ ID: []byte("abcdef"), PublicKey: []byte("foobar"), AttestationType: "test", Authenticator: webauthn.Authenticator{ AAGUID: []byte("baz"), SignCount: 1, CloneWarning: false, }, } actual := CredentialFromWebAuthn(expected, false).ToWebAuthn() assert.Equal(t, expected, actual) actualList := CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2, nil) assert.Equal(t, []webauthn.Credential{*expected}, actualList) actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1, nil) assert.Equal(t, []webauthn.Credential{*expected}, actualList) actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2, nil) assert.Len(t, actualList, 0) actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1, nil) assert.Len(t, actualList, 0) fromWebAuthn := CredentialFromWebAuthn(expected, true) assert.True(t, fromWebAuthn.IsPasswordless) fromWebAuthn = CredentialFromWebAuthn(expected, false) assert.False(t, fromWebAuthn.IsPasswordless) expected.Authenticator.AAGUID = uuid.Must(uuid.FromString("ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4")).Bytes() fromWebAuthn = CredentialFromWebAuthn(expected, false) assert.Equal(t, "Google Password Manager", fromWebAuthn.DisplayName) } func TestPasswordlessOnly(t *testing.T) { a := *CredentialFromWebAuthn(&webauthn.Credential{ID: []byte("a")}, false) b := *CredentialFromWebAuthn(&webauthn.Credential{ID: []byte("b")}, false) c := *CredentialFromWebAuthn(&webauthn.Credential{ID: []byte("c")}, true) d := *CredentialFromWebAuthn(&webauthn.Credential{ID: []byte("d")}, false) e := *CredentialFromWebAuthn(&webauthn.Credential{ID: []byte("e")}, true) expected := CredentialsWebAuthn{a, b, c, d, e} actual := expected.PasswordlessOnly(nil) require.Len(t, actual, 2) assert.Equal(t, []webauthn.Credential{*c.ToWebAuthn(), *e.ToWebAuthn()}, actual) } ================================================ FILE: identity/error_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func TestErrDuplicateCredentials(t *testing.T) { inner := errors.New("inner error") err := &ErrDuplicateCredentials{inner, nil, nil, ""} assert.ErrorIs(t, err, inner) } ================================================ FILE: identity/expandables.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import "github.com/ory/x/sqlxx" type Expandable = sqlxx.Expandable type Expandables = sqlxx.Expandables const ( ExpandFieldVerifiableAddresses Expandable = "VerifiableAddresses" ExpandFieldRecoveryAddresses Expandable = "RecoveryAddresses" ExpandFieldCredentials Expandable = "Credentials" ) // ExpandNothing expands nothing var ExpandNothing = Expandables{} // ExpandDefault expands the default fields: // // - Verifiable addresses // - Recovery addresses var ExpandDefault = Expandables{ ExpandFieldVerifiableAddresses, ExpandFieldRecoveryAddresses, } // ExpandCredentials expands the identity's credentials. var ExpandCredentials = Expandables{ ExpandFieldCredentials, } // ExpandEverything expands all the fields of an identity. var ExpandEverything = Expandables{ ExpandFieldVerifiableAddresses, ExpandFieldRecoveryAddresses, ExpandFieldCredentials, } ================================================ FILE: identity/extension_credentials.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "encoding/json" "fmt" "sort" "strings" "sync" "github.com/pkg/errors" "github.com/samber/lo" "github.com/ory/kratos/x" "github.com/ory/jsonschema/v3" "github.com/ory/kratos/schema" "github.com/ory/x/sqlxx" "github.com/ory/x/stringslice" ) type SchemaExtensionCredentials struct { i *Identity v map[CredentialsType][]string addresses []CredentialsCodeAddress l sync.Mutex } func NewSchemaExtensionCredentials(i *Identity) *SchemaExtensionCredentials { return &SchemaExtensionCredentials{i: i} } func (r *SchemaExtensionCredentials) setIdentifier(ct CredentialsType, value interface{}) { cred, ok := r.i.GetCredentials(ct) if !ok { cred = &Credentials{ Type: ct, Identifiers: []string{}, Config: sqlxx.JSONRawMessage{}, } } if r.v == nil { r.v = make(map[CredentialsType][]string) } r.v[ct] = stringslice.Unique(append(r.v[ct], strings.ToLower(fmt.Sprintf("%s", value)))) cred.Identifiers = r.v[ct] r.i.SetCredentials(ct, *cred) } func (r *SchemaExtensionCredentials) Run(ctx jsonschema.ValidationContext, s schema.ExtensionConfig, value interface{}) error { r.l.Lock() defer r.l.Unlock() if s.Credentials.Password.Identifier { r.setIdentifier(CredentialsTypePassword, value) } if s.Credentials.WebAuthn.Identifier { r.setIdentifier(CredentialsTypeWebAuthn, value) } if s.Credentials.Code.Identifier { via, err := NewCodeChannel(s.Credentials.Code.Via) if err != nil { return ctx.Error("ory.sh~/kratos/credentials/code/via", "channel type %q must be one of %s", s.Credentials.Code.Via, strings.Join([]string{ string(CodeChannelEmail), string(CodeChannelSMS), }, ", ")) } cred := r.i.GetCredentialsOr(CredentialsTypeCodeAuth, &Credentials{ Type: CredentialsTypeCodeAuth, Identifiers: []string{}, Config: sqlxx.JSONRawMessage("{}"), Version: 1, }) var conf CredentialsCode conf.Addresses = r.addresses value, err := x.NormalizeIdentifier(fmt.Sprintf("%s", value), string(via)) if err != nil { return &jsonschema.ValidationError{Message: err.Error()} } conf.Addresses = append(conf.Addresses, CredentialsCodeAddress{ Channel: via, Address: value, }) conf.Addresses = lo.UniqBy(conf.Addresses, func(item CredentialsCodeAddress) string { return fmt.Sprintf("%x:%s", item.Address, item.Channel) }) sort.SliceStable(conf.Addresses, func(i, j int) bool { if conf.Addresses[i].Address == conf.Addresses[j].Address { return conf.Addresses[i].Channel < conf.Addresses[j].Channel } return conf.Addresses[i].Address < conf.Addresses[j].Address }) if r.v == nil { r.v = make(map[CredentialsType][]string) } r.v[CredentialsTypeCodeAuth] = stringslice.Unique(append(r.v[CredentialsTypeCodeAuth], lo.Map(conf.Addresses, func(item CredentialsCodeAddress, _ int) string { return item.Address })..., )) r.addresses = conf.Addresses cred.Identifiers = r.v[CredentialsTypeCodeAuth] cred.Config, err = json.Marshal(conf) if err != nil { return errors.WithStack(err) } r.i.SetCredentials(CredentialsTypeCodeAuth, *cred) } return nil } func (r *SchemaExtensionCredentials) Finish() error { return nil } ================================================ FILE: identity/extension_credentials_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity_test import ( "bytes" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/jsonschema/v3" _ "github.com/ory/jsonschema/v3/fileloader" "github.com/ory/kratos/identity" "github.com/ory/kratos/schema" "github.com/ory/x/snapshotx" "github.com/ory/x/sqlxx" ) func TestSchemaExtensionCredentials(t *testing.T) { for k, tc := range []struct { expectErr error schema string doc string expectedIdentifiers []string existing *identity.Credentials ct identity.CredentialsType }{ { doc: `{"email":"foo@ory.sh"}`, schema: "file://./stub/extension/credentials/schema.json", expectedIdentifiers: []string{"foo@ory.sh"}, ct: identity.CredentialsTypePassword, }, { doc: `{"emails":["foo@ory.sh","foo@ory.sh","bar@ory.sh"], "username": "foobar"}`, schema: "file://./stub/extension/credentials/multi.schema.json", expectedIdentifiers: []string{"foo@ory.sh", "bar@ory.sh", "foobar"}, ct: identity.CredentialsTypePassword, }, { doc: `{"emails":["foo@ory.sh","foo@ory.sh","bar@ory.sh"], "username": "foobar"}`, schema: "file://./stub/extension/credentials/multi.schema.json", expectedIdentifiers: []string{"foo@ory.sh", "bar@ory.sh"}, ct: identity.CredentialsTypeWebAuthn, }, { doc: `{"emails":["FOO@ory.sh","bar@ory.sh"], "username": "foobar"}`, schema: "file://./stub/extension/credentials/multi.schema.json", expectedIdentifiers: []string{"foo@ory.sh", "bar@ory.sh", "foobar"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh"}, }, ct: identity.CredentialsTypePassword, }, { doc: `{"email":"foo@ory.sh"}`, schema: "file://./stub/extension/credentials/webauthn.schema.json", expectedIdentifiers: []string{"foo@ory.sh"}, ct: identity.CredentialsTypeWebAuthn, }, { doc: `{"email":"FOO@ory.sh"}`, schema: "file://./stub/extension/credentials/webauthn.schema.json", expectedIdentifiers: []string{"foo@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh"}, }, ct: identity.CredentialsTypeWebAuthn, }, { doc: `{"email":"foo@ory.sh"}`, schema: "file://./stub/extension/credentials/code.schema.json", expectedIdentifiers: []string{"foo@ory.sh"}, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"FOO@ory.sh"}`, schema: "file://./stub/extension/credentials/code.schema.json", expectedIdentifiers: []string{"foo@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh"}, }, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"FOO@ory.sh"}`, schema: "file://./stub/extension/credentials/code.schema.json", expectedIdentifiers: []string{"foo@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh", "foo@ory.sh"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"not-foo@ory.sh"}]}`), }, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"FOO@ory.sh","phone":"+49 176 671 11 638"}`, schema: "file://./stub/extension/credentials/code-phone-email.schema.json", expectedIdentifiers: []string{"+4917667111638", "foo@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh", "foo@ory.sh"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"not-foo@ory.sh"}]}`), }, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"FOO@ory.sh","phone":"+49 176 671 11 638"}`, schema: "file://./stub/extension/credentials/code-phone-email.schema.json", expectedIdentifiers: []string{"+4917667111638", "foo@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh", "foo@ory.sh"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"not-foo@ory.sh"}]}`), }, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"FOO@ory.sh","email2":"FOO@ory.sh","phone":"+49 176 671 11 638"}`, schema: "file://./stub/extension/credentials/code-phone-email.schema.json", expectedIdentifiers: []string{"+4917667111638", "foo@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh", "fOo@ory.sh"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"not-foo@ory.sh"}]}`), }, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"FOO@ory.sh","email2":"FOO@ory.sh","email3":"bar@ory.sh","phone":"+49 176 671 11 638"}`, schema: "file://./stub/extension/credentials/code-phone-email.schema.json", expectedIdentifiers: []string{"+4917667111638", "foo@ory.sh", "bar@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"not-foo@ory.sh", "fOo@ory.sh"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"not-foo@ory.sh"}]}`), }, ct: identity.CredentialsTypeCodeAuth, }, { doc: `{"email":"bar@ory.sh"}`, schema: "file://./stub/extension/credentials/email.schema.json", expectedIdentifiers: []string{"bar@ory.sh"}, existing: &identity.Credentials{ Identifiers: []string{"foo@ory.sh"}, Config: sqlxx.JSONRawMessage(`{"addresses":[{"channel":"email","address":"foo@ory.sh"}]}`), }, ct: identity.CredentialsTypeCodeAuth, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { c := jsonschema.NewCompiler() runner, err := schema.NewExtensionRunner(t.Context()) require.NoError(t, err) i := new(identity.Identity) e := identity.NewSchemaExtensionCredentials(i) if tc.existing != nil { i.SetCredentials(tc.ct, *tc.existing) } runner.AddRunner(e).Register(c) err = c.MustCompile(t.Context(), tc.schema).Validate(bytes.NewBufferString(tc.doc)) if tc.expectErr != nil { require.EqualError(t, err, tc.expectErr.Error()) } else { require.NoError(t, err) } require.NoError(t, e.Finish()) credentials, ok := i.GetCredentials(tc.ct) require.True(t, ok) assert.ElementsMatch(t, tc.expectedIdentifiers, credentials.Identifiers) snapshotx.SnapshotT(t, credentials, snapshotx.ExceptPaths("identifiers")) }) } } ================================================ FILE: identity/extension_recovery.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "fmt" "maps" "slices" "strings" "sync" "github.com/ory/jsonschema/v3" "github.com/ory/kratos/schema" ) type SchemaExtensionRecovery struct { l sync.Mutex v []RecoveryAddress i *Identity } func NewSchemaExtensionRecovery(i *Identity) *SchemaExtensionRecovery { return &SchemaExtensionRecovery{i: i} } func (r *SchemaExtensionRecovery) Run(ctx jsonschema.ValidationContext, s schema.ExtensionConfig, value interface{}) error { r.l.Lock() defer r.l.Unlock() var address *RecoveryAddress switch s.Recovery.Via { case "email": formatString := "email" formatter, ok := jsonschema.Formats[formatString] if !ok { supportedKeys := slices.Collect(maps.Keys(jsonschema.Formats)) return ctx.Error("format", "format %q is not supported. Supported formats are [%s]", formatString, strings.Join(supportedKeys, ", ")) } if !formatter(value) { return ctx.Error("format", "%q is not valid %q", value, formatString) } address = NewRecoveryEmailAddress( strings.ToLower(strings.TrimSpace( fmt.Sprintf("%s", value))), r.i.ID) case "sms": formatString := "tel" formatter, ok := jsonschema.Formats[formatString] if !ok { supportedKeys := slices.Collect(maps.Keys(jsonschema.Formats)) return ctx.Error("format", "format %q is not supported. Supported formats are [%s]", formatString, strings.Join(supportedKeys, ", ")) } if !formatter(value) { return ctx.Error("format", "%q is not valid %q", value, formatString) } address = NewRecoverySMSAddress( strings.TrimSpace( fmt.Sprintf("%s", value)), r.i.ID) case "": return nil default: return ctx.Error("", "recovery.via has unknown value %q", s.Recovery.Via) } if has := r.has(r.i.RecoveryAddresses, address); has != nil { if r.has(r.v, address) == nil { r.v = append(r.v, *has) } return nil } if has := r.has(r.v, address); has == nil { r.v = append(r.v, *address) } return nil } func (r *SchemaExtensionRecovery) has(haystack []RecoveryAddress, needle *RecoveryAddress) *RecoveryAddress { for _, has := range haystack { if has.Value == needle.Value && has.Via == needle.Via { return &has } } return nil } func (r *SchemaExtensionRecovery) Finish() error { r.i.RecoveryAddresses = r.v return nil } ================================================ FILE: identity/extension_recovery_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "bytes" "errors" "fmt" "reflect" "testing" "github.com/ory/jsonschema/v3" _ "github.com/ory/jsonschema/v3/fileloader" "github.com/ory/kratos/schema" "github.com/ory/kratos/x" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestSchemaExtensionRecovery(t *testing.T) { iid := x.NewUUID() for k, tc := range []struct { expectErr error schema string doc string expect []RecoveryAddress existing []RecoveryAddress description string }{ { description: "valid email, no existing", doc: `{"username":"foo@ory.sh"}`, schema: "file://./stub/extension/recovery/email.schema.json", expect: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, }, { description: "valid email, some existing, no overlap", doc: `{"username":"foo@ory.sh"}`, schema: "file://./stub/extension/recovery/email.schema.json", expect: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, existing: []RecoveryAddress{ { Value: "bar@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, }, { description: "valid emails, some existing, overlap", doc: `{"emails":["baz@ory.sh","foo@ory.sh"]}`, schema: "file://./stub/extension/recovery/email.schema.json", expect: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, { Value: "baz@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, existing: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, { Value: "bar@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, }, { description: "valid emails, no existing, overlap", doc: `{"emails":["foo@ory.sh","foo@ory.sh","baz@ory.sh"]}`, schema: "file://./stub/extension/recovery/email.schema.json", expect: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, { Value: "baz@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, existing: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, { Value: "bar@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, }, { description: "invalid email", doc: `{"emails":["foo@ory.sh","bar@ory.sh"], "username": "foobar"}`, schema: "file://./stub/extension/recovery/email.schema.json", expectErr: errors.New("I[#/username] S[#/properties/username/format] \"foobar\" is not valid \"email\""), }, { description: "valid emails, no existing", doc: `{"emails":["foo@ory.sh","bar@ory.sh","bar@ory.sh"], "username": "foobar@ory.sh"}`, schema: "file://./stub/extension/recovery/email.schema.json", expect: []RecoveryAddress{ { Value: "foo@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, { Value: "bar@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, { Value: "foobar@ory.sh", Via: AddressTypeEmail, IdentityID: iid, }, }, }, { description: "valid phone number, no existing", doc: `{"telephoneNumber":"+68672098006"}`, schema: "file://./stub/extension/recovery/sms.schema.json", expect: []RecoveryAddress{ { Value: "+68672098006", Via: AddressTypeSMS, IdentityID: iid, }, }, }, { description: "valid phone number, some existing, no overlap", doc: `{"telephoneNumber":"+68672098006"}`, schema: "file://./stub/extension/recovery/sms.schema.json", expect: []RecoveryAddress{ { Value: "+68672098006", Via: AddressTypeSMS, IdentityID: iid, }, }, existing: []RecoveryAddress{ { Value: "+12 345 67890123", Via: AddressTypeSMS, IdentityID: iid, }, }, }, { description: "valid phone number, some existing, overlap", doc: `{"telephoneNumber":"+68672098006"}`, schema: "file://./stub/extension/recovery/sms.schema.json", expect: []RecoveryAddress{ { Value: "+68672098006", Via: AddressTypeSMS, IdentityID: iid, }, }, existing: []RecoveryAddress{ { Value: "+68672098006", Via: AddressTypeSMS, IdentityID: iid, }, { Value: "+33 07856952", Via: AddressTypeSMS, IdentityID: iid, }, }, }, { description: "invalid phone number", doc: `{"telephoneNumber": "foobar"}`, schema: "file://./stub/extension/recovery/sms.schema.json", // We get 2 errors: one from the JSON schema `format` validation and one from the Go validation. expectErr: errors.New("I[#/telephoneNumber] S[#/properties/telephoneNumber] validation failed\n I[#/telephoneNumber] S[#/properties/telephoneNumber/format] \"foobar\" is not valid \"tel\"\n I[#/telephoneNumber] S[#/properties/telephoneNumber/format] \"foobar\" is not valid \"tel\""), }, } { t.Run(fmt.Sprintf("case=%d description=%s", k, tc.description), func(t *testing.T) { id := &Identity{ID: iid, RecoveryAddresses: tc.existing} c := jsonschema.NewCompiler() runner, err := schema.NewExtensionRunner(t.Context()) require.NoError(t, err) e := NewSchemaExtensionRecovery(id) runner.AddRunner(e).Register(c) err = c.MustCompile(t.Context(), tc.schema).Validate(bytes.NewBufferString(tc.doc)) if tc.expectErr != nil { require.EqualError(t, err, tc.expectErr.Error()) return } require.NoError(t, e.Finish()) addresses := id.RecoveryAddresses require.Len(t, addresses, len(tc.expect)) for _, actual := range addresses { var found bool for _, expect := range tc.expect { if reflect.DeepEqual(actual, expect) { found = true break } } assert.True(t, found, "%+v not in %+v", actual, tc.expect) } }) } } ================================================ FILE: identity/extension_verification.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "fmt" "maps" "slices" "strings" "sync" "time" "github.com/ory/jsonschema/v3" "github.com/ory/kratos/schema" ) func init() { jsonschema.Formats["no-validate"] = func(v interface{}) bool { return true } } type SchemaExtensionVerification struct { lifespan time.Duration l sync.Mutex v []VerifiableAddress i *Identity } func NewSchemaExtensionVerification(i *Identity, lifespan time.Duration) *SchemaExtensionVerification { return &SchemaExtensionVerification{i: i, lifespan: lifespan} } const ( ChannelTypeEmail = "email" ChannelTypeSMS = "sms" ) func (r *SchemaExtensionVerification) Run(ctx jsonschema.ValidationContext, s schema.ExtensionConfig, value interface{}) error { r.l.Lock() defer r.l.Unlock() if s.Verification.Via == "" { return nil } format, ok := s.RawSchema["format"] if !ok { format = "" } formatString, ok := format.(string) if !ok { return nil } if formatString == "" { switch s.Verification.Via { case ChannelTypeEmail: formatString = "email" formatter, ok := jsonschema.Formats[formatString] if !ok { supportedKeys := slices.Collect(maps.Keys(jsonschema.Formats)) return ctx.Error("format", "format %q is not supported. Supported formats are [%s]", formatString, strings.Join(supportedKeys, ", ")) } if !formatter(value) { return ctx.Error("format", "%q is not valid %q", value, formatString) } default: return ctx.Error("format", "no format specified. A format is required if verification is enabled. If this was intentional, please set \"format\" to \"no-validate\"") } } var normalized string switch formatString { case "email": normalized = strings.ToLower(strings.TrimSpace(fmt.Sprintf("%s", value))) default: normalized = strings.TrimSpace(fmt.Sprintf("%s", value)) } address := NewVerifiableAddress(normalized, r.i.ID, s.Verification.Via) r.appendAddress(address) return nil } func (r *SchemaExtensionVerification) Finish() error { r.i.VerifiableAddresses = merge(r.v, r.i.VerifiableAddresses) return nil } // merge merges the base with the overrides through comparison with `has`. It changes the base slice in place. func merge(base []VerifiableAddress, overrides []VerifiableAddress) []VerifiableAddress { for i := range base { if override := has(overrides, &base[i]); override != nil { base[i] = *override } } return base } func (r *SchemaExtensionVerification) appendAddress(address *VerifiableAddress) { if h := has(r.i.VerifiableAddresses, address); h != nil { if has(r.v, address) == nil { r.v = append(r.v, *h) } return } if has(r.v, address) == nil { r.v = append(r.v, *address) } } func has(haystack []VerifiableAddress, needle *VerifiableAddress) *VerifiableAddress { for _, has := range haystack { if has.Value == needle.Value && has.Via == needle.Via { return &has } } return nil } ================================================ FILE: identity/extension_verification_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "bytes" "fmt" "net" "reflect" "testing" "time" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/jsonschema/v3" _ "github.com/ory/jsonschema/v3/fileloader" "github.com/ory/kratos/schema" "github.com/ory/kratos/x" ) const ( emailSchemaPath = "file://./stub/extension/verify/email.schema.json" phoneSchemaPath = "file://./stub/extension/verify/phone.schema.json" missingFormatSchemaPath = "file://./stub/extension/verify/missing-format.schema.json" legacyEmailMissingFormatSchemaPath = "file://./stub/extension/verify/legacy-email-missing-format.schema.json" noValidateSchemaPath = "file://./stub/extension/verify/no-validate.schema.json" ) func TestSchemaExtensionVerification(t *testing.T) { net.IP{}.IsPrivate() t.Run("address verification", func(t *testing.T) { iid := x.NewUUID() for _, tc := range []struct { name string schema string expectErr error doc string existing []VerifiableAddress expect []VerifiableAddress }{ { name: "email:must create new address", schema: emailSchemaPath, doc: `{"username":"foo@ory.sh"}`, expect: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, }, }, { name: "email:must create new address because new and existing doesn't match", schema: emailSchemaPath, doc: `{"username":"foo@ory.sh"}`, existing: []VerifiableAddress{ { Value: "bar@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, }, expect: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, }, }, { name: "email:must find existing address in case of match", schema: emailSchemaPath, doc: `{"emails":["baz@ory.sh","foo@ory.sh"]}`, existing: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: true, Status: VerifiableAddressStatusCompleted, Via: AddressTypeEmail, IdentityID: iid, }, { Value: "bar@ory.sh", Verified: true, Status: VerifiableAddressStatusCompleted, Via: AddressTypeEmail, IdentityID: iid, }, }, expect: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: true, Status: VerifiableAddressStatusCompleted, Via: AddressTypeEmail, IdentityID: iid, }, { Value: "baz@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, }, }, { name: "email:must return only one address in case of duplication", schema: emailSchemaPath, doc: `{"emails":["foo@ory.sh","foo@ory.sh","baz@ory.sh"]}`, existing: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: true, Status: VerifiableAddressStatusCompleted, Via: AddressTypeEmail, IdentityID: iid, }, { Value: "bar@ory.sh", Verified: true, Status: VerifiableAddressStatusCompleted, Via: AddressTypeEmail, IdentityID: iid, }, }, expect: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: true, Status: VerifiableAddressStatusCompleted, Via: AddressTypeEmail, IdentityID: iid, }, { Value: "baz@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, }, }, { name: "email:must return error for malformed input", schema: emailSchemaPath, doc: `{"emails":["foo@ory.sh","bar@ory.sh"], "username": "foobar"}`, expectErr: errors.New("I[#/username] S[#/properties/username/format] \"foobar\" is not valid \"email\""), }, { name: "email:must merge email addresses from multiple attributes", schema: emailSchemaPath, doc: `{"emails":["foo@ory.sh","bar@ory.sh","bar@ory.sh"], "username": "foobar@ory.sh"}`, expect: []VerifiableAddress{ { Value: "foo@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, { Value: "bar@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, { Value: "foobar@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: AddressTypeEmail, IdentityID: iid, }, }, }, { name: "phone:must create new address", schema: phoneSchemaPath, doc: `{"username":"+18004444444"}`, expect: []VerifiableAddress{ { Value: "+18004444444", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, { name: "phone:must create new address because new and existing doesn't match", schema: phoneSchemaPath, doc: `{"username":"+18004444444"}`, existing: []VerifiableAddress{ { Value: "+442087599036", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, expect: []VerifiableAddress{ { Value: "+18004444444", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, { name: "phone:must find existing addresses in case of match", schema: phoneSchemaPath, doc: `{"phones":["+18004444444","+442087599036"]}`, existing: []VerifiableAddress{ { Value: "+442087599036", Verified: true, Status: VerifiableAddressStatusCompleted, Via: ChannelTypeSMS, IdentityID: iid, }, { Value: "+380634872774", Verified: true, Status: VerifiableAddressStatusCompleted, Via: ChannelTypeSMS, IdentityID: iid, }, }, expect: []VerifiableAddress{ { Value: "+442087599036", Verified: true, Status: VerifiableAddressStatusCompleted, Via: ChannelTypeSMS, IdentityID: iid, }, { Value: "+18004444444", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, { name: "phone:must return only one address in case of duplication", schema: phoneSchemaPath, doc: `{"phones": ["+18004444444","+18004444444","+442087599036"]}`, existing: []VerifiableAddress{ { Value: "+18004444444", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, { Value: "+380634872774", Verified: true, Status: VerifiableAddressStatusCompleted, Via: ChannelTypeSMS, IdentityID: iid, }, }, expect: []VerifiableAddress{ { Value: "+18004444444", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, { Value: "+442087599036", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, { name: "phone:must merge phones from multiple attributes", schema: phoneSchemaPath, doc: `{"phones": ["+18004444444","+18004444444","+442087599036"], "username": "+380634872774"}`, expect: []VerifiableAddress{ { Value: "+18004444444", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, { Value: "+442087599036", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, { Value: "+380634872774", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, { name: "phone:must return error for malformed input", schema: phoneSchemaPath, doc: `{"phones":["+18004444444","+18004444444","12112112"], "username": "+380634872774"}`, expectErr: errors.New("I[#/phones/2] S[#/properties/phones/items/format] \"12112112\" is not valid \"tel\""), }, { name: "missing format returns an error", schema: missingFormatSchemaPath, doc: `{"phone": "+380634872774"}`, expectErr: errors.New("I[#/phone] S[#/properties/phone/format] no format specified. A format is required if verification is enabled. If this was intentional, please set \"format\" to \"no-validate\""), }, { name: "missing format works for email if format is missing", schema: legacyEmailMissingFormatSchemaPath, doc: `{"email": "user@ory.sh"}`, expect: []VerifiableAddress{ { Value: "user@ory.sh", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeEmail, IdentityID: iid, }, }, }, { name: "format: no-validate works", schema: noValidateSchemaPath, doc: `{"phone": "not a phone number"}`, expect: []VerifiableAddress{ { Value: "not a phone number", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, { // see https://github.com/ory/kratos/issues/3933 name: "phone:should parse +16453331111", schema: phoneSchemaPath, doc: `{"phones":["+16453331111"]}`, expect: []VerifiableAddress{ { Value: "+16453331111", Verified: false, Status: VerifiableAddressStatusPending, Via: ChannelTypeSMS, IdentityID: iid, }, }, }, } { t.Run(fmt.Sprintf("case=%v", tc.name), func(t *testing.T) { id := &Identity{ID: iid, VerifiableAddresses: tc.existing} c := jsonschema.NewCompiler() runner, err := schema.NewExtensionRunner(t.Context()) require.NoError(t, err) e := NewSchemaExtensionVerification(id, time.Minute) runner.AddRunner(e).Register(c) err = c.MustCompile(t.Context(), tc.schema).Validate(bytes.NewBufferString(tc.doc)) if tc.expectErr != nil { require.EqualError(t, err, tc.expectErr.Error()) return } else { require.NoError(t, err) } require.NoError(t, e.Finish()) addresses := id.VerifiableAddresses require.Len(t, addresses, len(tc.expect)) mustContainAddress(t, tc.expect, addresses) }) } }) } func mustContainAddress(t *testing.T, expected, actual []VerifiableAddress) { for _, act := range actual { var found bool for _, expect := range expected { if reflect.DeepEqual(act, expect) { found = true break } } assert.True(t, found, "%+v not in %+v", act, expected) } } func TestMergeVerifiableAddresses(t *testing.T) { for _, tt := range []struct { name string base, overrides, expected []VerifiableAddress }{ { name: "empty base", base: []VerifiableAddress{}, overrides: []VerifiableAddress{{ Value: "override@ory.sh", Via: "email", }}, expected: []VerifiableAddress{}, }, { name: "no overlap", base: []VerifiableAddress{{ Value: "base@ory.sh", Via: "email", }}, overrides: []VerifiableAddress{{ Value: "override@ory.sh", Via: "email", }}, expected: []VerifiableAddress{{ Value: "base@ory.sh", Via: "email", }}, }, { name: "overrides", base: []VerifiableAddress{ {Value: "base@ory.sh", Via: "email"}, {Value: "common-1-is-overwritten@ory.sh", Via: "email"}, {Value: "common-2-is-ignored@ory.sh", Via: "no match"}, }, overrides: []VerifiableAddress{ {Value: "common-1-is-overwritten@ory.sh", Via: "email", Verified: true, Status: VerifiableAddressStatusCompleted}, {Value: "common-2-is-ignored@ory.sh", Via: "wrong via"}, {Value: "override-only-is-ignored@ory.sh", Via: "email"}, }, expected: []VerifiableAddress{ {Value: "base@ory.sh", Via: "email"}, {Value: "common-1-is-overwritten@ory.sh", Via: "email", Verified: true, Status: VerifiableAddressStatusCompleted}, {Value: "common-2-is-ignored@ory.sh", Via: "no match"}, }, }, } { t.Run("case="+tt.name, func(t *testing.T) { assert.Equal(t, tt.expected, merge(tt.base, tt.overrides)) }) } } ================================================ FILE: identity/handler.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "encoding/json" "io" "net/http" "strings" "time" "github.com/ory/kratos/x/nosurfx" "github.com/ory/kratos/x/redir" "github.com/ory/x/httprouterx" "github.com/ory/x/httpx" "github.com/gofrs/uuid" "github.com/ory/x/crdbx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/pagination/migrationpagination" "github.com/ory/x/pagination/pagepagination" "github.com/ory/x/sqlcon" "github.com/ory/kratos/hash" "github.com/ory/kratos/x" "github.com/ory/kratos/cipher" "github.com/ory/herodot" "github.com/pkg/errors" "github.com/ory/x/decoderx" "github.com/ory/x/jsonx" "github.com/ory/x/openapix" "github.com/ory/x/sqlxx" "github.com/ory/x/urlx" "github.com/ory/kratos/driver/config" ) const ( RouteCollection = "/identities" RouteItem = RouteCollection + "/{id}" RouteCredentialItem = RouteItem + "/credentials/{type}" BatchPatchIdentitiesLimit = 1000 BatchPatchIdentitiesWithPasswordLimit = 200 ) type ( dependencies interface { PoolProvider PrivilegedPoolProvider ManagementProvider httpx.WriterProvider config.Provider nosurfx.CSRFProvider cipher.Provider hash.HashProvider } HandlerProvider interface { IdentityHandler() *Handler } Handler struct{ r dependencies } ) func NewHandler(r dependencies) *Handler { return &Handler{r: r} } func (h *Handler) RegisterPublicRoutes(public *httprouterx.RouterPublic) { h.r.CSRFHandler().IgnoreGlobs( RouteCollection, RouteCollection+"/*", RouteCollection+"/*/credentials/*", httprouterx.AdminPrefix+RouteCollection, httprouterx.AdminPrefix+RouteCollection+"/*", httprouterx.AdminPrefix+RouteCollection+"/*/credentials/*", ) public.GET(RouteCollection, redir.RedirectToAdminRoute(h.r)) public.GET(RouteCollection+"/by/external/{externalID}", redir.RedirectToAdminRoute(h.r)) public.GET(RouteItem, redir.RedirectToAdminRoute(h.r)) public.DELETE(RouteItem, redir.RedirectToAdminRoute(h.r)) public.POST(RouteCollection, redir.RedirectToAdminRoute(h.r)) public.PUT(RouteItem, redir.RedirectToAdminRoute(h.r)) public.PATCH(RouteItem, redir.RedirectToAdminRoute(h.r)) public.DELETE(RouteCredentialItem, redir.RedirectToAdminRoute(h.r)) public.GET(httprouterx.AdminPrefix+RouteCollection, redir.RedirectToAdminRoute(h.r)) public.GET(httprouterx.AdminPrefix+RouteCollection+"/by/external/{externalID}", redir.RedirectToAdminRoute(h.r)) public.GET(httprouterx.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r)) public.DELETE(httprouterx.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r)) public.POST(httprouterx.AdminPrefix+RouteCollection, redir.RedirectToAdminRoute(h.r)) public.PUT(httprouterx.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r)) public.PATCH(httprouterx.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r)) public.DELETE(httprouterx.AdminPrefix+RouteCredentialItem, redir.RedirectToAdminRoute(h.r)) } func (h *Handler) RegisterAdminRoutes(admin *httprouterx.RouterAdmin) { admin.GET(RouteCollection, h.list) admin.GET(RouteItem, h.get) admin.GET(RouteCollection+"/by/external/{externalID}", h.getByExternalID) admin.DELETE(RouteItem, h.delete) admin.PATCH(RouteItem, h.patch) admin.POST(RouteCollection, h.create) admin.PATCH(RouteCollection, h.batchPatchIdentities) admin.PUT(RouteItem, h.update) admin.DELETE(RouteCredentialItem, h.deleteIdentityCredentials) } // Paginated Identity List Response // // swagger:response listIdentities type _ struct { migrationpagination.ResponseHeaderAnnotation // List of identities // // in:body Body []Identity } // Paginated List Identity Parameters // // Note: Filters cannot be combined. // // swagger:parameters listIdentities type _ struct { migrationpagination.RequestParameters // Retrieve multiple identities by their IDs. // // This parameter has the following limitations: // // - Duplicate or non-existent IDs are ignored. // - The order of returned IDs may be different from the request. // - This filter does not support pagination. You must implement your own pagination as the maximum number of items returned by this endpoint may not exceed a certain threshold (currently 500). // // required: false // in: query IdsFilter []string `json:"ids"` // CredentialsIdentifier is the identifier (username, email) of the credentials to look up using exact match. // Only one of CredentialsIdentifier and CredentialsIdentifierSimilar can be used. // // required: false // in: query CredentialsIdentifier string `json:"credentials_identifier"` // This is an EXPERIMENTAL parameter that WILL CHANGE. Do NOT rely on consistent, deterministic behavior. // THIS PARAMETER WILL BE REMOVED IN AN UPCOMING RELEASE WITHOUT ANY MIGRATION PATH. // // CredentialsIdentifierSimilar is the (partial) identifier (username, email) of the credentials to look up using similarity search. // Only one of CredentialsIdentifier and CredentialsIdentifierSimilar can be used. // // required: false // in: query CredentialsIdentifierSimilar string `json:"preview_credentials_identifier_similar"` // Include Credentials in Response // // Include any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return // the initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available. // // required: false // in: query DeclassifyCredentials []string `json:"include_credential"` // List identities that belong to a specific organization. // // required: false // in: query OrganizationID string `json:"organization_id"` crdbx.ConsistencyRequestParameters } func parseListIdentitiesParameters(r *http.Request) (params ListIdentityParameters, err error) { query := r.URL.Query() var requestedFilters int params.Expand = ExpandDefault if ids := query["ids"]; len(ids) > 0 { requestedFilters++ for _, v := range ids { id, err := uuid.FromString(v) if err != nil { return params, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid UUID value `%s` for parameter `ids`.", v)) } params.IdsFilter = append(params.IdsFilter, id) } } if len(params.IdsFilter) > 500 { return params, errors.WithStack(herodot.ErrBadRequest.WithReason("The number of ids to filter must not exceed 500.")) } if orgID := query.Get("organization_id"); orgID != "" { requestedFilters++ params.OrganizationID, err = uuid.FromString(orgID) if err != nil { return params, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid UUID value `%s` for parameter `organization_id`.", orgID)) } } if identifier := query.Get("credentials_identifier"); identifier != "" { requestedFilters++ params.Expand = ExpandEverything params.CredentialsIdentifier = identifier } if identifier := query.Get("preview_credentials_identifier_similar"); identifier != "" { requestedFilters++ params.Expand = ExpandEverything params.CredentialsIdentifierSimilar = identifier } for _, v := range query["include_credential"] { params.Expand = ExpandEverything tc, ok := ParseCredentialsType(v) if !ok { return params, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid value `%s` for parameter `include_credential`.", v)) } params.DeclassifyCredentials = append(params.DeclassifyCredentials, tc) } if requestedFilters > 1 { return params, errors.WithStack(herodot.ErrBadRequest.WithReason("You cannot combine multiple filters in this API")) } params.KeySetPagination, params.PagePagination, err = x.ParseKeysetOrPagePagination(r) if err != nil { return params, err } params.ConsistencyLevel = crdbx.ConsistencyLevelFromRequest(r) return params, nil } // swagger:route GET /admin/identities identity listIdentities // // # List Identities // // Lists all [identities](https://www.ory.sh/docs/kratos/concepts/identity-user-model) in the system. Note: filters cannot be combined. // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 200: listIdentities // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-medium func (h *Handler) list(w http.ResponseWriter, r *http.Request) { params, err := parseListIdentitiesParameters(r) if err != nil { h.r.Writer().WriteError(w, r, err) return } is, nextPage, err := h.r.IdentityPool().ListIdentities(r.Context(), params) if err != nil { h.r.Writer().WriteError(w, r, err) return } if params.PagePagination != nil { total := int64(len(is)) if params.CredentialsIdentifier == "" { total, err = h.r.IdentityPool().CountIdentities(r.Context()) if err != nil { h.r.Writer().WriteError(w, r, err) return } } u := *r.URL pagepagination.PaginationHeader(w, &u, total, params.PagePagination.Page, params.PagePagination.ItemsPerPage) } else if nextPage != nil { u := *r.URL keysetpagination.Header(w, &u, nextPage) } // Identities using the marshaler for including metadata_admin isam := make([]WithCredentialsAndAdminMetadataInJSON, len(is)) for i, identity := range is { emit, err := identity.WithDeclassifiedCredentials(r.Context(), h.r, params.DeclassifyCredentials) if err != nil { h.r.Writer().WriteError(w, r, err) return } isam[i] = WithCredentialsAndAdminMetadataInJSON(*emit) } h.r.Writer().Write(w, r, isam) } // Get Identity Parameters // // swagger:parameters getIdentity // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type getIdentity struct { // ID must be set to the ID of identity you want to get // // required: true // in: path ID string `json:"id"` // Include Credentials in Response // // Include any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return // the initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available. // // required: false // in: query DeclassifyCredentials []CredentialsType `json:"include_credential"` } // Get Identity By External ID Parameters // // swagger:parameters getIdentityByExternalID // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type getIdentityByExternalID struct { // ExternalID must be set to the ID of identity you want to get // // required: true // in: path ExternalID string `json:"externalID"` // Include Credentials in Response // // Include any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return // the initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available. // // required: false // in: query DeclassifyCredentials []CredentialsType `json:"include_credential"` } // swagger:route GET /admin/identities/{id} identity getIdentity // // # Get an Identity // // Return an [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model) by its ID. You can optionally // include credentials (e.g. social sign in connections) in the response by using the `include_credential` query parameter. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 200: identity // 404: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-low func (h *Handler) get(w http.ResponseWriter, r *http.Request) { i, err := h.r.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), x.ParseUUID(r.PathValue("id"))) if err != nil { h.r.Writer().WriteError(w, r, err) return } includeCredentials := r.URL.Query()["include_credential"] var declassify []CredentialsType for _, v := range includeCredentials { tc, ok := ParseCredentialsType(v) if ok { declassify = append(declassify, tc) } else { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid value `%s` for parameter `include_credential`.", declassify))) return } } emit, err := i.WithDeclassifiedCredentials(r.Context(), h.r, declassify) if err != nil { h.r.Writer().WriteError(w, r, err) return } h.r.Writer().Write(w, r, WithCredentialsAndAdminMetadataInJSON(*emit)) } // swagger:route GET /admin/identities/by/external/{externalID} identity getIdentityByExternalID // // # Get an Identity by its External ID // // Return an [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model) by its external ID. You can optionally // include credentials (e.g. social sign in connections) in the response by using the `include_credential` query parameter. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 200: identity // 404: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-medium func (h *Handler) getByExternalID(w http.ResponseWriter, r *http.Request) { externalID := r.PathValue("externalID") if externalID == "" { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReason("The external ID must not be empty."))) return } i, err := h.r.PrivilegedIdentityPool().FindIdentityByExternalID(r.Context(), externalID, ExpandEverything) if err != nil { h.r.Writer().WriteError(w, r, err) return } includeCredentials := r.URL.Query()["include_credential"] var declassify []CredentialsType for _, v := range includeCredentials { tc, ok := ParseCredentialsType(v) if ok { declassify = append(declassify, tc) } else { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid value `%s` for parameter `include_credential`.", declassify))) return } } emit, err := i.WithDeclassifiedCredentials(r.Context(), h.r, declassify) if err != nil { h.r.Writer().WriteError(w, r, err) return } h.r.Writer().Write(w, r, WithCredentialsAndAdminMetadataInJSON(*emit)) } // Create Identity Parameters // // swagger:parameters createIdentity // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type createIdentity struct { // in: body Body CreateIdentityBody } // Create Identity Body // // swagger:model createIdentityBody type CreateIdentityBody struct { // SchemaID is the ID of the JSON Schema to be used for validating the identity's traits. // // required: true SchemaID string `json:"schema_id"` // Traits represent an identity's traits. The identity is able to create, modify, and delete traits // in a self-service manner. The input will always be validated against the JSON Schema defined // in `schema_url`. // // required: true Traits json.RawMessage `json:"traits"` // Credentials represents all credentials that can be used for authenticating this identity. // // Use this structure to import credentials for a user. Credentials *IdentityWithCredentials `json:"credentials"` // VerifiableAddresses contains all the addresses that can be verified by the user. // // Use this structure to import verified addresses for an identity. Please keep in mind // that the address needs to be represented in the Identity Schema or this field will be overwritten // on the next identity update. VerifiableAddresses []VerifiableAddress `json:"verifiable_addresses"` // RecoveryAddresses contains all the addresses that can be used to recover an identity. // // Use this structure to import recovery addresses for an identity. Please keep in mind // that the address needs to be represented in the Identity Schema or this field will be overwritten // on the next identity update. RecoveryAddresses []RecoveryAddress `json:"recovery_addresses"` // Store metadata about the identity which the identity itself can see when calling for example the // session endpoint. Do not store sensitive information (e.g. credit score) about the identity in this field. MetadataPublic json.RawMessage `json:"metadata_public"` // Store metadata about the user which is only accessible through admin APIs such as `GET /admin/identities/`. MetadataAdmin json.RawMessage `json:"metadata_admin,omitempty"` // State is the identity's state. // // required: false State State `json:"state"` // OrganizationID is the ID of the organization to which the identity belongs. // // required: false OrganizationID uuid.NullUUID `json:"organization_id"` // ExternalID is an optional external ID of the identity. This is used to link // the identity to an external system. If set, the external ID must be unique // across all identities. // // required: false ExternalID string `json:"external_id,omitempty"` } // Create Identity and Import Credentials // // swagger:model identityWithCredentials type IdentityWithCredentials struct { // Password if set will import a password credential. Password *AdminIdentityImportCredentialsPassword `json:"password"` // OIDC if set will import an OIDC credential. OIDC *AdminIdentityImportCredentialsOIDC `json:"oidc"` // OIDC if set will import an OIDC credential. SAML *AdminIdentityImportCredentialsSAML `json:"saml"` } // Create Identity and Import Password Credentials // // swagger:model identityWithCredentialsPassword type AdminIdentityImportCredentialsPassword struct { // Configuration options for the import. Config AdminIdentityImportCredentialsPasswordConfig `json:"config"` } // Create Identity and Import Password Credentials Configuration // // swagger:model identityWithCredentialsPasswordConfig type AdminIdentityImportCredentialsPasswordConfig struct { // The hashed password in [PHC format](https://www.ory.sh/docs/kratos/manage-identities/import-user-accounts-identities#hashed-passwords) HashedPassword string `json:"hashed_password"` // The password in plain text if no hash is available. Password string `json:"password"` // If set to true, the password will be migrated using the password migration hook. UsePasswordMigrationHook bool `json:"use_password_migration_hook,omitempty"` } // Create Identity and Import Social Sign In Credentials // // swagger:model identityWithCredentialsOidc type AdminIdentityImportCredentialsOIDC struct { // Configuration options for the import. Config AdminIdentityImportCredentialsOIDCConfig `json:"config"` } // swagger:model identityWithCredentialsOidcConfig type AdminIdentityImportCredentialsOIDCConfig struct { // A list of OpenID Connect Providers Providers []AdminCreateIdentityImportCredentialsOIDCProvider `json:"providers"` } // Create Identity and Import Social Sign In Credentials Configuration // // swagger:model identityWithCredentialsOidcConfigProvider type AdminCreateIdentityImportCredentialsOIDCProvider struct { // The subject (`sub`) of the OpenID Connect connection. Usually the `sub` field of the ID Token. // // required: true Subject string `json:"subject"` // The OpenID Connect provider to link the subject to. Usually something like `google` or `github`. // // required: true Provider string `json:"provider"` // If set, this credential allows the user to sign in using the OpenID Connect provider without setting the subject first. // // required: false UseAutoLink bool `json:"use_auto_link,omitempty"` // The organization to assign for the provider. Organization uuid.NullUUID `json:"organization,omitempty"` } // Payload to import SAML credentials // // swagger:model identityWithCredentialsSaml type AdminIdentityImportCredentialsSAML struct { // Configuration options for the import. Config AdminIdentityImportCredentialsSAMLConfig `json:"config"` } // Payload of SAML providers // // swagger:model identityWithCredentialsSamlConfig type AdminIdentityImportCredentialsSAMLConfig struct { // A list of SAML Providers Providers []AdminCreateIdentityImportCredentialsSAMLProvider `json:"providers"` } // Payload of specific SAML provider // // swagger:model identityWithCredentialsSamlConfigProvider type AdminCreateIdentityImportCredentialsSAMLProvider struct { // The unique subject of the SAML connection. This value must be immutable at the source. // // required: true Subject string `json:"subject"` // The SAML provider to link the subject to. // // required: true Provider string `json:"provider"` // The organization to assign for the provider. Organization uuid.NullUUID `json:"organization"` } // swagger:route POST /admin/identities identity createIdentity // // # Create an Identity // // Create an [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model). This endpoint can also be used to // [import credentials](https://www.ory.sh/docs/kratos/manage-identities/import-user-accounts-identities) // for instance passwords, social sign in configurations or multifactor methods. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 201: identity // 400: errorGeneric // 409: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) create(w http.ResponseWriter, r *http.Request) { var cr CreateIdentityBody if err := jsonx.NewStrictDecoder(r.Body).Decode(&cr); err != nil { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithError(err.Error()))) return } i, err := h.identityFromCreateIdentityBody(r.Context(), &cr) if err != nil { h.r.Writer().WriteError(w, r, err) return } if err := h.r.IdentityManager().Create(r.Context(), i); err != nil { if errors.Is(err, sqlcon.ErrUniqueViolation) { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrConflict.WithReason("This identity conflicts with another identity that already exists."))) } else { h.r.Writer().WriteError(w, r, err) } return } h.r.Writer().WriteCreated(w, r, urlx.AppendPaths( h.r.Config().SelfAdminURL(r.Context()), "identities", i.ID.String(), ).String(), WithCredentialsNoConfigAndAdminMetadataInJSON(*i), ) } func (h *Handler) identityFromCreateIdentityBody(ctx context.Context, cr *CreateIdentityBody) (*Identity, error) { stateChangedAt := sqlxx.NullTime(time.Now()) state := StateActive if cr.State != "" { if err := cr.State.IsValid(); err != nil { return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("%s", err).WithWrap(err)) } state = cr.State } i := &Identity{ SchemaID: cr.SchemaID, Traits: []byte(cr.Traits), State: state, StateChangedAt: &stateChangedAt, VerifiableAddresses: cr.VerifiableAddresses, RecoveryAddresses: cr.RecoveryAddresses, MetadataAdmin: []byte(cr.MetadataAdmin), MetadataPublic: []byte(cr.MetadataPublic), OrganizationID: cr.OrganizationID, ExternalID: sqlxx.NullString(cr.ExternalID), } // Lowercase all emails, because the schema extension will otherwise not find them. for k := range i.VerifiableAddresses { i.VerifiableAddresses[k].Value = strings.ToLower(i.VerifiableAddresses[k].Value) } for k := range i.RecoveryAddresses { i.RecoveryAddresses[k].Value = strings.ToLower(i.RecoveryAddresses[k].Value) } if err := h.importCredentials(ctx, i, cr.Credentials); err != nil { return nil, err } return i, nil } // swagger:route PATCH /admin/identities identity batchPatchIdentities // // # Create multiple identities // // Creates multiple [identities](https://www.ory.com/docs/kratos/concepts/identity-user-model). // // You can also use this endpoint to [import credentials](https://www.ory.com/docs/kratos/manage-identities/import-user-accounts-identities), // including passwords, social sign-in settings, and multi-factor authentication methods. // // If the patch includes hashed passwords you can import up to 1,000 identities per request. // // If the patch includes at least one plaintext password you can import up to 200 identities per request. // // Avoid importing large batches with plaintext passwords. They can cause timeouts as the passwords need to be hashed before they are stored. // // If at least one identity is imported successfully, the response status is 200 OK. // If all imports fail, the response is one of the following 4xx errors: // - 400 Bad Request: The request payload is invalid or improperly formatted. // - 409 Conflict: Duplicate identities or conflicting data were detected. // // If you get a 504 Gateway Timeout: // - Reduce the batch size // - Avoid duplicate identities // - Pre-hash passwords with BCrypt // // If the issue persists, contact support. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 200: batchPatchIdentitiesResponse // 400: errorGeneric // 409: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) batchPatchIdentities(w http.ResponseWriter, r *http.Request) { var ( req BatchPatchIdentitiesBody res batchPatchIdentitiesResponse ) if err := jsonx.NewStrictDecoder(r.Body).Decode(&req); err != nil { h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.WithStack(err)) return } if len(req.Identities) > BatchPatchIdentitiesLimit { h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.WithStack(herodot.ErrBadRequest.WithReasonf( "The maximum number of identities per request that can be created or deleted at once is %d.", BatchPatchIdentitiesLimit))) return } res.Identities = make([]*BatchIdentityPatchResponse, len(req.Identities)) // Array to look up the index of the identity in the identities array. indexInIdentities := make([]*int, len(req.Identities)) identities := make([]*Identity, 0, len(req.Identities)) var withUnHashedPasswordCount int for i, patch := range req.Identities { if patch.Create != nil { res.Identities[i] = &BatchIdentityPatchResponse{ Action: ActionCreate, PatchID: patch.ID, } identity, err := h.identityFromCreateIdentityBody(r.Context(), patch.Create) if err != nil { h.r.Writer().WriteError(w, r, err) return } identities = append(identities, identity) idx := len(identities) - 1 indexInIdentities[i] = &idx if patch.Create.Credentials != nil && patch.Create.Credentials.Password != nil && patch.Create.Credentials.Password.Config.Password != "" { withUnHashedPasswordCount++ } } } if withUnHashedPasswordCount > BatchPatchIdentitiesWithPasswordLimit { h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, errors.WithStack(herodot.ErrBadRequest.WithReasonf( "The maximum number of identities per request that can be created with a plaintext password is %d.", BatchPatchIdentitiesWithPasswordLimit))) return } err := h.r.IdentityManager().CreateIdentities(r.Context(), identities) partialErr := new(CreateIdentitiesError) if err != nil && !errors.As(err, &partialErr) { h.r.Writer().WriteError(w, r, err) return } for resIdx, identitiesIdx := range indexInIdentities { if identitiesIdx != nil { ident := identities[*identitiesIdx] // Check if the identity was created successfully. if failed := partialErr.Find(ident); failed != nil { res.Identities[resIdx].Action = ActionError res.Identities[resIdx].Error = failed.Error } else { res.Identities[resIdx].IdentityID = &ident.ID } } } h.r.Writer().Write(w, r, &res) } // Update Identity Parameters // // swagger:parameters updateIdentity // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type updateIdentity struct { // ID must be set to the ID of identity you want to update // // required: true // in: path ID string `json:"id"` // in: body Body UpdateIdentityBody } // Update Identity Body // // swagger:model updateIdentityBody type UpdateIdentityBody struct { // SchemaID is the ID of the JSON Schema to be used for validating the identity's traits. If set // will update the Identity's SchemaID. // // required: true SchemaID string `json:"schema_id"` // Traits represent an identity's traits. The identity is able to create, modify, and delete traits // in a self-service manner. The input will always be validated against the JSON Schema defined // in `schema_id`. // // required: true Traits json.RawMessage `json:"traits"` // Credentials represents all credentials that can be used for authenticating this identity. // // Use this structure to import credentials for a user. // Note: this wil override completely identity's credentials. If used incorrectly, this can cause a user to lose // access to their account! Credentials *IdentityWithCredentials `json:"credentials"` // Store metadata about the identity which the identity itself can see when calling for example the // session endpoint. Do not store sensitive information (e.g. credit score) about the identity in this field. MetadataPublic json.RawMessage `json:"metadata_public"` // Store metadata about the user which is only accessible through admin APIs such as `GET /admin/identities/`. MetadataAdmin json.RawMessage `json:"metadata_admin,omitempty"` // State is the identity's state. // // required: true State State `json:"state"` // ExternalID is an optional external ID of the identity. This is used to link // the identity to an external system. If set, the external ID must be unique // across all identities. // // required: false ExternalID string `json:"external_id,omitempty"` } // swagger:route PUT /admin/identities/{id} identity updateIdentity // // # Update an Identity // // This endpoint updates an [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model). The full identity // payload, except credentials, is expected. For partial updates, use the [patchIdentity](https://www.ory.sh/docs/reference/api#tag/identity/operation/patchIdentity) operation. // // A credential can be provided via the `credentials` field in the request body. // If provided, the credentials will be imported and added to the existing credentials of the identity. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 200: identity // 400: errorGeneric // 404: errorGeneric // 409: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) update(w http.ResponseWriter, r *http.Request) { var ur UpdateIdentityBody if err := decoderx.Decode(r, &ur, decoderx.HTTPJSONDecoder()); err != nil { h.r.Writer().WriteError(w, r, err) return } id := x.ParseUUID(r.PathValue("id")) identity, err := h.r.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), id) if err != nil { h.r.Writer().WriteError(w, r, err) return } if ur.SchemaID != "" { identity.SchemaID = ur.SchemaID } if ur.State != "" && identity.State != ur.State { if err := ur.State.IsValid(); err != nil { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("%s", err).WithWrap(err))) return } stateChangedAt := sqlxx.NullTime(time.Now()) identity.State = ur.State identity.StateChangedAt = &stateChangedAt } identity.Traits = []byte(ur.Traits) identity.MetadataPublic = []byte(ur.MetadataPublic) identity.MetadataAdmin = []byte(ur.MetadataAdmin) identity.ExternalID = sqlxx.NullString(ur.ExternalID) // Although this is PUT and not PATCH, if the Credentials are not supplied keep the old one if ur.Credentials != nil { if err := h.importCredentials(r.Context(), identity, ur.Credentials); err != nil { h.r.Writer().WriteError(w, r, err) return } } if err := h.r.IdentityManager().Update( r.Context(), identity, ManagerAllowWriteProtectedTraits, ); err != nil { h.r.Writer().WriteError(w, r, err) return } h.r.Writer().Write(w, r, WithCredentialsNoConfigAndAdminMetadataInJSON(*identity)) } // Delete Identity Parameters // // swagger:parameters deleteIdentity // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type deleteIdentity struct { // ID is the identity's ID. // // required: true // in: path ID string `json:"id"` } // swagger:route DELETE /admin/identities/{id} identity deleteIdentity // // # Delete an Identity // // Calling this endpoint irrecoverably and permanently deletes the [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model) given its ID. This action can not be undone. // This endpoint returns 204 when the identity was deleted or 404 if the identity was not found. // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 204: emptyResponse // 404: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) delete(w http.ResponseWriter, r *http.Request) { if err := h.r.PrivilegedIdentityPool().DeleteIdentity(r.Context(), x.ParseUUID(r.PathValue("id"))); err != nil { h.r.Writer().WriteError(w, r, err) return } w.WriteHeader(http.StatusNoContent) } // Patch Identity Parameters // // swagger:parameters patchIdentity // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type patchIdentity struct { // ID must be set to the ID of identity you want to update // // required: true // in: path ID string `json:"id"` // in: body Body openapix.JSONPatchDocument } // swagger:route PATCH /admin/identities/{id} identity patchIdentity // // # Patch an Identity // // Partially updates an [identity's](https://www.ory.sh/docs/kratos/concepts/identity-user-model) field using [JSON Patch](https://jsonpatch.com/). // The fields `id`, `stateChangedAt` and `credentials` can not be updated using this method. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 200: identity // 400: errorGeneric // 404: errorGeneric // 409: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) patch(w http.ResponseWriter, r *http.Request) { requestBody, err := io.ReadAll(r.Body) if err != nil { h.r.Writer().WriteError(w, r, err) return } id := x.ParseUUID(r.PathValue("id")) identity, err := h.r.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), id) if err != nil { h.r.Writer().WriteError(w, r, err) return } oldState := identity.State patchedIdentity, err := jsonx.ApplyJSONPatch(requestBody, WithCredentialsAndAdminMetadataInJSON(*identity), "/id", "/stateChangedAt", "/credentials", "/credentials/oidc/**") if err != nil { h.r.Writer().WriteError(w, r, errors.WithStack( herodot. ErrBadRequest. WithReasonf("An error occured when applying the JSON patch"). WithErrorf("%v", err). WithWrap(err), )) return } if oldState != patchedIdentity.State { // Check if the changed state was actually valid if err := patchedIdentity.State.IsValid(); err != nil { h.r.Writer().WriteError(w, r, errors.WithStack( herodot. ErrBadRequest. WithReasonf("The supplied state ('%s') was not valid. Valid states are ('%s', '%s').", string(patchedIdentity.State), StateActive, StateInactive). WithErrorf("%v", err). WithWrap(err), )) return } // If the state changed, we need to update the timestamp of it stateChangedAt := sqlxx.NullTime(time.Now()) patchedIdentity.StateChangedAt = &stateChangedAt } updatedIdentity := Identity(patchedIdentity) if err := h.r.IdentityManager().Update( r.Context(), &updatedIdentity, ManagerAllowWriteProtectedTraits, ); err != nil { h.r.Writer().WriteError(w, r, err) return } h.r.Writer().Write(w, r, WithCredentialsNoConfigAndAdminMetadataInJSON(updatedIdentity)) } // Delete Credential Parameters // // swagger:parameters deleteIdentityCredentials type _ struct { // ID is the identity's ID. // // required: true // in: path ID string `json:"id"` // Type is the type of credentials to delete. // // required: true // in: path Type CredentialsType `json:"type"` // Identifier is the identifier of the OIDC/SAML credential to delete. // Find the identifier by calling the `GET /admin/identities/{id}?include_credential={oidc,saml}` endpoint. // // required: false // in: query Identifier string `json:"identifier"` } // swagger:route DELETE /admin/identities/{id}/credentials/{type} identity deleteIdentityCredentials // // # Delete a credential for a specific identity // // Delete an [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model) credential by its type. // You cannot delete passkeys or code auth credentials through this API. // // Consumes: // - application/json // // Produces: // - application/json // // Schemes: http, https // // Security: // oryAccessToken: // // Responses: // 204: emptyResponse // 404: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-high func (h *Handler) deleteIdentityCredentials(w http.ResponseWriter, r *http.Request) { ctx := r.Context() identity, err := h.r.PrivilegedIdentityPool().GetIdentityConfidential(ctx, x.ParseUUID(r.PathValue("id"))) if err != nil { h.r.Writer().WriteError(w, r, err) return } cred, ok := identity.GetCredentials(CredentialsType(r.PathValue("type"))) if !ok { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrNotFound.WithReasonf("You tried to remove a %s but this user have no %s set up.", r.PathValue("type"), r.PathValue("type")))) return } switch cred.Type { case CredentialsTypeLookup, CredentialsTypeTOTP: identity.DeleteCredentialsType(cred.Type) case CredentialsTypeWebAuthn: if err = identity.deleteCredentialWebAuthFromIdentity(); err != nil { h.r.Writer().WriteError(w, r, err) return } case CredentialsTypePassword, CredentialsTypeOIDC, CredentialsTypeSAML: firstFactor, err := h.r.IdentityManager().CountActiveFirstFactorCredentials(ctx, identity) if err != nil { h.r.Writer().WriteError(w, r, err) return } if firstFactor < 2 { h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReason("You cannot remove the last first factor credential."))) return } switch cred.Type { case CredentialsTypePassword: if err := identity.deleteCredentialPassword(); err != nil { h.r.Writer().WriteError(w, r, err) return } case CredentialsTypeOIDC, CredentialsTypeSAML: if err := identity.deleteCredentialOIDCSAMLFromIdentity(cred.Type, r.URL.Query().Get("identifier")); err != nil { h.r.Writer().WriteError(w, r, err) return } } default: // A bunch of credential type deletions are not yet implemented, e.g. passkeys, etc. h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Credentials type %s cannot be deleted.", cred.Type))) return } if err := h.r.IdentityManager().Update( ctx, identity, ManagerAllowWriteProtectedTraits, ); err != nil { h.r.Writer().WriteError(w, r, err) return } w.WriteHeader(http.StatusNoContent) } ================================================ FILE: identity/handler_import.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "encoding/json" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/hash" "github.com/ory/kratos/x" ) func (h *Handler) importCredentials(ctx context.Context, i *Identity, creds *IdentityWithCredentials) error { if creds == nil { return nil } // This method only support password and OIDC import at the moment. // If other methods are added please ensure that the available AAL is set correctly in the identity. // // It would actually be good if we would validate the identity post-creation to see if the credentials are working. if creds.Password != nil { // This method is somewhat hacky, because it does not set the credential's identifier. It relies on the // identity validation to set the identifier, which is called after this method. // // It would be good to make this explicit. if err := h.importPasswordCredentials(ctx, i, creds.Password); err != nil { return err } } if creds.OIDC != nil { if err := h.importOIDCCredentials(ctx, i, creds.OIDC); err != nil { return err } } if creds.SAML != nil { if err := h.importSAMLCredentials(ctx, i, creds.SAML); err != nil { return err } } return nil } func (h *Handler) importPasswordCredentials(ctx context.Context, i *Identity, creds *AdminIdentityImportCredentialsPassword) (err error) { if creds.Config.UsePasswordMigrationHook { return i.SetCredentialsWithConfig(CredentialsTypePassword, Credentials{}, CredentialsPassword{UsePasswordMigrationHook: true}) } // In here we deliberately ignore any password policies as the point here is to import passwords, even if they // are not matching the policy, as the user needs to able to sign in with their old password. hashed := []byte(creds.Config.HashedPassword) if len(creds.Config.Password) > 0 { // Importing a clear text password hashed, err = h.r.Hasher(ctx).Generate(ctx, []byte(creds.Config.Password)) if err != nil { return err } creds.Config.HashedPassword = string(hashed) } if !(hash.IsValidHashFormat(hashed)) { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("The imported password does not match any known hash format. For more information see https://www.ory.sh/dr/2")) } return i.SetCredentialsWithConfig(CredentialsTypePassword, Credentials{}, CredentialsPassword{HashedPassword: string(hashed)}) } func (h *Handler) importOIDCCredentials(_ context.Context, i *Identity, creds *AdminIdentityImportCredentialsOIDC) error { var target CredentialsOIDC c, ok := i.GetCredentials(CredentialsTypeOIDC) if !ok { var providers []CredentialsOIDCProvider var ids []string for _, p := range creds.Config.Providers { ids = append(ids, OIDCUniqueID(p.Provider, p.Subject)) provider := CredentialsOIDCProvider{ Subject: p.Subject, Provider: p.Provider, UseAutoLink: p.UseAutoLink, } if p.Organization.Valid { provider.Organization = p.Organization.UUID.String() } providers = append(providers, provider) } return i.SetCredentialsWithConfig( CredentialsTypeOIDC, Credentials{Identifiers: ids}, CredentialsOIDC{Providers: providers}, ) } if err := json.Unmarshal(c.Config, &target); err != nil { return errors.WithStack(x.PseudoPanic.WithWrap(err)) } for _, p := range creds.Config.Providers { c.Identifiers = append(c.Identifiers, OIDCUniqueID(p.Provider, p.Subject)) provider := CredentialsOIDCProvider{ Subject: p.Subject, Provider: p.Provider, UseAutoLink: p.UseAutoLink, } if p.Organization.Valid { provider.Organization = p.Organization.UUID.String() } target.Providers = append(target.Providers, provider) } return i.SetCredentialsWithConfig(CredentialsTypeOIDC, *c, &target) } func (h *Handler) importSAMLCredentials(_ context.Context, i *Identity, creds *AdminIdentityImportCredentialsSAML) error { var target CredentialsOIDC c, ok := i.GetCredentials(CredentialsTypeSAML) if !ok { var providers []CredentialsOIDCProvider var ids []string for _, p := range creds.Config.Providers { ids = append(ids, OIDCUniqueID(p.Provider, p.Subject)) provider := CredentialsOIDCProvider{ Subject: p.Subject, Provider: p.Provider, } if p.Organization.Valid { provider.Organization = p.Organization.UUID.String() } providers = append(providers, provider) } return i.SetCredentialsWithConfig( CredentialsTypeSAML, Credentials{Identifiers: ids}, CredentialsOIDC{Providers: providers}, ) } if err := json.Unmarshal(c.Config, &target); err != nil { return errors.WithStack(x.PseudoPanic.WithWrap(err)) } for _, p := range creds.Config.Providers { c.Identifiers = append(c.Identifiers, OIDCUniqueID(p.Provider, p.Subject)) provider := CredentialsOIDCProvider{ Subject: p.Subject, Provider: p.Provider, } if p.Organization.Valid { provider.Organization = p.Organization.UUID.String() } target.Providers = append(target.Providers, provider) } return i.SetCredentialsWithConfig(CredentialsTypeSAML, *c, &target) } ================================================ FILE: identity/handler_import_test.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/x/snapshotx" ) func TestImportCredentials(t *testing.T) { t.Parallel() ctx := context.Background() // Setup handler with minimal mock requirements h := &Handler{} testCases := []struct { name string setupIdentity func() *Identity credentials interface{} credType CredentialsType }{ { name: "OIDC new credential without organization", setupIdentity: func() *Identity { return &Identity{} }, credentials: &AdminIdentityImportCredentialsOIDC{ Config: AdminIdentityImportCredentialsOIDCConfig{ Providers: []AdminCreateIdentityImportCredentialsOIDCProvider{ { Provider: "github", Subject: "12345", }, }, }, }, credType: CredentialsTypeOIDC, }, { name: "OIDC new credential with organization", setupIdentity: func() *Identity { return &Identity{} }, credentials: &AdminIdentityImportCredentialsOIDC{ Config: AdminIdentityImportCredentialsOIDCConfig{ Providers: []AdminCreateIdentityImportCredentialsOIDCProvider{ { Provider: "github", Subject: "12345", Organization: uuid.NullUUID{UUID: uuid.FromStringOrNil("e7e3cbae-04cc-45f3-ae52-ea749a2ffaff"), Valid: true}, }, }, }, }, credType: CredentialsTypeOIDC, }, { name: "OIDC update credential without organization", setupIdentity: func() *Identity { i := &Identity{} _ = i.SetCredentialsWithConfig( CredentialsTypeOIDC, Credentials{ Identifiers: []string{OIDCUniqueID("google", "67890")}, }, CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Provider: "google", Subject: "67890", }, }, }, ) return i }, credentials: &AdminIdentityImportCredentialsOIDC{ Config: AdminIdentityImportCredentialsOIDCConfig{ Providers: []AdminCreateIdentityImportCredentialsOIDCProvider{ { Provider: "github", Subject: "12345", }, }, }, }, credType: CredentialsTypeOIDC, }, { name: "OIDC update credential with organization", setupIdentity: func() *Identity { i := &Identity{} _ = i.SetCredentialsWithConfig( CredentialsTypeOIDC, Credentials{ Identifiers: []string{OIDCUniqueID("google", "67890")}, }, CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Provider: "google", Subject: "67890", }, }, }, ) return i }, credentials: &AdminIdentityImportCredentialsOIDC{ Config: AdminIdentityImportCredentialsOIDCConfig{ Providers: []AdminCreateIdentityImportCredentialsOIDCProvider{ { Provider: "github", Subject: "12345", Organization: uuid.NullUUID{UUID: uuid.FromStringOrNil("e7e3cbae-04cc-45f3-ae52-ea749a2ffaff"), Valid: true}, }, }, }, }, credType: CredentialsTypeOIDC, }, { name: "OIDC update with multiple providers", setupIdentity: func() *Identity { i := &Identity{} _ = i.SetCredentialsWithConfig( CredentialsTypeOIDC, Credentials{ Identifiers: []string{OIDCUniqueID("google", "67890")}, }, CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Provider: "google", Subject: "67890", }, }, }, ) return i }, credentials: &AdminIdentityImportCredentialsOIDC{ Config: AdminIdentityImportCredentialsOIDCConfig{ Providers: []AdminCreateIdentityImportCredentialsOIDCProvider{ { Provider: "github", Subject: "12345", Organization: uuid.NullUUID{UUID: uuid.FromStringOrNil("e7e3cbae-04cc-45f3-ae52-ea749a2ffaff"), Valid: true}, }, { Provider: "gitlab", Subject: "abcdef", }, }, }, }, credType: CredentialsTypeOIDC, }, { name: "SAML new credential without organization", setupIdentity: func() *Identity { return &Identity{} }, credentials: &AdminIdentityImportCredentialsSAML{ Config: AdminIdentityImportCredentialsSAMLConfig{ Providers: []AdminCreateIdentityImportCredentialsSAMLProvider{ { Provider: "okta", Subject: "user123", }, }, }, }, credType: CredentialsTypeSAML, }, { name: "SAML new credential with organization", setupIdentity: func() *Identity { return &Identity{} }, credentials: &AdminIdentityImportCredentialsSAML{ Config: AdminIdentityImportCredentialsSAMLConfig{ Providers: []AdminCreateIdentityImportCredentialsSAMLProvider{ { Provider: "okta", Subject: "user123", Organization: uuid.NullUUID{UUID: uuid.FromStringOrNil("e7e3cbae-04cc-45f3-ae52-ea749a2ffaff"), Valid: true}, }, }, }, }, credType: CredentialsTypeSAML, }, { name: "SAML update credential without organization", setupIdentity: func() *Identity { i := &Identity{} _ = i.SetCredentialsWithConfig( CredentialsTypeSAML, Credentials{ Identifiers: []string{OIDCUniqueID("onelogin", "user456")}, }, CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Provider: "onelogin", Subject: "user456", }, }, }, ) return i }, credentials: &AdminIdentityImportCredentialsSAML{ Config: AdminIdentityImportCredentialsSAMLConfig{ Providers: []AdminCreateIdentityImportCredentialsSAMLProvider{ { Provider: "okta", Subject: "user123", }, }, }, }, credType: CredentialsTypeSAML, }, { name: "SAML update credential with organization", setupIdentity: func() *Identity { i := &Identity{} _ = i.SetCredentialsWithConfig( CredentialsTypeSAML, Credentials{ Identifiers: []string{OIDCUniqueID("onelogin", "user456")}, }, CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Provider: "onelogin", Subject: "user456", }, }, }, ) return i }, credentials: &AdminIdentityImportCredentialsSAML{ Config: AdminIdentityImportCredentialsSAMLConfig{ Providers: []AdminCreateIdentityImportCredentialsSAMLProvider{ { Provider: "okta", Subject: "user123", Organization: uuid.NullUUID{UUID: uuid.FromStringOrNil("e7e3cbae-04cc-45f3-ae52-ea749a2ffaff"), Valid: true}, }, }, }, }, credType: CredentialsTypeSAML, }, { name: "SAML update with multiple providers", setupIdentity: func() *Identity { i := &Identity{} _ = i.SetCredentialsWithConfig( CredentialsTypeSAML, Credentials{ Identifiers: []string{OIDCUniqueID("onelogin", "user456")}, }, CredentialsOIDC{ Providers: []CredentialsOIDCProvider{ { Provider: "onelogin", Subject: "user456", }, }, }, ) return i }, credentials: &AdminIdentityImportCredentialsSAML{ Config: AdminIdentityImportCredentialsSAMLConfig{ Providers: []AdminCreateIdentityImportCredentialsSAMLProvider{ { Provider: "okta", Subject: "user123", Organization: uuid.NullUUID{UUID: uuid.FromStringOrNil("e7e3cbae-04cc-45f3-ae52-ea749a2ffaff"), Valid: true}, }, { Provider: "auth0", Subject: "user789", }, }, }, }, credType: CredentialsTypeSAML, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Setup a fresh identity for each test i := tc.setupIdentity() var err error // Perform the import based on credential type switch tc.credType { case CredentialsTypeOIDC: err = h.importOIDCCredentials(ctx, i, tc.credentials.(*AdminIdentityImportCredentialsOIDC)) case CredentialsTypeSAML: err = h.importSAMLCredentials(ctx, i, tc.credentials.(*AdminIdentityImportCredentialsSAML)) } require.NoError(t, err) // Verify credential was set correctly creds, ok := i.GetCredentials(tc.credType) require.True(t, ok, "credentials should be set") // Verify the credentials contain proper identifiers and config assert.NotEmpty(t, creds.Identifiers) assert.NotEmpty(t, creds.Config) // Take a snapshot of the credentials snapshotx.SnapshotT(t, creds) // Additional checks based on credential type switch tc.credType { case CredentialsTypeOIDC: oidcCreds := tc.credentials.(*AdminIdentityImportCredentialsOIDC) for _, p := range oidcCreds.Config.Providers { id := OIDCUniqueID(p.Provider, p.Subject) assert.Contains(t, creds.Identifiers, id) } case CredentialsTypeSAML: samlCreds := tc.credentials.(*AdminIdentityImportCredentialsSAML) for _, p := range samlCreds.Config.Providers { id := OIDCUniqueID(p.Provider, p.Subject) assert.Contains(t, creds.Identifiers, id) } } }) } } ================================================ FILE: identity/handler_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity_test import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "net/url" "sort" "strconv" "strings" "testing" "time" "github.com/go-faker/faker/v4" "github.com/gofrs/uuid" "github.com/peterhellberg/link" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "golang.org/x/crypto/bcrypt" "github.com/ory/x/configx" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hash" "github.com/ory/kratos/identity" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/schema" "github.com/ory/kratos/x" "github.com/ory/x/ioutilx" "github.com/ory/x/randx" "github.com/ory/x/snapshotx" "github.com/ory/x/sqlxx" "github.com/ory/x/urlx" ) var ignoreDefault = []string{"id", "schema_url", "state_changed_at", "created_at", "updated_at"} func TestHandler(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(testhelpers.IdentitySchemasConfig(map[string]string{ "default": "file://./stub/identity.schema.json", "customer": "file://./stub/handler/customer.schema.json", "multiple_emails": "file://./stub/handler/multiple_emails.schema.json", "employee": "file://./stub/handler/employee.schema.json", })), ) // Start kratos server publicTS, adminTS := testhelpers.NewKratosServerWithCSRF(t, reg) mockServerURL := urlx.ParseOrPanic(publicTS.URL) defaultSchemaExternalURL := (&schema.Schema{ID: "default"}).SchemaURL(mockServerURL).String() getFull := func(t *testing.T, base *httptest.Server, href string, expectCode int) (gjson.Result, *http.Response) { t.Helper() res, err := base.Client().Get(base.URL + href) require.NoError(t, err) body, err := io.ReadAll(res.Body) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.EqualValuesf(t, expectCode, res.StatusCode, "%s", body) return gjson.ParseBytes(body), res } get := func(t *testing.T, base *httptest.Server, href string, expectCode int) gjson.Result { t.Helper() res, _ := getFull(t, base, href, expectCode) return res } remove := func(t *testing.T, base *httptest.Server, href string, expectCode int) { t.Helper() req, err := http.NewRequest("DELETE", base.URL+href, nil) require.NoError(t, err) res, err := base.Client().Do(req) require.NoError(t, err) defer func() { _ = res.Body.Close() }() require.EqualValues(t, expectCode, res.StatusCode, "%s", ioutilx.MustReadAll(res.Body)) } send := func(t *testing.T, base *httptest.Server, method, href string, expectCode int, send interface{}) gjson.Result { t.Helper() var b bytes.Buffer switch raw := send.(type) { case json.RawMessage: b = *bytes.NewBuffer(raw) default: if send != nil { require.NoError(t, json.NewEncoder(&b).Encode(send)) } } req, err := http.NewRequest(method, base.URL+href, &b) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") res, err := base.Client().Do(req) require.NoError(t, err) body, err := io.ReadAll(res.Body) require.NoError(t, err) require.NoError(t, res.Body.Close()) require.EqualValues(t, expectCode, res.StatusCode, "%s", body) return gjson.ParseBytes(body) } type patch map[string]interface{} t.Run("case=should return an empty list", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { parsed := get(t, ts, "/identities", http.StatusOK) require.True(t, parsed.IsArray(), "%s", parsed.Raw) assert.Len(t, parsed.Array(), 0) }) } }) t.Run("case=should return 404 on a non-existing resource", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { _ = get(t, ts, "/identities/does-not-exist", http.StatusNotFound) }) } }) t.Run("case=should fail to create an identity because schema id does not exist", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var i identity.CreateIdentityBody i.SchemaID = "does-not-exist" res := send(t, ts, "POST", "/identities", http.StatusBadRequest, &i) assert.Contains(t, res.Get("error.reason").String(), "does-not-exist", "%s", res) }) } }) t.Run("case=should fail to create an entity because schema is not validating", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var i identity.CreateIdentityBody i.Traits = []byte(`{"bar":123}`) res := send(t, ts, "POST", "/identities", http.StatusBadRequest, &i) assert.Contains(t, res.Get("error.reason").String(), "I[#/traits/bar] S[#/properties/traits/properties/bar/type] expected string, but got number") }) } }) t.Run("case=should fail to create an entity with schema_url set", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "POST", "/identities", http.StatusBadRequest, json.RawMessage(`{"schema_url":"12345","traits":{}}`)) assert.Contains(t, res.Get("error.message").String(), "schema_url") }) } }) t.Run("case=should create an identity without an ID", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var i identity.CreateIdentityBody i.Traits = []byte(`{"bar":"baz"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &i) assert.NotEmpty(t, res.Get("id").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.Empty(t, res.Get("credentials").String(), "%s", res.Raw) }) } }) t.Run("case=should create an identity with metadata", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var i identity.CreateIdentityBody i.Traits = []byte(`{"bar":"baz"}`) i.MetadataPublic = []byte(`{"public":"baz"}`) i.MetadataAdmin = []byte(`{"admin":"baz"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &i) assert.EqualValues(t, "baz", res.Get("metadata_admin.admin").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("metadata_public.public").String(), "%s", res.Raw) }) } }) t.Run("case=should create an identity with an organization ID", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { orgID := uuid.NullUUID{ UUID: x.NewUUID(), Valid: true, } i := identity.CreateIdentityBody{ Traits: []byte(`{"bar":"baz"}`), OrganizationID: orgID, } res := send(t, ts, "POST", "/identities", http.StatusCreated, &i) assert.EqualValues(t, orgID.UUID.String(), res.Get("organization_id").String(), "%s", res.Raw) }) } }) t.Run("case=should create an identity with an external ID", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { externalID := x.NewUUID().String() i := identity.CreateIdentityBody{ Traits: []byte(`{"bar":"baz"}`), ExternalID: externalID, } res := send(t, ts, "POST", "/identities", http.StatusCreated, &i) assert.EqualValues(t, externalID, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/by/external/"+externalID, http.StatusOK) assert.EqualValues(t, externalID, res.Get("external_id").String(), "%s", res.Raw) }) } }) t.Run("case=should be able to import users", func(t *testing.T) { ignoreDefault := []string{"id", "schema_url", "state_changed_at", "created_at", "updated_at"} t.Run("without any credentials", func(t *testing.T) { res := send(t, adminTS, "POST", "/identities", http.StatusCreated, identity.CreateIdentityBody{Traits: []byte(`{"email": "import-1@ory.sh"}`)}) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(ignoreDefault...)) }) t.Run("without traits", func(t *testing.T) { res := send(t, adminTS, "POST", "/identities", http.StatusCreated, json.RawMessage("{}")) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(ignoreDefault...)) }) t.Run("with malformed traits", func(t *testing.T) { send(t, adminTS, "POST", "/identities", http.StatusBadRequest, json.RawMessage(`{"traits": not valid JSON}`)) }) t.Run("with cleartext password and oidc credentials", func(t *testing.T) { res := send(t, adminTS, "POST", "/identities", http.StatusCreated, identity.CreateIdentityBody{ Traits: []byte(`{"email": "import-2@ory.sh"}`), Credentials: &identity.IdentityWithCredentials{ Password: &identity.AdminIdentityImportCredentialsPassword{ Config: identity.AdminIdentityImportCredentialsPasswordConfig{ Password: "123456", }, }, OIDC: &identity.AdminIdentityImportCredentialsOIDC{ Config: identity.AdminIdentityImportCredentialsOIDCConfig{ Providers: []identity.AdminCreateIdentityImportCredentialsOIDCProvider{ {Subject: "import-2", Provider: "google"}, {Subject: "import-2", Provider: "github"}, }, }, }, SAML: &identity.AdminIdentityImportCredentialsSAML{ Config: identity.AdminIdentityImportCredentialsSAMLConfig{ Providers: []identity.AdminCreateIdentityImportCredentialsSAMLProvider{ {Subject: "import-saml-2", Provider: "okta"}, {Subject: "import-saml-2", Provider: "onelogin"}, }, }, }, }, }) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(append(ignoreDefault, "hashed_password")...), snapshotx.ExceptPaths("credentials.oidc.identifiers")) identifiers := actual.Credentials[identity.CredentialsTypeOIDC].Identifiers assert.Len(t, identifiers, 2) assert.Contains(t, identifiers, "google:import-2") assert.Contains(t, identifiers, "github:import-2") identifiers = actual.Credentials[identity.CredentialsTypeSAML].Identifiers assert.Len(t, identifiers, 2) assert.Contains(t, identifiers, "okta:import-saml-2") assert.Contains(t, identifiers, "onelogin:import-saml-2") require.NoError(t, hash.Compare(t.Context(), []byte("123456"), []byte(gjson.GetBytes(actual.Credentials[identity.CredentialsTypePassword].Config, "hashed_password").String()))) }) t.Run("with organization oidc and saml credentials", func(t *testing.T) { org := "ad6a7dac-4eef-4f09-8e58-c099c14b6c36" res := send(t, adminTS, "POST", "/identities", http.StatusCreated, identity.CreateIdentityBody{ Traits: []byte(`{"email": "import-3@ory.sh"}`), Credentials: &identity.IdentityWithCredentials{ OIDC: &identity.AdminIdentityImportCredentialsOIDC{ Config: identity.AdminIdentityImportCredentialsOIDCConfig{ Providers: []identity.AdminCreateIdentityImportCredentialsOIDCProvider{ {Subject: "import-org-3", Provider: "google", Organization: uuid.NullUUID{Valid: true, UUID: uuid.FromStringOrNil(org)}}, {Subject: "import-org-3", Provider: "github", Organization: uuid.NullUUID{Valid: true, UUID: uuid.FromStringOrNil(org)}}, }, }, }, SAML: &identity.AdminIdentityImportCredentialsSAML{ Config: identity.AdminIdentityImportCredentialsSAMLConfig{ Providers: []identity.AdminCreateIdentityImportCredentialsSAMLProvider{ {Subject: "import-saml-org-3", Provider: "okta", Organization: uuid.NullUUID{Valid: true, UUID: uuid.FromStringOrNil(org)}}, {Subject: "import-saml-org-3", Provider: "onelogin", Organization: uuid.NullUUID{Valid: true, UUID: uuid.FromStringOrNil(org)}}, }, }, }, }, }) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(append(ignoreDefault, "hashed_password")...), snapshotx.ExceptPaths("credentials.oidc.identifiers")) identifiers := actual.Credentials[identity.CredentialsTypeOIDC].Identifiers assert.Len(t, identifiers, 2) assert.Contains(t, identifiers, "google:import-org-3") assert.Contains(t, identifiers, "github:import-org-3") identifiers = actual.Credentials[identity.CredentialsTypeSAML].Identifiers assert.Len(t, identifiers, 2) assert.Contains(t, identifiers, "okta:import-saml-org-3") assert.Contains(t, identifiers, "onelogin:import-saml-org-3") assert.Empty(t, []byte(gjson.GetBytes(actual.Credentials[identity.CredentialsTypePassword].Config, "hashed_password").String())) }) t.Run("with hashed passwords", func(t *testing.T) { for i, tt := range []struct{ name, hash, pass string }{ { name: "pkbdf2", hash: "$pbkdf2-sha256$i=1000,l=128$e8/arsEf4cvQihdNgqj0Nw$5xQQKNTyeTHx2Ld5/JDE7A", pass: "123456", }, { name: "bcrypt2", hash: "$2a$10$ZsCsoVQ3xfBG/K2z2XpBf.tm90GZmtOqtqWcB5.pYd5Eq8y7RlDyq", pass: "123456", }, { name: "argon2i", hash: "$argon2i$v=19$m=65536,t=3,p=4$STVE4CQ9qQ1dK/j224VMbA$o8b+k5wdHgBqf7ES+aWG2K7Y9diQ6ahEhbW8zcstXGo", pass: "123456", }, { name: "argon2id", hash: "$argon2id$v=19$m=16,t=2,p=1$bVI1aE1SaTV6SGQ3bzdXdw$fnjCcZYmEPOUOjYXsT92Cg", pass: "123456", }, { name: "scrypt", hash: "$scrypt$ln=16384,r=8,p=1$ZtQva9xCHzlSELH/mA7Kj5KjH2tCrkbwYzdxknkL0QQ=$pnTcXKaWVT+FwFDdk3vO1K0J7ZgOxdSU1tCJNYmn8zI=", pass: "123456", }, { name: "md5", hash: "$md5$4QrcOUm6Wau+VuBX8g+IPg==", pass: "123456", }, { name: "SSHA", hash: "{SSHA}JFZFs0oHzxbMwkSJmYVeI8MnTDy/276a", pass: "test123", }, { name: "SSHA256", hash: "{SSHA256}czO44OTV17PcF1cRxWrLZLy9xHd7CWyVYplr1rOhuMlx/7IK", pass: "test123", }, { name: "SSHA512", hash: "{SSHA512}xPUl/px+1cG55rUH4rzcwxdOIPSB2TingLpiJJumN2xyDWN4Ix1WQG3ihnvHaWUE8MYNkvMi5rf0C9NYixHsE6Yh59M=", pass: "test123", }, { name: "hmac", hash: "$hmac-sha256$YjhhZDA4YTNhNTQ3ZTM1ODI5YjgyMWI3NTM3MDMwMWRkOGM0YjA2YmRkNzc3MWY5YjU0MWE3NTkxNDA2ODcxOA==$MTIzNDU2", pass: "123456", }, } { t.Run("hash="+tt.name, func(t *testing.T) { traits := fmt.Sprintf(`{"email": "import-hash-%d@ory.sh"}`, i) res := send(t, adminTS, "POST", "/identities", http.StatusCreated, identity.CreateIdentityBody{ Traits: []byte(traits), Credentials: &identity.IdentityWithCredentials{Password: &identity.AdminIdentityImportCredentialsPassword{ Config: identity.AdminIdentityImportCredentialsPasswordConfig{HashedPassword: tt.hash}, }}, }) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(ignoreDefault...), snapshotx.ExceptNestedKeys("hashed_password")) require.NoError(t, hash.Compare(t.Context(), []byte(tt.pass), []byte(gjson.GetBytes(actual.Credentials[identity.CredentialsTypePassword].Config, "hashed_password").String()))) }) } }) t.Run("with password migration hook enabled", func(t *testing.T) { res := send(t, adminTS, "POST", "/identities", http.StatusCreated, identity.CreateIdentityBody{ Traits: []byte(`{"email": "pw-migration-hook@ory.sh"}`), Credentials: &identity.IdentityWithCredentials{Password: &identity.AdminIdentityImportCredentialsPassword{ Config: identity.AdminIdentityImportCredentialsPasswordConfig{UsePasswordMigrationHook: true}, }}, }) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(ignoreDefault...), snapshotx.ExceptNestedKeys("hashed_password")) assert.True(t, gjson.GetBytes(actual.Credentials[identity.CredentialsTypePassword].Config, "use_password_migration_hook").Bool()) }) t.Run("with not-normalized email", func(t *testing.T) { res := send(t, adminTS, "POST", "/identities", http.StatusCreated, identity.CreateIdentityBody{ SchemaID: "customer", Traits: []byte(`{"email": "UpperCased@ory.sh"}`), VerifiableAddresses: []identity.VerifiableAddress{{ Verified: true, Value: "UpperCased@ory.sh", Via: identity.AddressTypeEmail, Status: identity.VerifiableAddressStatusCompleted, }}, RecoveryAddresses: []identity.RecoveryAddress{{Value: "UpperCased@ory.sh"}}, }) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) require.Len(t, actual.VerifiableAddresses, 1) assert.True(t, actual.VerifiableAddresses[0].Verified) assert.Equal(t, "uppercased@ory.sh", actual.VerifiableAddresses[0].Value) require.Len(t, actual.RecoveryAddresses, 1) assert.Equal(t, "uppercased@ory.sh", actual.RecoveryAddresses[0].Value) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(ignoreDefault...), snapshotx.ExceptNestedKeys("verified_at")) }) }) t.Run("case=unable to set ID itself", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "POST", "/identities", http.StatusBadRequest, json.RawMessage(`{"id":"12345","traits":{}}`)) assert.Contains(t, res.Raw, "id") }) } }) t.Run("suite=create and batch list", func(t *testing.T) { var ids []uuid.UUID identitiesAmount := 5 listAmount := 3 t.Run("case=create multiple identities", func(t *testing.T) { for i := 0; i < identitiesAmount; i++ { res := send(t, adminTS, "POST", "/identities", http.StatusCreated, json.RawMessage(`{"traits": {"bar":"baz"}}`)) assert.NotEmpty(t, res.Get("id").String(), "%s", res.Raw) id := x.ParseUUID(res.Get("id").String()) ids = append(ids, id) } require.Len(t, ids, identitiesAmount) }) t.Run("case=list few identities", func(t *testing.T) { vals := url.Values{} vals.Add("ids", ids[0].String()) // duplicate ID is deduplicated in result for i := range listAmount { vals.Add("ids", ids[i].String()) } res := get(t, adminTS, "/identities?"+vals.Encode(), http.StatusOK) identities := res.Array() require.Len(t, identities, listAmount) }) }) t.Run("case=list identities by ID is capped at 500", func(t *testing.T) { vals := url.Values{} for range 501 { vals.Add("ids", x.NewUUID().String()) } res := get(t, adminTS, "/identities?"+vals.Encode(), http.StatusBadRequest) assert.Contains(t, res.Get("error.reason").String(), "must not exceed 500") }) t.Run("case=list identities cannot combine filters", func(t *testing.T) { filters := []string{ "ids=" + x.NewUUID().String(), "credentials_identifier=foo@bar.com", "preview_credentials_identifier_similar=bar.com", "organization_id=" + x.NewUUID().String(), } for i := range filters { for j := range filters { if i == j { continue // OK to use the same filter multiple times. Behavior varies by filter, though. } u := "/identities?" + filters[i] + "&" + filters[j] res := get(t, adminTS, u, http.StatusBadRequest) assert.Contains(t, res.Get("error.reason").String(), "cannot combine multiple filters") } } }) t.Run("case=malformed ids should return an error", func(t *testing.T) { res := get(t, adminTS, "/identities?ids=not-a-uuid", http.StatusBadRequest) assert.Contains(t, res.Get("error.reason").String(), "Invalid UUID value `not-a-uuid` for parameter `ids`.", "%s", res.Raw) }) t.Run("suite=create and update", func(t *testing.T) { var i identity.Identity createOIDCorSAMLIdentity := func(t *testing.T, ct identity.CredentialsType, identifier, accessToken, refreshToken, idToken string, encrypt bool) string { transform := func(token, suffix string) string { if !encrypt { return token } if token == "" { return "" } c, err := reg.Cipher(t.Context()).Encrypt(context.Background(), []byte(token+suffix)) require.NoError(t, err) return c } iID := x.NewUUID() toJSON := func(c identity.CredentialsOIDC) []byte { out, err := json.Marshal(&c) require.NoError(t, err) return out } require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), &identity.Identity{ ID: iID, Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, identifier)), Credentials: map[identity.CredentialsType]identity.Credentials{ ct: { Type: ct, Identifiers: []string{"bar:" + identifier}, Config: toJSON(identity.CredentialsOIDC{Providers: []identity.CredentialsOIDCProvider{ { Subject: "foo", Provider: "bar", InitialAccessToken: transform(accessToken, "0"), InitialRefreshToken: transform(refreshToken, "0"), InitialIDToken: transform(idToken, "0"), }, { Subject: "baz", Provider: "zab", InitialAccessToken: transform(accessToken, "1"), InitialRefreshToken: transform(refreshToken, "1"), InitialIDToken: transform(idToken, "1"), }, }}), }, identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{identifier}, }, }, VerifiableAddresses: []identity.VerifiableAddress{ { ID: x.NewUUID(), Value: identifier, Verified: false, CreatedAt: time.Now(), IdentityID: iID, }, }, })) return iID.String() } t.Run("case=should create an identity with an ID which is ignored", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(`{"traits": {"bar":"baz"}}`)) stateChangedAt := sqlxx.NullTime(res.Get("state_changed_at").Time()) i.Traits = []byte(res.Get("traits").Raw) i.ID = x.ParseUUID(res.Get("id").String()) i.StateChangedAt = &stateChangedAt assert.NotEmpty(t, res.Get("id").String()) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.Empty(t, res.Get("credentials").String(), "%s", res.Raw) assert.EqualValues(t, defaultSchemaExternalURL, res.Get("schema_url").String(), "%s", res.Raw) assert.EqualValues(t, config.DefaultIdentityTraitsSchemaID, res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("state").String(), "%s", res.Raw) }) } }) t.Run("case=should be able to get the identity", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.EqualValues(t, defaultSchemaExternalURL, res.Get("schema_url").String(), "%s", res.Raw) assert.EqualValues(t, config.DefaultIdentityTraitsSchemaID, res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("state").String(), "%s", res.Raw) assert.Empty(t, res.Get("credentials").String(), "%s", res.Raw) }) } }) t.Run("case=should return an empty array on a failed lookup with identifier", func(t *testing.T) { res := get(t, adminTS, "/identities?credentials_identifier=find.by.non.existing.identifier@bar.com", http.StatusOK) assert.EqualValues(t, int64(0), res.Get("#").Int(), "%s", res.Raw) }) t.Run("case=should be able to lookup the identity using identifier", func(t *testing.T) { ident := &identity.Identity{ Credentials: map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{"find.by.identifier@bar.com"}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), // foobar }, identity.CredentialsTypeOIDC: { Type: identity.CredentialsTypeOIDC, Identifiers: []string{"ProviderID:293b5d9b-1009-4600-a3e9-bd1845de22f2"}, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\"}"), }, identity.CredentialsTypeSAML: { Type: identity.CredentialsTypeSAML, Identifiers: []string{"SAMLProviderID:0851ac66-88cc-4775-aee0-9b4c79fdbfb9"}, Config: sqlxx.JSONRawMessage("{\"saml\" : \"secret\"}"), }, }, State: identity.StateActive, Traits: identity.Traits(`{"username":"find.by.identifier@bar.com"}`), } require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), ident)) t.Run("type=password", func(t *testing.T) { res := get(t, adminTS, "/identities?credentials_identifier=FIND.BY.IDENTIFIER@bar.com", http.StatusOK) assert.EqualValues(t, ident.ID.String(), res.Get("0.id").String(), "%s", res.Raw) assert.EqualValues(t, "find.by.identifier@bar.com", res.Get("0.traits.username").String(), "%s", res.Raw) assert.EqualValues(t, defaultSchemaExternalURL, res.Get("0.schema_url").String(), "%s", res.Raw) assert.EqualValues(t, config.DefaultIdentityTraitsSchemaID, res.Get("0.schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("0.state").String(), "%s", res.Raw) assert.EqualValues(t, "password", res.Get("0.credentials.password.type").String(), res.Raw) assert.EqualValues(t, "1", res.Get("0.credentials.password.identifiers.#").String(), res.Raw) assert.EqualValues(t, "find.by.identifier@bar.com", res.Get("0.credentials.password.identifiers.0").String(), res.Raw) }) t.Run("type=oidc", func(t *testing.T) { res := get(t, adminTS, "/identities?credentials_identifier=ProviderID:293b5d9b-1009-4600-a3e9-bd1845de22f2", http.StatusOK) assert.EqualValues(t, ident.ID.String(), res.Get("0.id").String(), "%s", res.Raw) assert.EqualValues(t, "find.by.identifier@bar.com", res.Get("0.traits.username").String(), "%s", res.Raw) assert.EqualValues(t, defaultSchemaExternalURL, res.Get("0.schema_url").String(), "%s", res.Raw) assert.EqualValues(t, config.DefaultIdentityTraitsSchemaID, res.Get("0.schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("0.state").String(), "%s", res.Raw) assert.EqualValues(t, "oidc", res.Get("0.credentials.oidc.type").String(), res.Raw) require.Len(t, res.Get("0.credentials.oidc.identifiers").Array(), 1, res.Raw) assert.EqualValues(t, "ProviderID:293b5d9b-1009-4600-a3e9-bd1845de22f2", res.Get("0.credentials.oidc.identifiers.0").String(), res.Raw) }) t.Run("type=oidc", func(t *testing.T) { res := get(t, adminTS, "/identities?credentials_identifier=SAMLProviderID:0851ac66-88cc-4775-aee0-9b4c79fdbfb9", http.StatusOK) assert.EqualValues(t, ident.ID.String(), res.Get("0.id").String(), "%s", res.Raw) assert.EqualValues(t, "find.by.identifier@bar.com", res.Get("0.traits.username").String(), "%s", res.Raw) assert.EqualValues(t, defaultSchemaExternalURL, res.Get("0.schema_url").String(), "%s", res.Raw) assert.EqualValues(t, config.DefaultIdentityTraitsSchemaID, res.Get("0.schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("0.state").String(), "%s", res.Raw) assert.EqualValues(t, "saml", res.Get("0.credentials.saml.type").String(), res.Raw) assert.Len(t, res.Get("0.credentials.saml.identifiers").Array(), 1, res.Raw) assert.EqualValues(t, "SAMLProviderID:0851ac66-88cc-4775-aee0-9b4c79fdbfb9", res.Get("0.credentials.saml.identifiers.0").String(), res.Raw) }) }) t.Run("case=should get oidc credential", func(t *testing.T) { id := createOIDCorSAMLIdentity(t, identity.CredentialsTypeOIDC, "foo.oidc@bar.com", "access_token", "refresh_token", "id_token", true) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities/"+id, http.StatusOK) assert.False(t, res.Get("credentials.oidc.config").Exists(), "credentials config should be omitted: %s", res.Raw) assert.False(t, res.Get("credentials.password.config").Exists(), "credentials config should be omitted: %s", res.Raw) res = get(t, ts, "/identities/"+id+"?include_credential=oidc", http.StatusOK) assert.True(t, res.Get("credentials").Exists(), "credentials should be included: %s", res.Raw) assert.True(t, res.Get("credentials.password").Exists(), "password meta should be included: %s", res.Raw) assert.False(t, res.Get("credentials.password.false").Exists(), "password credentials should not be included: %s", res.Raw) assert.Equal(t, "bar:foo.oidc@bar.com", res.Get("credentials.oidc.identifiers.0").Str) assert.True(t, res.Get("credentials.oidc.config").Exists(), "oidc credentials should be included: %s", res.Raw) assert.EqualValues(t, "foo", res.Get("credentials.oidc.config.providers.0.subject").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "bar", res.Get("credentials.oidc.config.providers.0.provider").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "access_token0", res.Get("credentials.oidc.config.providers.0.initial_access_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "refresh_token0", res.Get("credentials.oidc.config.providers.0.initial_refresh_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "id_token0", res.Get("credentials.oidc.config.providers.0.initial_id_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "baz", res.Get("credentials.oidc.config.providers.1.subject").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "zab", res.Get("credentials.oidc.config.providers.1.provider").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "access_token1", res.Get("credentials.oidc.config.providers.1.initial_access_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "refresh_token1", res.Get("credentials.oidc.config.providers.1.initial_refresh_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "id_token1", res.Get("credentials.oidc.config.providers.1.initial_id_token").String(), "credentials should be included: %s", res.Raw) }) } }) t.Run("case=should get saml credential", func(t *testing.T) { id := createOIDCorSAMLIdentity(t, identity.CredentialsTypeSAML, "foo.saml@bar.com", "access_token", "refresh_token", "id_token", true) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities/"+id, http.StatusOK) assert.False(t, res.Get("credentials.saml.config").Exists(), "credentials config should be omitted: %s", res.Raw) assert.False(t, res.Get("credentials.password.config").Exists(), "credentials config should be omitted: %s", res.Raw) res = get(t, ts, "/identities/"+id+"?include_credential=saml", http.StatusOK) assert.True(t, res.Get("credentials").Exists(), "credentials should be included: %s", res.Raw) assert.True(t, res.Get("credentials.password").Exists(), "password meta should be included: %s", res.Raw) assert.False(t, res.Get("credentials.password.false").Exists(), "password credentials should not be included: %s", res.Raw) assert.Equal(t, "bar:foo.saml@bar.com", res.Get("credentials.saml.identifiers.0").Str) assert.True(t, res.Get("credentials.saml.config").Exists(), "SAML config should be included: %s", res.Raw) assert.True(t, res.Get("credentials.saml.config").Exists(), "saml credentials should be included: %s", res.Raw) assert.EqualValues(t, "foo", res.Get("credentials.saml.config.providers.0.subject").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "bar", res.Get("credentials.saml.config.providers.0.provider").String(), "credentials should be included: %s", res.Raw) assert.False(t, res.Get("credentials.saml.config.providers.0.initial_access_token").Exists(), "SAML details should not be included: %s", res.Raw) assert.False(t, res.Get("credentials.saml.config.providers.0.initial_refresh_token").Exists(), "SAML details should not be included: %s", res.Raw) assert.False(t, res.Get("credentials.saml.config.providers.0.initial_id_token").Exists(), "SAML details should not be included: %s", res.Raw) assert.EqualValues(t, "baz", res.Get("credentials.saml.config.providers.1.subject").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "zab", res.Get("credentials.saml.config.providers.1.provider").String(), "credentials should be included: %s", res.Raw) assert.False(t, res.Get("credentials.saml.config.providers.1.initial_access_token").Exists(), "SAML details should not be included: %s", res.Raw) assert.False(t, res.Get("credentials.saml.config.providers.1.initial_refresh_token").Exists(), "SAML details should not be included: %s", res.Raw) assert.False(t, res.Get("credentials.saml.config.providers.1.initial_id_token").Exists(), "SAML details should not be included: %s", res.Raw) }) } }) t.Run("case=should not fail on empty tokens", func(t *testing.T) { id := createOIDCorSAMLIdentity(t, identity.CredentialsTypeOIDC, "foo.oidc.empty-tokens@bar.com", "", "", "", true) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities/"+id, http.StatusOK) assert.False(t, res.Get("credentials.oidc.config").Exists(), "credentials config should be omitted: %s", res.Raw) assert.False(t, res.Get("credentials.password.config").Exists(), "credentials config should be omitted: %s", res.Raw) res = get(t, ts, "/identities/"+id+"?include_credential=oidc", http.StatusOK) assert.True(t, res.Get("credentials").Exists(), "credentials should be included: %s", res.Raw) assert.True(t, res.Get("credentials.password").Exists(), "password meta should be included: %s", res.Raw) assert.False(t, res.Get("credentials.password.false").Exists(), "password credentials should not be included: %s", res.Raw) assert.True(t, res.Get("credentials.oidc.config").Exists(), "oidc credentials should be included: %s", res.Raw) assert.EqualValues(t, "foo", res.Get("credentials.oidc.config.providers.0.subject").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "bar", res.Get("credentials.oidc.config.providers.0.provider").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "", res.Get("credentials.oidc.config.providers.0.initial_access_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "", res.Get("credentials.oidc.config.providers.0.initial_refresh_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "", res.Get("credentials.oidc.config.providers.0.initial_id_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "baz", res.Get("credentials.oidc.config.providers.1.subject").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "zab", res.Get("credentials.oidc.config.providers.1.provider").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "", res.Get("credentials.oidc.config.providers.1.initial_access_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "", res.Get("credentials.oidc.config.providers.1.initial_refresh_token").String(), "credentials should be included: %s", res.Raw) assert.EqualValues(t, "", res.Get("credentials.oidc.config.providers.1.initial_id_token").String(), "credentials should be included: %s", res.Raw) }) } }) t.Run("case=should get identity with credentials", func(t *testing.T) { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) credentials := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: {Identifiers: []string{"zab", "bar"}, Type: identity.CredentialsTypePassword, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\"}")}, identity.CredentialsTypeOIDC: {Type: identity.CredentialsTypeOIDC, Identifiers: []string{"bar", "baz"}, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\"}")}, identity.CredentialsTypeWebAuthn: {Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{"foo", "bar"}, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\", \"user_handle\": \"rVIFaWRcTTuQLkXFmQWpgA==\"}")}, } i.Credentials = credentials require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) excludeKeys := snapshotx.ExceptNestedKeys("id", "created_at", "updated_at", "schema_url", "state_changed_at") t.Run("case=should get identity without credentials included", func(t *testing.T) { res := get(t, adminTS, "/identities/"+i.ID.String(), http.StatusOK) snapshotx.SnapshotT(t, json.RawMessage(res.Raw), excludeKeys) }) t.Run("case=should get identity with password credentials included", func(t *testing.T) { res := get(t, adminTS, "/identities/"+i.ID.String()+"?include_credential=password", http.StatusOK) snapshotx.SnapshotT(t, json.RawMessage(res.Raw), excludeKeys) }) t.Run("case=should get identity with password and webauthn credentials included", func(t *testing.T) { res := get(t, adminTS, "/identities/"+i.ID.String()+"?include_credential=password&include_credential=webauthn", http.StatusOK) snapshotx.SnapshotT(t, json.RawMessage(res.Raw), excludeKeys) }) }) t.Run("case=should pass if no oidc credentials are set", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(`{"traits": {"bar":"baz"}}`)) res = get(t, ts, "/identities/"+res.Get("id").String(), http.StatusOK) assert.False(t, res.Get("credentials.oidc.config").Exists(), "credentials config should be omitted: %s", res.Raw) assert.False(t, res.Get("credentials.password.config").Exists(), "credentials config should be omitted: %s", res.Raw) res = get(t, ts, "/identities/"+res.Get("id").String()+"?include_credential=oidc", http.StatusOK) assert.False(t, res.Get("credentials.password").Exists(), "password credentials should not be included: %s", res.Raw) assert.False(t, res.Get("credentials.oidc").Exists(), "oidc credentials should be included: %s", res.Raw) }) } }) t.Run("case=should return empty tokens if decryption fails", func(t *testing.T) { id := createOIDCorSAMLIdentity(t, identity.CredentialsTypeOIDC, "foo-failed.oidc@bar.com", "foo_token", "bar_token", "id_token", false) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities/"+i.ID.String()+"?include_credential=oidc", http.StatusOK) assert.NotContains(t, res.Raw, "identifier_credentials", res.Raw) res = get(t, ts, "/identities/"+id+"?include_credential=oidc", http.StatusOK) assert.Equal(t, "bar:foo-failed.oidc@bar.com", res.Get("credentials.oidc.identifiers.0").String(), "%s", res.Raw) assert.Equal(t, "", res.Get("credentials.oidc.config.providers.0.initial_access_token").String(), "%s", res.Raw) assert.Equal(t, "", res.Get("credentials.oidc.config.providers.0.initial_id_token").String(), "%s", res.Raw) assert.Equal(t, "", res.Get("credentials.oidc.config.providers.0.initial_refresh_token").String(), "%s", res.Raw) }) } }) t.Run("case=should return decrypted token", func(t *testing.T) { e, _ := reg.Cipher(t.Context()).Encrypt(context.Background(), []byte("foo_token")) id := createOIDCorSAMLIdentity(t, identity.CredentialsTypeOIDC, "foo-failed-2.oidc@bar.com", e, "bar_token", "id_token", false) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { t.Logf("no oidc token") res := get(t, ts, "/identities/"+i.ID.String()+"?include_credential=oidc", http.StatusOK) assert.NotContains(t, res.Raw, "identifier_credentials", res.Raw) t.Logf("get oidc token") res = get(t, ts, "/identities/"+id+"?include_credential=oidc", http.StatusOK) assert.Equal(t, "bar:foo-failed-2.oidc@bar.com", res.Get("credentials.oidc.identifiers.0").String(), "%s", res.Raw) assert.Equal(t, "foo_token", res.Get("credentials.oidc.config.providers.0.initial_access_token").String(), "%s", res.Raw) }) } }) t.Run("case=should update an identity and persist the changes", func(t *testing.T) { i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, x.NewUUID().String()))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { externalID := x.NewUUID().String() ur := identity.UpdateIdentityBody{ Traits: []byte(`{"bar":"baz","foo":"baz"}`), SchemaID: i.SchemaID, State: identity.StateInactive, MetadataPublic: []byte(`{"public":"metadata"}`), MetadataAdmin: []byte(`{"admin":"metadata"}`), ExternalID: externalID, } res := send(t, ts, "PUT", "/identities/"+i.ID.String(), http.StatusOK, &ur) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("traits.foo").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_admin.admin").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_public.public").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) assert.Equal(t, externalID, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_admin.admin").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_public.public").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) assert.Equal(t, externalID, res.Get("external_id").String(), "%s", res.Raw) }) } }) t.Run("case=should update an identity with credentials", func(t *testing.T) { i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, x.NewUUID().String()))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { credentials := identity.IdentityWithCredentials{ Password: &identity.AdminIdentityImportCredentialsPassword{ Config: identity.AdminIdentityImportCredentialsPasswordConfig{ Password: "pswd1234", }, }, } ur := identity.UpdateIdentityBody{ Traits: []byte(`{"bar":"baz","foo":"baz"}`), SchemaID: i.SchemaID, State: identity.StateInactive, MetadataPublic: []byte(`{"public":"metadata"}`), MetadataAdmin: []byte(`{"admin":"metadata"}`), Credentials: &credentials, } res := send(t, ts, "PUT", "/identities/"+i.ID.String(), http.StatusOK, &ur) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("traits.foo").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_admin.admin").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_public.public").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.EqualValues(t, "baz", res.Get("traits.bar").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_admin.admin").String(), "%s", res.Raw) assert.EqualValues(t, "metadata", res.Get("metadata_public.public").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(context.Background(), i.ID) require.NoError(t, err) require.NoError(t, hash.Compare(t.Context(), []byte("pswd1234"), []byte(gjson.GetBytes(actual.Credentials[identity.CredentialsTypePassword].Config, "hashed_password").String()))) }) } }) t.Run("case=should delete a user and no longer be able to retrieve it", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(`{"traits": {"bar":"baz"}}`)) remove(t, ts, "/identities/"+res.Get("id").String(), http.StatusNoContent) _ = get(t, ts, "/identities/"+res.Get("id").String(), http.StatusNotFound) }) } }) t.Run("case=should create an identity with linking marker", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { trait := x.NewUUID().String() payload := ` { "traits": { "bar": "` + trait + `" }, "credentials": { "oidc": { "config": { "providers": [ { "subject": "` + trait + `", "provider": "bar", "use_auto_link": true } ] } } } }` res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(payload)) i.ID = x.ParseUUID(res.Get("id").String()) identRes := send(t, adminTS, "GET", fmt.Sprintf("/identities/%s?include_credential=oidc", i.ID), http.StatusOK, nil) assert.True(t, identRes.Get("credentials.oidc.config.providers.0.use_auto_link").Bool()) assert.False(t, identRes.Get("credentials.oidc.config.providers.0.organization").Exists()) }) } }) t.Run("case=should create an identity without linking marker omitempty", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { trait := x.NewUUID().String() payload := ` { "traits": { "bar": "` + trait + `" }, "credentials": { "oidc": { "config": { "providers": [ { "subject": "` + trait + `", "provider": "bar", "use_auto_link": false } ] } } } }` res := send(t, ts, "POST", "/identities", http.StatusCreated, json.RawMessage(payload)) stateChangedAt := sqlxx.NullTime(res.Get("state_changed_at").Time()) i.Traits = []byte(res.Get("traits").Raw) i.ID = x.ParseUUID(res.Get("id").String()) i.StateChangedAt = &stateChangedAt assert.NotEmpty(t, res.Get("id").String()) i, err := reg.Persister().GetIdentityConfidential(context.Background(), i.ID) require.NoError(t, err) require.False(t, gjson.GetBytes(i.Credentials[identity.CredentialsTypeOIDC].Config, "providers.0.use_auto_link").Exists()) }) } }) }) t.Run("suite=PATCH identities", func(t *testing.T) { t.Run("case=fails with too many patches", func(t *testing.T) { tooMany := make([]*identity.BatchIdentityPatch, identity.BatchPatchIdentitiesLimit+1) for i := range tooMany { tooMany[i] = &identity.BatchIdentityPatch{Create: validCreateIdentityBody(t, "too-many-patches", i, false)} } res := send(t, adminTS, "PATCH", "/identities", http.StatusBadRequest, &identity.BatchPatchIdentitiesBody{Identities: tooMany}) assert.Contains(t, res.Get("error.reason").String(), strconv.Itoa(identity.BatchPatchIdentitiesLimit), "the error reason should contain the limit") }) t.Run("case=fails with too many identity plain text password patches", func(t *testing.T) { tooMany := make([]*identity.BatchIdentityPatch, identity.BatchPatchIdentitiesWithPasswordLimit+1) for i := range tooMany { tooMany[i] = &identity.BatchIdentityPatch{Create: validCreateIdentityBody(t, "too-many-patches", i, true)} } res := send(t, adminTS, "PATCH", "/identities", http.StatusBadRequest, &identity.BatchPatchIdentitiesBody{Identities: tooMany}) assert.Contains(t, res.Get("error.reason").String(), strconv.Itoa(identity.BatchPatchIdentitiesWithPasswordLimit), "the error reason should contain the limit") }) t.Run("case=fails some on a bad identity", func(t *testing.T) { // Test setup: we have a list of valid identitiy patches and a list of invalid ones. // Each run adds one invalid patch to the list and sends it to the server. // --> we expect the server to fail only the bad patches in the list. // Finally, we send just valid patches // --> we expect the server to succeed all patches in the list. t.Run("case=invalid patches fail", func(t *testing.T) { patches := []*identity.BatchIdentityPatch{ {Create: validCreateIdentityBody(t, "valid", 0, false)}, {Create: validCreateIdentityBody(t, "valid", 1, false)}, {Create: &identity.CreateIdentityBody{}}, // <-- invalid: missing all fields {Create: validCreateIdentityBody(t, "valid", 2, false)}, {Create: validCreateIdentityBody(t, "valid", 0, false)}, // <-- duplicate {Create: validCreateIdentityBody(t, "valid", 3, false)}, {Create: &identity.CreateIdentityBody{Traits: json.RawMessage(`"invalid traits"`)}}, // <-- invalid traits {Create: validCreateIdentityBody(t, "valid", 4, false)}, {Create: &identity.CreateIdentityBody{SchemaID: "nonexistent_schema", Traits: json.RawMessage(`{}`)}}, // <-- invalid schema ID } expectedToPass := []*identity.BatchIdentityPatch{patches[0], patches[1], patches[3], patches[5], patches[7]} // Create unique IDs for each patch patchIDs := make([]string, len(patches)) for i, p := range patches { id := uuid.NewV5(uuid.Nil, fmt.Sprintf("%d", i)) p.ID = &id patchIDs[i] = id.String() } req := &identity.BatchPatchIdentitiesBody{Identities: patches} body := send(t, adminTS, "PATCH", "/identities", http.StatusOK, req) var actions []string require.NoErrorf(t, json.Unmarshal(([]byte)(body.Get("identities.#.action").Raw), &actions), "%s", body) assert.Equalf(t, []string{"create", "create", "error", "create", "error", "create", "error", "create", "error"}, actions, "%s", body) // Check that all patch IDs are returned for i, gotPatchID := range body.Get("identities.#.patch_id").Array() { assert.Equal(t, patchIDs[i], gotPatchID.String()) } // Check specific errors assert.Equal(t, "Bad Request", body.Get("identities.2.error.status").String()) assert.Equal(t, "Conflict", body.Get("identities.4.error.status").String()) assert.Equal(t, "Bad Request", body.Get("identities.6.error.status").String()) assert.Equal(t, "Bad Request", body.Get("identities.8.error.status").String()) // Check that error reasons are specific, not generic assert.NotEqualf(t, "The request was malformed or contained invalid parameters", body.Get("identities.2.error.reason").String(), "error reason should be specific, not generic: %s", body) assert.NotEqualf(t, "The request was malformed or contained invalid parameters", body.Get("identities.6.error.reason").String(), "error reason should be specific, not generic: %s", body) assert.Containsf(t, body.Get("identities.8.error.reason").String(), "Unable to find JSON Schema ID: nonexistent_schema", "error reason should mention the missing schema ID: %s", body) var identityIDs []uuid.UUID require.NoErrorf(t, json.Unmarshal(([]byte)(body.Get("identities.#.identity").Raw), &identityIDs), "%s", body) actualIdentities, _, err := reg.Persister().ListIdentities(t.Context(), identity.ListIdentityParameters{IdsFilter: identityIDs}) require.NoError(t, err) actualIdentityIDs := make([]uuid.UUID, len(actualIdentities)) for i, id := range actualIdentities { actualIdentityIDs[i] = id.ID } assert.ElementsMatchf(t, identityIDs, actualIdentityIDs, "%s", body) expectedTraits := make(map[string]string, len(expectedToPass)) for i, p := range expectedToPass { expectedTraits[identityIDs[i].String()] = string(p.Create.Traits) } actualTraits := make(map[string]string, len(actualIdentities)) for _, id := range actualIdentities { actualTraits[id.ID.String()] = string(id.Traits) } assert.Equal(t, expectedTraits, actualTraits) }) t.Run("case=per-item errors surface specific reasons", func(t *testing.T) { // Regression test: batch endpoint must propagate specific error reasons // instead of replacing them with the generic herodot.ErrBadRequest message. uniqueSuffix := x.NewUUID().String() patches := []*identity.BatchIdentityPatch{ // 0: valid identity (should succeed) {Create: &identity.CreateIdentityBody{ SchemaID: "multiple_emails", Traits: json.RawMessage(fmt.Sprintf(`{"emails":["specific-err-%s@ory.sh"],"username":"specific-err-%s@ory.sh"}`, uniqueSuffix, uniqueSuffix)), State: "active", }}, // 1: unknown schema ID {Create: &identity.CreateIdentityBody{ SchemaID: "user_v1", Traits: json.RawMessage(`{"email":"test@ory.com"}`), }}, // 2: another unknown schema ID {Create: &identity.CreateIdentityBody{ SchemaID: "completely_made_up", Traits: json.RawMessage(`{}`), }}, // 3: traits are not an object (schema validation error) {Create: &identity.CreateIdentityBody{ SchemaID: "employee", Traits: json.RawMessage(`"just a string"`), }}, // 4: empty body (missing schema defaults to "default", but no traits) {Create: &identity.CreateIdentityBody{}}, } req := &identity.BatchPatchIdentitiesBody{Identities: patches} body := send(t, adminTS, "PATCH", "/identities", http.StatusOK, req) var actions []string require.NoErrorf(t, json.Unmarshal([]byte(body.Get("identities.#.action").Raw), &actions), "%s", body) assert.Equalf(t, []string{"create", "error", "error", "error", "error"}, actions, "%s", body) genericReason := "The request was malformed or contained invalid parameters" // Index 1: unknown schema "user_v1" assert.Equalf(t, float64(400), body.Get("identities.1.error.code").Float(), "%s", body) assert.Containsf(t, body.Get("identities.1.error.reason").String(), "Unable to find JSON Schema ID: user_v1", "expected specific schema-not-found reason: %s", body) // Index 2: unknown schema "completely_made_up" assert.Equalf(t, float64(400), body.Get("identities.2.error.code").Float(), "%s", body) assert.Containsf(t, body.Get("identities.2.error.reason").String(), "Unable to find JSON Schema ID: completely_made_up", "expected specific schema-not-found reason: %s", body) // Index 3: traits type mismatch (schema validation) assert.Equalf(t, float64(400), body.Get("identities.3.error.code").Float(), "%s", body) assert.NotEqualf(t, genericReason, body.Get("identities.3.error.reason").String(), "schema validation error should have a specific reason: %s", body) // Index 4: empty body (schema validation) assert.Equalf(t, float64(400), body.Get("identities.4.error.code").Float(), "%s", body) assert.NotEqualf(t, genericReason, body.Get("identities.4.error.reason").String(), "empty body error should have a specific reason: %s", body) }) t.Run("valid patches succeed", func(t *testing.T) { validPatches := []*identity.BatchIdentityPatch{ {Create: validCreateIdentityBody(t, "valid-patch", 0, false)}, {Create: validCreateIdentityBody(t, "valid-patch", 1, false)}, {Create: validCreateIdentityBody(t, "valid-patch", 2, false)}, {Create: validCreateIdentityBody(t, "valid-patch", 3, false)}, {Create: validCreateIdentityBody(t, "valid-patch", 4, false)}, } req := &identity.BatchPatchIdentitiesBody{Identities: validPatches} send(t, adminTS, "PATCH", "/identities", http.StatusOK, req) }) }) t.Run("case=external_id conflict returns per-item error", func(t *testing.T) { // Regression test for https://github.com/ory-corp/cloud/issues/10580 // When batch-importing identities with external_id and the external_id // already exists, the endpoint should return a per-item conflict error // instead of a 500 SQL syntax error. externalID1 := "ext-conflict-" + x.NewUUID().String() externalID2 := "ext-conflict-" + x.NewUUID().String() // First call: create two identities with unique external_ids. firstPatches := []*identity.BatchIdentityPatch{ {Create: &identity.CreateIdentityBody{ SchemaID: "multiple_emails", Traits: json.RawMessage(fmt.Sprintf(`{"emails":["first-a-%s@ory.sh"],"username":"first-a-%s@ory.sh"}`, externalID1, externalID1)), ExternalID: externalID1, State: "active", }}, {Create: &identity.CreateIdentityBody{ SchemaID: "multiple_emails", Traits: json.RawMessage(fmt.Sprintf(`{"emails":["first-b-%s@ory.sh"],"username":"first-b-%s@ory.sh"}`, externalID2, externalID2)), ExternalID: externalID2, State: "active", }}, } res := send(t, adminTS, "PATCH", "/identities", http.StatusOK, &identity.BatchPatchIdentitiesBody{Identities: firstPatches}) actions := res.Get("identities.#.action").Array() require.Len(t, actions, 2) assert.Equal(t, "create", actions[0].String()) assert.Equal(t, "create", actions[1].String()) // Second call: different traits/emails, but reuse the same external_ids. // The only conflict is on external_id. secondPatches := []*identity.BatchIdentityPatch{ {Create: &identity.CreateIdentityBody{ SchemaID: "multiple_emails", Traits: json.RawMessage(fmt.Sprintf(`{"emails":["second-a-%s@ory.sh"],"username":"second-a-%s@ory.sh"}`, externalID1, externalID1)), ExternalID: externalID1, State: "active", }}, {Create: &identity.CreateIdentityBody{ SchemaID: "multiple_emails", Traits: json.RawMessage(fmt.Sprintf(`{"emails":["second-b-%s@ory.sh"],"username":"second-b-%s@ory.sh"}`, externalID2, externalID2)), ExternalID: externalID2, State: "active", }}, } res = send(t, adminTS, "PATCH", "/identities", http.StatusOK, &identity.BatchPatchIdentitiesBody{Identities: secondPatches}) actions = res.Get("identities.#.action").Array() require.Lenf(t, actions, 2, "%s", res.Raw) assert.Equalf(t, "error", actions[0].String(), "expected conflict error for duplicate external_id: %s", res.Raw) assert.Equalf(t, "Conflict", res.Get("identities.0.error.status").String(), "%s", res.Raw) assert.Equalf(t, "error", actions[1].String(), "expected conflict error for duplicate external_id: %s", res.Raw) assert.Equalf(t, "Conflict", res.Get("identities.1.error.status").String(), "%s", res.Raw) }) t.Run("case=ignores create nil bodies", func(t *testing.T) { patches := []*identity.BatchIdentityPatch{ {Create: nil}, {Create: validCreateIdentityBody(t, "nil-batch-import", 0, false)}, {Create: nil}, {Create: validCreateIdentityBody(t, "nil-batch-import", 1, false)}, {Create: nil}, {Create: validCreateIdentityBody(t, "nil-batch-import", 2, false)}, {Create: nil}, {Create: validCreateIdentityBody(t, "nil-batch-import", 3, false)}, {Create: nil}, } req := &identity.BatchPatchIdentitiesBody{Identities: patches} res := send(t, adminTS, "PATCH", "/identities", http.StatusOK, req) assert.Len(t, res.Get("identities").Array(), len(patches)) assert.Equal(t, "null", res.Get("identities.0").Raw) assert.Equal(t, "null", res.Get("identities.2").Raw) assert.Equal(t, "null", res.Get("identities.4").Raw) assert.Equal(t, "null", res.Get("identities.6").Raw) assert.Equal(t, "null", res.Get("identities.8").Raw) }) t.Run("case=success", func(t *testing.T) { patches := []*identity.BatchIdentityPatch{ {Create: validCreateIdentityBody(t, "Batch-Import", 0, false)}, {Create: validCreateIdentityBody(t, "batch-import", 1, false)}, {Create: validCreateIdentityBody(t, "batch-import", 2, false)}, {Create: validCreateIdentityBody(t, "batch-import", 3, false)}, } req := &identity.BatchPatchIdentitiesBody{Identities: patches} res := send(t, adminTS, "PATCH", "/identities", http.StatusOK, req) assert.Len(t, res.Get("identities").Array(), len(patches)) for i, patch := range patches { t.Run(fmt.Sprintf("assert=identity %d", i), func(t *testing.T) { identityID := res.Get(fmt.Sprintf("identities.%d.identity", i)).String() require.NotEmpty(t, identityID) res := get(t, adminTS, "/identities/"+identityID, http.StatusOK) snapshotx.SnapshotT(t, res.Value(), snapshotx.ExceptNestedKeys( // All these keys change randomly, so we need to test them individually below "id", "schema_url", "created_at", "updated_at", "state_changed_at", "verifiable_addresses", "recovery_addresses", "identifiers")) emails := gjson.Parse(strings.ToLower(gjson.GetBytes(patch.Create.Traits, "emails").Raw)) assert.Equal(t, identityID, res.Get("id").String()) assert.EqualValues(t, patch.Create.Traits, res.Get("traits").Raw) assertJSONArrayElementsMatch(t, emails, res.Get("credentials.password.identifiers")) assertJSONArrayElementsMatch(t, emails, res.Get("recovery_addresses.#.value")) assertJSONArrayElementsMatch(t, emails, res.Get("verifiable_addresses.#.value")) // Test that the verified addresses are imported correctly assert.Len(t, res.Get("verifiable_addresses.#(verified=true)#").Array(), 2) assert.Len(t, res.Get("verifiable_addresses.#(verified=false)#").Array(), 2) assert.Len(t, res.Get("verifiable_addresses.#(status=pending)#").Array(), 2) assert.Len(t, res.Get("verifiable_addresses.#(status=sent)#").Array(), 1) assert.Len(t, res.Get("verifiable_addresses.#(status=completed)#").Array(), 1) }) } }) }) t.Run("case=PATCH update of state should update state changed at timestamp", func(t *testing.T) { id := x.NewUUID().String() email := "UPPER" + id + "@ory.sh" i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject": %q, "email": %q}`, id, email))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/state", "value": identity.StateInactive}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) assert.EqualValues(t, id, res.Get("traits.subject").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("traits.email").String(), "%s", res.Raw) assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.EqualValues(t, id, res.Get("traits.subject").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("traits.email").String(), "%s", res.Raw) assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) }) } }) t.Run("case=PATCH update external_id", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { id := x.NewUUID().String() externalID1 := x.NewUUID().String() externalID2 := x.NewUUID().String() email := "UPPER" + id + "@ory.sh" i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject": %q, "email": %q}`, id, email))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.Empty(t, res.Get("external_id").String(), "%s", res.Raw) t.Run("set external_id works", func(t *testing.T) { res = send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &[]patch{{"op": "replace", "path": "/external_id", "value": externalID1}}) assert.EqualValues(t, externalID1, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, externalID1, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/by/external/"+externalID1, http.StatusOK) assert.EqualValues(t, externalID1, res.Get("external_id").String(), "%s", res.Raw) }) t.Run("set external_id to empty clears it", func(t *testing.T) { res = send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &[]patch{{"op": "replace", "path": "/external_id", "value": ""}}) assert.Empty(t, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.Empty(t, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/by/external/"+externalID1, http.StatusNotFound) }) t.Run("set external_id again works", func(t *testing.T) { res = send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &[]patch{{"op": "replace", "path": "/external_id", "value": externalID2}}) assert.EqualValues(t, externalID2, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, externalID2, res.Get("external_id").String(), "%s", res.Raw) res = get(t, ts, "/identities/by/external/"+externalID2, http.StatusOK) assert.EqualValues(t, externalID2, res.Get("external_id").String(), "%s", res.Raw) }) }) } }) t.Run("case=PATCH update with uppercase emails should work", func(t *testing.T) { // Regression test for https://github.com/ory/kratos/issues/3187 for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { email := "UPPER" + x.NewUUID().String() + "@ory.sh" lowercaseEmail := strings.ToLower(email) var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + email + `"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) assert.EqualValues(t, lowercaseEmail, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, lowercaseEmail, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) identityID := res.Get("id").String() patch := []patch{ { "op": "replace", "path": "/verifiable_addresses/0/verified", "value": true, }, } res = send(t, ts, "PATCH", "/identities/"+identityID, http.StatusOK, &patch) assert.EqualValues(t, email, res.Get("traits.email").String(), "%s", res.Raw) assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("state").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+identityID, http.StatusOK) assert.EqualValues(t, identityID, res.Get("id").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("traits.email").String(), "%s", res.Raw) assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("state").String(), "%s", res.Raw) }) } }) t.Run("case=PATCH should update verified_at timestamp", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { email := x.NewUUID().String() + "@ory.sh" var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + email + `"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Falsef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.Falsef(t, res.Get("verifiable_addresses.0.verified_at").Exists(), "%s", res.Raw) identityID := res.Get("id").String() // set to verified, should also update verified_at timestamp patch1 := []patch{ { "op": "replace", "path": "/verifiable_addresses/0/verified", "value": true, }, } now := time.Now() res = send(t, ts, "PATCH", "/identities/"+identityID, http.StatusOK, &patch1) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Truef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.updated_at").Time(), 5*time.Second, "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.verified_at").Time(), 5*time.Second, "%s", res.Raw) res = get(t, ts, "/identities/"+identityID, http.StatusOK) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Truef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.updated_at").Time(), 5*time.Second, "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.verified_at").Time(), 5*time.Second, "%s", res.Raw) // update only verified_at timestamp verifiedAt := time.Date(1999, 1, 7, 8, 23, 19, 0, time.UTC) patch2 := []patch{ { "op": "replace", "path": "/verifiable_addresses/0/verified_at", "value": verifiedAt.Format(time.RFC3339), }, } now = time.Now() res = send(t, ts, "PATCH", "/identities/"+identityID, http.StatusOK, &patch2) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Truef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.Equalf(t, verifiedAt, res.Get("verifiable_addresses.0.verified_at").Time(), "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.updated_at").Time(), 5*time.Second, "%s", res.Raw) res = get(t, ts, "/identities/"+identityID, http.StatusOK) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Truef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.Equalf(t, verifiedAt, res.Get("verifiable_addresses.0.verified_at").Time(), "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.updated_at").Time(), 5*time.Second, "%s", res.Raw) // remove verified status patch3 := []patch{ { "op": "replace", "path": "/verifiable_addresses/0/verified", "value": false, }, } now = time.Now() res = send(t, ts, "PATCH", "/identities/"+identityID, http.StatusOK, &patch3) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Falsef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.Falsef(t, res.Get("verifiable_addresses.0.verified_at").Exists(), "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.updated_at").Time(), 5*time.Second, "%s", res.Raw) res = get(t, ts, "/identities/"+identityID, http.StatusOK) assert.EqualValues(t, email, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, email, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) assert.Falsef(t, res.Get("verifiable_addresses.0.verified").Bool(), "%s", res.Raw) assert.Falsef(t, res.Get("verifiable_addresses.0.verified_at").Exists(), "%s", res.Raw) assert.WithinDurationf(t, now, res.Get("verifiable_addresses.0.updated_at").Time(), 5*time.Second, "%s", res.Raw) }) } }) t.Run("case=PATCH update should not persist if schema id is invalid", func(t *testing.T) { sub := x.NewUUID().String() i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, sub))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/schema_id", "value": "invalid-id"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) assert.Contains(t, res.Get("error.reason").String(), "invalid-id", "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) // Assert that the schema ID is unchanged assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, sub, res.Get("traits.subject").String(), "%s", res.Raw) assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) }) } }) t.Run("case=PATCH update should not persist if invalid state is supplied", func(t *testing.T) { sub := x.NewUUID().String() i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, sub))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/state", "value": "invalid-value"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) assert.EqualValues(t, "The supplied state ('invalid-value') was not valid. Valid states are ('active', 'inactive').", res.Get("error.reason").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) // Assert that the schema ID is unchanged assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, sub, res.Get("traits.subject").String(), "%s", res.Raw) assert.False(t, res.Get("metadata_admin.admin").Exists(), "%s", res.Raw) assert.False(t, res.Get("metadata_public.public").Exists(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) }) } }) t.Run("case=PATCH update should update nested fields", func(t *testing.T) { sub := x.NewUUID().String() i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, sub))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/traits/subject", "value": "patched-subject"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) assert.EqualValues(t, "patched-subject", res.Get("traits.subject").String(), "%s", res.Raw) res = get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) // Assert that the schema ID is unchanged assert.EqualValues(t, i.SchemaID, res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, "patched-subject", res.Get("traits.subject").String(), "%s", res.Raw) }) } }) t.Run("case=PATCH should fail if no JSON payload is sent", func(t *testing.T) { sub := x.NewUUID().String() i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, sub))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, nil) assert.Equal(t, res.Get("error.message").Str, "invalid state detected", res.Raw) }) } }) t.Run("case=PATCH should fail if credentials are updated", func(t *testing.T) { sub := x.NewUUID().String() i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, sub))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/credentials", "value": "patched-credentials"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) assert.EqualValues(t, "patch includes denied path: /credentials", res.Get("error.message").String(), "%s", res.Raw) }) } }) t.Run("case=PATCH should fail if credential orgs are updated", func(t *testing.T) { email := x.NewUUID().String() + "@ory.sh" i := &identity.Identity{Traits: identity.Traits(`{"email":"` + email + `"}`)} i.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "some-provider"}]}`), }) require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/credentials/oidc/config/providers/0/organization", "value": "foo"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusBadRequest, &patch) assert.EqualValues(t, "patch includes denied path: /credentials/oidc/config/providers/0/organization", res.Get("error.message").String(), "%s", res.Raw) }) } }) t.Run("case=PATCH should allow to update credential password", func(t *testing.T) { email := uuid.NewV5(uuid.Nil, t.Name()).String() + "@ory.sh" i := &identity.Identity{Traits: identity.Traits(`{"email":"` + email + `"}`)} i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password": "secret", "some-random-key":" some-random-value"}`), }) require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*i), snapshotx.ExceptNestedKeys(ignoreDefault...)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/credentials/password/config/hashed_password", "value": "foo"}, } send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) updated, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), i.ID) require.NoError(t, err) assert.Equal(t, "foo", gjson.GetBytes(updated.Credentials[identity.CredentialsTypePassword].Config, "hashed_password").String()) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*updated), snapshotx.ExceptNestedKeys(ignoreDefault...)) }) } }) t.Run("case=PATCH should not invalidate credentials ory/cloud#148", func(t *testing.T) { // see https://github.com/ory/cloud/issues/148 createCredentials := func(t *testing.T) (*identity.Identity, string, string) { t.Helper() email := x.NewUUID().String() + "@ory.sh" password := "ljanf123akf" p, err := reg.Hasher(t.Context()).Generate(context.Background(), []byte(password)) require.NoError(t, err) i := &identity.Identity{Traits: identity.Traits(`{"email":"` + email + `"}`)} i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"` + string(p) + `"}`), }) require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) return i, email, password } for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { i, email, password := createCredentials(t) values := func(v url.Values) { v.Set("identifier", email) v.Set("password", password) } // verify login works initially loginResponse := testhelpers.SubmitLoginForm(t, true, ts.Client(), ts, values, false, true, 200, "") require.NotEmpty(t, gjson.Get(loginResponse, "session_token").String(), "expected to find a session token, found none") patch := []patch{ {"op": "replace", "path": "/metadata_public", "value": map[string]string{"role": "user"}}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) assert.EqualValues(t, "user", res.Get("metadata_public.role").String(), "%s", res.Raw) assert.NotEqualValues(t, i.StateChangedAt, sqlxx.NullTime(res.Get("state_changed_at").Time()), "%s", res.Raw) loginResponse = testhelpers.SubmitLoginForm(t, true, ts.Client(), ts, values, false, true, 200, "") msgs := gjson.Get(loginResponse, "ui.messages") require.Empty(t, msgs.Array(), "expected to find no messages: %s", msgs.String()) }) } }) t.Run("case=PATCH should update metadata_admin correctly", func(t *testing.T) { i := &identity.Identity{Traits: identity.Traits(fmt.Sprintf(`{"subject":"%s"}`, x.NewUUID().String()))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "add", "path": "/metadata_admin", "value": "metadata admin"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) assert.True(t, res.Get("metadata_admin").Exists(), "%s", res.Raw) assert.EqualValues(t, "metadata admin", res.Get("metadata_admin").String(), "%s", res.Raw) }) } }) t.Run("case=PATCH should update nested metadata_admin fields correctly", func(t *testing.T) { id := x.NewUUID().String() i := &identity.Identity{MetadataAdmin: sqlxx.NullJSONRawMessage(fmt.Sprintf(`{"id": "%s", "allowed": true}`, id))} require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { patch := []patch{ {"op": "replace", "path": "/metadata_admin/allowed", "value": "false"}, } res := send(t, ts, "PATCH", "/identities/"+i.ID.String(), http.StatusOK, &patch) assert.True(t, res.Get("metadata_admin.allowed").Exists(), "%s", res.Raw) assert.EqualValues(t, false, res.Get("metadata_admin.allowed").Bool(), "%s", res.Raw) assert.EqualValues(t, id, res.Get("metadata_admin.id").String(), "%s", res.Raw) }) } }) t.Run("case=should return entity with credentials metadata", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { // create identity with credentials i := identity.NewIdentity("") i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Config: sqlxx.JSONRawMessage(`{"secret":"pst"}`), }) i.Traits = identity.Traits("{}") require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i)) res := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.True(t, res.Get("credentials").Exists()) // Should contain changed date assert.True(t, res.Get("credentials.password.updated_at").Exists()) // Should not contain secrets assert.False(t, res.Get("credentials.password.config").Exists()) }) } }) t.Run("case=should not be able to create an identity with an invalid schema", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "unknown" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`) res := send(t, ts, "POST", "/identities", http.StatusBadRequest, &cr) assert.Contains(t, res.Raw, "unknown") }) } }) t.Run("case=should not be able to create an identity with an invalid state", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`) cr.State = "invalid-state" res := send(t, ts, "POST", "/identities", http.StatusBadRequest, &cr) assert.Contains(t, res.Get("error.reason").String(), `identity state is not valid`, "%s", res.Raw) }) } }) t.Run("case=should create an identity with a different schema", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) assert.JSONEq(t, string(cr.Traits), res.Get("traits").Raw, "%s", res.Raw) assert.EqualValues(t, "employee", res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("state").String(), "%s", res.Raw) assert.EqualValues(t, mockServerURL.String()+"/schemas/ZW1wbG95ZWU", res.Get("schema_url").String(), "%s", res.Raw) }) } }) t.Run("case=should create an identity with an explicit active state", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`) cr.State = identity.StateActive res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) assert.JSONEq(t, string(cr.Traits), res.Get("traits").Raw, "%s", res.Raw) assert.EqualValues(t, "employee", res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateActive, res.Get("state").String(), "%s", res.Raw) assert.EqualValues(t, mockServerURL.String()+"/schemas/ZW1wbG95ZWU", res.Get("schema_url").String(), "%s", res.Raw) }) } }) t.Run("case=should create an identity with an explicit inactive state", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`) cr.State = identity.StateInactive res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) assert.JSONEq(t, string(cr.Traits), res.Get("traits").Raw, "%s", res.Raw) assert.EqualValues(t, "employee", res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, identity.StateInactive, res.Get("state").String(), "%s", res.Raw) assert.EqualValues(t, mockServerURL.String()+"/schemas/ZW1wbG95ZWU", res.Get("schema_url").String(), "%s", res.Raw) }) } }) t.Run("case=should create and sync metadata and update privileged traits", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" originalEmail := x.NewUUID().String() + "@ory.sh" cr.Traits = []byte(`{"email":"` + originalEmail + `"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) assert.EqualValues(t, originalEmail, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, originalEmail, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) id := res.Get("id").String() updatedEmail := x.NewUUID().String() + "@ory.sh" res = send(t, ts, "PUT", "/identities/"+id, http.StatusOK, &identity.UpdateIdentityBody{ Traits: []byte(`{"email":"` + updatedEmail + `", "department": "ory"}`), }) assert.EqualValues(t, "employee", res.Get("schema_id").String(), "%s", res.Raw) assert.EqualValues(t, mockServerURL.String()+"/schemas/ZW1wbG95ZWU", res.Get("schema_url").String(), "%s", res.Raw) assert.EqualValues(t, updatedEmail, res.Get("traits.email").String(), "%s", res.Raw) assert.EqualValues(t, "ory", res.Get("traits.department").String(), "%s", res.Raw) assert.EqualValues(t, updatedEmail, res.Get("recovery_addresses.0.value").String(), "%s", res.Raw) assert.EqualValues(t, updatedEmail, res.Get("verifiable_addresses.0.value").String(), "%s", res.Raw) }) } }) t.Run("case=should update the schema id and fail because traits are invalid", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh", "department": "ory"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) id := res.Get("id").String() res = send(t, ts, "PUT", "/identities/"+id, http.StatusBadRequest, &identity.UpdateIdentityBody{ SchemaID: "customer", Traits: cr.Traits, }) assert.Contains(t, res.Get("error.reason").String(), `additionalProperties "department" not allowed`, "%s", res.Raw) }) } }) t.Run("case=should fail to update identity if state is invalid", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh", "department": "ory"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) id := res.Get("id").String() res = send(t, ts, "PUT", "/identities/"+id, http.StatusBadRequest, &identity.UpdateIdentityBody{ State: "invalid-state", Traits: []byte(`{"email":"` + faker.Email() + `", "department": "ory"}`), }) assert.Contains(t, res.Get("error.reason").String(), `identity state is not valid`, "%s", res.Raw) }) } }) t.Run("case=should update the schema id", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh", "department": "ory"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) id := res.Get("id").String() res = send(t, ts, "PUT", "/identities/"+id, http.StatusOK, &identity.UpdateIdentityBody{ SchemaID: "customer", Traits: []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh", "address": "ory street"}`), }) assert.EqualValues(t, "ory street", res.Get("traits.address").String(), "%s", res.Raw) }) } }) t.Run("case=should be able to update multiple identities", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { for i := 0; i <= 5; i++ { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"department": "ory"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) id := res.Get("id").String() _ = send(t, ts, "PUT", "/identities/"+id, http.StatusOK, &identity.UpdateIdentityBody{ SchemaID: "employee", Traits: []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`), }) _ = send(t, ts, "PUT", "/identities/"+id, http.StatusOK, &identity.UpdateIdentityBody{ SchemaID: "employee", Traits: []byte(`{}`), }) } }) } }) t.Run("case=should fail to update identity if input json is empty or json file does not exist", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { var cr identity.CreateIdentityBody cr.SchemaID = "employee" cr.Traits = []byte(`{"email":"` + x.NewUUID().String() + `@ory.sh", "department": "ory"}`) res := send(t, ts, "POST", "/identities", http.StatusCreated, &cr) id := res.Get("id").String() res = send(t, ts, "PUT", "/identities/"+id, http.StatusBadRequest, nil) assert.Contains(t, res.Get("error.reason").String(), `Unable to decode HTTP Request Body because its HTTP `+ `Header "Content-Length" is zero`, "%s", res.Raw) }) } }) t.Run("case=should list all identities", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities", http.StatusOK) assert.Empty(t, res.Get("#.credentials").Array(), "credentials config should be omitted") assert.Equal(t, res.Get("#").Int(), res.Get("#.metadata_public|#").Int(), "metadata_public config should be included") assert.Equal(t, res.Get("#").Int(), res.Get("#.metadata_admin|#").Int(), "metadata_admin config should be included") assert.EqualValues(t, "baz", res.Get(`#(traits.bar=="baz").traits.bar`).String()) }) } }) t.Run("organizations", func(t *testing.T) { t.Run("case=should list organization identities", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { orgID := uuid.Must(uuid.NewV4()) email := x.NewUUID().String() + "@ory.sh" require.NoError(t, reg.IdentityManager().Create(t.Context(), &identity.Identity{ Traits: identity.Traits(`{"email":"` + email + `"}`), OrganizationID: uuid.NullUUID{UUID: orgID, Valid: true}, })) res := get(t, ts, "/identities?organization_id="+orgID.String(), http.StatusOK) assert.Len(t, res.Array(), 1) assert.EqualValues(t, email, res.Get(`0.traits.email`).String(), "%s", res.Raw) }) } }) t.Run("case=malformed organization id should return an error", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities?organization_id=not-a-uuid", http.StatusBadRequest) assert.Contains(t, res.Get("error.reason").String(), "Invalid UUID value `not-a-uuid` for parameter `organization_id`.", "%s", res.Raw) }) } }) t.Run("case=unknown organization id should return an empty list", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { id := x.NewUUID() res := get(t, ts, "/identities?organization_id="+id.String(), http.StatusOK) assert.Empty(t, res.Array()) }) } }) }) t.Run("case=should list all identities with credentials", func(t *testing.T) { t.Run("include_credential=oidc should include OIDC credentials config", func(t *testing.T) { res := get(t, adminTS, "/identities?include_credential=oidc", http.StatusOK) require.True(t, res.IsArray()) require.GreaterOrEqual(t, len(res.Array()), 2) var foundOIDC, foundSAML bool for _, id := range res.Array() { if id.Get("credentials.oidc.identifiers.0").Str == "bar:foo.oidc@bar.com" { foundOIDC = true snapshotx.SnapshotT(t, id.Get("credentials.oidc.config").String()) } if id.Get("credentials.saml.identifiers.0").Str == "bar:foo.saml@bar.com" { foundSAML = true assert.False(t, id.Get("credentials.saml.config").Exists(), "SAML config is not included") } } assert.True(t, foundOIDC, "OIDC credential included") assert.True(t, foundSAML, "SAML credential included") }) t.Run("include_credential=saml should not include SAML tokens", func(t *testing.T) { res := get(t, adminTS, "/identities?include_credential=saml", http.StatusOK) samlProviders := res.Get("#.credentials.saml.config.providers|@flatten") assert.Greaterf(t, samlProviders.Get("#.subject|#").Int(), int64(0), "SAML config should contain subject: %s", samlProviders.Raw) assert.Zerof(t, res.Get("#.initial_id_token|#").Int(), "SAML config should not contain initial_id_token: %s", samlProviders.Raw) }) t.Run("include_credential=totp should not include OIDC credentials config", func(t *testing.T) { res := get(t, adminTS, "/identities?include_credential=totp", http.StatusOK) assert.Empty(t, res.Get("#.credentials.oidc.config").Array(), "OIDC config should not be included") assert.Empty(t, res.Get("#.credentials.saml.config").Array(), "SAML config should not be included") }) }) t.Run("case=should not be able to list all identities with credentials due to wrong credentials type", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities?include_credential=XYZ", http.StatusBadRequest) assert.Contains(t, res.Get("error.message").String(), "The request was malformed or contained invalid parameters", "%s", res.Raw) }) } }) t.Run("case=should list all identities with eventual consistency", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := get(t, ts, "/identities?consistency=eventual", http.StatusOK) assert.EqualValues(t, "baz", res.Get(`#(traits.bar=="baz").traits.bar`).String(), "%s", res.Raw) }) } }) t.Run("case=should not be able to update an identity that does not exist yet", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "PUT", "/identities/not-found", http.StatusNotFound, json.RawMessage(`{"traits": {"bar":"baz"}}`)) assert.Contains(t, res.Get("error.message").String(), "Unable to locate the resource", "%s", res.Raw) }) } }) t.Run("case=should not be able to patch an identity that does not exist yet", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { res := send(t, ts, "PATCH", "/identities/not-found", http.StatusNotFound, json.RawMessage(`{"traits": {"bar":"baz"}}`)) assert.Contains(t, res.Get("error.message").String(), "Unable to locate the resource", "%s", res.Raw) }) } }) t.Run("case=should return 404 for non-existing identities", func(t *testing.T) { for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("endpoint="+name, func(t *testing.T) { remove(t, ts, "/identities/"+x.NewUUID().String(), http.StatusNotFound) }) } }) t.Run("case=should delete credential of a specific user and no longer be able to retrieve it", func(t *testing.T) { type M = map[identity.CredentialsType]identity.Credentials createIdentity := func(creds M) func(*testing.T) *identity.Identity { return func(t *testing.T) *identity.Identity { i := identity.NewIdentity("") for k, v := range creds { v.Type = k creds[k] = v } i.Credentials = creds i.Traits = identity.Traits("{}") require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i)) return i } } for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} { t.Run("type=remove unknown identity/"+name, func(t *testing.T) { remove(t, ts, "/identities/"+x.NewUUID().String()+"/credentials/azerty", http.StatusNotFound) }) t.Run("type=remove unknown type/"+name, func(t *testing.T) { i := createIdentity(M{ identity.CredentialsTypePassword: { Config: []byte(`{"hashed_password":"some_valid_hash"}`), Identifiers: []string{x.NewUUID().String()}, }, })(t) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/azerty", http.StatusNotFound) }) t.Run("type=deny to remove password type/"+name, func(t *testing.T) { i := createIdentity(M{ identity.CredentialsTypePassword: { Config: []byte(`{"hashed_password":"some_valid_hash"}`), Identifiers: []string{x.NewUUID().String()}, }, })(t) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/password", http.StatusBadRequest) }) t.Run("type=allow to remove password type/"+name, func(t *testing.T) { sub := x.NewUUID().String() pwIdentifier := x.NewUUID().String() i := createIdentity(M{ identity.CredentialsTypePassword: { Config: []byte(`{"hashed_password":"some_valid_hash"}`), Identifiers: []string{pwIdentifier}, }, identity.CredentialsTypeOIDC: { Config: []byte(fmt.Sprintf(`{"providers":[{"subject":"%s","provider":"gh"}]}`, sub)), Identifiers: []string{identity.OIDCUniqueID("gh", sub)}, }, })(t) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/password", http.StatusNoContent) actual, creds, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(t.Context(), identity.CredentialsTypePassword, pwIdentifier) require.NoError(t, err) assert.Equal(t, "{}", string(creds.Config)) assert.Equal(t, i.ID, actual.ID) }) t.Run("type=remove oidc type/"+name, func(t *testing.T) { // force ordering among github identifiers githubSubject := "0" + randx.MustString(7, randx.Numeric) githubSubject2 := "1" + randx.MustString(7, randx.Numeric) googleSubject := randx.MustString(8, randx.Numeric) initialConfig := []byte(fmt.Sprintf(`{ "providers": [ { "subject": %q, "provider": "github" }, { "subject": %q, "provider": "github" }, { "subject": %q, "provider": "google" } ] }`, githubSubject, githubSubject2, googleSubject)) identifiers := []string{ identity.OIDCUniqueID("github", githubSubject), identity.OIDCUniqueID("github", githubSubject2), identity.OIDCUniqueID("google", googleSubject), } i := createIdentity(M{ identity.CredentialsTypeOIDC: { Identifiers: identifiers, Config: initialConfig, }, })(t) res := get(t, ts, "/identities/"+i.ID.String()+"?include_credential=oidc", http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.Len(t, res.Get("credentials.oidc.identifiers").Array(), 3, "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.oidc.identifiers.0").String(), identifiers[0], "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.oidc.identifiers.1").String(), identifiers[1], "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.oidc.identifiers.2").String(), identifiers[2], "%s", res.Raw) oidConfig := gjson.Parse(res.Get("credentials.oidc.config").String()) assert.Len(t, res.Get("credentials.oidc.identifiers").Array(), 3, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.provider").String(), "github", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.subject").String(), githubSubject, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.provider").String(), "github", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.subject").String(), githubSubject2, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.2.provider").String(), "google", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.2.subject").String(), googleSubject, "%s", res.Raw) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/oidc?identifier="+identifiers[1], http.StatusNoContent) res = get(t, ts, "/identities/"+i.ID.String()+"?include_credential=oidc", http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.Len(t, res.Get("credentials.oidc.identifiers").Array(), 2, "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.oidc.identifiers.0").String(), identifiers[0], "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.oidc.identifiers.1").String(), identifiers[2], "%s", res.Raw) oidConfig = gjson.Parse(res.Get("credentials.oidc.config").String()) assert.Len(t, res.Get("credentials.oidc.identifiers").Array(), 2, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.provider").String(), "github", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.subject").String(), githubSubject, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.provider").String(), "google", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.subject").String(), googleSubject, "%s", res.Raw) }) t.Run("type=remove saml type/"+name, func(t *testing.T) { // force ordering among identifiers entraSubject1 := "0" + randx.MustString(7, randx.Numeric) entraSubject2 := "1" + randx.MustString(7, randx.Numeric) oktaSubject := randx.MustString(8, randx.Numeric) initialConfig := []byte(fmt.Sprintf(`{ "providers": [ { "subject": %q, "provider": "entra" }, { "subject": %q, "provider": "entra" }, { "subject": %q, "provider": "okta" } ] }`, entraSubject1, entraSubject2, oktaSubject)) identifiers := []string{ identity.OIDCUniqueID("entra", entraSubject1), identity.OIDCUniqueID("entra", entraSubject2), identity.OIDCUniqueID("okta", oktaSubject), } i := createIdentity(M{ identity.CredentialsTypePassword: { Identifiers: []string{x.NewUUID().String()}, Config: []byte(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), // foobar }, identity.CredentialsTypeWebAuthn: { Identifiers: []string{x.NewUUID().String()}, Config: []byte(`{"credentials":[{"is_passwordless":true}]}`), }, identity.CredentialsTypeSAML: { Identifiers: identifiers, Config: initialConfig, }, })(t) res := get(t, ts, "/identities/"+i.ID.String()+"?include_credential=saml", http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.Len(t, res.Get("credentials.saml.identifiers").Array(), 3, "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.saml.identifiers.0").String(), identifiers[0], "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.saml.identifiers.1").String(), identifiers[1], "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.saml.identifiers.2").String(), identifiers[2], "%s", res.Raw) oidConfig := gjson.Parse(res.Get("credentials.saml.config").String()) assert.Len(t, res.Get("credentials.saml.identifiers").Array(), 3, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.provider").String(), "entra", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.subject").String(), entraSubject1, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.provider").String(), "entra", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.subject").String(), entraSubject2, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.2.provider").String(), "okta", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.2.subject").String(), oktaSubject, "%s", res.Raw) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/saml?identifier="+identifiers[1], http.StatusNoContent) res = get(t, ts, "/identities/"+i.ID.String()+"?include_credential=saml", http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) assert.Len(t, res.Get("credentials.saml.identifiers").Array(), 2, "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.saml.identifiers.0").String(), identifiers[0], "%s", res.Raw) assert.EqualValues(t, res.Get("credentials.saml.identifiers.1").String(), identifiers[2], "%s", res.Raw) oidConfig = gjson.Parse(res.Get("credentials.saml.config").String()) assert.Len(t, res.Get("credentials.saml.identifiers").Array(), 2, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.provider").String(), "entra", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.0.subject").String(), entraSubject1, "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.provider").String(), "okta", "%s", res.Raw) assert.EqualValues(t, oidConfig.Get("providers.1.subject").String(), oktaSubject, "%s", res.Raw) }) t.Run("type=remove webauthn passwordless type/"+name, func(t *testing.T) { expected := `{"credentials":[{"id":"THTndqZP5Mjvae1BFvJMaMfEMm7O7HE1ju+7PBaYA7Y=","added_at":"2022-12-16T14:11:55Z","public_key":"pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU=","display_name":"test","authenticator":{"aaguid":"rc4AAjW8xgpkiwsl8fBVAw==","sign_count":0,"clone_warning":false},"is_passwordless":true,"attestation_type":"none"}],"user_handle":"Ef5JiMpMRwuzauWs/9J0gQ=="}` i := createIdentity(M{identity.CredentialsTypeWebAuthn: {Config: []byte(expected)}})(t) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/webauthn", http.StatusNoContent) // Check that webauthn has not been deleted res := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(append(ignoreDefault, "hashed_password")...), snapshotx.ExceptPaths("credentials.oidc.identifiers")) }) t.Run("type=remove webauthn passwordless and multiple fido mfa type/"+name, func(t *testing.T) { message, err := json.Marshal(identity.CredentialsWebAuthnConfig{ Credentials: identity.CredentialsWebAuthn{ { // Passwordless 1 ID: []byte("THTndqZP5Mjvae1BFvJMaMfEMm7O7HE1ju+7PBaYA7Y="), AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, }, IsPasswordless: true, AttestationType: "none", }, { // Passwordless 2 ID: []byte("THTndqZP5Mjvae1BFvJMaMfEMm7O7HE2ju+7PBaYA7Y="), AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, }, IsPasswordless: true, AttestationType: "none", }, { // MFA 1 ID: []byte("THTndqZP5Mjvae1BFvJMaMfEMm7O7HE3ju+7PBaYA7Y="), AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, }, IsPasswordless: false, AttestationType: "none", }, { // MFA 2 ID: []byte("THTndqZP5Mjvae1BFvJMaMfEMm7O7HE4ju+7PBaYA7Y="), AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, }, IsPasswordless: false, AttestationType: "none", }, }, UserHandle: []byte("Ef5JiMpMRwuzauWs/9J0gQ=="), }) require.NoError(t, err) i := createIdentity(M{identity.CredentialsTypeWebAuthn: {Config: message}})(t) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/webauthn", http.StatusNoContent) // Check that webauthn has not been deleted res := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), res.Get("id").String(), "%s", res.Raw) actual, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), uuid.FromStringOrNil(res.Get("id").String())) require.NoError(t, err) snapshotx.SnapshotT(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), snapshotx.ExceptNestedKeys(append(ignoreDefault, "hashed_password")...), snapshotx.ExceptPaths("credentials.oidc.identifiers")) }) for ct, ctConf := range map[identity.CredentialsType][]byte{ identity.CredentialsTypeLookup: []byte(`{"recovery_codes": [{"code": "aaa"}]}`), identity.CredentialsTypeTOTP: []byte(`{"totp_url":"otpauth://totp/test"}`), identity.CredentialsTypeWebAuthn: []byte(`{"credentials":[{"id":"THTndqZP5Mjvae1BFvJMaMfEMm7O7HE1ju+7PBaYA7Y=","added_at":"2022-12-16T14:11:55Z","public_key":"pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU=","display_name":"test","authenticator":{"aaguid":"rc4AAjW8xgpkiwsl8fBVAw==","sign_count":0,"clone_warning":false},"is_passwordless":false,"attestation_type":"none"}],"user_handle":"Ef5JiMpMRwuzauWs/9J0gQ=="}`), } { t.Run("type=remove "+string(ct)+"/"+name, func(t *testing.T) { for _, tc := range []struct { desc string exist bool setup func(t *testing.T) *identity.Identity }{ { desc: "with", exist: true, setup: createIdentity(M{ identity.CredentialsTypePassword: {Config: []byte(`{"secret":"pst"}`)}, ct: {Config: ctConf}, }), }, { desc: "without", exist: false, setup: createIdentity(M{ identity.CredentialsTypePassword: {Config: []byte(`{"secret":"pst"}`)}, }), }, { desc: "multiple", exist: true, setup: createIdentity(M{ identity.CredentialsTypePassword: {Config: []byte(`{"secret":"pst"}`)}, identity.CredentialsTypeOIDC: {Config: []byte(`{"id":"pst"}`)}, ct: {Config: ctConf}, }), }, } { t.Run("type=remove "+string(ct)+"/"+name+"/"+tc.desc, func(t *testing.T) { i := tc.setup(t) credName := string(ct) // Initial Querying resBefore := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), resBefore.Get("id").String(), "%s", resBefore.Raw) assert.True(t, resBefore.Get("credentials").Exists()) if tc.exist { assert.True(t, resBefore.Get("credentials").Get(credName).Exists()) // Remove remove(t, ts, "/identities/"+i.ID.String()+"/credentials/"+credName, http.StatusNoContent) // Query back resAfter := get(t, ts, "/identities/"+i.ID.String(), http.StatusOK) assert.EqualValues(t, i.ID.String(), resAfter.Get("id").String(), "%s", resAfter.Raw) assert.True(t, resAfter.Get("credentials").Exists()) // Check results expected := resBefore.Get("credentials").Map() delete(expected, credName) expectedKeys := x.Keys(expected) sort.Strings(expectedKeys) result := resAfter.Get("credentials").Map() resultKeys := x.Keys(result) sort.Strings(resultKeys) assert.Equal(t, resultKeys, expectedKeys) } else { assert.False(t, resBefore.Get("credentials").Get(credName).Exists()) remove(t, ts, "/identities/"+i.ID.String()+"/credentials/"+credName, http.StatusNotFound) } }) } }) } } }) t.Run("case=should paginate all identities", func(t *testing.T) { // Start new server _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(testhelpers.IdentitySchemasConfig(map[string]string{ "default": "file://./stub/identity.schema.json", "customer": "file://./stub/handler/customer.schema.json", "multiple_emails": "file://./stub/handler/multiple_emails.schema.json", "employee": "file://./stub/handler/employee.schema.json", })), ) _, ts := testhelpers.NewKratosServerWithCSRF(t, reg) var toCreate []*identity.Identity count := 500 for range count { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Traits = identity.Traits(`{"email":"` + x.NewUUID().String() + `@ory.sh"}`) toCreate = append(toCreate, i) } require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentities(context.Background(), toCreate...)) for _, perPage := range []int{10, 50, 100, 500} { t.Run(fmt.Sprintf("perPage=%d", perPage), func(t *testing.T) { t.Parallel() body, _ := getFull(t, ts, fmt.Sprintf("/identities?per_page=%d", perPage), http.StatusOK) assert.Len(t, body.Array(), perPage) }) } t.Run("iterate over next page", func(t *testing.T) { perPage := 10 run := func(t *testing.T, path string, knownIDs map[string]struct{}) (next *url.URL, res *http.Response) { t.Logf("Requesting %s", path) body, res := getFull(t, ts, path, http.StatusOK) for _, i := range body.Array() { id := i.Get("id").String() _, seen := knownIDs[id] require.Falsef(t, seen, "ID %s was previously returned from the API", id) knownIDs[id] = struct{}{} } links := link.ParseResponse(res) if nextLink, ok := links["next"]; ok { next, err := url.Parse(nextLink.URI) require.NoError(t, err) return next, res } return nil, res } t.Run("using token pagination", func(t *testing.T) { knownIDs := make(map[string]struct{}) var pages int path := fmt.Sprintf("/admin/identities?page_size=%d", perPage) for { pages++ next, res := run(t, path, knownIDs) assert.NotContains(t, res.Header, "X-Total-Count", "not supported in token pagination") if next == nil { break } assert.NotContains(t, next.Query(), "page") assert.NotContains(t, next.Query(), "per_page") path = next.Path + "?" + next.Query().Encode() } assert.Len(t, knownIDs, count) assert.Equal(t, count/perPage, pages) }) t.Run("using page pagination", func(t *testing.T) { knownIDs := make(map[string]struct{}) var pages int path := fmt.Sprintf("/admin/identities?page=0&per_page=%d", perPage) for { pages++ next, res := run(t, path, knownIDs) assert.Equal(t, strconv.Itoa(count), res.Header.Get("X-Total-Count")) if next == nil { break } path = next.Path + "?" + next.Query().Encode() } assert.Len(t, knownIDs, count) assert.Equal(t, count/perPage, pages) }) }) }) } func validCreateIdentityBody(t *testing.T, prefix string, i int, plainPassword bool) *identity.CreateIdentityBody { var ( verifiableAddresses []identity.VerifiableAddress recoveryAddresses []identity.RecoveryAddress ) traits := struct { Emails []string `json:"emails"` Username string `json:"username"` }{} verificationStates := []identity.VerifiableAddressStatus{ identity.VerifiableAddressStatusPending, identity.VerifiableAddressStatusSent, identity.VerifiableAddressStatusCompleted, } for j := range 4 { email := fmt.Sprintf("%s-%d-%d@ory.sh", prefix, i, j) traits.Emails = append(traits.Emails, email) verifiableAddresses = append(verifiableAddresses, identity.VerifiableAddress{ Value: email, Via: identity.AddressTypeEmail, Verified: j%2 == 0, Status: verificationStates[j%len(verificationStates)], }) recoveryAddresses = append(recoveryAddresses, identity.RecoveryAddress{ Value: email, Via: identity.AddressTypeEmail, }) } traits.Username = traits.Emails[0] rawTraits, _ := json.Marshal(traits) conf := identity.AdminIdentityImportCredentialsPasswordConfig{ Password: fmt.Sprintf("password-%d", i), } if !plainPassword { g, err := bcrypt.GenerateFromPassword([]byte(fmt.Sprintf("password-%d", i)), 6) require.NoError(t, err) conf.Password = string(g) } externalID := "" if i%2 == 0 { externalID = fmt.Sprintf("external-id-%s-%d", prefix, i) } return &identity.CreateIdentityBody{ SchemaID: "multiple_emails", Traits: rawTraits, Credentials: &identity.IdentityWithCredentials{ Password: &identity.AdminIdentityImportCredentialsPassword{ Config: conf, }, }, VerifiableAddresses: verifiableAddresses, RecoveryAddresses: recoveryAddresses, MetadataPublic: json.RawMessage(fmt.Sprintf(`{"public-%d":"public"}`, i)), MetadataAdmin: json.RawMessage(fmt.Sprintf(`{"admin-%d":"admin"}`, i)), State: "active", ExternalID: externalID, } } func assertJSONArrayElementsMatch(t *testing.T, expected, actual gjson.Result, msgAndArgs ...any) { t.Helper() var expectedStrings, actualStrings []string expected.ForEach(func(_, value gjson.Result) bool { expectedStrings = append(expectedStrings, value.String()) return true }) actual.ForEach(func(_, value gjson.Result) bool { actualStrings = append(actualStrings, value.String()) return true }) assert.ElementsMatch(t, expectedStrings, actualStrings, msgAndArgs...) } ================================================ FILE: identity/identity.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "database/sql/driver" "encoding/json" "fmt" "slices" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/samber/lo" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "github.com/ory/herodot" "github.com/ory/kratos/cipher" "github.com/ory/kratos/driver/config" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/sqlxx" ) // An Identity's State // // The state can either be `active` or `inactive`. // // swagger:enum State type State string const ( StateActive State = "active" StateInactive State = "inactive" ) func (lt State) IsValid() error { switch lt { case StateActive, StateInactive: return nil } return errors.New("identity state is not valid") } // Identity represents an Ory Kratos identity // // An [identity](https://www.ory.sh/docs/kratos/concepts/identity-user-model) represents a (human) user in Ory. // // swagger:model identity type Identity struct { // ID is the identity's unique identifier. // // The Identity ID can not be changed and can not be chosen. This ensures future // compatibility and optimization for distributed stores such as CockroachDB. // // required: true ID uuid.UUID `json:"id" faker:"-" db:"id"` // ExternalID is an optional external ID of the identity. This is used to link // the identity to an external system. If set, the external ID must be unique // across all identities. // // required: false ExternalID sqlxx.NullString `json:"external_id,omitempty" faker:"-" db:"external_id"` // Credentials represents all credentials that can be used for authenticating this identity. Credentials map[CredentialsType]Credentials `json:"credentials,omitempty" faker:"-" db:"-"` // InternalAvailableAAL defines the maximum available AAL for this identity. // // - If the user has at least one two-factor authentication method configured, the AAL will be 2. // - If the user has only a password configured, the AAL will be 1. // // This field is AAL2 as soon as a second factor credential is found. A first factor is not required for this // field to return `aal2`. // // This field is primarily used to determine whether the user needs to upgrade to AAL2 without having to check // all the credentials in the database. Use with caution! InternalAvailableAAL NullableAuthenticatorAssuranceLevel `json:"-" faker:"-" db:"available_aal"` // // IdentifierCredentials contains the access and refresh token for oidc identifier // IdentifierCredentials []IdentifierCredential `json:"identifier_credentials,omitempty" faker:"-" db:"-"` // SchemaID is the ID of the JSON Schema to be used for validating the identity's traits. // // required: true SchemaID string `json:"schema_id" faker:"-" db:"schema_id"` // SchemaURL is the URL of the endpoint where the identity's traits schema can be fetched from. // // format: url // required: true SchemaURL string `json:"schema_url" faker:"-" db:"-"` // State is the identity's state. // // This value has currently no effect. State State `json:"state" faker:"-" db:"state"` // StateChangedAt contains the last time when the identity's state changed. StateChangedAt *sqlxx.NullTime `json:"state_changed_at,omitempty" faker:"-" db:"state_changed_at"` // Traits represent an identity's traits. The identity is able to create, modify, and delete traits // in a self-service manner. The input will always be validated against the JSON Schema defined // in `schema_url`. // // required: true Traits Traits `json:"traits" faker:"-" db:"traits"` // VerifiableAddresses contains all the addresses that can be verified by the user. // // Extensions: // --- // x-omitempty: true // --- VerifiableAddresses []VerifiableAddress `json:"verifiable_addresses,omitempty" faker:"-" has_many:"identity_verifiable_addresses" fk_id:"identity_id" order_by:"id asc"` // RecoveryAddresses contains all the addresses that can be used to recover an identity. // // Extensions: // --- // x-omitempty: true // --- RecoveryAddresses []RecoveryAddress `json:"recovery_addresses,omitempty" faker:"-" has_many:"identity_recovery_addresses" fk_id:"identity_id" order_by:"id asc"` // Store metadata about the identity which the identity itself can see when calling for example the // session endpoint. Do not store sensitive information (e.g. credit score) about the identity in this field. MetadataPublic sqlxx.NullJSONRawMessage `json:"metadata_public" faker:"-" db:"metadata_public"` // Store metadata about the user which is only accessible through admin APIs such as `GET /admin/identities/`. MetadataAdmin sqlxx.NullJSONRawMessage `json:"metadata_admin,omitempty" faker:"-" db:"metadata_admin"` // CreatedAt is a helper struct field for gobuffalo.pop. CreatedAt time.Time `json:"created_at" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. UpdatedAt time.Time `json:"updated_at" db:"updated_at"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` OrganizationID uuid.NullUUID `json:"organization_id,omitempty" faker:"-" db:"organization_id"` } func (i *Identity) PageToken() keysetpagination.PageToken { return keysetpagination.StringPageToken(i.ID.String()) } func DefaultPageToken() keysetpagination.PageToken { return keysetpagination.StringPageToken(uuid.Nil.String()) } // Traits represent an identity's traits. The identity is able to create, modify, and delete traits // in a self-service manner. The input will always be validated against the JSON Schema defined // in `schema_url`. // // swagger:model identityTraits type Traits json.RawMessage func (t *Traits) Scan(value any) error { return sqlxx.JSONScan(t, value) } func (t Traits) Value() (driver.Value, error) { return string(t), nil } func (t *Traits) String() string { return string(*t) } // MarshalJSON returns m as the JSON encoding of m. func (t Traits) MarshalJSON() ([]byte, error) { if t == nil { return []byte("null"), nil } return t, nil } // UnmarshalJSON sets *m to a copy of data. func (t *Traits) UnmarshalJSON(data []byte) error { if t == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } *t = append((*t)[0:0], data...) return nil } func (i Identity) TableName(context.Context) string { return "identities" } func (i *Identity) IsActive() bool { return i.State == StateActive } func (i *Identity) SetCredentials(t CredentialsType, c Credentials) { if i.Credentials == nil { i.Credentials = make(map[CredentialsType]Credentials) } c.Type = t c.IdentityID = i.ID i.Credentials[t] = c } func (i *Identity) SetCredentialsWithConfig(t CredentialsType, c Credentials, conf any) (err error) { if i.Credentials == nil { i.Credentials = make(map[CredentialsType]Credentials) } c.Config, err = json.Marshal(conf) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode %s credentials to JSON: %s", t, err)) } c.Type = t i.Credentials[t] = c return nil } func (i *Identity) DeleteCredentialsType(t CredentialsType) { if i.Credentials == nil { return } delete(i.Credentials, t) } // GetCredentialsOr returns the credentials for a given CredentialsType. If the // credentials do not exist, the fallback is returned. func (i *Identity) GetCredentialsOr(t CredentialsType, fallback *Credentials) *Credentials { c, ok := i.GetCredentials(t) if !ok { return fallback } return c } type CredentialsOptions func(c *Credentials) func WithAdditionalIdentifier(identifier string) CredentialsOptions { return func(c *Credentials) { c.Identifiers = append(c.Identifiers, identifier) } } func (i *Identity) UpsertCredentialsConfig(t CredentialsType, conf []byte, version int, opt ...CredentialsOptions) { c, ok := i.GetCredentials(t) if !ok { c = &Credentials{} } for _, optionFn := range opt { optionFn(c) } c.Type = t c.IdentityID = i.ID c.Config = conf c.Version = version i.SetCredentials(t, *c) } func (i *Identity) GetCredentials(t CredentialsType) (*Credentials, bool) { if c, ok := i.Credentials[t]; ok { return &c, true } return nil, false } func (i *Identity) ParseCredentials(t CredentialsType, config any) (*Credentials, error) { if c, ok := i.Credentials[t]; ok { if err := json.Unmarshal(c.Config, config); err != nil { return nil, errors.WithStack(err) } return &c, nil } return nil, herodot.ErrNotFound.WithReasonf("identity does not have credential type %s", t) } func (i *Identity) CopyWithoutCredentials() *Identity { ii := *i ii.Credentials = nil return &ii } // MergeOIDCCredentials merges the new credentials into the existing credentials. // If the provider already exists, the provider is replace and the identifier is // updated. This function requires the new credentials to have exactly one // provider and one identifier, as returned by identity.NewCredentialsOIDC. func (i *Identity) MergeOIDCCredentials(t CredentialsType, newCreds Credentials) (err error) { creds, ok := i.Credentials[t] if !ok { i.SetCredentials(t, newCreds) return nil } var conf CredentialsOIDC if err = json.Unmarshal(creds.Config, &conf); err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode old %s credentials from JSON: %s", creds.Config, err)) } var newConf CredentialsOIDC if err = json.Unmarshal(newCreds.Config, &newConf); err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode new %s credentials from JSON: %s", newCreds.Config, err)) } if len(newConf.Providers) != 1 { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Expected exactly one provider to merge credentials.")) } newProvider := newConf.Providers[0] // The identifier should have been set already, but we set it here just in case. if len(newCreds.Identifiers) != 1 { newCreds.Identifiers = []string{OIDCUniqueID(newProvider.Provider, newProvider.Subject)} } // Delete `use_auto_link` providers and their identifiers var obsoleteIdentifiers []string conf.Providers = slices.DeleteFunc(conf.Providers, func(p CredentialsOIDCProvider) bool { if p.Provider == newProvider.Provider && p.UseAutoLink { obsoleteIdentifiers = append(obsoleteIdentifiers, OIDCUniqueID(p.Provider, p.Subject)) return true } return false }) creds.Identifiers = slices.DeleteFunc(creds.Identifiers, func(identifier string) bool { return slices.Contains(obsoleteIdentifiers, identifier) }) creds.Identifiers = append(creds.Identifiers, newCreds.Identifiers...) conf.Providers = append(conf.Providers, newProvider) return i.SetCredentialsWithConfig(t, creds, conf) } func NewIdentity(traitsSchemaID string) *Identity { if traitsSchemaID == "" { traitsSchemaID = config.DefaultIdentityTraitsSchemaID } stateChangedAt := sqlxx.NullTime(time.Now().UTC()) return &Identity{ ID: uuid.Nil, Credentials: map[CredentialsType]Credentials{}, Traits: Traits("{}"), SchemaID: traitsSchemaID, VerifiableAddresses: []VerifiableAddress{}, State: StateActive, StateChangedAt: &stateChangedAt, } } func (i Identity) GetID() uuid.UUID { return i.ID } func (i Identity) MarshalJSON() ([]byte, error) { type localIdentity Identity i.Credentials = nil i.MetadataAdmin = nil result, err := json.Marshal(localIdentity(i)) if err != nil { return nil, err } return result, nil } func (i *Identity) UnmarshalJSON(b []byte) error { type localIdentity Identity err := json.Unmarshal(b, (*localIdentity)(i)) i.Credentials = nil i.MetadataAdmin = nil return err } // SetAvailableAAL sets the InternalAvailableAAL field based on the credentials stored in the identity. // // If a second factor is set up, the AAL will be set to 2. If only a first factor is set up, the AAL will be set to 1. // // A first factor is NOT required for the AAL to be set to 2 if a second factor is set up. func (i *Identity) SetAvailableAAL(ctx context.Context, m *Manager) (err error) { if c, err := m.CountActiveMultiFactorCredentials(ctx, i); err != nil { return err } else if c > 0 { i.InternalAvailableAAL = NewNullableAuthenticatorAssuranceLevel(AuthenticatorAssuranceLevel2) return nil } if c, err := m.CountActiveFirstFactorCredentials(ctx, i); err != nil { return err } else if c > 0 { i.InternalAvailableAAL = NewNullableAuthenticatorAssuranceLevel(AuthenticatorAssuranceLevel1) return nil } i.InternalAvailableAAL = NewNullableAuthenticatorAssuranceLevel(NoAuthenticatorAssuranceLevel) return nil } type WithAdminMetadataInJSON Identity func (i WithAdminMetadataInJSON) MarshalJSON() ([]byte, error) { type localIdentity Identity i.Credentials = nil return json.Marshal(localIdentity(i)) } type WithCredentialsAndAdminMetadataInJSON Identity func (i WithCredentialsAndAdminMetadataInJSON) MarshalJSON() ([]byte, error) { type localIdentity Identity return json.Marshal(localIdentity(i)) } type WithCredentialsNoConfigAndAdminMetadataInJSON Identity func (i WithCredentialsNoConfigAndAdminMetadataInJSON) MarshalJSON() ([]byte, error) { type localIdentity Identity for k, v := range i.Credentials { v.Config = nil i.Credentials[k] = v } return json.Marshal(localIdentity(i)) } func (i *Identity) Validate() error { expected := i.NID if expected == uuid.Nil { return errors.WithStack(herodot.ErrInternalServerError.WithReason("Received empty nid.")) } i.RecoveryAddresses = lo.Filter(i.RecoveryAddresses, func(v RecoveryAddress, key int) bool { return v.NID == expected }) i.VerifiableAddresses = lo.Filter(i.VerifiableAddresses, func(v VerifiableAddress, key int) bool { return v.NID == expected }) for k := range i.Credentials { c := i.Credentials[k] if c.NID != expected { delete(i.Credentials, k) continue } } return nil } // CollectVerifiableAddresses returns a slice of all verifiable addresses of the given identities. func CollectVerifiableAddresses(i []*Identity) (res []VerifiableAddress) { res = make([]VerifiableAddress, 0, len(i)) for _, id := range i { res = append(res, id.VerifiableAddresses...) } return res } // CollectRecoveryAddresses returns a slice of all recovery addresses of the given identities. func CollectRecoveryAddresses(i []*Identity) (res []RecoveryAddress) { res = make([]RecoveryAddress, 0, len(i)) for _, id := range i { res = append(res, id.RecoveryAddresses...) } return res } func (i *Identity) WithDeclassifiedCredentials(ctx context.Context, c cipher.Provider, includeCredentials []CredentialsType) (*Identity, error) { credsToPublish := make(map[CredentialsType]Credentials, len(i.Credentials)) for ct, original := range i.Credentials { if !slices.Contains(includeCredentials, ct) { toPublish := original toPublish.Config = []byte{} credsToPublish[ct] = toPublish continue } switch ct { case CredentialsTypeOIDC, CredentialsTypeSAML: toPublish := original toPublish.Config = []byte{} var i int var err error gjson.GetBytes(original.Config, "providers").ForEach(func(_, v gjson.Result) bool { if ct == CredentialsTypeOIDC { // Don't expose these for SAML for _, token := range []string{"initial_id_token", "initial_access_token", "initial_refresh_token"} { key := fmt.Sprintf("%d.%s", i, token) ciphertext := v.Get(token).String() plaintext, decryptErr := c.Cipher(ctx).Decrypt(ctx, ciphertext) if decryptErr != nil { plaintext = []byte{} } toPublish.Config, err = sjson.SetBytes(toPublish.Config, "providers."+key, string(plaintext)) if err != nil { return false } } } toPublish.Config, err = sjson.SetBytes(toPublish.Config, fmt.Sprintf("providers.%d.subject", i), v.Get("subject").String()) if err != nil { return false } toPublish.Config, err = sjson.SetBytes(toPublish.Config, fmt.Sprintf("providers.%d.provider", i), v.Get("provider").String()) if err != nil { return false } if org := v.Get("organization").String(); org != "" { toPublish.Config, err = sjson.SetBytes(toPublish.Config, fmt.Sprintf("providers.%d.organization", i), org) if err != nil { return false } } if useAutoLink := v.Get("use_auto_link").Bool(); useAutoLink { toPublish.Config, err = sjson.SetBytes(toPublish.Config, fmt.Sprintf("providers.%d.use_auto_link", i), useAutoLink) if err != nil { return false } } i++ return true }) if err != nil { return nil, err } credsToPublish[ct] = toPublish default: credsToPublish[ct] = original } } ii := *i ii.Credentials = credsToPublish return &ii, nil } func (i *Identity) deleteCredentialPassword() error { cred, ok := i.GetCredentials(CredentialsTypePassword) if !ok { return errors.WithStack(herodot.ErrNotFound.WithReasonf("You tried to remove a password credential but this user has no such credential set up.")) } cred.Config = []byte("{}") i.SetCredentials(CredentialsTypePassword, *cred) return nil } func (i *Identity) deleteCredentialWebAuthFromIdentity() error { cred, ok := i.GetCredentials(CredentialsTypeWebAuthn) if !ok { // This should never happend as it's checked earlier in the code; // But we never know... return errors.WithStack(herodot.ErrNotFound.WithReasonf("You tried to remove a WebAuthn credential but this user has no such credential set up.")) } var cc CredentialsWebAuthnConfig if err := json.Unmarshal(cred.Config, &cc); err != nil { // Database has been tampered or the json schema are incompatible (migration issue); return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode identity credentials.").WithDebug(err.Error())) } updated := make([]CredentialWebAuthn, 0) for k, cred := range cc.Credentials { if cred.IsPasswordless { updated = append(updated, cc.Credentials[k]) } } if len(updated) == 0 { i.DeleteCredentialsType(CredentialsTypeWebAuthn) return nil } cc.Credentials = updated message, err := json.Marshal(cc) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error())) } cred.Config = message i.SetCredentials(CredentialsTypeWebAuthn, *cred) return nil } func (i *Identity) deleteCredentialOIDCSAMLFromIdentity(ct CredentialsType, identifierToDelete string) error { switch ct { case CredentialsTypeOIDC, CredentialsTypeSAML: // ok default: return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unexpected credential type encountered: got %q, expected [%s, %s]", ct, CredentialsTypeOIDC, CredentialsTypeSAML)) } if identifierToDelete == "" { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("You must provide an identifier to delete this credential.")) } _, hasOIDC := i.GetCredentials(ct) if !hasOIDC { return errors.WithStack(herodot.ErrNotFound.WithReasonf("You tried to remove a %s credential but this user has no such credential set up.", ct)) } var oidcConfig CredentialsOIDC creds, err := i.ParseCredentials(ct, &oidcConfig) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode identity credentials.").WithDebug(err.Error())) } updatedIdentifiers := make([]string, 0, len(oidcConfig.Providers)) updatedProviders := make([]CredentialsOIDCProvider, 0, len(oidcConfig.Providers)) var found bool for _, cfg := range oidcConfig.Providers { if identifierToDelete == OIDCUniqueID(cfg.Provider, cfg.Subject) { found = true continue } updatedIdentifiers = append(updatedIdentifiers, OIDCUniqueID(cfg.Provider, cfg.Subject)) updatedProviders = append(updatedProviders, cfg) } if !found { return errors.WithStack(herodot.ErrNotFound.WithReasonf("The identifier `%s` was not found among OIDC credentials.", identifierToDelete)) } creds.Identifiers = updatedIdentifiers creds.Config, err = json.Marshal(&CredentialsOIDC{Providers: updatedProviders}) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error())) } i.Credentials[ct] = *creds return nil } // Patch Identities Parameters // // swagger:parameters batchPatchIdentities // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type batchPatchIdentitites struct { // in: body Body BatchPatchIdentitiesBody } // Patch Identities Body // // swagger:model patchIdentitiesBody // //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type BatchPatchIdentitiesBody struct { // Identities holds the list of patches to apply // // required Identities []*BatchIdentityPatch `json:"identities"` // Future fields: // RemotePatchesURL string // Async bool } // Payload for patching an identity // // swagger:model identityPatch // //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type BatchIdentityPatch struct { // The identity to create. Create *CreateIdentityBody `json:"create"` // The ID of this patch. // // The patch ID is optional. If specified, the ID will be returned in the // response, so consumers of this API can correlate the response with the // patch. ID *uuid.UUID `json:"patch_id"` } // swagger:enum BatchPatchAction // //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type BatchPatchAction string const ( // Create this identity. ActionCreate BatchPatchAction = "create" // Error indicates that the patch failed. ActionError BatchPatchAction = "error" // Future actions: // // Delete this identity. // ActionDelete BatchPatchAction = "delete" // // ActionUpdate BatchPatchAction = "update" ) // Patch identities response // // swagger:model batchPatchIdentitiesResponse // //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type batchPatchIdentitiesResponse struct { // The patch responses for the individual identities. Identities []*BatchIdentityPatchResponse `json:"identities"` } // Response for a single identity patch // // swagger:model identityPatchResponse // //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type BatchIdentityPatchResponse struct { // The action for this specific patch Action BatchPatchAction `json:"action"` // The identity ID payload of this patch IdentityID *uuid.UUID `json:"identity,omitempty"` // The ID of this patch response, if an ID was specified in the patch. PatchID *uuid.UUID `json:"patch_id,omitempty"` // The error message, if the action was "error". Error *herodot.DefaultError `json:"error,omitempty"` } ================================================ FILE: identity/identity_recovery.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "fmt" "time" "github.com/gofrs/uuid" ) type ( // RecoveryAddressStatus must not exceed 16 characters as that is the limitation in the SQL Schema. RecoveryAddressStatus string // swagger:model recoveryIdentityAddress RecoveryAddress struct { ID uuid.UUID `json:"id" db:"id" faker:"-"` // required: true Value string `json:"value" db:"value"` // required: true Via string `json:"via" db:"via"` // IdentityID is a helper struct field for gobuffalo.pop. IdentityID uuid.UUID `json:"-" faker:"-" db:"identity_id"` // CreatedAt is a helper struct field for gobuffalo.pop. CreatedAt time.Time `json:"created_at" faker:"-" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. UpdatedAt time.Time `json:"updated_at" faker:"-" db:"updated_at"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` } ) func (a RecoveryAddress) TableName() string { return "identity_recovery_addresses" } func (a RecoveryAddress) GetID() uuid.UUID { return a.ID } // Signature returns a unique string representation for the recovery address. func (a RecoveryAddress) Signature() string { return fmt.Sprintf("%v|%v|%v|%v", a.Value, a.Via, a.IdentityID, a.NID) } func NewRecoveryEmailAddress( value string, identity uuid.UUID, ) *RecoveryAddress { return &RecoveryAddress{ Value: value, Via: AddressTypeEmail, IdentityID: identity, } } func NewRecoverySMSAddress( value string, identity uuid.UUID, ) *RecoveryAddress { return &RecoveryAddress{ Value: value, Via: AddressTypeSMS, IdentityID: identity, } } ================================================ FILE: identity/identity_recovery_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "testing" "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/ory/kratos/x" ) func TestNewRecoveryEmailAddress(t *testing.T) { iid := x.NewUUID() a := NewRecoveryEmailAddress("foo@ory.sh", iid) assert.Equal(t, a.Value, "foo@ory.sh") assert.Equal(t, a.Via, AddressTypeEmail) assert.Equal(t, iid, a.IdentityID) assert.Equal(t, uuid.Nil, a.ID) } // TestRecoveryAddress_Hash tests that the hash considers all fields that are // written to the database (ignoring some well-known fields like the ID or // timestamps). func TestRecoveryAddress_Hash(t *testing.T) { cases := []struct { name string a RecoveryAddress }{ { name: "full fields", a: RecoveryAddress{ ID: x.NewUUID(), Value: "foo@bar.me", Via: AddressTypeEmail, CreatedAt: time.Now(), UpdatedAt: time.Now(), IdentityID: x.NewUUID(), NID: x.NewUUID(), }, }, { name: "empty fields", a: RecoveryAddress{}, }, { name: "email constructor", a: *NewRecoveryEmailAddress("foo@ory.sh", x.NewUUID()), }, { name: "full fields", a: RecoveryAddress{ ID: x.NewUUID(), Value: "6502530000", Via: AddressTypeSMS, CreatedAt: time.Now(), UpdatedAt: time.Now(), IdentityID: x.NewUUID(), NID: x.NewUUID(), }, }, { name: "SMS constructor", a: *NewRecoverySMSAddress("6502530000", x.NewUUID()), }, } for _, tc := range cases { t.Run("case="+tc.name, func(t *testing.T) { assert.Equal(t, reflectiveHash(tc.a), tc.a.Signature(), ) }) } } ================================================ FILE: identity/identity_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "bytes" "context" "encoding/json" "fmt" "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/kratos/cipher" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/x" "github.com/ory/x/snapshotx" "github.com/ory/x/sqlxx" ) func TestNewIdentity(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) assert.Equal(t, uuid.Nil, i.ID) assert.NotEmpty(t, i.Traits) assert.NotNil(t, i.Credentials) assert.True(t, i.IsActive()) } func TestIdentityCredentialsOr(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Credentials = nil expected := &Credentials{ID: x.NewUUID(), Type: CredentialsTypePassword} assert.Equal(t, expected, i.GetCredentialsOr(CredentialsTypePassword, expected)) expected = &Credentials{ID: x.NewUUID(), Type: CredentialsTypeWebAuthn} i.SetCredentials(CredentialsTypeWebAuthn, *expected) assert.Equal(t, expected, i.GetCredentialsOr(CredentialsTypeWebAuthn, nil)) } func TestIdentityCredentialsOrCreate(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Credentials = nil expected := &Credentials{Config: []byte("true"), IdentityID: i.ID, Type: CredentialsTypePassword} i.UpsertCredentialsConfig(CredentialsTypePassword, []byte("true"), 0) actual, ok := i.GetCredentials(CredentialsTypePassword) assert.True(t, ok) assert.Equal(t, expected, actual) } func TestIdentityCredentials(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Credentials = nil // Shouldn't error if map is nil i.DeleteCredentialsType(CredentialsTypeTOTP) expectedTOTP := Credentials{ID: x.NewUUID(), Type: CredentialsTypeTOTP} i.SetCredentials(CredentialsTypeTOTP, expectedTOTP) actual, found := i.GetCredentials(CredentialsTypeTOTP) assert.True(t, found, "should set and find the credential if map was nil") assert.Equal(t, &expectedTOTP, actual) expectedPassword := Credentials{ID: x.NewUUID(), Type: CredentialsTypePassword} i.SetCredentials(CredentialsTypePassword, expectedPassword) actual, found = i.GetCredentials(CredentialsTypePassword) assert.True(t, found, "should set and find the credential if map was not nil") assert.Equal(t, &expectedPassword, actual) expectedOIDC := Credentials{ID: x.NewUUID()} i.SetCredentials(CredentialsTypeOIDC, expectedOIDC) actual, found = i.GetCredentials(CredentialsTypeOIDC) assert.True(t, found) assert.Equal(t, expectedOIDC.ID, actual.ID) assert.Equal(t, CredentialsTypeOIDC, actual.Type, "should set the type if we forgot to set it in the credentials struct") i.DeleteCredentialsType(CredentialsTypePassword) _, found = i.GetCredentials(CredentialsTypePassword) assert.False(t, found, "should delete a credential properly") actual, found = i.GetCredentials(CredentialsTypeTOTP) assert.True(t, found, "but not alter other credentials") assert.Equal(t, &expectedTOTP, actual) } func TestMarshalExcludesCredentials(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Credentials = map[CredentialsType]Credentials{ CredentialsTypePassword: { ID: uuid.UUID{}, }, } rawJSON, err := json.Marshal(i) require.NoError(t, err) creds := gjson.GetBytes(rawJSON, "credentials") assert.Falsef(t, creds.Exists(), "Credentials should not be rendered to JSON, but got: %q", creds.Raw) // To ensure the original identity is not changed / Unmarshal has no side effects: require.NotEmpty(t, i.Credentials) } func TestMarshalExcludesCredentialsByReference(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Credentials = map[CredentialsType]Credentials{ CredentialsTypePassword: { ID: uuid.UUID{}, }, } var b bytes.Buffer require.Nil(t, json.NewEncoder(&b).Encode(&i)) assert.False(t, gjson.Get(b.String(), "credentials").Exists(), "Credentials should not be rendered to json") // To ensure the original identity is not changed / Unmarshal has no side effects: require.NotEmpty(t, i.Credentials) } func TestMarshalIgnoresAdminMetadata(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.MetadataAdmin = []byte(`{"admin":"bar"}`) i.MetadataPublic = []byte(`{"public":"bar"}`) var b bytes.Buffer require.Nil(t, json.NewEncoder(&b).Encode(&i)) assert.False(t, gjson.Get(b.String(), "metadata_admin.admin").Exists(), "Admin metadata should not be rendered to json but got: %s", b.String()) assert.Equal(t, "bar", gjson.Get(b.String(), "metadata_public.public").String(), "Public metadata should be rendered to json") // To ensure the original identity is not changed / Unmarshal has no side effects: require.NotEmpty(t, i.MetadataAdmin) require.NotEmpty(t, i.MetadataPublic) } func TestUnMarshallIgnoresCredentials(t *testing.T) { t.Parallel() jsonText := `{"id":"3234ad11-49c6-49e2-bfac-537f3e06cd85","schema_id":"default","schema_url":"","traits":{}, "credentials" : {"password":{"type":"","identifiers":null,"config":null,"updatedAt":"0001-01-01T00:00:00Z"}}}` var i Identity err := json.Unmarshal([]byte(jsonText), &i) assert.Nil(t, err) assert.Nil(t, i.Credentials) assert.Equal(t, "3234ad11-49c6-49e2-bfac-537f3e06cd85", i.ID.String()) } func TestUnMarshallIgnoresAdminMetadata(t *testing.T) { t.Parallel() jsonText := `{"id":"3234ad11-49c6-49e2-bfac-537f3e06cd85","schema_id":"default","schema_url":"","traits":{}, "admin_metadata" : {"foo":"bar"}}` var i Identity err := json.Unmarshal([]byte(jsonText), &i) assert.Nil(t, err) assert.Nil(t, i.MetadataAdmin) } func TestMarshalIdentityWithCredentialsWhenCredentialsNil(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Credentials = nil var b bytes.Buffer require.Nil(t, json.NewEncoder(&b).Encode(WithCredentialsNoConfigAndAdminMetadataInJSON(*i))) assert.False(t, gjson.Get(b.String(), "credentials").Exists()) } func TestMarshalIdentityWithAdminMetadata(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) i.MetadataAdmin = []byte(`{"some":"metadata"}`) var b bytes.Buffer require.Nil(t, json.NewEncoder(&b).Encode(WithAdminMetadataInJSON(*i))) assert.Equal(t, "metadata", gjson.GetBytes(i.MetadataAdmin, "some").String(), "Original metadata_admin should not be touched by marshalling") } func TestMarshalIdentityWithCredentialsNoConfig(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) credentials := map[CredentialsType]Credentials{ CredentialsTypePassword: { Type: CredentialsTypePassword, Config: sqlxx.JSONRawMessage(`{"some":"secret"}`), }, } i.Credentials = credentials i.MetadataAdmin = []byte(`{"some":"metadata"}`) rawJSON, err := json.Marshal((*WithCredentialsNoConfigAndAdminMetadataInJSON)(i)) require.NoError(t, err) credentialsInJSON := gjson.GetBytes(rawJSON, "credentials") assert.Truef(t, credentialsInJSON.Exists(), "Credentials should be rendered to JSON, but got: %q", credentialsInJSON.Raw) assert.JSONEq(t, `{"password":{"type":"password","identifiers":null,"updated_at":"0001-01-01T00:00:00Z","created_at":"0001-01-01T00:00:00Z","version":0}}`, credentialsInJSON.Raw) assert.Equal(t, credentials, i.Credentials, "Original credentials should not be touched by marshalling") assert.Equal(t, "metadata", gjson.GetBytes(i.MetadataAdmin, "some").String(), "Original metadata_admin should not be touched by marshalling") } func TestMarshalIdentityWithAll(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) credentials := map[CredentialsType]Credentials{ CredentialsTypePassword: { Type: CredentialsTypePassword, Config: sqlxx.JSONRawMessage("{\"some\" : \"secret\"}"), }, } i.Credentials = credentials i.MetadataAdmin = []byte(`{"some":"metadata"}`) var b bytes.Buffer require.Nil(t, json.NewEncoder(&b).Encode(WithCredentialsAndAdminMetadataInJSON(*i))) credentialsInJSON := gjson.Get(b.String(), "credentials") assert.True(t, credentialsInJSON.Exists()) snapshotx.SnapshotT(t, json.RawMessage(credentialsInJSON.Raw)) assert.Equal(t, credentials, i.Credentials, "Original credentials should not be touched by marshalling") assert.Equal(t, "metadata", gjson.GetBytes(i.MetadataAdmin, "some").String(), "Original credentials should not be touched by marshalling") } func TestValidateNID(t *testing.T) { t.Parallel() nid := x.NewUUID() for k, tc := range []struct { i *Identity expect *Identity expectedErr bool }{ {i: &Identity{}, expectedErr: true}, {i: &Identity{NID: nid}}, { i: &Identity{NID: nid, RecoveryAddresses: []RecoveryAddress{{NID: x.NewUUID()}}}, expect: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{}, RecoveryAddresses: []RecoveryAddress{}}, }, { i: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{{NID: x.NewUUID()}}}, expect: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{}, RecoveryAddresses: []RecoveryAddress{}}, }, { i: &Identity{NID: nid, Credentials: map[CredentialsType]Credentials{CredentialsTypePassword: {NID: x.NewUUID()}}}, expect: &Identity{NID: nid, Credentials: map[CredentialsType]Credentials{}, VerifiableAddresses: []VerifiableAddress{}, RecoveryAddresses: []RecoveryAddress{}}, }, { i: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{{NID: x.NewUUID()}}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}}, expect: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}}, }, { i: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{{NID: nid}}, RecoveryAddresses: []RecoveryAddress{{NID: x.NewUUID()}}}, expect: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{{NID: nid}}, RecoveryAddresses: []RecoveryAddress{}}, }, { i: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{{NID: nid}}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}}, expect: &Identity{NID: nid, VerifiableAddresses: []VerifiableAddress{{NID: nid}}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}}, }, { i: &Identity{NID: nid, Credentials: map[CredentialsType]Credentials{CredentialsTypePassword: {NID: x.NewUUID()}}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}, VerifiableAddresses: []VerifiableAddress{{NID: nid}}}, expect: &Identity{NID: nid, Credentials: map[CredentialsType]Credentials{}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}, VerifiableAddresses: []VerifiableAddress{{NID: nid}}}, }, { i: &Identity{NID: nid, Credentials: map[CredentialsType]Credentials{CredentialsTypePassword: {NID: nid}}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}, VerifiableAddresses: []VerifiableAddress{{NID: nid}}}, expect: &Identity{NID: nid, Credentials: map[CredentialsType]Credentials{CredentialsTypePassword: {NID: nid}}, RecoveryAddresses: []RecoveryAddress{{NID: nid}}, VerifiableAddresses: []VerifiableAddress{{NID: nid}}}, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { err := tc.i.Validate() if tc.expectedErr { require.Error(t, err) } else { require.NoError(t, err) if tc.expect != nil { assert.EqualValues(t, tc.expect, tc.i) } } }) } } // TestRecoveryAddresses tests the CollectRecoveryAddresses are collected from all identities. func TestRecoveryAddresses(t *testing.T) { t.Parallel() var addresses []RecoveryAddress for i := range 10 { addresses = append(addresses, RecoveryAddress{ Value: fmt.Sprintf("address-%d", i), }) } id1 := &Identity{RecoveryAddresses: addresses[:5]} id2 := &Identity{} id3 := &Identity{RecoveryAddresses: addresses[5:]} assert.Equal(t, addresses, CollectRecoveryAddresses([]*Identity{id1, id2, id3})) } // TestVerifiableAddresses tests the VerfifableAddresses are collected from all identities. func TestVerifiableAddresses(t *testing.T) { t.Parallel() var addresses []VerifiableAddress for i := 0; i < 10; i++ { addresses = append(addresses, VerifiableAddress{ Value: fmt.Sprintf("address-%d", i), }) } id1 := &Identity{VerifiableAddresses: addresses[:5]} id2 := &Identity{} id3 := &Identity{VerifiableAddresses: addresses[5:]} assert.Equal(t, addresses, CollectVerifiableAddresses([]*Identity{id1, id2, id3})) } type cipherProvider struct{} func (c *cipherProvider) Cipher(context.Context) cipher.Cipher { return cipher.NewNoop() } func TestWithDeclassifiedCredentials(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) credentials := map[CredentialsType]Credentials{ CredentialsTypePassword: { Identifiers: []string{"zab", "bar"}, Type: CredentialsTypePassword, Config: sqlxx.JSONRawMessage(`{"some": "secret"}`), }, CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"bar", "baz"}, // hint: // echo '666f6f' | xxd -r -p Config: sqlxx.JSONRawMessage(`{"providers": [{"subject":"bar","provider":"oidc1","initial_id_token":"666f6f"}]}`), }, CredentialsTypeSAML: { Type: CredentialsTypeSAML, Identifiers: []string{"qux", "quz"}, // hint: // echo 'this should not appear in output' | xxd -ps -c 0 Config: sqlxx.JSONRawMessage(`{"providers": [{"subject":"qux","provider":"saml1","initial_id_token":"746869732073686f756c64206e6f742061707065617220696e206f75747075740a"}]}`), }, CredentialsTypeWebAuthn: { Type: CredentialsTypeWebAuthn, Identifiers: []string{"foo", "bar"}, Config: sqlxx.JSONRawMessage(`{"some": "secret"}`), }, } i.Credentials = credentials t.Run("case=no-include", func(t *testing.T) { actualIdentity, err := i.WithDeclassifiedCredentials(t.Context(), &cipherProvider{}, nil) require.NoError(t, err) for ct, actual := range actualIdentity.Credentials { t.Run("credential="+string(ct), func(t *testing.T) { snapshotx.SnapshotT(t, actual) }) } }) t.Run("case=include-webauthn", func(t *testing.T) { actualIdentity, err := i.WithDeclassifiedCredentials(t.Context(), &cipherProvider{}, []CredentialsType{CredentialsTypeWebAuthn}) require.NoError(t, err) for ct, actual := range actualIdentity.Credentials { t.Run("credential="+string(ct), func(t *testing.T) { snapshotx.SnapshotT(t, actual) }) } }) t.Run("case=include-multi", func(t *testing.T) { actualIdentity, err := i.WithDeclassifiedCredentials(t.Context(), &cipherProvider{}, []CredentialsType{CredentialsTypeWebAuthn, CredentialsTypePassword}) require.NoError(t, err) for ct, actual := range actualIdentity.Credentials { t.Run("credential="+string(ct), func(t *testing.T) { snapshotx.SnapshotT(t, actual) }) } }) t.Run("case=oidc", func(t *testing.T) { actualIdentity, err := i.WithDeclassifiedCredentials(t.Context(), &cipherProvider{}, []CredentialsType{CredentialsTypeOIDC}) require.NoError(t, err) for ct, actual := range actualIdentity.Credentials { t.Run("credential="+string(ct), func(t *testing.T) { snapshotx.SnapshotT(t, actual) }) } }) t.Run("case=saml", func(t *testing.T) { actualIdentity, err := i.WithDeclassifiedCredentials(t.Context(), &cipherProvider{}, []CredentialsType{CredentialsTypeSAML}) require.NoError(t, err) for ct, actual := range actualIdentity.Credentials { t.Run("credential="+string(ct), func(t *testing.T) { snapshotx.SnapshotT(t, actual) }) } }) } func TestDeleteCredentialOIDCSAMLFromIdentity(t *testing.T) { t.Parallel() i := NewIdentity(config.DefaultIdentityTraitsSchemaID) err := i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeOIDC, "") assert.Error(t, err) err = i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeOIDC, "does-not-exist") assert.Error(t, err) err = i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeSAML, "") assert.Error(t, err) err = i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeSAML, "does-not-exist") assert.Error(t, err) credentials := map[CredentialsType]Credentials{ CredentialsTypePassword: { Identifiers: []string{"zab", "bar"}, Type: CredentialsTypePassword, Config: sqlxx.JSONRawMessage(`{"some" : "secret"}`), }, CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"bar:1234", "baz:5678"}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "bar", "subject": "1234"}, {"provider": "baz", "subject": "5678"}]}`), }, CredentialsTypeSAML: { Type: CredentialsTypeSAML, Identifiers: []string{"bar:1234", "baz:5678"}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "bar", "subject": "1234"}, {"provider": "baz", "subject": "5678"}]}`), }, CredentialsTypeWebAuthn: { Type: CredentialsTypeWebAuthn, Identifiers: []string{"foo", "bar"}, Config: sqlxx.JSONRawMessage(`{"some" : "secret"}`), }, } i.Credentials = credentials err = i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeOIDC, "zab") assert.Error(t, err) err = i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeOIDC, "foo") assert.Error(t, err) err = i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeOIDC, "bar") assert.Error(t, err, "matches multiple OIDC credentials") require.NoError(t, i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeOIDC, "bar:1234")) assert.Len(t, i.Credentials, 4) assert.Contains(t, i.Credentials, CredentialsTypePassword) assert.EqualValues(t, i.Credentials[CredentialsTypePassword].Identifiers, []string{"zab", "bar"}) assert.Contains(t, i.Credentials, CredentialsTypeWebAuthn) assert.EqualValues(t, i.Credentials[CredentialsTypeWebAuthn].Identifiers, []string{"foo", "bar"}) assert.Contains(t, i.Credentials, CredentialsTypeSAML) assert.EqualValues(t, i.Credentials[CredentialsTypeSAML].Identifiers, []string{"bar:1234", "baz:5678"}) assert.Contains(t, i.Credentials, CredentialsTypeOIDC) oidc, ok := i.GetCredentials(CredentialsTypeOIDC) require.True(t, ok) assert.EqualValues(t, oidc.Identifiers, []string{"baz:5678"}) var cfg CredentialsOIDC _, err = i.ParseCredentials(CredentialsTypeOIDC, &cfg) require.NoError(t, err) assert.EqualValues(t, CredentialsOIDC{Providers: []CredentialsOIDCProvider{{Provider: "baz", Subject: "5678"}}}, cfg) require.NoError(t, i.deleteCredentialOIDCSAMLFromIdentity(CredentialsTypeSAML, "baz:5678")) assert.Len(t, i.Credentials, 4) assert.Contains(t, i.Credentials, CredentialsTypeOIDC) assert.EqualValues(t, i.Credentials[CredentialsTypeOIDC].Identifiers, []string{"baz:5678"}) assert.Contains(t, i.Credentials, CredentialsTypeSAML) saml, ok := i.GetCredentials(CredentialsTypeSAML) require.True(t, ok) assert.EqualValues(t, saml.Identifiers, []string{"bar:1234"}) var samlCfg CredentialsOIDC _, err = i.ParseCredentials(CredentialsTypeSAML, &samlCfg) require.NoError(t, err) assert.EqualValues(t, CredentialsOIDC{Providers: []CredentialsOIDCProvider{{Provider: "bar", Subject: "1234"}}}, samlCfg) } func TestMergeOIDCCredentials(t *testing.T) { t.Parallel() for _, tc := range []struct { name string identity *Identity newCredentials Credentials expectedIdentity *Identity assertErr assert.ErrorAssertionFunc }{ { name: "adds OIDC credential if not exists", identity: &Identity{}, newCredentials: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`{"providers":[{"provider":"oidc","subject":"1234"}]}`), }, expectedIdentity: &Identity{ Credentials: map[CredentialsType]Credentials{ CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`{"providers":[{"provider":"oidc","subject":"1234"}]}`), }, }, }, }, { name: "merges OIDC credential if exists", identity: &Identity{ Credentials: map[CredentialsType]Credentials{ CredentialsTypePassword: { Type: CredentialsTypePassword, Identifiers: []string{"user@example.com"}, }, CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"foo", "replace:1234", "bar", "baz", "replace:abc", "replace:dont-replace"}, Config: sqlxx.JSONRawMessage(`{"providers": [ {"provider": "replace", "subject": "1234", "use_auto_link": true}, {"provider": "dont-touch", "subject": "foo"}, {"provider": "replace", "subject": "abc", "use_auto_link": true}, {"provider": "also-dont-touch", "subject": "bar", "use_auto_link": true}, {"provider": "replace", "subject": "dont-replace", "use_auto_link": false} ]}`), }, }, }, newCredentials: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "replace", "subject": "new-subject"}]}`), }, expectedIdentity: &Identity{ Credentials: map[CredentialsType]Credentials{ CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"foo", "bar", "baz", "replace:dont-replace", "replace:new-subject"}, Config: sqlxx.JSONRawMessage(`{ "providers" : [ { "subject" : "foo", "provider" : "dont-touch", "initial_id_token" : "", "initial_access_token" : "", "initial_refresh_token" : "" }, { "subject" : "bar", "provider" : "also-dont-touch", "initial_id_token" : "", "initial_access_token" : "", "initial_refresh_token" : "", "use_auto_link": true }, { "subject" : "dont-replace", "provider" : "replace", "initial_id_token" : "", "initial_access_token" : "", "initial_refresh_token" : "" }, { "subject" : "new-subject", "provider" : "replace", "initial_id_token" : "", "initial_access_token" : "", "initial_refresh_token" : "" } ] }`), }, CredentialsTypePassword: { Type: CredentialsTypePassword, Identifiers: []string{"user@example.com"}, }, }, }, }, { name: "errs if new credential has no provider", identity: &Identity{ Credentials: map[CredentialsType]Credentials{ CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "oidc", "subject": "1234"}]}`), }, }, }, newCredentials: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`{"providers": []}`), }, assertErr: assert.Error, }, { name: "errs if identity credentials are invalid", identity: &Identity{ Credentials: map[CredentialsType]Credentials{ CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage("invalid"), }, }, }, newCredentials: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "replace", "subject": "new-subject"}]}`), }, assertErr: assert.Error, }, { name: "errs if new credential config is invalid", identity: &Identity{ Credentials: map[CredentialsType]Credentials{ CredentialsTypeOIDC: { Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`{"providers": [{"provider": "oidc", "subject": "1234"}]}`), }, }, }, newCredentials: Credentials{ Type: CredentialsTypeOIDC, Identifiers: []string{"oidc:1234"}, Config: sqlxx.JSONRawMessage(`invalid`), }, assertErr: assert.Error, }, } { t.Run("case="+tc.name, func(t *testing.T) { err := tc.identity.MergeOIDCCredentials(CredentialsTypeOIDC, tc.newCredentials) if tc.assertErr != nil { tc.assertErr(t, err) } else { require.NoError(t, err) } if tc.expectedIdentity != nil { var buf bytes.Buffer require.NoError(t, json.Compact(&buf, tc.expectedIdentity.Credentials[CredentialsTypeOIDC].Config)) tc.expectedIdentity.UpsertCredentialsConfig(CredentialsTypeOIDC, buf.Bytes(), 0) assert.EqualExportedValues(t, tc.expectedIdentity, tc.identity) } }) } } ================================================ FILE: identity/identity_verification.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "fmt" "time" "github.com/gofrs/uuid" "github.com/ory/x/sqlxx" ) const ( VerifiableAddressStatusPending VerifiableAddressStatus = "pending" VerifiableAddressStatusSent VerifiableAddressStatus = "sent" VerifiableAddressStatusCompleted VerifiableAddressStatus = "completed" ) // VerifiableAddressStatus must not exceed 16 characters as that is the limitation in the SQL Schema // // swagger:model identityVerifiableAddressStatus type VerifiableAddressStatus string // VerifiableAddress is an identity's verifiable address // // swagger:model verifiableIdentityAddress type VerifiableAddress struct { // The ID // ID uuid.UUID `json:"id" db:"id" faker:"-"` // The address value // // example foo@user.com // required: true Value string `json:"value" db:"value"` // Indicates if the address has already been verified // // example: true // required: true Verified bool `json:"verified" db:"verified"` // The delivery method // // enum: email,sms // example: email // required: true Via string `json:"via" db:"via"` // The verified address status // // enum: pending,sent,completed // example: sent // required: true Status VerifiableAddressStatus `json:"status" db:"status"` // When the address was verified // // example: 2014-01-01T23:28:56.782Z // required: false VerifiedAt *sqlxx.NullTime `json:"verified_at,omitempty" faker:"-" db:"verified_at"` // When this entry was created // // example: 2014-01-01T23:28:56.782Z CreatedAt time.Time `json:"created_at" faker:"-" db:"created_at"` // When this entry was last updated // // example: 2014-01-01T23:28:56.782Z UpdatedAt time.Time `json:"updated_at" faker:"-" db:"updated_at"` // IdentityID is a helper struct field for gobuffalo.pop. IdentityID uuid.UUID `json:"-" faker:"-" db:"identity_id"` NID uuid.UUID `json:"-" faker:"-" db:"nid"` } func (a VerifiableAddress) TableName() string { return "identity_verifiable_addresses" } func NewVerifiableEmailAddress(value string, identity uuid.UUID) *VerifiableAddress { return NewVerifiableAddress(value, identity, AddressTypeEmail) } func NewVerifiableAddress(value string, identity uuid.UUID, channel string) *VerifiableAddress { return &VerifiableAddress{ Value: value, Verified: false, Status: VerifiableAddressStatusPending, Via: channel, IdentityID: identity, } } func (a VerifiableAddress) GetID() uuid.UUID { return a.ID } // Signature returns a unique string representation for the recovery address. func (a VerifiableAddress) Signature() string { return fmt.Sprintf("%v|%v|%v|%v|%v|%v|%v", a.Value, a.Verified, a.Via, a.Status, a.VerifiedAt, a.IdentityID, a.NID) } ================================================ FILE: identity/identity_verification_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "fmt" "reflect" "strings" "testing" "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/ory/x/sqlxx" "github.com/ory/kratos/x" ) func TestNewVerifiableEmailAddress(t *testing.T) { iid := x.NewUUID() a := NewVerifiableEmailAddress("foo@ory.sh", iid) var nullTime *sqlxx.NullTime assert.Equal(t, a.Value, "foo@ory.sh") assert.Equal(t, a.Via, AddressTypeEmail) assert.Equal(t, a.Status, VerifiableAddressStatusPending) assert.Equal(t, a.Verified, false) assert.EqualValues(t, nullTime, a.VerifiedAt) assert.Equal(t, iid, a.IdentityID) assert.Equal(t, uuid.Nil, a.ID) } var tagsIgnoredForHashing = map[string]struct{}{ "id": {}, "created_at": {}, "updated_at": {}, // "verified_at": {}, // we explicitly want to be able to update just this field and nothing else } func reflectiveHash(record any) string { var ( val = reflect.ValueOf(record) typ = reflect.TypeOf(record) values = []string{} ) for i := 0; i < val.NumField(); i++ { dbTag, ok := typ.Field(i).Tag.Lookup("db") if !ok { continue } if _, ignore := tagsIgnoredForHashing[dbTag]; ignore { continue } if !val.Field(i).CanInterface() { continue } values = append(values, fmt.Sprintf("%v", val.Field(i).Interface())) } return strings.Join(values, "|") } // TestVerifiableAddress_Hash tests that the hash considers all fields that are // written to the database (ignoring some well-known fields like the ID or // timestamps). func TestVerifiableAddress_Hash(t *testing.T) { now := sqlxx.NullTime(time.Now()) cases := []struct { name string a VerifiableAddress }{ { name: "full fields", a: VerifiableAddress{ ID: x.NewUUID(), Value: "foo@bar.me", Verified: false, Via: AddressTypeEmail, Status: VerifiableAddressStatusPending, VerifiedAt: &now, CreatedAt: time.Now(), UpdatedAt: time.Now(), IdentityID: x.NewUUID(), NID: x.NewUUID(), }, }, { name: "empty fields", a: VerifiableAddress{}, }, { name: "constructor", a: *NewVerifiableEmailAddress("foo@ory.sh", x.NewUUID()), }, } for _, tc := range cases { t.Run("case="+tc.name, func(t *testing.T) { assert.Equal(t, reflectiveHash(tc.a), tc.a.Signature(), ) }) } } ================================================ FILE: identity/manager.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "encoding/json" "fmt" "reflect" "slices" "sort" "strings" stderrors "errors" "github.com/gofrs/uuid" "github.com/mohae/deepcopy" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/jsonschema/v3" "github.com/ory/kratos/courier" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/schema" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) var ErrProtectedFieldModified = herodot.ErrForbidden. WithReasonf(`A field was modified that updates one or more credentials-related settings. This action was blocked because an unprivileged method was used to execute the update. This is either a configuration issue or a bug and should be reported to the system administrator.`) type ( managerDependencies interface { config.Provider PoolProvider PrivilegedPoolProvider otelx.Provider courier.Provider ValidationProvider ActiveCredentialsCounterStrategyProvider logrusx.Provider } ManagementProvider interface { IdentityManager() *Manager } Manager struct { r managerDependencies } ManagerOptions struct { ExposeValidationErrors bool AllowWriteProtectedTraits bool } ManagerOption func(*ManagerOptions) ) func NewManager(r managerDependencies) *Manager { return &Manager{r: r} } func ManagerExposeValidationErrorsForInternalTypeAssertion(options *ManagerOptions) { options.ExposeValidationErrors = true } func ManagerAllowWriteProtectedTraits(options *ManagerOptions) { options.AllowWriteProtectedTraits = true } func newManagerOptions(opts []ManagerOption) *ManagerOptions { var o ManagerOptions for _, f := range opts { f(&o) } return &o } func (m *Manager) Create(ctx context.Context, i *Identity, opts ...ManagerOption) (err error) { ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.Create") defer otelx.End(span, &err) if i.SchemaID == "" { i.SchemaID = m.r.Config().DefaultIdentityTraitsSchemaID(ctx) } o := newManagerOptions(opts) if err := m.ValidateIdentity(ctx, i, o); err != nil { return err } if err := m.r.PrivilegedIdentityPool().CreateIdentity(ctx, i); err != nil { if errors.Is(err, sqlcon.ErrUniqueViolation) { return m.findExistingAuthMethod(ctx, err, i) } return err } return nil } func (m *Manager) ConflictingIdentity(ctx context.Context, i *Identity) (found *Identity, foundConflictAddress string, conflictAddressType string, err error) { for ct, cred := range i.Credentials { for _, id := range cred.Identifiers { found, _, err = m.r.PrivilegedIdentityPool().FindByCredentialsIdentifier(ctx, ct, id) if err != nil { continue } // FindByCredentialsIdentifier does not expand identity credentials. if err = m.r.PrivilegedIdentityPool().HydrateIdentityAssociations(ctx, found, ExpandCredentials); err != nil { return nil, "", "", err } return found, id, ct.String(), nil } } // If the conflict is not in the identifiers table, it is coming from the verifiable or recovery address. for _, va := range i.VerifiableAddresses { conflictingAddress, err := m.r.PrivilegedIdentityPool().FindVerifiableAddressByValue(ctx, va.Via, va.Value) if errors.Is(err, sqlcon.ErrNoRows) { continue } else if err != nil { return nil, "", "", err } foundConflictAddress = conflictingAddress.Value found, err = m.r.PrivilegedIdentityPool().GetIdentity(ctx, conflictingAddress.IdentityID, ExpandCredentials) if err != nil { return nil, "", "", err } return found, foundConflictAddress, va.Via, nil } // Last option: check the recovery address for _, va := range i.RecoveryAddresses { conflictingAddress, err := m.r.PrivilegedIdentityPool().FindRecoveryAddressByValue(ctx, va.Via, va.Value) if errors.Is(err, sqlcon.ErrNoRows) { continue } else if err != nil { return nil, "", "", err } foundConflictAddress = conflictingAddress.Value found, err = m.r.PrivilegedIdentityPool().GetIdentity(ctx, conflictingAddress.IdentityID, ExpandCredentials) if err != nil { return nil, "", "", err } return found, foundConflictAddress, string(va.Via), nil } return nil, "", "", sqlcon.ErrNoRows } func (m *Manager) findExistingAuthMethod(ctx context.Context, e error, i *Identity) (err error) { if !m.r.Config().SelfServiceFlowRegistrationLoginHints(ctx) { return &ErrDuplicateCredentials{error: e} } found, foundConflictAddress, conflictingAddressType, err := m.ConflictingIdentity(ctx, i) if err != nil { if errors.Is(err, sqlcon.ErrNoRows) { return &ErrDuplicateCredentials{error: e} } return err } // We need to sort the credentials for the error message to be deterministic. var creds []Credentials for _, cred := range found.Credentials { creds = append(creds, cred) } sort.Slice(creds, func(i, j int) bool { return creds[i].Type < creds[j].Type }) duplicateCredErr := &ErrDuplicateCredentials{error: e} // OIDC credentials are not email addresses but the sub claim from the OIDC provider. // This is useless for the user, so in that case, we don't set the identifier hint. if conflictingAddressType != CredentialsTypeOIDC.String() { duplicateCredErr.SetIdentifierHint(strings.Trim(foundConflictAddress, " ")) } for _, cred := range creds { if cred.Config == nil { continue } // Basically, we only have password, oidc, and webauthn as first factor credentials. // We don't care about second factor, because they don't help the user understand how to sign // in to the first factor (obviously). switch cred.Type { case CredentialsTypePassword: if duplicateCredErr.IdentifierHint() == "" && len(cred.Identifiers) == 1 { duplicateCredErr.SetIdentifierHint(cred.Identifiers[0]) } var cfg CredentialsPassword if err := json.Unmarshal(cred.Config, &cfg); err != nil { // just ignore this credential if the config is invalid continue } if cfg.HashedPassword == "" { // just ignore this credential if the hashed password is empty continue } duplicateCredErr.AddCredentialsType(cred.Type) case CredentialsTypeCodeAuth: duplicateCredErr.AddCredentialsType(cred.Type) case CredentialsTypeOIDC: var cfg CredentialsOIDC if err := json.Unmarshal(cred.Config, &cfg); err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to JSON decode identity credentials %s for identity %s.", cred.Type, found.ID)) } available := make([]string, 0, len(cfg.Providers)) for _, provider := range cfg.Providers { available = append(available, provider.Provider) } duplicateCredErr.AddCredentialsType(cred.Type) duplicateCredErr.availableOIDCProviders = available case CredentialsTypeWebAuthn: var cfg CredentialsWebAuthnConfig if err := json.Unmarshal(cred.Config, &cfg); err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to JSON decode identity credentials %s for identity %s.", cred.Type, found.ID)) } if duplicateCredErr.IdentifierHint() == "" && len(cred.Identifiers) == 1 { duplicateCredErr.SetIdentifierHint(cred.Identifiers[0]) } for _, webauthn := range cfg.Credentials { if webauthn.IsPasswordless { duplicateCredErr.AddCredentialsType(cred.Type) break } } case CredentialsTypePasskey: var cfg CredentialsWebAuthnConfig if err := json.Unmarshal(cred.Config, &cfg); err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to JSON decode identity credentials %s for identity %s.", cred.Type, found.ID)) } if duplicateCredErr.IdentifierHint() == "" && len(cred.Identifiers) == 1 { duplicateCredErr.SetIdentifierHint(cred.Identifiers[0]) } for _, webauthn := range cfg.Credentials { if webauthn.IsPasswordless { duplicateCredErr.AddCredentialsType(cred.Type) break } } } } return duplicateCredErr } type ErrDuplicateCredentials struct { error availableCredentials []CredentialsType availableOIDCProviders []string identifierHint string } var _ schema.DuplicateCredentialsHinter = (*ErrDuplicateCredentials)(nil) func (e *ErrDuplicateCredentials) Unwrap() error { return e.error } func (e *ErrDuplicateCredentials) AddCredentialsType(ct CredentialsType) { e.availableCredentials = append(e.availableCredentials, ct) } func (e *ErrDuplicateCredentials) SetIdentifierHint(hint string) { if hint != "" { e.identifierHint = hint } } func (e *ErrDuplicateCredentials) AvailableCredentials() []string { res := make([]string, len(e.availableCredentials)) for k, v := range e.availableCredentials { res[k] = string(v) } slices.Sort(res) return res } func (e *ErrDuplicateCredentials) AvailableOIDCProviders() []string { if e.availableOIDCProviders == nil { return []string{} } slices.Sort(e.availableOIDCProviders) return e.availableOIDCProviders } func (e *ErrDuplicateCredentials) IdentifierHint() string { return e.identifierHint } func (e *ErrDuplicateCredentials) HasHints() bool { return len(e.availableCredentials) > 0 || len(e.availableOIDCProviders) > 0 || len(e.identifierHint) > 0 } type FailedIdentity struct { Identity *Identity Error *herodot.DefaultError } type CreateIdentitiesError struct { failedIdentities map[*Identity]*herodot.DefaultError } func NewCreateIdentitiesError(capacity int) *CreateIdentitiesError { return &CreateIdentitiesError{ failedIdentities: make(map[*Identity]*herodot.DefaultError, capacity), } } func (e *CreateIdentitiesError) Error() string { e.init() return fmt.Sprintf("create identities error: %d identities failed", len(e.failedIdentities)) } func (e *CreateIdentitiesError) Unwrap() []error { e.init() var errs []error for _, err := range e.failedIdentities { errs = append(errs, err) } return errs } func (e *CreateIdentitiesError) AddFailedIdentity(ident *Identity, err *herodot.DefaultError) { e.init() e.failedIdentities[ident] = err } func (e *CreateIdentitiesError) Merge(other *CreateIdentitiesError) { e.init() for k, v := range other.failedIdentities { e.failedIdentities[k] = v } } func (e *CreateIdentitiesError) Contains(ident *Identity) bool { e.init() _, found := e.failedIdentities[ident] return found } func (e *CreateIdentitiesError) Find(ident *Identity) *FailedIdentity { e.init() if err, found := e.failedIdentities[ident]; found { return &FailedIdentity{Identity: ident, Error: err} } return nil } func (e *CreateIdentitiesError) ErrOrNil() error { if e == nil || len(e.failedIdentities) == 0 { return nil } return e } func (e *CreateIdentitiesError) init() { if e.failedIdentities == nil { e.failedIdentities = map[*Identity]*herodot.DefaultError{} } } func (m *Manager) CreateIdentities(ctx context.Context, identities []*Identity, opts ...ManagerOption) (err error) { ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.CreateIdentities") defer otelx.End(span, &err) createIdentitiesError := NewCreateIdentitiesError(len(identities)) validIdentities := make([]*Identity, 0, len(identities)) for _, ident := range identities { if ident.SchemaID == "" { ident.SchemaID = m.r.Config().DefaultIdentityTraitsSchemaID(ctx) } o := newManagerOptions(opts) if err := m.ValidateIdentity(ctx, ident, o); err != nil { reason := err.Error() if e, ok := stderrors.AsType[*herodot.DefaultError](err); ok { reason = e.Reason() } createIdentitiesError.AddFailedIdentity(ident, herodot.ErrBadRequest.WithReason(reason).WithWrap(err)) continue } validIdentities = append(validIdentities, ident) } if err := m.r.PrivilegedIdentityPool().CreateIdentities(ctx, validIdentities...); err != nil { if partialErr := new(CreateIdentitiesError); errors.As(err, &partialErr) { createIdentitiesError.Merge(partialErr) } else { return err } } return createIdentitiesError.ErrOrNil() } func (m *Manager) requiresPrivilegedAccess(ctx context.Context, original, updated *Identity, o *ManagerOptions) (err error) { _, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.requiresPrivilegedAccess") defer otelx.End(span, &err) if !o.AllowWriteProtectedTraits { if !CredentialsEqual(updated.Credentials, original.Credentials) { // reset the identity *updated = *original return errors.WithStack(ErrProtectedFieldModified) } if !reflect.DeepEqual(original.VerifiableAddresses, updated.VerifiableAddresses) && /* prevent nil != []string{} */ len(original.VerifiableAddresses)+len(updated.VerifiableAddresses) != 0 { // reset the identity *updated = *original return errors.WithStack(ErrProtectedFieldModified) } } return nil } func (m *Manager) Update(ctx context.Context, updated *Identity, opts ...ManagerOption) (err error) { ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.Update") defer otelx.End(span, &err) o := newManagerOptions(opts) if err := m.ValidateIdentity(ctx, updated, o); err != nil { return err } original, err := m.r.PrivilegedIdentityPool().GetIdentityConfidential(ctx, updated.ID) if err != nil { return err } if err := m.requiresPrivilegedAccess(ctx, original, updated, o); err != nil { return err } return m.r.PrivilegedIdentityPool().UpdateIdentity(ctx, updated, DiffAgainst(original)) } func (m *Manager) UpdateSchemaID(ctx context.Context, id uuid.UUID, schemaID string, opts ...ManagerOption) (err error) { ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.UpdateSchemaID") defer otelx.End(span, &err) o := newManagerOptions(opts) original, err := m.r.PrivilegedIdentityPool().GetIdentityConfidential(ctx, id) if err != nil { return err } if !o.AllowWriteProtectedTraits && original.SchemaID != schemaID { return errors.WithStack(ErrProtectedFieldModified) } original.SchemaID = schemaID if err := m.ValidateIdentity(ctx, original, o); err != nil { return err } return m.r.PrivilegedIdentityPool().UpdateIdentity(ctx, original) } func (m *Manager) SetTraits(ctx context.Context, id uuid.UUID, traits Traits, opts ...ManagerOption) (_ *Identity, err error) { ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.SetTraits") defer otelx.End(span, &err) o := newManagerOptions(opts) original, err := m.r.PrivilegedIdentityPool().GetIdentityConfidential(ctx, id) if err != nil { return nil, err } // original is used to check whether protected traits were modified updated := deepcopy.Copy(original).(*Identity) updated.Traits = traits if err := m.ValidateIdentity(ctx, updated, o); err != nil { return nil, err } if err := m.requiresPrivilegedAccess(ctx, original, updated, o); err != nil { return nil, err } return updated, nil } // RefreshAvailableAAL refreshes the available AAL for the identity. // // This method is a no-op if everything is up-to date. // // Please make sure to load all credentials before using this method. func (m *Manager) RefreshAvailableAAL(ctx context.Context, i *Identity) (err error) { if len(i.Credentials) == 0 { if err := m.r.PrivilegedIdentityPool().HydrateIdentityAssociations(ctx, i, ExpandCredentials); err != nil { return err } } aalBefore := i.InternalAvailableAAL if err := i.SetAvailableAAL(ctx, m); err != nil { return err } if aalBefore.String != i.InternalAvailableAAL.String || aalBefore.Valid != i.InternalAvailableAAL.Valid { return m.r.PrivilegedIdentityPool().UpdateIdentityColumns(ctx, i, "available_aal") } return nil } func (m *Manager) UpdateTraits(ctx context.Context, id uuid.UUID, traits Traits, opts ...ManagerOption) (err error) { ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.UpdateTraits") defer otelx.End(span, &err) updated, err := m.SetTraits(ctx, id, traits, opts...) if err != nil { return err } return m.r.PrivilegedIdentityPool().UpdateIdentity(ctx, updated) } func (m *Manager) ValidateIdentity(ctx context.Context, i *Identity, o *ManagerOptions) (err error) { if err := m.r.IdentityValidator().Validate(ctx, i); err != nil { var validationErr *jsonschema.ValidationError if errors.As(err, &validationErr) && !o.ExposeValidationErrors { return herodot.ErrBadRequest.WithReasonf("%s", err).WithWrap(err) } return err } if err := i.SetAvailableAAL(ctx, m); err != nil { return err } return nil } func (m *Manager) CountActiveFirstFactorCredentials(ctx context.Context, i *Identity) (count int, err error) { // This trace is more noisy than it's worth in diagnostic power. // ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.CountActiveFirstFactorCredentials") // defer otelx.End(span, &err) for _, strategy := range m.r.ActiveCredentialsCounterStrategies(ctx) { current, err := strategy.CountActiveFirstFactorCredentials(ctx, i.Credentials) if err != nil { return 0, err } count += current } return count, nil } func (m *Manager) CountActiveMultiFactorCredentials(ctx context.Context, i *Identity) (count int, err error) { // This trace is more noisy than it's worth in diagnostic power. // ctx, span := m.r.Tracer(ctx).Tracer().Start(ctx, "identity.Manager.CountActiveMultiFactorCredentials") // defer otelx.End(span, &err) for _, strategy := range m.r.ActiveCredentialsCounterStrategies(ctx) { current, err := strategy.CountActiveMultiFactorCredentials(ctx, i.Credentials) if err != nil { return 0, err } count += current } return count, nil } ================================================ FILE: identity/manager_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity_test import ( "encoding/json" "fmt" "testing" "time" "github.com/ory/x/configx" "github.com/ory/x/sqlcon" _ "embed" "github.com/gofrs/uuid" "github.com/ory/x/sqlxx" "github.com/ory/kratos/pkg/testhelpers" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/pkg" "github.com/ory/kratos/x" ) //go:embed stub/aal.json var refreshAALStubs []byte func TestManager(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]interface{}{ config.ViperKeyCourierSMTPURL: "smtp://foo@bar@dev.null/", config.ViperKeySelfServiceRegistrationLoginHints: true, config.ViperKeyDefaultIdentitySchemaID: "default", }), configx.WithValues(testhelpers.IdentitySchemasConfig(map[string]string{ "default": "file://./stub/manager.schema.json", "extension": "file://./stub/extension.schema.json", })), ) t.Run("case=should fail to create because validation fails", func(t *testing.T) { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Traits = identity.Traits(`{"email":"not an email"}`) require.Error(t, reg.IdentityManager().Create(t.Context(), i)) }) newTraits := func(email string, unprotected string) identity.Traits { return identity.Traits(fmt.Sprintf(`{"email":"%[1]s","email_verify":"%[1]s","email_recovery":"%[1]s","email_creds":"%[1]s","unprotected": "%[2]s"}`, email, unprotected)) } checkExtensionFields := func(i *identity.Identity, expected string) func(*testing.T) { return func(t *testing.T) { require.Len(t, i.VerifiableAddresses, 1) assert.EqualValues(t, expected, i.VerifiableAddresses[0].Value) assert.EqualValues(t, identity.AddressTypeEmail, i.VerifiableAddresses[0].Via) require.Len(t, i.RecoveryAddresses, 1) assert.EqualValues(t, expected, i.RecoveryAddresses[0].Value) assert.EqualValues(t, identity.AddressTypeEmail, i.RecoveryAddresses[0].Via) require.NotNil(t, i.Credentials[identity.CredentialsTypePassword]) assert.Equal(t, []string{expected}, i.Credentials[identity.CredentialsTypePassword].Identifiers) } } checkExtensionFieldsForIdentities := func(t *testing.T, expected string, original *identity.Identity) { fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) identities := []identity.Identity{*original, *fromStore} for k := range identities { t.Run(fmt.Sprintf("identity=%d", k), checkExtensionFields(&identities[k], expected)) } } t.Run("method=CreateIdentities", func(t *testing.T) { t.Run("case=should set AAL to 2 if password and TOTP is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, // By explicitly not setting the identifier, we mimic the behavior of the PATCH endpoint. // This tests a bug we introduced on the PATCH endpoint where the AAL value would not be correct. Identifiers: []string{}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, identity.CredentialsTypeTOTP: { Type: identity.CredentialsTypeTOTP, // By explicitly not setting the identifier, we mimic the behavior of the PATCH endpoint. // This tests a bug we introduced on the PATCH endpoint where the AAL value would not be correct. Identifiers: []string{}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }, } require.NoError(t, reg.IdentityManager().CreateIdentities(t.Context(), []*identity.Identity{original})) fromStore, err := reg.PrivilegedIdentityPool().GetIdentity(t.Context(), original.ID, identity.ExpandNothing) require.NoError(t, err) got, ok := fromStore.InternalAvailableAAL.ToAAL() require.True(t, ok) assert.Equal(t, identity.AuthenticatorAssuranceLevel2, got) }) }) t.Run("method=Create", func(t *testing.T) { t.Run("case=should create identity and track extension fields", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) checkExtensionFieldsForIdentities(t, email, original) got, ok := original.InternalAvailableAAL.ToAAL() require.True(t, ok) assert.Equal(t, identity.NoAuthenticatorAssuranceLevel, got) }) t.Run("case=correctly set AAL", func(t *testing.T) { t.Run("case=should set AAL to 0 if no credentials are available", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) got, ok := original.InternalAvailableAAL.ToAAL() require.True(t, ok) assert.Equal(t, identity.NoAuthenticatorAssuranceLevel, got) }) t.Run("case=should set AAL to 1 if password is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, } require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) got, ok := original.InternalAvailableAAL.ToAAL() require.True(t, ok) assert.Equal(t, identity.AuthenticatorAssuranceLevel1, got) }) t.Run("case=should set AAL to 2 if password and TOTP is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, identity.CredentialsTypeTOTP: { Type: identity.CredentialsTypeTOTP, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }, } require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) got, ok := original.InternalAvailableAAL.ToAAL() require.True(t, ok) assert.Equal(t, identity.AuthenticatorAssuranceLevel2, got) }) t.Run("case=should set AAL to 2 if only TOTP is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeTOTP: { Type: identity.CredentialsTypeTOTP, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }, } require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) got, ok := original.InternalAvailableAAL.ToAAL() require.True(t, ok) assert.Equal(t, identity.AuthenticatorAssuranceLevel2, got) }) }) t.Run("case=should expose validation errors with option", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = identity.Traits(`{"email":"not an email"}`) err := reg.IdentityManager().Create(t.Context(), original, identity.ManagerExposeValidationErrorsForInternalTypeAssertion) require.Error(t, err) assert.Contains(t, err.Error(), "\"not an email\" is not valid \"email\"") }) t.Run("case=should not expose validation errors without option", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = identity.Traits(`{"email":"not an email"}`) err := reg.IdentityManager().Create(t.Context(), original) require.Error(t, err) assert.NotContains(t, err.Error(), "\"not an email\" is not valid \"email\"") }) t.Run("case=should correctly hint at the duplicate credential", func(t *testing.T) { createIdentity := func(email string, field string, creds map[identity.CredentialsType]identity.Credentials) *identity.Identity { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.Traits = identity.Traits(fmt.Sprintf(`{"%s":"%s"}`, field, email)) i.Credentials = creds return i } t.Run("case=credential identifier duplicate", func(t *testing.T) { t.Run("type=password", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, } first := createIdentity(email, "email_creds", creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, "email_creds", creds) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.EqualValues(t, []string{identity.CredentialsTypePassword.String()}, verr.AvailableCredentials()) assert.Len(t, verr.AvailableOIDCProviders(), 0) assert.Equal(t, verr.IdentifierHint(), email) }) t.Run("type=webauthn", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeWebAuthn: { Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"credentials": [{"is_passwordless":true}]}`), }, } first := createIdentity(email, "email_webauthn", creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, "email_webauthn", nil) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.EqualValues(t, []string{identity.CredentialsTypeWebAuthn.String()}, verr.AvailableCredentials()) assert.Len(t, verr.AvailableOIDCProviders(), 0) assert.Equal(t, verr.IdentifierHint(), email) }) t.Run("type=oidc", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeOIDC: { Type: identity.CredentialsTypeOIDC, // Identifiers in OIDC are not email addresses, but a unique user ID. Identifiers: []string{"google:" + uuid.Must(uuid.NewV4()).String()}, Config: sqlxx.JSONRawMessage(`{"providers":[{"provider": "google"},{"provider": "github"}]}`), }, } first := createIdentity(email, "email_creds", creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, "email_creds", creds) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.ElementsMatch(t, []string{"oidc"}, verr.AvailableCredentials()) assert.ElementsMatch(t, []string{"google", "github"}, verr.AvailableOIDCProviders()) // The conflicting identifier is the oidc subject, which is not useful for the user assert.Equal(t, email, verr.IdentifierHint()) }) t.Run("type=password+oidc+webauthn", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, identity.CredentialsTypeOIDC: { Type: identity.CredentialsTypeOIDC, // Identifiers in OIDC are not email addresses, but a unique user ID. Identifiers: []string{"google:" + uuid.Must(uuid.NewV4()).String()}, Config: sqlxx.JSONRawMessage(`{"providers":[{"provider": "google"},{"provider": "github"}]}`), }, identity.CredentialsTypeWebAuthn: { Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"credentials": [{"is_passwordless":true}]}`), }, } first := createIdentity(email, "email_creds", creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, "email_creds", creds) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.ElementsMatch(t, []string{"password", "oidc", "webauthn"}, verr.AvailableCredentials()) assert.ElementsMatch(t, []string{"google", "github"}, verr.AvailableOIDCProviders()) assert.Equal(t, email, verr.IdentifierHint()) }) t.Run("type=code", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeCodeAuth: { Type: identity.CredentialsTypeCodeAuth, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{}`), }, } first := createIdentity(email, "email_creds", creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, "email_creds", creds) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.EqualValues(t, []string{identity.CredentialsTypeCodeAuth.String()}, verr.AvailableCredentials()) assert.Len(t, verr.AvailableOIDCProviders(), 0) assert.Equal(t, verr.IdentifierHint(), email) }) }) runAddress := func(t *testing.T, field string) { t.Run("case=password duplicate", func(t *testing.T) { // This test mimics a case where an existing user with email + password exists, and the // new user tries to sign up with a verification email (NOT email + password) that matches // this existing record. Here, the end result is that we want to show the // user: "Sign up with email foo@bar.com and your password instead." email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, } first := createIdentity(email, field, creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, field, nil) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.EqualValues(t, []string{identity.CredentialsTypePassword.String()}, verr.AvailableCredentials()) assert.Len(t, verr.AvailableOIDCProviders(), 0) assert.Equal(t, verr.IdentifierHint(), email) }) t.Run("case=OIDC duplicate", func(t *testing.T) { // This test mimics a case where user signed up using Social Sign In exists, and the // new user tries to sign up with a verification email (NOT email + password) that matches // this existing record (for example by using another social sign in provider. // Here, the end result is that we want to show "Sign in using google instead". email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" creds := map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeOIDC: { Type: identity.CredentialsTypeOIDC, // Identifiers in OIDC are not email addresses, but a unique user ID. Identifiers: []string{"google:" + uuid.Must(uuid.NewV4()).String()}, Config: sqlxx.JSONRawMessage(`{"providers":[{"provider": "google"},{"provider": "github"}]}`), }, } first := createIdentity(email, field, creds) require.NoError(t, reg.IdentityManager().Create(t.Context(), first)) second := createIdentity(email, field, nil) err := reg.IdentityManager().Create(t.Context(), second) require.Error(t, err) verr := new(identity.ErrDuplicateCredentials) assert.ErrorAs(t, err, &verr) assert.EqualValues(t, []string{identity.CredentialsTypeOIDC.String()}, verr.AvailableCredentials()) assert.EqualValues(t, verr.AvailableOIDCProviders(), []string{"github", "google"}) assert.Equal(t, verr.IdentifierHint(), email) }) } t.Run("case=verifiable address", func(t *testing.T) { runAddress(t, "email_verify") }) t.Run("case=recovery address", func(t *testing.T) { runAddress(t, "email_recovery") }) }) }) t.Run("method=Update", func(t *testing.T) { t.Run("case=should update identity and update extension fields", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits("baz@ory.sh", "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) original.Traits = newTraits("bar@ory.sh", "") require.NoError(t, reg.IdentityManager().Update(t.Context(), original, identity.ManagerAllowWriteProtectedTraits)) checkExtensionFieldsForIdentities(t, "bar@ory.sh", original) }) t.Run("case=should set AAL to 1 if password is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, } require.NoError(t, reg.IdentityManager().Update(t.Context(), original, identity.ManagerAllowWriteProtectedTraits)) assert.EqualValues(t, identity.AuthenticatorAssuranceLevel1, original.InternalAvailableAAL.String) }) t.Run("case=should set AAL to 2 if password and TOTP is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: { Type: identity.CredentialsTypePassword, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"hashed_password":"$2a$08$.cOYmAd.vCpDOoiVJrO5B.hjTLKQQ6cAK40u8uB.FnZDyPvVvQ9Q."}`), }, } require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) assert.EqualValues(t, identity.AuthenticatorAssuranceLevel1, original.InternalAvailableAAL.String) require.NoError(t, reg.IdentityManager().Update(t.Context(), original, identity.ManagerAllowWriteProtectedTraits)) assert.EqualValues(t, identity.AuthenticatorAssuranceLevel1, original.InternalAvailableAAL.String, "Updating without changes should not change AAL") original.Credentials[identity.CredentialsTypeTOTP] = identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), } require.NoError(t, reg.IdentityManager().Update(t.Context(), original, identity.ManagerAllowWriteProtectedTraits)) assert.EqualValues(t, identity.AuthenticatorAssuranceLevel2, original.InternalAvailableAAL.String) }) t.Run("case=should set AAL to 2 if only TOTP is set", func(t *testing.T) { email := uuid.Must(uuid.NewV4()).String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) original.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeTOTP: { Type: identity.CredentialsTypeTOTP, Identifiers: []string{email}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }, } require.NoError(t, reg.IdentityManager().Update(t.Context(), original, identity.ManagerAllowWriteProtectedTraits)) assert.True(t, original.InternalAvailableAAL.Valid) assert.EqualValues(t, identity.AuthenticatorAssuranceLevel2, original.InternalAvailableAAL.String) }) t.Run("case=should not update protected traits without option", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits("email-update-1@ory.sh", "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) original.Traits = newTraits("email-update-2@ory.sh", "") err := reg.IdentityManager().Update(t.Context(), original) require.Error(t, err) assert.Equal(t, identity.ErrProtectedFieldModified, errors.Cause(err)) fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) // As UpdateTraits takes only the ID as a parameter it cannot update the identity in place. // That is why we only check the identity in the store. checkExtensionFields(fromStore, "email-update-1@ory.sh")(t) }) t.Run("case=should update unprotected traits with multiple credential identifiers", func(t *testing.T) { original := identity.NewIdentity("extension") original.Traits = identity.Traits(`{"email": "email-update-ewisdfuja@ory.sh", "names": ["username1", "username2"], "age": 30}`) require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) assert.Len(t, original.Credentials[identity.CredentialsTypePassword].Identifiers, 3) original.Traits = identity.Traits(`{"email": "email-update-ewisdfuja@ory.sh", "names": ["username1", "username2"], "age": 31}`) require.NoError(t, reg.IdentityManager().Update(t.Context(), original)) fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) assert.JSONEq(t, string(original.Traits), string(fromStore.Traits)) }) t.Run("case=should update unprotected traits with verified user", func(t *testing.T) { email := x.NewUUID().String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(email, "initial") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) // mock successful verification process addr := original.VerifiableAddresses[0] addr.Verified = true addr.VerifiedAt = new(sqlxx.NullTime(time.Now().UTC())) require.NoError(t, reg.PrivilegedIdentityPool().UpdateVerifiableAddress(t.Context(), &addr)) // reload to properly set the verified address var err error original, err = reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) original.Traits = newTraits(email, "updated") require.NoError(t, reg.IdentityManager().Update(t.Context(), original)) fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) assert.JSONEq(t, string(original.Traits), string(fromStore.Traits)) }) t.Run("case=changing recovery address removes it from the store", func(t *testing.T) { originalEmail := x.NewUUID().String() + "@ory.sh" original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits(originalEmail, "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) checkExtensionFields(fromStore, originalEmail)(t) newEmail := x.NewUUID().String() + "@ory.sh" original.Traits = newTraits(newEmail, "") require.NoError(t, reg.IdentityManager().Update(t.Context(), original, identity.ManagerAllowWriteProtectedTraits)) fromStore, err = reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) checkExtensionFields(fromStore, newEmail)(t) recoveryAddresses, err := reg.PrivilegedIdentityPool().ListRecoveryAddresses(t.Context(), 0, 500) require.NoError(t, err) var foundRecoveryAddress bool for _, a := range recoveryAddresses { assert.NotEqual(t, a.Value, originalEmail) if a.Value == newEmail { foundRecoveryAddress = true } } require.True(t, foundRecoveryAddress) verifiableAddresses, err := reg.PrivilegedIdentityPool().ListVerifiableAddresses(t.Context(), 0, 500) require.NoError(t, err) var foundVerifiableAddress bool for _, a := range verifiableAddresses { assert.NotEqual(t, a.Value, originalEmail) if a.Value == newEmail { foundVerifiableAddress = true } } require.True(t, foundVerifiableAddress) }) }) t.Run("method=CountActiveFirstFactorCredentials", func(t *testing.T) { id := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) count, err := reg.IdentityManager().CountActiveFirstFactorCredentials(t.Context(), id) require.NoError(t, err) assert.Equal(t, 0, count) id.Credentials[identity.CredentialsTypePassword] = identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{"foo"}, Config: []byte(`{"hashed_password":"$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"}`), } count, err = reg.IdentityManager().CountActiveFirstFactorCredentials(t.Context(), id) require.NoError(t, err) assert.Equal(t, 1, count) }) t.Run("method=CountActiveMultiFactorCredentials", func(t *testing.T) { id := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) count, err := reg.IdentityManager().CountActiveMultiFactorCredentials(t.Context(), id) require.NoError(t, err) assert.Equal(t, 0, count) id.Credentials[identity.CredentialsTypePassword] = identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{"foo"}, Config: []byte(`{"hashed_password":"$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"}`), } count, err = reg.IdentityManager().CountActiveMultiFactorCredentials(t.Context(), id) require.NoError(t, err) assert.Equal(t, 0, count) id.Credentials[identity.CredentialsTypeWebAuthn] = identity.Credentials{ Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{"foo"}, Config: []byte(`{"credentials":[{"is_passwordless":false}]}`), } count, err = reg.IdentityManager().CountActiveMultiFactorCredentials(t.Context(), id) require.NoError(t, err) assert.Equal(t, 1, count) }) t.Run("method=UpdateTraits", func(t *testing.T) { t.Run("case=should update protected traits with option", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits("email-updatetraits-1@ory.sh", "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) require.NoError(t, reg.IdentityManager().UpdateTraits( t.Context(), original.ID, newTraits("email-updatetraits-2@ory.sh", ""), identity.ManagerAllowWriteProtectedTraits)) fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) // As UpdateTraits takes only the ID as a parameter it cannot update the identity in place. // That is why we only check the identity in the store. checkExtensionFields(fromStore, "email-updatetraits-2@ory.sh")(t) }) t.Run("case=should update identity and update extension fields", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = identity.Traits(`{"email":"baz@ory.sh","email_verify":"baz@ory.sh","email_recovery":"baz@ory.sh","email_creds":"baz@ory.sh","unprotected": "foo"}`) require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) // These should all fail because they modify existing keys require.Error(t, reg.IdentityManager().UpdateTraits(t.Context(), original.ID, identity.Traits(`{"email":"not-baz@ory.sh","email_verify":"baz@ory.sh","email_recovery":"baz@ory.sh","email_creds":"baz@ory.sh","unprotected": "foo"}`))) require.Error(t, reg.IdentityManager().UpdateTraits(t.Context(), original.ID, identity.Traits(`{"email":"baz@ory.sh","email_verify":"not-baz@ory.sh","email_recovery":"not-baz@ory.sh","email_creds":"baz@ory.sh","unprotected": "foo"}`))) require.Error(t, reg.IdentityManager().UpdateTraits(t.Context(), original.ID, identity.Traits(`{"email":"baz@ory.sh","email_verify":"baz@ory.sh","email_recovery":"baz@ory.sh","email_creds":"not-baz@ory.sh","unprotected": "foo"}`))) require.NoError(t, reg.IdentityManager().UpdateTraits(t.Context(), original.ID, identity.Traits(`{"email":"baz@ory.sh","email_verify":"baz@ory.sh","email_recovery":"baz@ory.sh","email_creds":"baz@ory.sh","unprotected": "bar"}`))) checkExtensionFieldsForIdentities(t, "baz@ory.sh", original) actual, err := reg.IdentityPool().GetIdentity(t.Context(), original.ID, identity.ExpandNothing) require.NoError(t, err) assert.JSONEq(t, `{"email":"baz@ory.sh","email_verify":"baz@ory.sh","email_recovery":"baz@ory.sh","email_creds":"baz@ory.sh","unprotected": "bar"}`, string(actual.Traits)) }) t.Run("case=should not update protected traits without option", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits("email-updatetraits-1@ory.sh", "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) err := reg.IdentityManager().UpdateTraits( t.Context(), original.ID, newTraits("email-updatetraits-2@ory.sh", "")) require.Error(t, err) assert.Equal(t, identity.ErrProtectedFieldModified, errors.Cause(err)) fromStore, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), original.ID) require.NoError(t, err) // As UpdateTraits takes only the ID as a parameter it cannot update the identity in place. // That is why we only check the identity in the store. checkExtensionFields(fromStore, "email-updatetraits-1@ory.sh")(t) }) t.Run("case=should always update updated_at field", func(t *testing.T) { original := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) original.Traits = newTraits("email-updatetraits-3@ory.sh", "") require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) time.Sleep(time.Millisecond) require.NoError(t, reg.IdentityManager().UpdateTraits( t.Context(), original.ID, newTraits("email-updatetraits-4@ory.sh", ""), identity.ManagerAllowWriteProtectedTraits)) updated, err := reg.IdentityPool().GetIdentity(t.Context(), original.ID, identity.ExpandNothing) require.NoError(t, err) assert.NotEqual(t, original.UpdatedAt, updated.UpdatedAt, "UpdatedAt field should be updated") }) }) t.Run("method=RefreshAvailableAAL", func(t *testing.T) { var cases []struct { Credentials []identity.Credentials `json:"credentials"` Description string `json:"description"` Expected string `json:"expected"` } require.NoError(t, json.Unmarshal(refreshAALStubs, &cases)) for k, tc := range cases { t.Run("case="+tc.Description, func(t *testing.T) { email := x.NewUUID().String() + "@ory.sh" id := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) id.Traits = identity.Traits(`{"email":"` + email + `"}`) require.NoError(t, reg.IdentityManager().Create(t.Context(), id)) assert.EqualValues(t, identity.NoAuthenticatorAssuranceLevel, id.InternalAvailableAAL.String) for _, c := range tc.Credentials { for k := range c.Identifiers { switch c.Identifiers[k] { case "{email}": c.Identifiers[k] = email case "{id}": c.Identifiers[k] = id.ID.String() } } id.SetCredentials(c.Type, c) } // We use the privileged pool here because we don't want to refresh AAL here but in the code below. require.NoError(t, reg.PrivilegedIdentityPool().UpdateIdentity(t.Context(), id)) expand := identity.ExpandNothing if k%2 == 1 { // expand every other test case to test if RefreshAvailableAAL behaves correctly expand = identity.ExpandCredentials } actual, err := reg.IdentityPool().GetIdentity(t.Context(), id.ID, expand) require.NoError(t, err) require.NoError(t, reg.IdentityManager().RefreshAvailableAAL(t.Context(), actual)) assert.NotEmpty(t, actual.Credentials) assert.EqualValues(t, tc.Expected, actual.InternalAvailableAAL.String) }) } }) t.Run("method=ConflictingIdentity", func(t *testing.T) { conflicOnIdentifier := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) conflicOnIdentifier.Traits = identity.Traits(`{"email":"conflict-on-identifier@example.com"}`) require.NoError(t, reg.IdentityManager().Create(t.Context(), conflicOnIdentifier)) conflicOnVerifiableAddress := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) conflicOnVerifiableAddress.Traits = identity.Traits(`{"email":"user-va@example.com", "email_verify":"conflict-on-va@example.com"}`) require.NoError(t, reg.IdentityManager().Create(t.Context(), conflicOnVerifiableAddress)) conflicOnRecoveryAddress := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) conflicOnRecoveryAddress.Traits = identity.Traits(`{"email":"user-ra@example.com", "email_recovery":"conflict-on-ra@example.com"}`) require.NoError(t, reg.IdentityManager().Create(t.Context(), conflicOnRecoveryAddress)) t.Run("case=returns not found if no conflict", func(t *testing.T) { found, foundConflictAddress, addressType, err := reg.IdentityManager().ConflictingIdentity(t.Context(), &identity.Identity{ Credentials: map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: {Identifiers: []string{"no-conflict@example.com"}}, }, }) assert.ErrorIs(t, err, sqlcon.ErrNoRows) assert.Nil(t, found) assert.Empty(t, foundConflictAddress) assert.Empty(t, addressType) }) t.Run("case=conflict on identifier", func(t *testing.T) { found, foundConflictAddress, addressType, err := reg.IdentityManager().ConflictingIdentity(t.Context(), &identity.Identity{ Credentials: map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypePassword: {Identifiers: []string{"conflict-on-identifier@example.com"}}, }, }) require.NoError(t, err) assert.Equal(t, conflicOnIdentifier.ID, found.ID) assert.Equal(t, "conflict-on-identifier@example.com", foundConflictAddress) assert.EqualValues(t, string(identity.CredentialsTypePassword), addressType) }) t.Run("case=conflict on verifiable address", func(t *testing.T) { found, foundConflictAddress, addressType, err := reg.IdentityManager().ConflictingIdentity(t.Context(), &identity.Identity{ VerifiableAddresses: []identity.VerifiableAddress{{ Value: "conflict-on-va@example.com", Via: "email", }}, }) require.NoError(t, err) assert.Equal(t, conflicOnVerifiableAddress.ID, found.ID) assert.Equal(t, "conflict-on-va@example.com", foundConflictAddress) assert.Equal(t, "email", addressType) }) t.Run("case=conflict on recovery address", func(t *testing.T) { found, foundConflictAddress, addressType, err := reg.IdentityManager().ConflictingIdentity(t.Context(), &identity.Identity{ RecoveryAddresses: []identity.RecoveryAddress{{ Value: "conflict-on-ra@example.com", Via: "email", }}, }) require.NoError(t, err) assert.Equal(t, conflicOnRecoveryAddress.ID, found.ID) assert.Equal(t, "conflict-on-ra@example.com", foundConflictAddress) assert.Equal(t, "email", addressType) }) }) } func TestManagerNoDefaultNamedSchema(t *testing.T) { _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]interface{}{ config.ViperKeyDefaultIdentitySchemaID: "user_v0", config.ViperKeyIdentitySchemas: config.Schemas{ {ID: "user_v0", URL: "file://./stub/manager.schema.json"}, }, config.ViperKeyPublicBaseURL: "https://www.ory.sh/", })) t.Run("case=should create identity with default schema", func(t *testing.T) { stateChangedAt := sqlxx.NullTime(time.Now().UTC()) original := &identity.Identity{ SchemaID: "", Traits: []byte(identity.Traits(`{"email":"foo@ory.sh"}`)), State: identity.StateActive, StateChangedAt: &stateChangedAt, } require.NoError(t, reg.IdentityManager().Create(t.Context(), original)) }) } ================================================ FILE: identity/pool.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "github.com/ory/kratos/x" "github.com/ory/x/crdbx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/sqlxx" "github.com/gofrs/uuid" ) func NewUpdateIdentityOptions(opts []UpdateIdentityModifier) UpdateIdentityOptions { var o UpdateIdentityOptions for _, opt := range opts { opt(&o) } return o } // DiffAgainst instructs UpdateIdentity to attempt a minimal update of the // identity's data in the database by computing a diff against `existing` and // only updating what is necessary, rather than bulk-replacing everything. Use // with caution. If `existing` is different from what is stored in the database // at the time of the update, the results are undefined. An error is returned if // `existing` has a mismatching IdentityID or NID. func DiffAgainst(existing *Identity) UpdateIdentityModifier { return func(o *UpdateIdentityOptions) { o.fromDatabase = existing } } func (o UpdateIdentityOptions) FromDatabase() *Identity { return o.fromDatabase } type ( ListIdentityParameters struct { Expand Expandables IdsFilter []uuid.UUID CredentialsIdentifier string CredentialsIdentifierSimilar string DeclassifyCredentials []CredentialsType KeySetPagination []keysetpagination.Option OrganizationID uuid.UUID ConsistencyLevel crdbx.ConsistencyLevel StatementTransformer func(string) string // DEPRECATED PagePagination *x.Page } UpdateIdentityModifier func(*UpdateIdentityOptions) UpdateIdentityOptions struct { fromDatabase *Identity } Pool interface { // ListIdentities lists all identities in the store given the page and itemsPerPage. ListIdentities(ctx context.Context, params ListIdentityParameters) ([]Identity, *keysetpagination.Paginator, error) // CountIdentities counts the number of identities in the store. CountIdentities(ctx context.Context) (int64, error) // GetIdentity returns an identity by its id. Will return an error if the identity does not exist or backend // connectivity is broken. GetIdentity(context.Context, uuid.UUID, sqlxx.Expandables) (*Identity, error) // FindVerifiableAddressByValue returns a matching address or sql.ErrNoRows if no address could be found. FindVerifiableAddressByValue(ctx context.Context, via, address string) (*VerifiableAddress, error) // FindRecoveryAddressByValue returns a matching address or sql.ErrNoRows if no address could be found. FindRecoveryAddressByValue(ctx context.Context, via, address string) (*RecoveryAddress, error) // FindAllRecoveryAddressesForIdentityByRecoveryAddressValue finds all recovery addresses for an identity if at least one of its recovery addresses matches the provided value. FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx context.Context, anyRecoveryAddress string) ([]RecoveryAddress, error) } PoolProvider interface { IdentityPool() Pool } PrivilegedPoolProvider interface { PrivilegedIdentityPool() PrivilegedPool } PrivilegedPool interface { Pool // FindByCredentialsIdentifier returns an identity by querying for it's credential identifiers. FindByCredentialsIdentifier(ctx context.Context, ct CredentialsType, match string) (*Identity, *Credentials, error) // DeleteIdentity removes an identity by its id. Will return an error // if identity does not exists, or backend connectivity is broken. DeleteIdentity(context.Context, uuid.UUID) error // DeleteIdentities removes identities by its id. Will return an error // if any identity does not exists, or backend connectivity is broken. DeleteIdentities(context.Context, []uuid.UUID) error // UpdateVerifiableAddress updates an identity's verifiable address. UpdateVerifiableAddress(ctx context.Context, address *VerifiableAddress, updateColumns ...string) error // CreateIdentity creates an identity. It is capable of setting credentials without encoding. Will return an error // if identity exists, backend connectivity is broken, or trait validation fails. CreateIdentity(context.Context, *Identity) error // CreateIdentities creates multiple identities. It is capable of setting credentials without encoding. Will return an error // if identity exists, backend connectivity is broken, or trait validation fails. CreateIdentities(context.Context, ...*Identity) error // UpdateIdentity updates an identity including its confidential / privileged / protected data. UpdateIdentity(context.Context, *Identity, ...UpdateIdentityModifier) error // UpdateIdentityColumns updates targeted columns of an identity. UpdateIdentityColumns(ctx context.Context, i *Identity, columns ...string) error // GetIdentityConfidential returns the identity including it's raw credentials. // // This should only be used internally. Please be aware that this method uses HydrateIdentityAssociations // internally, which must not be executed as part of a transaction. GetIdentityConfidential(context.Context, uuid.UUID) (*Identity, error) // ListVerifiableAddresses lists all tracked verifiable addresses, regardless of whether they are already verified // or not. ListVerifiableAddresses(ctx context.Context, page, itemsPerPage int) ([]VerifiableAddress, error) // ListRecoveryAddresses lists all tracked recovery addresses. ListRecoveryAddresses(ctx context.Context, page, itemsPerPage int) ([]RecoveryAddress, error) // HydrateIdentityAssociations hydrates the associations of an identity. // // Please be aware that this method must not be called within a transaction if more than one element is expanded. // It may error with "conn busy" otherwise. HydrateIdentityAssociations(ctx context.Context, i *Identity, expandables Expandables) error // InjectTraitsSchemaURL sets the identity's traits JSON schema URL from the schema's ID. InjectTraitsSchemaURL(ctx context.Context, i *Identity) error // FindIdentityByCredentialIdentifier returns an identity by matching the identifier to any of the identity's credentials. FindIdentityByCredentialIdentifier(ctx context.Context, identifier string, caseSensitive bool, expandables Expandables) (*Identity, error) // FindIdentityByWebauthnUserHandle returns an identity matching a webauthn user handle. FindIdentityByWebauthnUserHandle(ctx context.Context, userHandle []byte) (*Identity, error) // FindIdentityByCredentialsIdentifier returns an identity by its external ID. FindIdentityByExternalID(ctx context.Context, externalID string, expand sqlxx.Expandables) (*Identity, error) } ) func (p ListIdentityParameters) TransformStatement(statement string) string { if p.StatementTransformer != nil { return p.StatementTransformer(statement) } return statement } ================================================ FILE: identity/registry.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "net/url" ) type Registry interface { IdentityPool() Pool } type Configuration interface { SelfAdminURL() *url.URL DefaultIdentityTraitsSchemaURL() *url.URL } ================================================ FILE: identity/stub/aal.json ================================================ [ { "description": "password is available aal1", "expected": "aal1", "credentials": [ { "type": "password", "identifiers": [ "{email}" ], "config": { "hashed_password": "$2a$fake" } } ] }, { "description": "password without identifier is no credential and ergo aal0", "expected": "aal0", "credentials": [ { "type": "password", "config": { "hashed_password": "$2a$fake" } } ] }, { "description": "second factor totp returns available aal2 even if no password is set", "expected": "aal2", "credentials": [ { "type": "totp", "config": { "totp_url": "totp://" } } ] }, { "description": "second factor totp returns aal0 if totp credentials is not set up", "expected": "aal0", "credentials": [ { "type": "totp", "identifiers": [ "{email}" ], "config": {} } ] }, { "description": "password and totp is also available aal2", "expected": "aal1", "credentials": [ { "type": "password", "identifiers": [ "{email}" ], "config": { "hashed_password": "$2a$fake" } }, { "type": "totp", "identifiers": [ "{email}" ], "config": {} } ] } ] ================================================ FILE: identity/stub/expand.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "traits": { "additionalProperties": false, "properties": { "email": { "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "totp": { "account_name": true } }, "recovery": { "via": "email" }, "verification": { "via": "email" } }, "title": "Email address", "type": "string", "maxLength": 320 }, "name": { "minLength": 1, "title": "Name", "type": "string", "maxLength": 256 } }, "required": [ "email", "name" ], "type": "object" } }, "title": "Person", "type": "object" } ================================================ FILE: identity/stub/extension/credentials/code-phone-email.schema.json ================================================ { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "code": { "identifier": true, "via": "email" } } } }, "email2": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "code": { "identifier": true, "via": "email" } } } }, "email3": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "code": { "identifier": true, "via": "email" } } } }, "phone": { "type": "string", "format": "tel", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "code": { "identifier": true, "via": "sms" } } } } } } ================================================ FILE: identity/stub/extension/credentials/code.schema.json ================================================ { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "code": { "identifier": true, "via": "email" } } } } } } ================================================ FILE: identity/stub/extension/credentials/email.schema.json ================================================ { "$id": "https://schemas.ory.sh/presets/kratos/identity.email.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "email": { "type": "string", "format": "email", "title": "E-Mail", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "totp": { "account_name": true }, "code": { "identifier": true, "via": "email" }, "passkey": { "display_name": true } }, "recovery": { "via": "email" }, "verification": { "via": "email" } }, "maxLength": 320 } }, "required": [ "email" ], "additionalProperties": false } ================================================ FILE: identity/stub/extension/credentials/multi.schema.json ================================================ { "type": "object", "properties": { "emails": { "type": "array", "items": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true } } } } }, "username": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } } } } ================================================ FILE: identity/stub/extension/credentials/schema.json ================================================ { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } } } } ================================================ FILE: identity/stub/extension/credentials/webauthn.schema.json ================================================ { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true } } } } } } ================================================ FILE: identity/stub/extension/recovery/email.schema.json ================================================ { "type": "object", "properties": { "emails": { "type": "array", "items": { "type": "string", "ory.sh/kratos": { "recovery": { "via": "email" } } } }, "username": { "type": "string", "ory.sh/kratos": { "recovery": { "via": "email" } } } } } ================================================ FILE: identity/stub/extension/recovery/schema.json ================================================ { "type": "object", "properties": { "emails": { "type": "array", "items": { "type": "string", "ory.sh/kratos": { "recovery": { "via": "email" } } } }, "username": { "type": "string", "ory.sh/kratos": { "recovery": { "via": "email" } } } } } ================================================ FILE: identity/stub/extension/recovery/sms.schema.json ================================================ { "type": "object", "properties": { "telephoneNumber": { "type": "string", "format": "tel", "title": "Telephone Number", "minLength": 3, "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "code": { "identifier": true, "via": "sms" } }, "verification": { "via": "sms" }, "recovery": { "via": "sms" } } } }, "required": [ "telephoneNumber" ], "additionalProperties": false } ================================================ FILE: identity/stub/extension/verify/email.schema.json ================================================ { "type": "object", "properties": { "emails": { "type": "array", "items": { "type": "string", "ory.sh/kratos": { "verification": { "via": "email" } } } }, "username": { "type": "string", "ory.sh/kratos": { "verification": { "via": "email" } } } } } ================================================ FILE: identity/stub/extension/verify/legacy-email-missing-format.schema.json ================================================ { "type": "object", "properties": { "email": { "type": "string", "ory.sh/kratos": { "verification": { "via": "email" } } } } } ================================================ FILE: identity/stub/extension/verify/missing-format.schema.json ================================================ { "type": "object", "properties": { "phone": { "type": "string", "ory.sh/kratos": { "verification": { "via": "sms" } } } } } ================================================ FILE: identity/stub/extension/verify/no-validate.schema.json ================================================ { "type": "object", "properties": { "phone": { "type": "string", "format": "noformat", "ory.sh/kratos": { "verification": { "via": "sms" } } } } } ================================================ FILE: identity/stub/extension/verify/phone.schema.json ================================================ { "type": "object", "properties": { "phones": { "type": "array", "items": { "type": "string", "format": "tel", "ory.sh/kratos": { "verification": { "via": "sms" } } } }, "username": { "type": "string", "format": "tel", "ory.sh/kratos": { "verification": { "via": "sms" } } } } } ================================================ FILE: identity/stub/extension.schema.json ================================================ { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } }, "names": { "type": "array", "items": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } } }, "age": { "description": "Age in years which must be equal to or greater than zero.", "type": "integer", "minimum": 1 } }, "required": [ "email" ] } }, "required": [ "traits" ], "additionalProperties": false } ================================================ FILE: identity/stub/handler/customer.schema.json ================================================ { "$id": "https://example.com/customer.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "additionalProperties": false, "type": "object", "properties": { "address": { "type": "string" }, "email": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } } } } } ================================================ FILE: identity/stub/handler/employee.schema.json ================================================ { "$id": "https://example.com/employee.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "department": { "type": "string" }, "email": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } } } } } ================================================ FILE: identity/stub/handler/multiple_emails.schema.json ================================================ { "$id": "https://example.com/customer.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "emails": { "type": "array", "items": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } }, "username": { "type": "string", "ory.sh/kratos": { "recovery": { "via": "email" } } } } } } } ================================================ FILE: identity/stub/identity-2.schema.json ================================================ { "$id": "https://example.com/registration.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "bar": { "type": "string" } } } } } ================================================ FILE: identity/stub/identity.schema.json ================================================ { "$id": "https://example.com/registration.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "bar": { "type": "string" }, "email": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } } } } } } ================================================ FILE: identity/stub/localhost-ref.schema.json ================================================ { "$id": "https://example.com/registration.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "bar": { "type": "string" }, "email": { "$ref": "http://192.168.178.1:1234/" } } } } } ================================================ FILE: identity/stub/manager.schema.json ================================================ { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } }, "email_creds": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } }, "email_webauthn": { "type": "string", "format": "email", "ory.sh/kratos": { "credentials": { "webauthn": { "identifier": true } } } }, "email_verify": { "type": "string", "format": "email", "ory.sh/kratos": { "verification": { "via": "email" } } }, "email_recovery": { "type": "string", "format": "email", "ory.sh/kratos": { "recovery": { "via": "email" } } }, "unprotected": { "type": "string" } }, "required": [ ] } }, "additionalProperties": false } ================================================ FILE: identity/stub/webauthn/v0.json ================================================ { "credentials": [ { "id": "HQ4LaIJ9NiqS1r0CQpWY+K0gMvhOq4yk5BHuO/YlitcurSpBK7weDXOvBcuN4lvn6DAmjGfmj/J/6bpOmtdT8Q==", "public_key": "pQECAyYgASFYILAYFLoH1T8bQMSbPrNBCMMS5U7OFWRwv2U+GkAoiBADIlggBv+8ni7XVZYBB8ufMbP/d9fDxbmOkVVHOgcJifnoOR4=", "attestation_type": "none", "authenticator": { "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==", "sign_count": 4, "clone_warning": false }, "display_name": "asdf", "added_at": "2022-02-28T16:40:39Z" }, { "id": "1Q4LaIJ9NiqS1r0CQpWY+K0gMvhOq4yk5BHuO/YlitcurSpBK7weDXOvBcuN4lvn6DAmjGfmj/J/6bpOmtdT8Q==", "public_key": "pQECAyYgASFYILAYFLoH1T8bQMSbPrNBCMMS5U7OFWRwv2U+GkAoiBADIlggBv+8ni7XVZYBB8ufMbP/d9fDxbmOkVVHOgcJifnoOR4=", "attestation_type": "none", "authenticator": { "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==", "sign_count": 4, "clone_warning": false }, "display_name": "asdf", "added_at": "2022-02-28T16:40:39Z" } ] } ================================================ FILE: identity/stub/webauthn/v1.json ================================================ { "credentials": [ { "id": "HQ4LaIJ9NiqS1r0CQpWY+K0gMvhOq4yk5BHuO/YlitcurSpBK7weDXOvBcuN4lvn6DAmjGfmj/J/6bpOmtdT8Q==", "public_key": "pQECAyYgASFYILAYFLoH1T8bQMSbPrNBCMMS5U7OFWRwv2U+GkAoiBADIlggBv+8ni7XVZYBB8ufMbP/d9fDxbmOkVVHOgcJifnoOR4=", "attestation_type": "none", "authenticator": { "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==", "sign_count": 4, "clone_warning": false }, "display_name": "asdf", "added_at": "2022-02-28T16:40:39Z", "is_passwordless": true } ], "user_handle":"2gZaSs9fTEeGmsBlC4gfgg==" } ================================================ FILE: identity/test/pool.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "encoding/base64" "encoding/json" "fmt" "net/http" "slices" "strings" "testing" "time" "github.com/go-faker/faker/v4" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/ory/herodot" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence" idpersistence "github.com/ory/kratos/persistence/sql/identity" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/schema" "github.com/ory/kratos/x" "github.com/ory/x/assertx" "github.com/ory/x/contextx" "github.com/ory/x/crdbx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/randx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlxx" "github.com/ory/x/urlx" ) // assertContainsValues is a test helper that checks if a slice contains expected values and doesn't contain unexpected values. func assertContainsValues(t *testing.T, actual []string, shouldContain, shouldNotContain []string) { t.Helper() for _, expected := range shouldContain { assert.Contains(t, actual, expected) } for _, notExpected := range shouldNotContain { assert.NotContains(t, actual, notExpected) } } func TestPool(ctx context.Context, p persistence.Persister, m *identity.Manager, dbname string) func(t *testing.T) { return func(t *testing.T) { nid, p := testhelpers.NewNetworkUnlessExisting(t, ctx, p) exampleServerURL := urlx.ParseOrPanic("http://example.com") expandSchema := schema.Schema{ ID: "expandSchema", URL: urlx.ParseOrPanic("file://./stub/expand.schema.json"), RawURL: "file://./stub/expand.schema.json", } defaultSchema := schema.Schema{ ID: config.DefaultIdentityTraitsSchemaID, URL: urlx.ParseOrPanic("file://./stub/identity.schema.json"), RawURL: "file://./stub/identity.schema.json", } altSchema := schema.Schema{ ID: "altSchema", URL: urlx.ParseOrPanic("file://./stub/identity-2.schema.json"), RawURL: "file://./stub/identity-2.schema.json", } multipleEmailsSchema := schema.Schema{ ID: "multiple_emails", URL: urlx.ParseOrPanic("file://./stub/handler/multiple_emails.schema.json"), RawURL: "file://./stub/identity-2.schema.json", } ctx := contextx.WithConfigValues(ctx, map[string]any{ config.ViperKeyPublicBaseURL: exampleServerURL.String(), config.ViperKeyIdentitySchemas: []config.Schema{ { ID: altSchema.ID, URL: altSchema.RawURL, }, { ID: defaultSchema.ID, URL: defaultSchema.RawURL, }, { ID: expandSchema.ID, URL: expandSchema.RawURL, }, { ID: multipleEmailsSchema.ID, URL: multipleEmailsSchema.RawURL, }, }, }) t.Run("case=expand", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).RawQuery("DELETE FROM identities WHERE nid = ?", nid).Exec()) t.Cleanup(func() { require.NoError(t, p.GetConnection(ctx).RawQuery("DELETE FROM identities WHERE nid = ?", nid).Exec()) }) expected := identity.NewIdentity(expandSchema.ID) expected.Traits = identity.Traits(`{"email":"` + uuid.Must(uuid.NewV4()).String() + "@ory.sh" + `","name":"john doe"}`) require.NoError(t, m.ValidateIdentity(ctx, expected, new(identity.ManagerOptions))) require.NoError(t, p.CreateIdentity(ctx, expected)) require.NoError(t, identity.UpgradeCredentials(expected)) assert.NotEmpty(t, expected.RecoveryAddresses) assert.NotEmpty(t, expected.VerifiableAddresses) assert.NotEmpty(t, expected.Credentials) assert.NotEqual(t, uuid.Nil, expected.RecoveryAddresses[0].ID) assert.NotEqual(t, uuid.Nil, expected.VerifiableAddresses[0].ID) runner := func(t *testing.T, expand sqlxx.Expandables, cb func(*testing.T, *identity.Identity)) { assertion := func(t *testing.T, actual *identity.Identity) { assertx.EqualAsJSONExcept(t, expected, actual, []string{ "verifiable_addresses", "recovery_addresses", "updated_at", "created_at", "credentials", "state_changed_at", }) cb(t, actual) } t.Run("find", func(t *testing.T) { actual, err := p.GetIdentity(ctx, expected.ID, expand) require.NoError(t, err) assertion(t, actual) }) t.Run("list/page-pagination", func(t *testing.T) { actual, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: expand, PagePagination: &x.Page{Page: 0, ItemsPerPage: 10}}) require.NoError(t, err) require.Len(t, actual, 1) assertion(t, &actual[0]) }) t.Run("list/token-pagination", func(t *testing.T) { actual, next, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: expand, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(10)}}) require.NoError(t, err) require.Len(t, actual, 1) require.True(t, next.IsLast()) assertion(t, &actual[0]) }) } t.Run("expand=nothing", func(t *testing.T) { runner(t, identity.ExpandNothing, func(t *testing.T, actual *identity.Identity) { assert.Empty(t, actual.RecoveryAddresses) assert.Empty(t, actual.VerifiableAddresses) assert.Empty(t, actual.Credentials) }) }) t.Run("expand=credentials", func(t *testing.T) { runner(t, identity.ExpandCredentials, func(t *testing.T, actual *identity.Identity) { assert.Empty(t, actual.RecoveryAddresses) assert.Empty(t, actual.VerifiableAddresses) require.Len(t, actual.Credentials, 2) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypePassword], actual.Credentials[identity.CredentialsTypePassword], []string{"updated_at", "created_at"}) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypeWebAuthn], actual.Credentials[identity.CredentialsTypeWebAuthn], []string{"updated_at", "created_at"}) }) }) t.Run("expand=recovery address", func(t *testing.T) { runner(t, sqlxx.Expandables{identity.ExpandFieldRecoveryAddresses}, func(t *testing.T, actual *identity.Identity) { assert.Empty(t, actual.Credentials) assert.Empty(t, actual.VerifiableAddresses) require.Len(t, actual.RecoveryAddresses, 1) assertx.EqualAsJSONExcept(t, expected.RecoveryAddresses, actual.RecoveryAddresses, []string{"0.updated_at", "0.created_at"}) }) }) t.Run("expand=verification address", func(t *testing.T) { runner(t, sqlxx.Expandables{identity.ExpandFieldVerifiableAddresses}, func(t *testing.T, actual *identity.Identity) { assert.Empty(t, actual.Credentials) assert.Empty(t, actual.RecoveryAddresses) require.Len(t, actual.VerifiableAddresses, 1) assertx.EqualAsJSONExcept(t, expected.VerifiableAddresses, actual.VerifiableAddresses, []string{"0.updated_at", "0.created_at"}) }) }) t.Run("expand=default", func(t *testing.T) { runner(t, identity.ExpandDefault, func(t *testing.T, actual *identity.Identity) { assert.Empty(t, actual.Credentials) require.Len(t, actual.RecoveryAddresses, 1) assertx.EqualAsJSONExcept(t, expected.RecoveryAddresses, actual.RecoveryAddresses, []string{"0.updated_at", "0.created_at"}) require.Len(t, actual.VerifiableAddresses, 1) assertx.EqualAsJSONExcept(t, expected.VerifiableAddresses, actual.VerifiableAddresses, []string{"0.updated_at", "0.created_at"}) }) }) t.Run("expand=everything", func(t *testing.T) { runner(t, identity.ExpandEverything, func(t *testing.T, actual *identity.Identity) { require.Len(t, actual.Credentials, 2) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypePassword], actual.Credentials[identity.CredentialsTypePassword], []string{"updated_at", "created_at"}) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypeWebAuthn], actual.Credentials[identity.CredentialsTypeWebAuthn], []string{"updated_at", "created_at"}) require.Len(t, actual.RecoveryAddresses, 1) assertx.EqualAsJSONExcept(t, expected.RecoveryAddresses, actual.RecoveryAddresses, []string{"0.updated_at", "0.created_at"}) require.Len(t, actual.VerifiableAddresses, 1) assertx.EqualAsJSONExcept(t, expected.VerifiableAddresses, actual.VerifiableAddresses, []string{"0.updated_at", "0.created_at"}) }) }) t.Run("expand=load", func(t *testing.T) { runner(t, identity.ExpandNothing, func(t *testing.T, actual *identity.Identity) { require.NoError(t, p.HydrateIdentityAssociations(ctx, actual, identity.ExpandEverything)) require.Len(t, actual.Credentials, 2) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypePassword], actual.Credentials[identity.CredentialsTypePassword], []string{"updated_at", "created_at"}) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypeWebAuthn], actual.Credentials[identity.CredentialsTypeWebAuthn], []string{"updated_at", "created_at"}) require.Len(t, actual.RecoveryAddresses, 1) assertx.EqualAsJSONExcept(t, expected.RecoveryAddresses, actual.RecoveryAddresses, []string{"0.updated_at", "0.created_at"}) require.Len(t, actual.VerifiableAddresses, 1) assertx.EqualAsJSONExcept(t, expected.VerifiableAddresses, actual.VerifiableAddresses, []string{"0.updated_at", "0.created_at"}) }) }) t.Run("confidential", func(t *testing.T) { // confidential is like expand=all actual, err := p.GetIdentityConfidential(ctx, expected.ID) require.NoError(t, err) assertx.EqualAsJSONExcept(t, expected, actual, []string{ "verifiable_addresses", "recovery_addresses", "updated_at", "created_at", "credentials", "state_changed_at", }) require.Len(t, actual.Credentials, 2) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypePassword], actual.Credentials[identity.CredentialsTypePassword], []string{"updated_at", "created_at"}) assertx.EqualAsJSONExcept(t, expected.Credentials[identity.CredentialsTypeWebAuthn], actual.Credentials[identity.CredentialsTypeWebAuthn], []string{"updated_at", "created_at"}) require.Len(t, actual.RecoveryAddresses, 1) assertx.EqualAsJSONExcept(t, expected.RecoveryAddresses, actual.RecoveryAddresses, []string{"0.updated_at", "0.created_at"}) require.Len(t, actual.VerifiableAddresses, 1) assertx.EqualAsJSONExcept(t, expected.VerifiableAddresses, actual.VerifiableAddresses, []string{"0.updated_at", "0.created_at"}) }) }) var createdIDs []uuid.UUID passwordIdentity := func(schemaID string, credentialsID string) *identity.Identity { i := identity.NewIdentity(schemaID) i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{credentialsID}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), }) return i } oidcIdentity := func(schemaID string, credentialsID string) *identity.Identity { i := identity.NewIdentity(schemaID) i.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{credentialsID}, Config: sqlxx.JSONRawMessage(`{}`), }) return i } assertEqual := func(t *testing.T, expected, actual *identity.Identity) { assert.Empty(t, actual.Credentials) require.Equal(t, expected.Traits, actual.Traits) require.Equal(t, expected.ID, actual.ID) } t.Run("case=should create and set missing ID", func(t *testing.T) { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{x.NewUUID().String()}, Config: sqlxx.JSONRawMessage(`{}`), }) i.ID = uuid.Nil require.NoError(t, p.CreateIdentity(ctx, i)) assert.NotEqual(t, uuid.Nil, i.ID) assert.Equal(t, nid, i.NID) createdIDs = append(createdIDs, i.ID) count, err := p.CountIdentities(ctx) require.NoError(t, err) assert.EqualValues(t, int64(1), count) t.Run("different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) count, err := p.CountIdentities(ctx) require.NoError(t, err) assert.EqualValues(t, int64(0), count) }) }) t.Run("case=create with default values", func(t *testing.T) { expected := passwordIdentity("", x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, err := p.GetIdentity(ctx, expected.ID, identity.ExpandDefault) require.NoError(t, err) assert.Equal(t, expected.ID, actual.ID) assert.Equal(t, config.DefaultIdentityTraitsSchemaID, actual.SchemaID) assert.Equal(t, defaultSchema.SchemaURL(exampleServerURL).String(), actual.SchemaURL) assertEqual(t, expected, actual) count, err := p.CountIdentities(ctx) require.NoError(t, err) assert.EqualValues(t, 2, count) t.Run("different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.GetIdentity(ctx, expected.ID, identity.ExpandDefault) require.ErrorIs(t, err, sqlcon.ErrNoRows) count, err := p.CountIdentities(ctx) require.NoError(t, err) assert.EqualValues(t, int64(0), count) }) }) t.Run("case=should set external ID", func(t *testing.T) { i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{x.NewUUID().String()}, Config: sqlxx.JSONRawMessage(`{}`), }) i.ID = uuid.Nil externalID := sqlxx.NullString("external-id-" + randx.MustString(10, randx.AlphaNum)) i.ExternalID = externalID require.NoError(t, p.CreateIdentity(ctx, i)) assert.NotEqual(t, uuid.Nil, i.ID) assert.Equal(t, nid, i.NID) assert.Equal(t, externalID, i.ExternalID) createdIDs = append(createdIDs, i.ID) t.Run("find by external ID", func(t *testing.T) { i2, err := p.FindIdentityByExternalID(ctx, externalID.String(), identity.ExpandEverything) require.NoError(t, err) assert.Equal(t, i.ID, i2.ID) }) t.Run("must be unique", func(t *testing.T) { i2 := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) i2.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{x.NewUUID().String()}, Config: sqlxx.JSONRawMessage(`{}`), }) i2.ExternalID = externalID err := new(herodot.DefaultError) require.ErrorAs(t, p.CreateIdentity(ctx, i2), &err) assert.Equal(t, http.StatusConflict, err.CodeField) }) }) t.Run("case=create with null AAL", func(t *testing.T) { expected := passwordIdentity("", "id-"+uuid.Must(uuid.NewV4()).String()) expected.InternalAvailableAAL.Valid = false require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, err := p.GetIdentity(ctx, expected.ID, identity.ExpandDefault) require.NoError(t, err) assert.False(t, actual.InternalAvailableAAL.Valid) }) t.Run("suite=create multiple identities", func(t *testing.T) { t.Run("create multiple identities", func(t *testing.T) { identities := make([]*identity.Identity, 100) for i := range identities { identities[i] = NewTestIdentity(4, "persister-create-multiple", i) } require.NoError(t, p.CreateIdentities(ctx, identities...)) createdAt := time.Now().UTC() for _, id := range identities { idFromDB, err := p.GetIdentity(ctx, id.ID, identity.ExpandEverything) require.NoError(t, err) credFromDB := idFromDB.Credentials[identity.CredentialsTypePassword] assert.Equal(t, id.ID, idFromDB.ID) assert.Equal(t, id.SchemaID, idFromDB.SchemaID) assert.Equal(t, id.SchemaURL, idFromDB.SchemaURL) assert.Equal(t, id.State, idFromDB.State) // We test that the values are plausible in the handler test already. assert.Equal(t, len(id.VerifiableAddresses), len(idFromDB.VerifiableAddresses)) assert.Equal(t, len(id.RecoveryAddresses), len(idFromDB.RecoveryAddresses)) assert.Equal(t, id.Credentials["password"].Identifiers, credFromDB.Identifiers) assert.WithinDuration(t, createdAt, credFromDB.CreatedAt, time.Minute) assert.WithinDuration(t, createdAt, credFromDB.UpdatedAt, time.Minute) // because of mysql precision assert.WithinDuration(t, id.CreatedAt, idFromDB.CreatedAt, time.Second) assert.WithinDuration(t, id.UpdatedAt, idFromDB.UpdatedAt, time.Second) require.NoError(t, p.DeleteIdentity(ctx, id.ID)) } }) t.Run("create exactly the non-conflicting ones", func(t *testing.T) { identities := make([]*identity.Identity, 100) for i := range identities { identities[i] = NewTestIdentity(4, "persister-create-multiple-2", i%60) } err := p.CreateIdentities(ctx, identities...) if dbname == "mysql" { // partial inserts are not supported on mysql assert.ErrorIs(t, err, sqlcon.ErrUniqueViolation) return } createdAt := time.Now().UTC() errWithCtx := new(identity.CreateIdentitiesError) require.ErrorAsf(t, err, &errWithCtx, "%#v", err) for _, id := range identities[:60] { require.NotZero(t, id.ID) idFromDB, err := p.GetIdentity(ctx, id.ID, identity.ExpandEverything) require.NoError(t, err) credFromDB := idFromDB.Credentials[identity.CredentialsTypePassword] assert.Equal(t, id.ID, idFromDB.ID) assert.Equal(t, id.SchemaID, idFromDB.SchemaID) assert.Equal(t, id.SchemaURL, idFromDB.SchemaURL) assert.Equal(t, id.State, idFromDB.State) // We test that the values are plausible in the handler test already. assert.Equal(t, len(id.VerifiableAddresses), len(idFromDB.VerifiableAddresses)) assert.Equal(t, len(id.RecoveryAddresses), len(idFromDB.RecoveryAddresses)) assert.Equal(t, id.Credentials["password"].Identifiers, credFromDB.Identifiers) assert.WithinDuration(t, createdAt, credFromDB.CreatedAt, time.Minute) assert.WithinDuration(t, createdAt, credFromDB.UpdatedAt, time.Minute) // because of mysql precision assert.WithinDuration(t, id.CreatedAt, idFromDB.CreatedAt, time.Second) assert.WithinDuration(t, id.UpdatedAt, idFromDB.UpdatedAt, time.Second) require.NoError(t, p.DeleteIdentity(ctx, id.ID)) } for _, id := range identities[60:] { failed := errWithCtx.Find(id) assert.NotNil(t, failed) } }) }) t.Run("case=external_id conflict in batch create returns partial error", func(t *testing.T) { // Regression test for https://github.com/ory-corp/cloud/issues/10580 // When all identities in a batch conflict on external_id, CreateIdentities // should return a CreateIdentitiesError, not a raw SQL error. // On Postgres, the bug causes: ERROR: syntax error at or near ")" (SQLSTATE 42601) // because DeleteIdentities is called with an empty ID slice, generating IN (). // Step 1: Create two identities with external_ids. first := make([]*identity.Identity, 2) for i := range first { first[i] = NewTestIdentity(1, "ext-id-conflict-first", i) first[i].ExternalID = sqlxx.NullString(fmt.Sprintf("ext-conflict-pool-%d", i)) } require.NoError(t, p.CreateIdentities(ctx, first...)) for _, id := range first { createdIDs = append(createdIDs, id.ID) } // Step 2: Create new identities with different traits but same external_ids. second := make([]*identity.Identity, 2) for i := range second { second[i] = NewTestIdentity(1, "ext-id-conflict-second", i) second[i].ExternalID = sqlxx.NullString(fmt.Sprintf("ext-conflict-pool-%d", i)) } err := p.CreateIdentities(ctx, second...) if dbname == "mysql" { assert.ErrorIs(t, err, sqlcon.ErrUniqueViolation) return } errWithCtx := new(identity.CreateIdentitiesError) require.ErrorAsf(t, err, &errWithCtx, "expected CreateIdentitiesError, got: %#v", err) for _, id := range second { assert.NotNil(t, errWithCtx.Find(id), "expected identity %s to be in the error", id.ID) } }) t.Run("case=should error when the identity ID does not exist", func(t *testing.T) { _, err := p.GetIdentity(ctx, uuid.UUID{}, identity.ExpandNothing) require.Error(t, err) _, err = p.GetIdentity(ctx, x.NewUUID(), identity.ExpandNothing) require.Error(t, err) _, err = p.GetIdentityConfidential(ctx, x.NewUUID()) require.Error(t, err) }) t.Run("case=run migrations when fetching credentials", func(t *testing.T) { expected := func(schemaID string, credentialsID string) *identity.Identity { i := identity.NewIdentity(schemaID) i.SetCredentials(identity.CredentialsTypeWebAuthn, identity.Credentials{ Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{credentialsID}, Config: sqlxx.JSONRawMessage(`{"credentials":[{}]}`), }) return i }(altSchema.ID, "webauthn") require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, err := p.GetIdentityConfidential(ctx, expected.ID) require.NoError(t, err) c := actual.GetCredentialsOr(identity.CredentialsTypeWebAuthn, &identity.Credentials{}) assert.True(t, gjson.GetBytes(c.Config, "credentials.0.is_passwordless").Exists()) assert.Equal(t, base64.StdEncoding.EncodeToString(expected.ID[:]), gjson.GetBytes(c.Config, "user_handle").String()) }) t.Run("case=create and keep set values", func(t *testing.T) { expected := passwordIdentity(altSchema.ID, x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, err := p.GetIdentity(ctx, expected.ID, identity.ExpandDefault) require.NoError(t, err) assert.Equal(t, altSchema.ID, actual.SchemaID) assert.Equal(t, altSchema.SchemaURL(exampleServerURL).String(), actual.SchemaURL) assertEqual(t, expected, actual) actual, err = p.GetIdentityConfidential(ctx, expected.ID) require.NoError(t, err) require.Equal(t, expected.Traits, actual.Traits) require.Equal(t, expected.ID, actual.ID) assert.NotEmpty(t, actual.Credentials) assert.NotEmpty(t, expected.Credentials) for m, expected := range expected.Credentials { assert.Equal(t, expected.ID, actual.Credentials[m].ID) assert.JSONEq(t, string(expected.Config), string(actual.Credentials[m].Config)) assert.Equal(t, expected.Identifiers, actual.Credentials[m].Identifiers) assert.Equal(t, expected.Type, actual.Credentials[m].Type) } t.Run("different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.ErrorIs(t, err, sqlcon.ErrNoRows) _, err = p.GetIdentityConfidential(ctx, expected.ID) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=fail on duplicate credential identifiers if type is password", func(t *testing.T) { email := randx.MustString(16, randx.AlphaLowerNum) + "@bar.com" initial := passwordIdentity("", email) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) for _, transform := range []func(string) string{ strings.ToLower, func(s string) string { return s[:1] + strings.ToUpper(s[1:2]) + s[2:] }, strings.ToUpper, func(s string) string { left, right, _ := strings.Cut(s, "@"); return left + "@" + strings.Title(right) }, } { ids := transform(email) expected := passwordIdentity("", ids) err := p.CreateIdentity(ctx, expected) require.ErrorIs(t, err, sqlcon.ErrUniqueViolation, "%+v", err) _, err = p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.Error(t, err) t.Run("succeeds on different network/id="+ids, func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) expected := passwordIdentity("", ids) err := p.CreateIdentity(ctx, expected) require.NoError(t, err) _, err = p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.NoError(t, err) }) } }) t.Run("case=fail on duplicate credential identifiers if type is oidc", func(t *testing.T) { oidcID := randx.MustString(16, randx.AlphaLowerNum) initial := oidcIdentity("", oidcID) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) expected := oidcIdentity("", oidcID) require.Error(t, p.CreateIdentity(ctx, expected)) _, err := p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.Error(t, err) second := oidcIdentity("", strings.ToUpper(oidcID)) require.NoError(t, p.CreateIdentity(ctx, second), "should work because oidc is not case-sensitive") createdIDs = append(createdIDs, second.ID) t.Run("succeeds on different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) expected := oidcIdentity("", oidcID) require.NoError(t, p.CreateIdentity(ctx, expected)) _, err = p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.NoError(t, err) }) }) t.Run("case=create with invalid traits data", func(t *testing.T) { expected := oidcIdentity("", x.NewUUID().String()) expected.Traits = identity.Traits(`{"bar":123}`) // bar should be a string err := p.CreateIdentity(ctx, expected) require.Error(t, err) assert.Contains(t, fmt.Sprintf("%+v", err.Error()), "malformed") }) t.Run("case=get classified credentials", func(t *testing.T) { initial := oidcIdentity("", x.NewUUID().String()) initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"aylmao-oidc"}, Config: sqlxx.JSONRawMessage(`{"ay":"lmao"}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) initial, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.NotEqual(t, uuid.Nil, initial.ID) require.NotEmpty(t, initial.Credentials) t.Run("fails on different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.GetIdentityConfidential(ctx, initial.ID) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=update an identity and set credentials", func(t *testing.T) { initial := oidcIdentity("", x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) assert.Equal(t, config.DefaultIdentityTraitsSchemaID, initial.SchemaID) assert.Equal(t, defaultSchema.SchemaURL(exampleServerURL).String(), initial.SchemaURL) expected := initial.CopyWithoutCredentials() expected.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{"ignore-me"}, Config: sqlxx.JSONRawMessage(`{"oh":"nono"}`), }) expected.Traits = identity.Traits(`{"update":"me"}`) expected.SchemaID = altSchema.ID require.NoError(t, p.UpdateIdentity(ctx, expected)) actual, err := p.GetIdentityConfidential(ctx, expected.ID) require.NoError(t, err) assert.Equal(t, altSchema.ID, actual.SchemaID) assert.Equal(t, altSchema.SchemaURL(exampleServerURL).String(), actual.SchemaURL) assert.NotEmpty(t, actual.Credentials[identity.CredentialsTypePassword]) assert.Empty(t, actual.Credentials[identity.CredentialsTypeOIDC]) assert.Equal(t, expected.Credentials[identity.CredentialsTypeOIDC], actual.Credentials[identity.CredentialsTypeOIDC]) t.Run("fails on different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) require.ErrorIs(t, p.UpdateIdentity(ctx, expected), sqlcon.ErrNoRows) }) }) t.Run("case=fail to update because validation fails", func(t *testing.T) { initial := oidcIdentity("", x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) initial.Traits = identity.Traits(`{"bar":123}`) err := p.UpdateIdentity(ctx, initial) require.Error(t, err) require.Contains(t, err.Error(), "malformed") }) t.Run("case=update an identity column", func(t *testing.T) { initial := oidcIdentity("", x.NewUUID().String()) initial.InternalAvailableAAL = identity.NewNullableAuthenticatorAssuranceLevel(identity.NoAuthenticatorAssuranceLevel) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) initial.InternalAvailableAAL = identity.NewNullableAuthenticatorAssuranceLevel(identity.AuthenticatorAssuranceLevel1) initial.State = identity.StateInactive require.NoError(t, p.UpdateIdentityColumns(ctx, initial, "available_aal")) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) assert.Equal(t, string(identity.AuthenticatorAssuranceLevel1), actual.InternalAvailableAAL.String) assert.Equal(t, identity.StateActive, actual.State, "the state remains unchanged") }) t.Run("case=should fail to insert identity because credentials from traits exist", func(t *testing.T) { email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" first := passwordIdentity("", email) first.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, first)) createdIDs = append(createdIDs, first.ID) second := passwordIdentity("", email) require.Error(t, p.CreateIdentity(ctx, second)) t.Run("passes on different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) require.NoError(t, p.CreateIdentity(ctx, second)) }) t.Run("case=should fail to update identity because credentials exist", func(t *testing.T) { first := passwordIdentity("", x.NewUUID().String()) first.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, first)) createdIDs = append(createdIDs, first.ID) c := first.Credentials[identity.CredentialsTypePassword] c.Identifiers = []string{email} first.Credentials[identity.CredentialsTypePassword] = c require.Error(t, p.UpdateIdentity(ctx, first)) t.Run("passes on different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) first := passwordIdentity("", x.NewUUID().String()) first.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, first)) c := first.Credentials[identity.CredentialsTypePassword] c.Identifiers = []string{"test-identity@ory.sh"} first.Credentials[identity.CredentialsTypePassword] = c require.NoError(t, p.UpdateIdentity(ctx, first)) }) }) }) t.Run("case=should succeed to update credentials from traits", func(t *testing.T) { expected := passwordIdentity("", x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) expected.Traits = identity.Traits(`{"email":"update-test-identity@ory.sh"}`) require.NoError(t, p.UpdateIdentity(ctx, expected)) actual, err := p.GetIdentityConfidential(ctx, expected.ID) require.NoError(t, err) assert.Equal(t, expected.Credentials[identity.CredentialsTypePassword].Identifiers, actual.Credentials[identity.CredentialsTypePassword].Identifiers) }) t.Run("case=delete an identity", func(t *testing.T) { expected := passwordIdentity("", x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, expected)) t.Run("fails on different network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) require.ErrorIs(t, p.DeleteIdentity(ctx, expected.ID), sqlcon.ErrNoRows) p = testhelpers.ExistingNetwork(t, p, nid) _, err := p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.NoError(t, err) }) require.NoError(t, p.DeleteIdentity(ctx, expected.ID)) _, err := p.GetIdentity(ctx, expected.ID, identity.ExpandNothing) require.Error(t, err) }) t.Run("case=create with empty credentials config", func(t *testing.T) { // This test covers a case where the config value of a credentials setting is empty. This causes // issues with postgres' json field. expected := passwordIdentity("", x.NewUUID().String()) expected.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{"id-missing-creds-config"}, Config: sqlxx.JSONRawMessage(``), }) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) }) t.Run("case=list", func(t *testing.T) { is, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: identity.ExpandDefault}) require.NoError(t, err) require.NotEmpty(t, is) require.Len(t, is, len(createdIDs)) for _, id := range createdIDs { var found bool for _, i := range is { if i.ID == id { expected, err := p.GetIdentity(ctx, id, identity.ExpandDefault) require.NoError(t, err) assertx.EqualAsJSON(t, expected, i) found = true } } require.True(t, found, id) } t.Run("no results on other network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) is, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: identity.ExpandDefault}) require.NoError(t, err) assert.Len(t, is, 0) }) t.Run("list some using ids filter", func(t *testing.T) { filterIds := createdIDs[:2] is, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{Expand: identity.ExpandDefault, IdsFilter: filterIds}) require.NoError(t, err) assert.Len(t, is, len(filterIds)) }) t.Run("eventually consistent", func(t *testing.T) { if dbname != "cockroach" { t.Skipf("Test only works with cockroachdb") return } id := x.NewUUID().String() another := oidcIdentity("", id) require.NoError(t, p.CreateIdentity(ctx, another)) createdIDs = append(createdIDs, another.ID) is, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ Expand: identity.ExpandDefault, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(25)}, ConsistencyLevel: crdbx.ConsistencyLevelStrong, }) require.NoError(t, err) require.Len(t, is, len(createdIDs)) var results []identity.Identity // It takes about 4.8 seconds to replicate the data. for i := 0; i < 8; i++ { time.Sleep(time.Second) // The error here is explicitly ignored because the table / schema might not yet be replicated. // This can lead to "ERROR: cached plan must not change result type (SQLSTATE 0A000)" whih is caused // because the prepared query exist but the schema is not yet replicated. is, _, _ := p.ListIdentities(ctx, identity.ListIdentityParameters{ Expand: identity.ExpandEverything, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(25)}, ConsistencyLevel: crdbx.ConsistencyLevelEventual, }) if len(is) == len(createdIDs) { results = is } } require.NotZero(t, len(results)) require.Len(t, results, len(createdIDs), "Could not find all identities after 8 seconds") var found bool for _, i := range results { if i.ID == another.ID { found = true } } require.True(t, found, id, "Unable to find created identity in eventually consistent results.") }) }) t.Run("case=find identity by its credentials identifier", func(t *testing.T) { var expectedIdentifiers []string var expectedIdentities []*identity.Identity for _, c := range []identity.CredentialsType{ identity.CredentialsTypePassword, identity.CredentialsTypeWebAuthn, identity.CredentialsTypeOIDC, } { identityIdentifier := fmt.Sprintf("find-identity-by-identifier-%s@ory.sh", c) expected := identity.NewIdentity("") expected.SetCredentials(c, identity.Credentials{Type: c, Identifiers: []string{identityIdentifier}, Config: sqlxx.JSONRawMessage(`{}`)}) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) expectedIdentifiers = append(expectedIdentifiers, identityIdentifier) expectedIdentities = append(expectedIdentities, expected) } create := identity.NewIdentity("") create.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{Type: identity.CredentialsTypePassword, Identifiers: []string{"find-identity-by-identifier-common@ory.sh"}, Config: sqlxx.JSONRawMessage(`{}`)}) create.SetCredentials(identity.CredentialsTypeWebAuthn, identity.Credentials{Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{"find-identity-by-identifier-common@ory.sh"}, Config: sqlxx.JSONRawMessage(`{}`)}) require.NoError(t, p.CreateIdentity(ctx, create)) actual, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ Expand: identity.ExpandEverything, }) require.NoError(t, err) require.Greater(t, len(actual), 0) for c, ct := range []identity.CredentialsType{ identity.CredentialsTypePassword, identity.CredentialsTypeWebAuthn, } { t.Run(ct.String(), func(t *testing.T) { actual, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ // Match is normalized CredentialsIdentifier: expectedIdentifiers[c], }) require.NoError(t, err) expected := expectedIdentities[c] require.Len(t, actual, 1) assertx.EqualAsJSONExcept(t, expected, actual[0], []string{"credentials.config", "created_at", "updated_at", "state_changed_at"}) }) } t.Run("similarity search", func(t *testing.T) { actual, _, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ CredentialsIdentifierSimilar: "find-identity-by-identifier", Expand: identity.ExpandCredentials, }) require.NoError(t, err) assert.Len(t, actual, 4) // webauthn, common, password, oidc outer: for _, e := range append(expectedIdentities[:2], create) { for _, a := range actual { if e.ID == a.ID { assertx.EqualAsJSONExcept(t, e, a, []string{"credentials.config", "created_at", "updated_at", "state_changed_at"}) continue outer } } actualCredentials := make([]map[identity.CredentialsType]identity.Credentials, len(actual)) for k, a := range actual { actualCredentials[k] = a.Credentials } t.Fatalf("expected identity %+v not found in actual result set %+v", e.Credentials, actualCredentials) } }) t.Run("find by OIDC identifier", func(t *testing.T) { actual, next, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ CredentialsIdentifier: "find-identity-by-identifier-oidc@ory.sh", Expand: identity.ExpandEverything, }) require.NoError(t, err) assert.Len(t, actual, 1) assert.True(t, next.IsLast()) }) t.Run("one result set even if multiple matches", func(t *testing.T) { actual, next, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ CredentialsIdentifier: "find-identity-by-identifier-common@ory.sh", Expand: identity.ExpandEverything, }) require.NoError(t, err) assert.Len(t, actual, 1) assert.True(t, next.IsLast()) }) t.Run("non existing identifier", func(t *testing.T) { actual, next, err := p.ListIdentities(ctx, identity.ListIdentityParameters{ CredentialsIdentifier: "find-identity-by-identifier-non-existing@ory.sh", Expand: identity.ExpandEverything, }) require.NoError(t, err) assert.Len(t, actual, 0) assert.True(t, next.IsLast()) }) t.Run("not if on another network", func(t *testing.T) { _, on := testhelpers.NewNetwork(t, ctx, p) actual, next, err := on.ListIdentities(ctx, identity.ListIdentityParameters{ CredentialsIdentifier: expectedIdentifiers[0], Expand: identity.ExpandEverything, }) require.NoError(t, err) assert.Len(t, actual, 0) assert.True(t, next.IsLast()) }) }) t.Run("case=find identity by its credentials type and identifier", func(t *testing.T) { email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" expected := passwordIdentity("", email) expected.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, creds, err := p.FindByCredentialsIdentifier(ctx, identity.CredentialsTypePassword, email) require.NoError(t, err) assert.EqualValues(t, expected.Credentials[identity.CredentialsTypePassword].ID, creds.ID) assert.EqualValues(t, expected.Credentials[identity.CredentialsTypePassword].Identifiers, creds.Identifiers) assert.JSONEq(t, string(expected.Credentials[identity.CredentialsTypePassword].Config), string(creds.Config)) // assert.EqualValues(t, expected.Credentials[CredentialsTypePassword].CreatedAt.Unix(), creds.CreatedAt.Unix()) // assert.EqualValues(t, expected.Credentials[CredentialsTypePassword].UpdatedAt.Unix(), creds.UpdatedAt.Unix()) require.Equal(t, expected.Traits, actual.Traits) require.Equal(t, expected.ID, actual.ID) require.NotNil(t, actual.Credentials[identity.CredentialsTypePassword]) assert.EqualValues(t, expected.Credentials[identity.CredentialsTypePassword].ID, actual.Credentials[identity.CredentialsTypePassword].ID) assert.EqualValues(t, expected.Credentials[identity.CredentialsTypePassword].Identifiers, actual.Credentials[identity.CredentialsTypePassword].Identifiers) assert.JSONEq(t, string(expected.Credentials[identity.CredentialsTypePassword].Config), string(actual.Credentials[identity.CredentialsTypePassword].Config)) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, _, err := p.FindByCredentialsIdentifier(ctx, identity.CredentialsTypePassword, email) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=find identity by its webauthn credential user handle", func(t *testing.T) { expected := identity.NewIdentity("") expected.SetCredentials(identity.CredentialsTypeWebAuthn, identity.Credentials{ Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{"find-webauth-user-handle-identifier@ory.sh"}, Config: sqlxx.JSONRawMessage(`{ "credentials": [ { "added_at": "2024-02-13T10:36:16Z", "attestation_type": "none", "authenticator": { "aaguid": "+/wwBxVOTsyMC24CBVfXvQ==", "clone_warning": false, "sign_count": 0 }, "display_name": "Yubikey", "id": "f2uGd/Bg1rGcGXtYp4MT4WcN+eA=", "is_passwordless": true, "public_key": "pQECAyYgASFYIBkNvUxvjdhuA36FworTmS/rxZR1I+NyRWBpoTYY/R+CIlggw+gFFrFoEi+rS82zq7+tDHAukBUJcFpQ7Z3NLBZH5vk=" } ], "user_handle": "51z80nYJTSGmr6UBe1VGLg==" }`), }) expected.Traits = identity.Traits(`{}`) userHandle := x.Must(base64.StdEncoding.DecodeString("51z80nYJTSGmr6UBe1VGLg==")) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, err := p.FindIdentityByWebauthnUserHandle(ctx, userHandle) require.NoError(t, err) expected.Credentials = nil assertEqual(t, expected, actual) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err = p.FindIdentityByWebauthnUserHandle(ctx, userHandle) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=find identity only by credentials identifier", func(t *testing.T) { email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" expected := passwordIdentity("", email) expected.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, err := p.FindIdentityByCredentialIdentifier(ctx, strings.ToUpper(email), false, identity.ExpandDefault) require.NoError(t, err) expected.Credentials = nil assertEqual(t, expected, actual) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindIdentityByCredentialIdentifier(ctx, strings.ToUpper(email), false, identity.ExpandDefault) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=find identity only by credentials identifier case sensitive", func(t *testing.T) { email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" expected := passwordIdentity("", email) expected.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) _, err := p.FindIdentityByCredentialIdentifier(ctx, strings.ToUpper(email), true, identity.ExpandDefault) require.ErrorIs(t, err, sqlcon.ErrNoRows) actual, err := p.FindIdentityByCredentialIdentifier(ctx, email, true, identity.ExpandDefault) require.NoError(t, err) expected.Credentials = nil assertEqual(t, expected, actual) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindIdentityByCredentialIdentifier(ctx, email, true, identity.ExpandDefault) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=find identity by its credentials respects cases", func(t *testing.T) { baseEmail := randx.MustString(16, randx.AlphaLowerNum) caseSensitive := baseEmail + "@ory.sh" caseInsensitiveWithSpaces := " " + strings.ToUpper(baseEmail) + "@ORY.sh " expected := identity.NewIdentity("") for _, c := range []identity.CredentialsType{ identity.CredentialsTypePassword, identity.CredentialsTypeOIDC, identity.CredentialsTypeTOTP, identity.CredentialsTypeLookup, identity.CredentialsTypeWebAuthn, } { expected.SetCredentials(c, identity.Credentials{Type: c, Identifiers: []string{caseSensitive}, Config: sqlxx.JSONRawMessage(`{}`)}) } require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) t.Run("case sensitive", func(t *testing.T) { for _, ct := range []identity.CredentialsType{ identity.CredentialsTypeOIDC, identity.CredentialsTypeTOTP, identity.CredentialsTypeLookup, } { t.Run(ct.String(), func(t *testing.T) { _, _, err := p.FindByCredentialsIdentifier(ctx, ct, caseInsensitiveWithSpaces) require.Error(t, err) actual, creds, err := p.FindByCredentialsIdentifier(ctx, ct, caseSensitive) require.NoError(t, err) assertx.EqualAsJSONExcept(t, expected.Credentials[ct], creds, []string{"created_at", "updated_at", "id"}) assertx.EqualAsJSONExcept(t, expected, actual, []string{"created_at", "state_changed_at", "updated_at", "id"}) }) } }) t.Run("case insensitive", func(t *testing.T) { for _, ct := range []identity.CredentialsType{ identity.CredentialsTypePassword, identity.CredentialsTypeWebAuthn, } { t.Run(ct.String(), func(t *testing.T) { for _, cs := range []string{caseSensitive, caseInsensitiveWithSpaces} { actual, creds, err := p.FindByCredentialsIdentifier(ctx, ct, cs) require.NoError(t, err) ec := expected.Credentials[ct] ec.Identifiers = []string{strings.ToLower(caseSensitive)} assertx.EqualAsJSONExcept(t, ec, creds, []string{"created_at", "updated_at", "id", "config.user_handle", "config.credentials", "version"}) assertx.EqualAsJSONExcept(t, expected, actual, []string{"created_at", "state_changed_at", "updated_at", "id"}) } }) } }) }) t.Run("case=find identity by its credentials case insensitive", func(t *testing.T) { identifier := x.NewUUID().String() expected := passwordIdentity("", strings.ToUpper(identifier)) expected.Traits = identity.Traits(`{}`) require.NoError(t, p.CreateIdentity(ctx, expected)) createdIDs = append(createdIDs, expected.ID) actual, creds, err := p.FindByCredentialsIdentifier(ctx, identity.CredentialsTypePassword, identifier) require.NoError(t, err) assert.EqualValues(t, expected.Credentials[identity.CredentialsTypePassword].ID, creds.ID) assert.EqualValues(t, []string{strings.ToLower(identifier)}, creds.Identifiers) assert.JSONEq(t, string(expected.Credentials[identity.CredentialsTypePassword].Config), string(creds.Config)) require.Equal(t, expected.Traits, actual.Traits) require.Equal(t, expected.ID, actual.ID) require.NotNil(t, actual.Credentials[identity.CredentialsTypePassword]) assert.EqualValues(t, expected.Credentials[identity.CredentialsTypePassword].ID, actual.Credentials[identity.CredentialsTypePassword].ID) assert.EqualValues(t, []string{strings.ToLower(identifier)}, actual.Credentials[identity.CredentialsTypePassword].Identifiers) assert.JSONEq(t, string(expected.Credentials[identity.CredentialsTypePassword].Config), string(actual.Credentials[identity.CredentialsTypePassword].Config)) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, _, err := p.FindByCredentialsIdentifier(ctx, identity.CredentialsTypePassword, identifier) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("suite=verifiable-address", func(t *testing.T) { createIdentityWithAddresses := func(t *testing.T, email string) identity.VerifiableAddress { var i identity.Identity require.NoError(t, faker.FakeData(&i)) address := identity.NewVerifiableEmailAddress(email, i.ID) i.VerifiableAddresses = append(i.VerifiableAddresses, *address) require.NoError(t, p.CreateIdentity(ctx, &i)) return i.VerifiableAddresses[0] } t.Run("case=not found", func(t *testing.T) { _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "does-not-exist") require.ErrorIs(t, err, sqlcon.ErrNoRows) }) transform := func(k int, value string) string { switch k % 5 { case 0: value = strings.ToLower(value) case 1: value = strings.ToUpper(value) case 2: value = " " + value case 3: value = value + " " } return value } t.Run("case=create and find", func(t *testing.T) { addresses := make([]identity.VerifiableAddress, 15) for k := range addresses { value := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" addresses[k] = createIdentityWithAddresses(t, transform(k, value)) require.NotEmpty(t, addresses[k].ID) } compare := func(t *testing.T, expected, actual identity.VerifiableAddress) { actual.CreatedAt = actual.CreatedAt.UTC().Truncate(time.Hour * 24) actual.UpdatedAt = actual.UpdatedAt.UTC().Truncate(time.Hour * 24) expected.CreatedAt = expected.CreatedAt.UTC().Truncate(time.Hour * 24) expected.UpdatedAt = expected.UpdatedAt.UTC().Truncate(time.Hour * 24) expected.Value = strings.TrimSpace(strings.ToLower(expected.Value)) assert.EqualValues(t, expected, actual) } for k, expected := range addresses { t.Run("method=FindVerifiableAddressByValue", func(t *testing.T) { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { actual, err := p.FindVerifiableAddressByValue(ctx, expected.Via, transform(k+1, expected.Value)) require.NoError(t, err) compare(t, expected, *actual) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, expected.Via, transform(k+1, expected.Value)) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) }) } }) t.Run("case=update", func(t *testing.T) { address := createIdentityWithAddresses(t, "verification.TestPersister.Update@ory.sh ") address.Value = "new-codE " require.NoError(t, p.UpdateVerifiableAddress(ctx, &address)) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) require.ErrorIs(t, p.UpdateVerifiableAddress(ctx, &address), sqlcon.ErrNoRows) }) actual, err := p.FindVerifiableAddressByValue(ctx, address.Via, address.Value) require.NoError(t, err) assert.Equal(t, "new-code", actual.Value) }) t.Run("case=create and update and find", func(t *testing.T) { var i identity.Identity require.NoError(t, faker.FakeData(&i)) address := identity.NewVerifiableEmailAddress("verification.TestPersister.Update-Identity@ory.sh", i.ID) i.VerifiableAddresses = append(i.VerifiableAddresses, *address) require.NoError(t, p.CreateIdentity(ctx, &i)) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity@ory.sh") require.NoError(t, err) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity@ory.sh") require.ErrorIs(t, err, sqlcon.ErrNoRows) }) address = identity.NewVerifiableEmailAddress("verification.TestPersister.Update-Identity-next@ory.sh", i.ID) i.VerifiableAddresses = []identity.VerifiableAddress{*address} require.NoError(t, p.UpdateIdentity(ctx, &i)) _, err = p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity@ory.sh") require.EqualError(t, err, sqlcon.ErrNoRows.Error()) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity@ory.sh") require.ErrorIs(t, err, sqlcon.ErrNoRows) }) actual, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity-next@ory.sh") require.NoError(t, err) assert.Equal(t, identity.AddressTypeEmail, actual.Via) assert.Equal(t, "verification.testpersister.update-identity-next@ory.sh", actual.Value) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity-next@ory.sh") require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) t.Run("case=create and update and find case insensitive", func(t *testing.T) { var i identity.Identity require.NoError(t, faker.FakeData(&i)) address := identity.NewVerifiableEmailAddress("verification.TestPersister.Update-Identity-case-insensitive@ory.sh", i.ID) i.VerifiableAddresses = append(i.VerifiableAddresses, *address) require.NoError(t, p.CreateIdentity(ctx, &i)) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("verification.TestPersister.Update-Identity-case-insensitive@ory.sh")) require.NoError(t, err) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("verification.TestPersister.Update-Identity-case-insensitive@ory.sh")) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) address = identity.NewVerifiableEmailAddress("verification.TestPersister.Update-Identity-case-insensitive-next@ory.sh", i.ID) i.VerifiableAddresses = []identity.VerifiableAddress{*address} require.NoError(t, p.UpdateIdentity(ctx, &i)) _, err = p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("verification.TestPersister.Update-Identity-case-insensitive@ory.sh")) require.EqualError(t, err, sqlcon.ErrNoRows.Error()) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("verification.TestPersister.Update-Identity-case-insensitive@ory.sh")) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) actual, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("verification.TestPersister.Update-Identity-case-insensitive-next@ory.sh")) require.NoError(t, err) assert.Equal(t, identity.AddressTypeEmail, actual.Via) assert.Equal(t, "verification.testpersister.update-identity-case-insensitive-next@ory.sh", actual.Value) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindVerifiableAddressByValue(ctx, identity.AddressTypeEmail, "verification.TestPersister.Update-Identity-case-insensitive-next@ory.sh") require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) }) t.Run("suite=credential-types", func(t *testing.T) { for _, ct := range identity.AllCredentialTypes { t.Run("type="+ct.String(), func(t *testing.T) { id, err := idpersistence.FindIdentityCredentialsTypeByName(p.GetConnection(ctx), ct) require.NoError(t, err) require.NotEqual(t, uuid.Nil, id) name, err := idpersistence.FindIdentityCredentialsTypeByID(p.GetConnection(ctx), id) require.NoError(t, err) assert.Equal(t, ct, name) }) } _, err := idpersistence.FindIdentityCredentialsTypeByName(p.GetConnection(ctx), "unknown") require.Error(t, err) _, err = idpersistence.FindIdentityCredentialsTypeByID(p.GetConnection(ctx), x.NewUUID()) require.Error(t, err) }) t.Run("suite=recovery-address", func(t *testing.T) { sortAddresses := func(addresses []identity.RecoveryAddress) { slices.SortFunc(addresses, func(a, b identity.RecoveryAddress) int { return strings.Compare(a.Value, b.Value) }) } createIdentityWithAddresses := func(t *testing.T, email string) *identity.Identity { var i identity.Identity require.NoError(t, faker.FakeData(&i)) i.Traits = []byte(`{"email":"` + email + `"}`) address := identity.NewRecoveryEmailAddress(email, i.ID) i.RecoveryAddresses = append(i.RecoveryAddresses, *address) addressOther := identity.NewRecoveryEmailAddress(email+"_other", i.ID) i.RecoveryAddresses = append(i.RecoveryAddresses, *addressOther) require.NoError(t, p.CreateIdentity(ctx, &i)) return &i } t.Run("case=not found", func(t *testing.T) { _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, "does-not-exist") require.ErrorIs(t, err, sqlcon.ErrNoRows) allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, "does-not-exist") require.NoError(t, err) require.Len(t, allAddresses, 0) }) t.Run("case=create and find", func(t *testing.T) { addresses := make([]identity.RecoveryAddress, 15) for k := range addresses { addresses[k] = createIdentityWithAddresses(t, randx.MustString(16, randx.AlphaLowerNum)+"@ory.sh").RecoveryAddresses[0] require.NotEmpty(t, addresses[k].ID) } compare := func(t *testing.T, expected, actual identity.RecoveryAddress) { actual.CreatedAt = actual.CreatedAt.UTC().Truncate(time.Hour * 24) actual.UpdatedAt = actual.UpdatedAt.UTC().Truncate(time.Hour * 24) expected.CreatedAt = expected.CreatedAt.UTC().Truncate(time.Hour * 24) expected.UpdatedAt = expected.UpdatedAt.UTC().Truncate(time.Hour * 24) assert.EqualValues(t, expected, actual) } for k, expected := range addresses { t.Run("method=FindVerifiableAddressByValue", func(t *testing.T) { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { actual, err := p.FindRecoveryAddressByValue(ctx, expected.Via, expected.Value) require.NoError(t, err) compare(t, expected, *actual) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, expected.Via, expected.Value) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) }) t.Run("method=FindAllRecoveryAddressesForIdentityByRecoveryAddressValue", func(t *testing.T) { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, expected.Value) require.NoError(t, err) require.Len(t, allAddresses, 2) sortAddresses(allAddresses) require.Equal(t, expected.Value, allAddresses[0].Value) require.Equal(t, expected.Value+"_other", allAddresses[1].Value) }) t.Run("not if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, expected.Value) require.NoError(t, err) require.Len(t, allAddresses, 0) }) }) } }) t.Run("case=create and update and find", func(t *testing.T) { email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" emailLower := strings.ToLower(email) id := createIdentityWithAddresses(t, email) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, email) require.NoError(t, err) allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, emailLower) require.NoError(t, err) require.Len(t, allAddresses, 2) sortAddresses(allAddresses) require.Equal(t, allAddresses[0].Value, emailLower) require.Equal(t, allAddresses[1].Value, emailLower+"_other") t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, email) require.ErrorIs(t, err, sqlcon.ErrNoRows) allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, emailLower) require.NoError(t, err) require.Len(t, allAddresses, 0) }) emailNext := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" id.RecoveryAddresses = []identity.RecoveryAddress{{Via: identity.AddressTypeEmail, Value: emailNext}, {Via: identity.AddressTypeEmail, Value: emailNext + "_other"}} require.NoError(t, p.UpdateIdentity(ctx, id)) _, err = p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, email) require.EqualError(t, err, sqlcon.ErrNoRows.Error()) allAddresses, err = p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, emailLower) require.NoError(t, err) require.Len(t, allAddresses, 0) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, email) require.ErrorIs(t, err, sqlcon.ErrNoRows) allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, emailLower) require.NoError(t, err) require.Len(t, allAddresses, 0) }) emailNextLower := strings.ToLower(emailNext) actual, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, emailNext) require.NoError(t, err) assert.Equal(t, identity.AddressTypeEmail, actual.Via) assert.Equal(t, emailNextLower, actual.Value) allAddresses, err = p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, emailNextLower) require.NoError(t, err) require.Len(t, allAddresses, 2) sortAddresses(allAddresses) assert.Equal(t, identity.AddressTypeEmail, allAddresses[0].Via) assert.Equal(t, emailNextLower, allAddresses[0].Value) assert.Equal(t, identity.AddressTypeEmail, allAddresses[1].Via) assert.Equal(t, emailNextLower+"_other", allAddresses[1].Value) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, emailNext) require.ErrorIs(t, err, sqlcon.ErrNoRows) allAddresses, err := p.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx, emailNextLower) require.NoError(t, err) require.Len(t, allAddresses, 0) }) }) t.Run("case=create and update and find case insensitive", func(t *testing.T) { id := createIdentityWithAddresses(t, "recovery.TestPersister.Update-case-insensitive@ory.sh") _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("recovery.TestPersister.Update-case-insensitive@ory.sh")) require.NoError(t, err) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("Recovery.TestPersister.Update-case-insensitive@ory.sh")) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) id.RecoveryAddresses = []identity.RecoveryAddress{{Via: identity.AddressTypeEmail, Value: "recovery.TestPersister.Update-case-insensitive-next@ory.sh"}} require.NoError(t, p.UpdateIdentity(ctx, id)) _, err = p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("recovery.TestPersister.Update-case-insensitive@ory.sh")) require.EqualError(t, err, sqlcon.ErrNoRows.Error()) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("recovery.TestPersister.Update-case-insensitive@ory.sh")) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) actual, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("recovery.TestPersister.Update-case-insensitive-next@ory.sh")) require.NoError(t, err) assert.Equal(t, identity.AddressTypeEmail, actual.Via) assert.Equal(t, "recovery.testpersister.update-case-insensitive-next@ory.sh", actual.Value) t.Run("can not find if on another network", func(t *testing.T) { _, p := testhelpers.NewNetwork(t, ctx, p) _, err := p.FindRecoveryAddressByValue(ctx, identity.AddressTypeEmail, strings.ToUpper("recovery.TestPersister.Update-case-insensitive-next@ory.sh")) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) }) t.Run("network reference isolation", func(t *testing.T) { nid1, p := testhelpers.NewNetwork(t, ctx, p) nid2, _ := testhelpers.NewNetwork(t, ctx, p) var m []identity.CredentialsTypeTable require.NoError(t, p.GetConnection(ctx).All(&m)) iid := x.NewUUID() require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at) VALUES (?, ?, 'default', '{}', ?, ?)", iid, nid1, time.Now(), time.Now()).Exec()) cid1, cid2 := x.NewUUID(), x.NewUUID() require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credentials (id, identity_id, nid, identity_credential_type_id, created_at, updated_at, config) VALUES (?, ?, ?, ?, ?, ?, '{}')", cid1, iid, nid1, m[0].ID, time.Now(), time.Now()).Exec()) require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credentials (id, identity_id, nid, identity_credential_type_id, created_at, updated_at, config) VALUES (?, ?, ?, ?, ?, ?, '{}')", cid2, iid, nid2, m[0].ID, time.Now(), time.Now()).Exec()) ici1, ici2 := x.NewUUID(), x.NewUUID() require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credential_identifiers (id, identity_id, identity_credential_id, nid, identifier, created_at, updated_at, identity_credential_type_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", ici1, iid, cid1, nid1, "nid1", time.Now(), time.Now(), m[0].ID).Exec()) require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credential_identifiers (id, identity_id, identity_credential_id, nid, identifier, created_at, updated_at, identity_credential_type_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", ici2, iid, cid2, nid2, "nid2", time.Now(), time.Now(), m[0].ID).Exec()) _, err := p.GetIdentity(ctx, nid1, identity.ExpandNothing) require.ErrorIs(t, err, sqlcon.ErrNoRows) _, err = p.GetIdentityConfidential(ctx, nid1) require.ErrorIs(t, err, sqlcon.ErrNoRows) i, c, err := p.FindByCredentialsIdentifier(ctx, m[0].Name, "nid1") require.NoError(t, err) assert.Equal(t, "nid1", c.Identifiers[0]) require.Len(t, i.Credentials, 1) _, _, err = p.FindByCredentialsIdentifier(ctx, m[0].Name, "nid2") require.ErrorIs(t, err, sqlcon.ErrNoRows) i, err = p.GetIdentityConfidential(ctx, iid) require.NoError(t, err) require.Len(t, i.Credentials, 1) assert.Equal(t, "nid1", i.Credentials[m[0].Name].Identifiers[0]) }) t.Run("suite=update-verifiable-addresses-edge-cases", func(t *testing.T) { t.Run("case=add new verifiable addresses", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) originalEmail := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" new1Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" new2Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" initial.VerifiableAddresses = []identity.VerifiableAddress{ {Value: originalEmail, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.VerifiableAddresses, 1) // Add two new addresses updated := fromDB.CopyWithoutCredentials() updated.VerifiableAddresses = append(updated.VerifiableAddresses, identity.VerifiableAddress{Value: new1Email, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, identity.VerifiableAddress{Value: new2Email, Via: identity.AddressTypeEmail, Verified: true, Status: identity.VerifiableAddressStatusCompleted}, ) require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.VerifiableAddresses, 3) values := []string{actual.VerifiableAddresses[0].Value, actual.VerifiableAddresses[1].Value, actual.VerifiableAddresses[2].Value} assertContainsValues(t, values, []string{originalEmail, new1Email, new2Email}, nil) // Verify the new verified address has verified_at set for _, addr := range actual.VerifiableAddresses { if addr.Value == new2Email { assert.True(t, addr.Verified) assert.NotNil(t, addr.VerifiedAt) } } }) t.Run("case=remove all verifiable addresses", func(t *testing.T) { email1 := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" email2 := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" initial := passwordIdentity("", x.NewUUID().String()) initial.VerifiableAddresses = []identity.VerifiableAddress{ {Value: email1, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, {Value: email2, Via: identity.AddressTypeEmail, Verified: true, Status: identity.VerifiableAddressStatusCompleted}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.VerifiableAddresses, 2) // Remove all addresses updated := fromDB.CopyWithoutCredentials() updated.VerifiableAddresses = []identity.VerifiableAddress{} require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) assert.Len(t, actual.VerifiableAddresses, 0) }) t.Run("case=remove some and add some verifiable addresses", func(t *testing.T) { keepEmail := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" removeEmail := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" addEmail := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" initial := passwordIdentity("", x.NewUUID().String()) initial.VerifiableAddresses = []identity.VerifiableAddress{ {Value: keepEmail, Via: identity.AddressTypeEmail, Verified: true, Status: identity.VerifiableAddressStatusCompleted}, {Value: removeEmail, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.VerifiableAddresses, 2) // Keep one, remove one, add one updated := fromDB.CopyWithoutCredentials() var keptAddress identity.VerifiableAddress for _, addr := range updated.VerifiableAddresses { if addr.Value == keepEmail { keptAddress = addr break } } updated.VerifiableAddresses = []identity.VerifiableAddress{ keptAddress, {Value: addEmail, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusSent}, } require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.VerifiableAddresses, 2) values := []string{actual.VerifiableAddresses[0].Value, actual.VerifiableAddresses[1].Value} assertContainsValues(t, values, []string{keepEmail, addEmail}, []string{removeEmail}) }) t.Run("case=update existing verifiable address properties", func(t *testing.T) { changeEmail := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" initial := passwordIdentity("", x.NewUUID().String()) initial.VerifiableAddresses = []identity.VerifiableAddress{ {Value: changeEmail, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) oldAddr := fromDB.VerifiableAddresses[0] assert.False(t, oldAddr.Verified) assert.Nil(t, oldAddr.VerifiedAt) // Change the address value - this should be treated as removal + addition updated := fromDB.CopyWithoutCredentials() updated.VerifiableAddresses = []identity.VerifiableAddress{ {Value: changeEmail, Via: identity.AddressTypeEmail, Verified: true, Status: identity.VerifiableAddressStatusCompleted}, } require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.VerifiableAddresses, 1) assert.Equal(t, changeEmail, actual.VerifiableAddresses[0].Value) assert.True(t, actual.VerifiableAddresses[0].Verified) assert.NotNil(t, actual.VerifiableAddresses[0].VerifiedAt) }) t.Run("case=replace all verifiable addresses at once", func(t *testing.T) { old1Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" old2Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" old3Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" initial := passwordIdentity("", x.NewUUID().String()) initial.VerifiableAddresses = []identity.VerifiableAddress{ {Value: old1Email, Via: identity.AddressTypeEmail, Verified: true, Status: identity.VerifiableAddressStatusCompleted}, {Value: old2Email, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, {Value: old3Email, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusSent}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.VerifiableAddresses, 3) new1Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" new2Email := "dev+" + uuid.Must(uuid.NewV4()).String() + "+@ory.com" // Replace all addresses with new ones updated := fromDB.CopyWithoutCredentials() updated.VerifiableAddresses = []identity.VerifiableAddress{ {Value: new1Email, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, {Value: new2Email, Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, } require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.VerifiableAddresses, 2) values := []string{actual.VerifiableAddresses[0].Value, actual.VerifiableAddresses[1].Value} assertContainsValues(t, values, []string{new1Email, new2Email}, []string{old1Email, old2Email, old3Email}) }) }) t.Run("suite=update-recovery-addresses-edge-cases", func(t *testing.T) { t.Run("case=add new recovery addresses", func(t *testing.T) { initialEmail := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" recovery1Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" recovery2Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" initial := passwordIdentity("", x.NewUUID().String()) initial.RecoveryAddresses = []identity.RecoveryAddress{ {Value: initialEmail, Via: identity.AddressTypeEmail}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.RecoveryAddresses, 1) // Add two new addresses updated := fromDB.CopyWithoutCredentials() updated.RecoveryAddresses = append(updated.RecoveryAddresses, identity.RecoveryAddress{Value: recovery1Email, Via: identity.AddressTypeEmail}, identity.RecoveryAddress{Value: recovery2Email, Via: identity.AddressTypeEmail}, ) require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.RecoveryAddresses, 3) values := []string{actual.RecoveryAddresses[0].Value, actual.RecoveryAddresses[1].Value, actual.RecoveryAddresses[2].Value} assertContainsValues(t, values, []string{initialEmail, recovery1Email, recovery2Email}, nil) }) t.Run("case=remove all recovery addresses", func(t *testing.T) { remove1Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" remove2Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" initial := passwordIdentity("", x.NewUUID().String()) initial.RecoveryAddresses = []identity.RecoveryAddress{ {Value: remove1Email, Via: identity.AddressTypeEmail}, {Value: remove2Email, Via: identity.AddressTypeEmail}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.RecoveryAddresses, 2) // Remove all addresses updated := fromDB.CopyWithoutCredentials() updated.RecoveryAddresses = []identity.RecoveryAddress{} require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) assert.Len(t, actual.RecoveryAddresses, 0) }) t.Run("case=remove some and add some recovery addresses", func(t *testing.T) { keepEmail := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" removeEmail := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" addEmail := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" initial := passwordIdentity("", x.NewUUID().String()) initial.RecoveryAddresses = []identity.RecoveryAddress{ {Value: keepEmail, Via: identity.AddressTypeEmail}, {Value: removeEmail, Via: identity.AddressTypeEmail}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.RecoveryAddresses, 2) // Keep one, remove one, add one updated := fromDB.CopyWithoutCredentials() var keptAddress identity.RecoveryAddress for _, addr := range updated.RecoveryAddresses { if addr.Value == keepEmail { keptAddress = addr break } } updated.RecoveryAddresses = []identity.RecoveryAddress{ keptAddress, {Value: addEmail, Via: identity.AddressTypeEmail}, } require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.RecoveryAddresses, 2) values := []string{actual.RecoveryAddresses[0].Value, actual.RecoveryAddresses[1].Value} assertContainsValues(t, values, []string{keepEmail, addEmail}, []string{removeEmail}) }) t.Run("case=replace all recovery addresses at once", func(t *testing.T) { old1Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" old2Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" old3Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" new1Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" new2Email := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" initial := passwordIdentity("", x.NewUUID().String()) initial.RecoveryAddresses = []identity.RecoveryAddress{ {Value: old1Email, Via: identity.AddressTypeEmail}, {Value: old2Email, Via: identity.AddressTypeEmail}, {Value: old3Email, Via: identity.AddressTypeEmail}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, fromDB.RecoveryAddresses, 3) // Replace all addresses with new ones updated := fromDB.CopyWithoutCredentials() updated.RecoveryAddresses = []identity.RecoveryAddress{ {Value: new1Email, Via: identity.AddressTypeEmail}, {Value: new2Email, Via: identity.AddressTypeEmail}, } require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) require.Len(t, actual.RecoveryAddresses, 2) values := []string{actual.RecoveryAddresses[0].Value, actual.RecoveryAddresses[1].Value} assertContainsValues(t, values, []string{new1Email, new2Email}, []string{old1Email, old2Email, old3Email}) }) }) t.Run("suite=update-credentials-edge-cases", func(t *testing.T) { t.Run("case=add new credential type", func(t *testing.T) { totpIdentifier := randx.MustString(16, randx.AlphaLowerNum) initial := passwordIdentity("", x.NewUUID().String()) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 1) _, hasPassword := fromDB.Credentials[identity.CredentialsTypePassword] assert.True(t, hasPassword) oldPasswordCredID := fromDB.Credentials[identity.CredentialsTypePassword].ID // Add TOTP credential initial.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{totpIdentifier}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }) require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, actual.Credentials, 2) _, hasPassword = actual.Credentials[identity.CredentialsTypePassword] _, hasTOTP := actual.Credentials[identity.CredentialsTypeTOTP] assert.True(t, hasPassword) assert.True(t, hasTOTP) assert.Equal(t, []string{totpIdentifier}, actual.Credentials[identity.CredentialsTypeTOTP].Identifiers) // Verify that the password credential was not recreated (ID should remain the same) assert.Equal(t, oldPasswordCredID, actual.Credentials[identity.CredentialsTypePassword].ID, "password credential should not be recreated when adding TOTP") }) t.Run("case=remove all credentials", func(t *testing.T) { oidcIdentifier := randx.MustString(16, randx.AlphaLowerNum) initial := passwordIdentity("", x.NewUUID().String()) initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{oidcIdentifier}, Config: sqlxx.JSONRawMessage(`{}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 2) // Remove all credentials initial.Credentials = map[identity.CredentialsType]identity.Credentials{} require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) assert.Len(t, actual.Credentials, 0) }) t.Run("case=remove one credential type and keep others", func(t *testing.T) { oidcIdentifier := randx.MustString(16, randx.AlphaLowerNum) totpIdentifier := randx.MustString(16, randx.AlphaLowerNum) initial := passwordIdentity("", x.NewUUID().String()) initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{oidcIdentifier}, Config: sqlxx.JSONRawMessage(`{}`), }) initial.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{totpIdentifier}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 3) oldOIDCCredID := fromDB.Credentials[identity.CredentialsTypeOIDC].ID oldTOTPCredID := fromDB.Credentials[identity.CredentialsTypeTOTP].ID // Remove password credential, keep OIDC and TOTP delete(initial.Credentials, identity.CredentialsTypePassword) require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, actual.Credentials, 2) _, hasPassword := actual.Credentials[identity.CredentialsTypePassword] _, hasOIDC := actual.Credentials[identity.CredentialsTypeOIDC] _, hasTOTP := actual.Credentials[identity.CredentialsTypeTOTP] assert.False(t, hasPassword) assert.True(t, hasOIDC) assert.True(t, hasTOTP) // Verify that OIDC and TOTP credentials were not recreated (IDs should remain the same) assert.Equal(t, oldOIDCCredID, actual.Credentials[identity.CredentialsTypeOIDC].ID, "OIDC credential should not be recreated when removing password") assert.Equal(t, oldTOTPCredID, actual.Credentials[identity.CredentialsTypeTOTP].ID, "TOTP credential should not be recreated when removing password") }) t.Run("case=update credential config and identifiers", func(t *testing.T) { oldEmail := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" newEmail := randx.MustString(16, randx.AlphaLowerNum) + "@ory.sh" initial := passwordIdentity("", oldEmail) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) oldCred := fromDB.Credentials[identity.CredentialsTypePassword] // Update password credential with new identifier and config initial.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{newEmail}, Config: sqlxx.JSONRawMessage(`{"new":"config"}`), }) require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) newCred := actual.Credentials[identity.CredentialsTypePassword] assert.NotEqual(t, oldCred.ID, newCred.ID) assert.Equal(t, []string{newEmail}, newCred.Identifiers) assert.JSONEq(t, `{"new":"config"}`, string(newCred.Config)) }) t.Run("case=replace all credentials at once", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"oidc-replace"}, Config: sqlxx.JSONRawMessage(`{}`), }) initial.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{"totp-replace"}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 3) // Replace all credentials with webauthn initial.Credentials = map[identity.CredentialsType]identity.Credentials{ identity.CredentialsTypeWebAuthn: { Type: identity.CredentialsTypeWebAuthn, Identifiers: []string{"webauthn-new"}, Config: sqlxx.JSONRawMessage(`{"credentials":[]}`), }, } require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, actual.Credentials, 1) _, hasWebAuthn := actual.Credentials[identity.CredentialsTypeWebAuthn] assert.True(t, hasWebAuthn) assert.Equal(t, []string{"webauthn-new"}, actual.Credentials[identity.CredentialsTypeWebAuthn].Identifiers) }) t.Run("case=update with no changes", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"oidc-no-change"}, Config: sqlxx.JSONRawMessage(`{}`), }) initial.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{"totp-no-change"}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 3) oldPasswordCredID := fromDB.Credentials[identity.CredentialsTypePassword].ID oldOIDCCredID := fromDB.Credentials[identity.CredentialsTypeOIDC].ID oldTOTPCredID := fromDB.Credentials[identity.CredentialsTypeTOTP].ID // Update without changing anything require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) // Verify no credentials were added or removed require.Len(t, actual.Credentials, 3, "credential count should not change when nothing is modified") // Verify all credential IDs remained the same (nothing was recreated) assert.Equal(t, oldPasswordCredID, actual.Credentials[identity.CredentialsTypePassword].ID, "password credential should not be recreated when nothing changes") assert.Equal(t, oldOIDCCredID, actual.Credentials[identity.CredentialsTypeOIDC].ID, "OIDC credential should not be recreated when nothing changes") assert.Equal(t, oldTOTPCredID, actual.Credentials[identity.CredentialsTypeTOTP].ID, "TOTP credential should not be recreated when nothing changes") }) t.Run("case=update with json whitespace differences", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) // Create with compact JSON initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"oidc-whitespace"}, Config: sqlxx.JSONRawMessage(`{"foo":"bar","baz":"qux"}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 2) oldPasswordCredID := fromDB.Credentials[identity.CredentialsTypePassword].ID oldOIDCCredID := fromDB.Credentials[identity.CredentialsTypeOIDC].ID // Update with same JSON but different whitespace formatting initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"oidc-whitespace"}, // Same JSON content but with different whitespace Config: sqlxx.JSONRawMessage(`{ "foo": "bar", "baz": "qux" }`), }) require.NoError(t, p.UpdateIdentity(ctx, initial, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) // Verify no credentials were added or removed require.Len(t, actual.Credentials, 2, "credential count should not change") // Verify credential IDs remained the same (nothing was recreated despite JSON formatting difference) assert.Equal(t, oldPasswordCredID, actual.Credentials[identity.CredentialsTypePassword].ID, "password credential should not be recreated") assert.Equal(t, oldOIDCCredID, actual.Credentials[identity.CredentialsTypeOIDC].ID, "OIDC credential should not be recreated when JSON has different whitespace") }) t.Run("case=update traits with fromDatabase parameter", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) initial.Traits = identity.Traits(`{"email":"initial@ory.sh","name":"Initial Name"}`) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) // Update traits using DiffAgainst updated := fromDB.CopyWithoutCredentials() updated.Traits = identity.Traits(`{"email":"updated@ory.sh","name":"Updated Name"}`) require.NoError(t, p.UpdateIdentity(ctx, updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentity(ctx, initial.ID, identity.ExpandDefault) require.NoError(t, err) assert.JSONEq(t, `{"email":"updated@ory.sh","name":"Updated Name"}`, string(actual.Traits)) }) t.Run("case=update without fromDatabase parameter", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) initial.SetCredentials(identity.CredentialsTypeOIDC, identity.Credentials{ Type: identity.CredentialsTypeOIDC, Identifiers: []string{"oidc-no-from-db"}, Config: sqlxx.JSONRawMessage(`{}`), }) require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, fromDB.Credentials, 2) oldPasswordCredID := fromDB.Credentials[identity.CredentialsTypePassword].ID oldOIDCCredID := fromDB.Credentials[identity.CredentialsTypeOIDC].ID // Update without providing fromDatabase - should fetch from DB internally updated := *fromDB updated.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{"totp-no-from-db"}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }) require.NoError(t, p.UpdateIdentity(ctx, &updated)) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, actual.Credentials, 3) _, hasTOTP := actual.Credentials[identity.CredentialsTypeTOTP] assert.True(t, hasTOTP) // Verify that password and OIDC credentials were not recreated (IDs should remain the same) assert.Equal(t, oldPasswordCredID, actual.Credentials[identity.CredentialsTypePassword].ID, "password credential should not be recreated when adding TOTP without fromDatabase") assert.Equal(t, oldOIDCCredID, actual.Credentials[identity.CredentialsTypeOIDC].ID, "OIDC credential should not be recreated when adding TOTP without fromDatabase") }) }) t.Run("suite=update-combined-changes", func(t *testing.T) { t.Run("case=update addresses and credentials simultaneously", func(t *testing.T) { initial := passwordIdentity("", x.NewUUID().String()) initial.VerifiableAddresses = []identity.VerifiableAddress{ {Value: "combined-verify@ory.sh", Via: identity.AddressTypeEmail, Verified: false, Status: identity.VerifiableAddressStatusPending}, } initial.RecoveryAddresses = []identity.RecoveryAddress{ {Value: "combined-recovery@ory.sh", Via: identity.AddressTypeEmail}, } require.NoError(t, p.CreateIdentity(ctx, initial)) createdIDs = append(createdIDs, initial.ID) fromDB, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) // Change everything at once updated := *fromDB updated.VerifiableAddresses = []identity.VerifiableAddress{ {Value: "combined-verify-new@ory.sh", Via: identity.AddressTypeEmail, Verified: true, Status: identity.VerifiableAddressStatusCompleted}, } updated.RecoveryAddresses = []identity.RecoveryAddress{ {Value: "combined-recovery-new@ory.sh", Via: identity.AddressTypeEmail}, } updated.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{ Type: identity.CredentialsTypeTOTP, Identifiers: []string{"combined-totp"}, Config: sqlxx.JSONRawMessage(`{"totp_url":"otpauth://totp/test"}`), }) require.NoError(t, p.UpdateIdentity(ctx, &updated, identity.DiffAgainst(fromDB))) actual, err := p.GetIdentityConfidential(ctx, initial.ID) require.NoError(t, err) require.Len(t, actual.VerifiableAddresses, 1) require.Len(t, actual.RecoveryAddresses, 1) require.Len(t, actual.Credentials, 2) assert.Equal(t, "combined-verify-new@ory.sh", actual.VerifiableAddresses[0].Value) assert.Equal(t, "combined-recovery-new@ory.sh", actual.RecoveryAddresses[0].Value) _, hasTOTP := actual.Credentials[identity.CredentialsTypeTOTP] assert.True(t, hasTOTP) }) }) } } func NewTestIdentity(numAddresses int, prefix string, i int) *identity.Identity { var ( verifiableAddresses []identity.VerifiableAddress recoveryAddresses []identity.RecoveryAddress ) traits := struct { Emails []string `json:"emails"` Username string `json:"username"` }{} verificationStates := []identity.VerifiableAddressStatus{ identity.VerifiableAddressStatusPending, identity.VerifiableAddressStatusSent, identity.VerifiableAddressStatusCompleted, } for j := 0; j < numAddresses; j++ { email := fmt.Sprintf("%s-%d-%d@ory.sh", prefix, i, j) traits.Emails = append(traits.Emails, email) verifiableAddresses = append(verifiableAddresses, identity.VerifiableAddress{ Value: email, Via: identity.AddressTypeEmail, Verified: j%2 == 0, Status: verificationStates[j%len(verificationStates)], }) recoveryAddresses = append(recoveryAddresses, identity.RecoveryAddress{ Value: email, Via: identity.AddressTypeEmail, }) } traits.Username = traits.Emails[0] rawTraits, _ := json.Marshal(traits) id := &identity.Identity{ SchemaID: "multiple_emails", Traits: rawTraits, VerifiableAddresses: verifiableAddresses, RecoveryAddresses: recoveryAddresses, State: "active", } id.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ Type: identity.CredentialsTypePassword, Identifiers: []string{traits.Username}, Config: sqlxx.JSONRawMessage(`{}`), }) return id } ================================================ FILE: identity/validator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "context" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/tidwall/sjson" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/schema" "github.com/ory/x/otelx" ) type ( validatorDependencies interface { schema.IdentitySchemaProvider config.Provider } Validator struct { v *schema.Validator d validatorDependencies } ValidationProvider interface { IdentityValidator() *Validator } ) func NewValidator(d validatorDependencies) *Validator { return &Validator{v: schema.NewValidator(), d: d} } func (v *Validator) ValidateWithRunner(ctx context.Context, i *Identity, runners ...schema.ValidateExtension) error { runner, err := schema.NewExtensionRunner(ctx, schema.WithValidateRunners(runners...)) if err != nil { return err } ss, err := v.d.IdentityTraitsSchemas(ctx) if err != nil { return err } s, err := ss.GetByID(i.SchemaID) if err != nil { return err } if len(i.Traits) == 0 { i.Traits = []byte(`{}`) } traits, err := sjson.SetRawBytes([]byte(`{}`), "traits", i.Traits) if err != nil { return errors.WithStack(herodot.ErrBadRequest.WithError(err.Error())) } return v.v.Validate(ctx, s.URL.String(), traits, schema.WithExtensionRunner(runner)) } func (v *Validator) Validate(ctx context.Context, i *Identity) error { return otelx.WithSpan(ctx, "identity.Validator.Validate", func(ctx context.Context) error { return v.ValidateWithRunner(ctx, i, NewSchemaExtensionCredentials(i), NewSchemaExtensionVerification(i, v.d.Config().SelfServiceFlowVerificationRequestLifespan(ctx)), NewSchemaExtensionRecovery(i), ) }) } ================================================ FILE: identity/validator_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity_test import ( "context" "encoding/base64" "fmt" "net/http" "net/http/httptest" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/herodot" "github.com/ory/jsonschema/v3/httploader" "github.com/ory/kratos/driver/config" . "github.com/ory/kratos/identity" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/x/configx" "github.com/ory/x/httpx" ) func TestSchemaValidatorDisallowsInternalNetworkRequests(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(testhelpers.IdentitySchemasConfig(map[string]string{ "localhost": "https://localhost/schema/whatever", "privateRef": "file://stub/localhost-ref.schema.json", "inlineRef": "base64://" + base64.StdEncoding.EncodeToString([]byte(`{"traits": {}}`)), })), configx.WithValue(config.ViperKeyClientHTTPNoPrivateIPRanges, true), ) v := NewValidator(reg) for id, expectedErr := range map[string]string{ "localhost": "is not a permitted destination", "privateRef": "is not a permitted destination", "inlineRef": "", } { t.Run(id, func(t *testing.T) { t.Parallel() i := &Identity{ SchemaID: id, Traits: Traits(`{ "firstName": "first-name", "lastName": "last-name", "age": 1 }`), } ctx := context.WithValue(t.Context(), httploader.ContextKey, reg.HTTPClient(t.Context())) err := v.Validate(ctx, i) if expectedErr == "" { assert.NoError(t, err) return } var hErr *herodot.DefaultError require.ErrorAs(t, err, &hErr) assert.Contains(t, hErr.Debug(), expectedErr) }) } } func TestSchemaValidator(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) defer ctrl.Finish() router := http.NewServeMux() router.HandleFunc("GET /schema/{name}", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{ "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "` + r.PathValue("name") + `": { "type": "string", "description": "The person's first name." }, "lastName": { "type": "string", "description": "The person's last name." }, "age": { "description": "Age in years which must be equal to or greater than zero.", "type": "integer", "minimum": 1 } }, "additionalProperties": false } }, "additionalProperties": false }`)) }) ts := httptest.NewServer(router) defer ts.Close() _, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(testhelpers.IdentitySchemasConfig(map[string]string{ "default": ts.URL + "/schema/firstName", "whatever": ts.URL + "/schema/whatever", "unreachable-url": ts.URL + "/404-not-found", })), ) v := NewValidator(reg) for k, tc := range []struct { i *Identity err string }{ { i: &Identity{ Traits: Traits(`{ "firstName": "first-name", "lastName": "last-name", "age": 1 }`), }, }, { i: &Identity{ Traits: Traits(`{ "firstName": "first-name", "lastName": "last-name", "age": -1 }`), }, err: "I[#/traits/age] S[#/properties/traits/properties/age/minimum] must be >= 1 but found -1", }, { i: &Identity{ Traits: Traits(`{ "whatever": "first-name", "lastName": "last-name", "age": 1 }`), }, err: `I[#/traits] S[#/properties/traits/additionalProperties] additionalProperties "whatever" not allowed`, }, { i: &Identity{ SchemaID: "whatever", Traits: Traits(`{ "whatever": "first-name", "lastName": "last-name", "age": 1 }`), }, }, { i: &Identity{ SchemaID: "whatever", Traits: Traits(`{ "firstName": "first-name", "lastName": "last-name", "age": 1 }`), }, err: `I[#/traits] S[#/properties/traits/additionalProperties] additionalProperties "firstName" not allowed`, }, { i: &Identity{ SchemaID: "unreachable-url", Traits: Traits(`{ "firstName": "first-name", "lastName": "last-name", "age": 1 }`), }, err: "Invalid configuration", }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { ctx := context.WithValue(t.Context(), httploader.ContextKey, httpx.NewResilientClient()) err := v.Validate(ctx, tc.i) if tc.err == "" { require.NoError(t, err) } else { require.EqualError(t, err, tc.err) } }) } } ================================================ FILE: install.sh ================================================ #!/bin/sh set -e # Code generated by godownloader on 2020-07-08T20:40:41Z. DO NOT EDIT. # usage() { this=$1 cat </dev/null } echoerr() { echo "$@" 1>&2 } log_prefix() { echo "$0" } _logp=6 log_set_priority() { _logp="$1" } log_priority() { if test -z "$1"; then echo "$_logp" return fi [ "$1" -le "$_logp" ] } log_tag() { case $1 in 0) echo "emerg" ;; 1) echo "alert" ;; 2) echo "crit" ;; 3) echo "err" ;; 4) echo "warning" ;; 5) echo "notice" ;; 6) echo "info" ;; 7) echo "debug" ;; *) echo "$1" ;; esac } log_debug() { log_priority 7 || return 0 echoerr "$(log_prefix)" "$(log_tag 7)" "$@" } log_info() { log_priority 6 || return 0 echoerr "$(log_prefix)" "$(log_tag 6)" "$@" } log_err() { log_priority 3 || return 0 echoerr "$(log_prefix)" "$(log_tag 3)" "$@" } log_crit() { log_priority 2 || return 0 echoerr "$(log_prefix)" "$(log_tag 2)" "$@" } uname_os() { os=$(uname -s | tr '[:upper:]' '[:lower:]') case "$os" in cygwin_nt*) os="windows" ;; mingw*) os="windows" ;; msys_nt*) os="windows" ;; esac echo "$os" } uname_arch() { arch=$(uname -m) case $arch in x86_64) arch="amd64" ;; x86) arch="386" ;; i686) arch="386" ;; i386) arch="386" ;; aarch64) arch="arm64" ;; armv5*) arch="armv5" ;; armv6*) arch="armv6" ;; armv7*) arch="armv7" ;; esac echo ${arch} } uname_os_check() { os=$(uname_os) case "$os" in darwin) return 0 ;; dragonfly) return 0 ;; freebsd) return 0 ;; linux) return 0 ;; android) return 0 ;; nacl) return 0 ;; netbsd) return 0 ;; openbsd) return 0 ;; plan9) return 0 ;; solaris) return 0 ;; windows) return 0 ;; esac log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" return 1 } uname_arch_check() { arch=$(uname_arch) case "$arch" in 386) return 0 ;; amd64) return 0 ;; arm64) return 0 ;; armv5) return 0 ;; armv6) return 0 ;; armv7) return 0 ;; ppc64) return 0 ;; ppc64le) return 0 ;; mips) return 0 ;; mipsle) return 0 ;; mips64) return 0 ;; mips64le) return 0 ;; s390x) return 0 ;; amd64p32) return 0 ;; esac log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" return 1 } untar() { tarball=$1 case "${tarball}" in *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; *.tar) tar --no-same-owner -xf "${tarball}" ;; *.zip) unzip "${tarball}" ;; *) log_err "untar unknown archive format for ${tarball}" return 1 ;; esac } http_download_curl() { local_file=$1 source_url=$2 header=$3 if [ -z "$header" ]; then code=$(curl --retry 7 --retry-connrefused -w '%{http_code}' -sL -o "$local_file" "$source_url") else code=$(curl --retry 7 --retry-connrefused -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") fi if [ "$code" != "200" ]; then log_debug "http_download_curl received HTTP status $code" return 1 fi return 0 } http_download_wget() { local_file=$1 source_url=$2 header=$3 if [ -z "$header" ]; then wget -q -O "$local_file" "$source_url" else wget -q --header "$header" -O "$local_file" "$source_url" fi } http_download() { log_debug "http_download $2" if is_command curl; then http_download_curl "$@" return elif is_command wget; then http_download_wget "$@" return fi log_crit "http_download unable to find wget or curl" return 1 } http_copy() { tmp=$(mktemp) http_download "${tmp}" "$1" "$2" || return 1 body=$(cat "$tmp") rm -f "${tmp}" echo "$body" } github_release() { owner_repo=$1 version=$2 test -z "$version" && version="latest" giturl="https://github.com/${owner_repo}/releases/${version}" json=$(http_copy "$giturl" "Accept:application/json") test -z "$json" && return 1 version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') test -z "$version" && return 1 echo "$version" } hash_sha256() { TARGET=${1:-/dev/stdin} if is_command gsha256sum; then hash=$(gsha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command sha256sum; then hash=$(sha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command shasum; then hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command openssl; then hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f a else log_crit "hash_sha256 unable to find command to compute sha-256 hash" return 1 fi } hash_sha256_verify() { TARGET=$1 checksums=$2 if [ -z "$checksums" ]; then log_err "hash_sha256_verify checksum file not specified in arg2" return 1 fi BASENAME=${TARGET##*/} want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) if [ -z "$want" ]; then log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" return 1 fi got=$(hash_sha256 "$TARGET") if [ "$want" != "$got" ]; then log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" return 1 fi } cat /dev/null < `, basename, cmd.CommandPath(), cmd.CommandPath(), )); err != nil { return err } var b bytes.Buffer if err := GenMarkdownCustom(cmd, &b, trimExt); err != nil { return err } _, err = f.WriteString(b.String()) return err } ================================================ FILE: oryx/clidoc/md_docs.go ================================================ //Copyright 2015 Red Hat Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package clidoc import ( "bytes" "fmt" "html" "io" "os" "path/filepath" "regexp" "sort" "strings" "github.com/ory/x/cmdx" "github.com/spf13/cobra" ) func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) if flags.HasAvailableFlags() { buf.WriteString("### Options\n\n```\n") flags.PrintDefaults() buf.WriteString("```\n\n") } parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(buf) if parentFlags.HasAvailableFlags() { buf.WriteString("### Options inherited from parent commands\n\n```\n") parentFlags.PrintDefaults() buf.WriteString("```\n\n") } return nil } // GenMarkdown creates markdown output. func GenMarkdown(cmd *cobra.Command, w io.Writer) error { return GenMarkdownCustom(cmd, w, func(s string) string { return s }) } // GenMarkdownCustom creates custom markdown output. func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error { cmd.InitDefaultHelpCmd() cmd.InitDefaultHelpFlag() buf := new(bytes.Buffer) name := cmd.CommandPath() buf.WriteString("## " + html.EscapeString(name) + "\n\n") buf.WriteString(fenceIndentedBlocks(cmd.Short) + "\n\n") if len(cmd.Long) > 0 { buf.WriteString("### Synopsis\n\n") long, err := cmdx.TemplateCommandField(cmd, cmd.Long) if err != nil { buf.WriteString(fmt.Sprintf("\n\n", err.Error())) long = cmd.Long } buf.WriteString(fenceIndentedBlocks(long) + "\n\n") } if cmd.Runnable() { buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) } if len(cmd.Example) > 0 { buf.WriteString("### Examples\n\n") example, err := cmdx.TemplateCommandField(cmd, cmd.Example) if err != nil { buf.WriteString(fmt.Sprintf("\n\n", err.Error())) example = cmd.Example } buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", example)) } if err := printOptions(buf, cmd, name); err != nil { return err } if hasSeeAlso(cmd) { buf.WriteString("### See also\n\n") if cmd.HasParent() { parent := cmd.Parent() pname := parent.CommandPath() link := pname + ".md" link = strings.Replace(link, " ", "_", -1) short := parent.Short if short != "" { short = fmt.Sprintf(" %s", short) } buf.WriteString(fmt.Sprintf("* [%s](%s)%s\n", pname, linkHandler(link), short)) cmd.VisitParents(func(c *cobra.Command) { if c.DisableAutoGenTag { cmd.DisableAutoGenTag = c.DisableAutoGenTag } }) } children := cmd.Commands() sort.Sort(byName(children)) for _, child := range children { if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() { continue } cname := name + " " + child.Name() link := cname + ".md" link = strings.Replace(link, " ", "_", -1) short := child.Short if short != "" { short = fmt.Sprintf(" - %s", short) } buf.WriteString(fmt.Sprintf("* [%s](%s)\t%s\n", cname, linkHandler(link), short)) } buf.WriteString("\n") } _, err := buf.WriteTo(w) return err } // GenMarkdownTree will generate a markdown page for this command and all // descendants in the directory given. The header may be nil. // This function may not work correctly if your command names have `-` in them. // If you have `cmd` with two subcmds, `sub` and `sub-third`, // and `sub` has a subcommand called `third`, it is undefined which // help output will be in the file `cmd-sub-third.1`. func GenMarkdownTree(cmd *cobra.Command, dir string) error { identity := func(s string) string { return s } emptyStr := func(s string) string { return "" } return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) } // GenMarkdownTreeCustom is the the same as GenMarkdownTree, but // with custom filePrepender and linkHandler. func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { continue } if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil { return err } } basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md" filename := filepath.Join(dir, basename) f, err := os.Create(filename) //#nosec:G304) //#nosec:G304 if err != nil { return err } defer (func() { _ = f.Close() })() if _, err := io.WriteString(f, filePrepender(filename)); err != nil { return err } if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil { return err } return nil } var indentedBlock = regexp.MustCompile(`(?m)^(?: {4}|\t).*(?:\n(?: {4}|\t).*)*`) func fenceIndentedBlocks(s string) string { return indentedBlock.ReplaceAllStringFunc(s, func(block string) string { // trim trailing newline to keep fence tidy block = strings.TrimRight(block, "\n") // de-indent exactly one level for nicer fenced output lines := strings.Split(block, "\n") for i, ln := range lines { switch { case strings.HasPrefix(ln, " "): lines[i] = ln[4:] case strings.HasPrefix(ln, "\t"): lines[i] = ln[1:] } } b := strings.Join(lines, "\n") // guard against already fenced content if strings.HasPrefix(strings.TrimSpace(b), "```") { return block } return "```\n" + b + "\n```" }) } ================================================ FILE: oryx/clidoc/testdata/hydra-client-admin.md ================================================ --- id: hydra-client-admin title: hydra client admin description: hydra client admin --- ## hydra client admin Foo bar baz bar ``` short with multiple ``` ### Synopsis Run the admin server ``` <[some argument]> <[some argument]> <[some argument]> <[some argument]> <[some argument]> ``` ``` hydra client admin [flags] ``` ### Options ``` -h, --help help for admin ``` ### See also * [hydra client](hydra-client) Run client commands ================================================ FILE: oryx/clidoc/testdata/hydra-client-public.md ================================================ --- id: hydra-client-public title: hydra client public description: hydra client public --- ## hydra client public ### Synopsis Run the public server <[some argument]> ``` hydra client public [flags] ``` ### Options ``` -h, --help help for public ``` ### See also * [hydra client](hydra-client) Run client commands ================================================ FILE: oryx/clidoc/testdata/hydra-client.md ================================================ --- id: hydra-client title: hydra client description: hydra client --- ## hydra client Run client commands ### Synopsis Manage OAuth2 clients <[some argument]> ``` hydra client [flags] ``` ### Examples ``` hydra client --whatever ``` ### Options ``` -h, --help help for client ``` ### See also * [hydra](hydra) * [hydra client admin](hydra-client-admin) - Foo bar baz bar short with multiple * [hydra client public](hydra-client-public) ================================================ FILE: oryx/clidoc/testdata/hydra-serve.md ================================================ --- id: hydra-serve title: hydra serve description: hydra serve --- ## hydra serve ### Synopsis Manage the server <[some argument]> ``` hydra serve [flags] ``` ### Options ``` -h, --help help for serve ``` ### See also * [hydra](hydra) ================================================ FILE: oryx/clidoc/testdata/hydra.md ================================================ --- id: hydra title: hydra description: hydra --- ## hydra ### Synopsis A sample text root <[some argument]> ``` hydra [flags] ``` ### Options ``` -h, --help help for hydra ``` ### See also * [hydra client](hydra-client) - Run client commands * [hydra serve](hydra-serve) ================================================ FILE: oryx/clidoc/util.go ================================================ // Copyright 2015 Red Hat Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package clidoc import ( "github.com/spf13/cobra" ) // Test to see if we have a reason to print See Also information in docs // Basically this is a test for a parent command or a subcommand which is // both not deprecated and not the autogenerated help command. func hasSeeAlso(cmd *cobra.Command) bool { if cmd.HasParent() { return true } for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { continue } return true } return false } type byName []*cobra.Command func (s byName) Len() int { return len(s) } func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } ================================================ FILE: oryx/cmdx/args.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "fmt" "github.com/spf13/cobra" ) // MinArgs fatals if args does not satisfy min. // Deprecated: set cobra.MinimumNArgs on the cmd.Args field instead func MinArgs(cmd *cobra.Command, args []string, min int) { if len(args) < min { Fatalf(`%s Expected at least %d command line arguments but only got %d.`, cmd.UsageString(), min, len(args)) } } // ExactArgs fatals if args does not equal l. // Deprecated: set cobra.ExactArgs on the cmd.Args field instead func ExactArgs(cmd *cobra.Command, args []string, l int) { if len(args) < l { Fatalf(`%s Expected exactly %d command line arguments but got %d.`, cmd.UsageString(), l, len(args)) } } // RangeArgs fatals if args does not satisfy any of the lengths set in r. // Deprecated: set cobra.Ar on the cmd.RangeArgs field instead func RangeArgs(cmd *cobra.Command, args []string, r []int) { for _, a := range r { if len(args) == a { return } } Fatalf(`%s Expected exact %v command line arguments but got %d.`, cmd.UsageString(), r, len(args)) } // ZeroOrTwoArgs requires either no or 2 arguments. func ZeroOrTwoArgs(cmd *cobra.Command, args []string) error { // zero or exactly two args if len(args) != 0 && len(args) != 2 { return fmt.Errorf("expected zero or two args, got %d: %+v", len(args), args) } return nil } ================================================ FILE: oryx/cmdx/env.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx // EnvVarExamplesHelpMessage returns a string containing documentation on how to use environment variables. func EnvVarExamplesHelpMessage(name string) string { return `This command exposes a variety of controls via environment variables. Here are some examples on how to configure environment variables: Linux / macOS: $ export FOO=bar $ export BAZ=bar $ ` + name + ` ... $ FOO=bar BAZ=bar ` + name + ` ... Docker: $ docker run -e FOO=bar -e BAZ=bar ... Windows (cmd): > set FOO=bar > set BAZ=bar > ` + name + ` ... Windows (powershell): > $env:FOO = "bar" > $env:BAZ = "bar" > ` + name + ` ` } ================================================ FILE: oryx/cmdx/helper.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "testing" "golang.org/x/sync/errgroup" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/stretchr/testify/require" "github.com/pkg/errors" "github.com/ory/x/logrusx" ) var ( // ErrNilDependency is returned if a dependency is missing. ErrNilDependency = fmt.Errorf("a dependency was expected to be defined but is nil. Please open an issue with the stack trace") // ErrNoPrintButFail is returned to detect a failure state that was already reported to the user in some way ErrNoPrintButFail = fmt.Errorf("this error should never be printed") debugStdout, debugStderr = io.Discard, io.Discard ) func init() { if os.Getenv("DEBUG") != "" { debugStdout = os.Stdout debugStderr = os.Stderr } } // FailSilently is supposed to be used within a commands RunE function. // It silences cobras error handling and returns the ErrNoPrintButFail error. func FailSilently(cmd *cobra.Command) error { cmd.SilenceErrors = true cmd.SilenceUsage = true return errors.WithStack(ErrNoPrintButFail) } // Must fatals with the optional message if err is not nil. // Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error. func Must(err error, message string, args ...interface{}) { if err == nil { return } _, _ = fmt.Fprintf(os.Stderr, message+"\n", args...) os.Exit(1) } // CheckResponse fatals if err is nil or the response.StatusCode does not match the expectedStatusCode // Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error. func CheckResponse(err error, expectedStatusCode int, response *http.Response) { Must(err, "Command failed because error occurred: %s", err) if response.StatusCode != expectedStatusCode { out, err := io.ReadAll(response.Body) if err != nil { out = []byte{} } pretty, err := json.MarshalIndent(json.RawMessage(out), "", "\t") if err == nil { out = pretty } Fatalf( `Command failed because status code %d was expected but code %d was received. Response payload: %s`, expectedStatusCode, response.StatusCode, out, ) } } // FormatResponse takes an object and prints a json.MarshalIdent version of it or fatals. // Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error. func FormatResponse(o interface{}) string { out, err := json.MarshalIndent(o, "", "\t") Must(err, `Command failed because an error occurred while prettifying output: %s`, err) return string(out) } // Fatalf prints to os.Stderr and exists with code 1. // Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error. func Fatalf(message string, args ...interface{}) { if len(args) > 0 { _, _ = fmt.Fprintf(os.Stderr, message+"\n", args...) } else { _, _ = fmt.Fprintln(os.Stderr, message) } os.Exit(1) } // ExpectDependency expects every dependency to be not nil or it fatals. // Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error. func ExpectDependency(logger *logrusx.Logger, dependencies ...interface{}) { if logger == nil { panic("missing logger for dependency check") } for _, d := range dependencies { if d == nil { logger.WithError(errors.WithStack(ErrNilDependency)).Fatalf("A fatal issue occurred.") } } } // CallbackWriter will execute each callback once the message is received. // The full matched message is passed to the callback. An error returned from the callback is returned by Write. type CallbackWriter struct { Callbacks map[string]func([]byte) error buf bytes.Buffer } func (c *CallbackWriter) Write(msg []byte) (int, error) { for p, cb := range c.Callbacks { if bytes.Contains(msg, []byte(p)) { if err := cb(msg); err != nil { return 0, err } } } return c.buf.Write(msg) } func (c *CallbackWriter) String() string { return c.buf.String() } var _ io.Writer = (*CallbackWriter)(nil) func prepareCmd(cmd *cobra.Command, stdIn io.Reader, stdOut, stdErr io.Writer, args []string) { cmd.SetIn(stdIn) outs := []io.Writer{debugStdout} if stdOut != nil { outs = append(outs, stdOut) } cmd.SetOut(io.MultiWriter(outs...)) errs := []io.Writer{debugStderr} if stdErr != nil { errs = append(errs, stdErr) } cmd.SetErr(io.MultiWriter(errs...)) if args == nil { args = []string{} } cmd.SetArgs(args) } // ExecBackgroundCtx runs the cobra command in the background. func ExecBackgroundCtx(ctx context.Context, cmd *cobra.Command, stdIn io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group { prepareCmd(cmd, stdIn, stdOut, stdErr, args) eg := &errgroup.Group{} eg.Go(func() error { defer cmd.SetIn(nil) return cmd.ExecuteContext(ctx) }) return eg } // Exec runs the provided cobra command with the given reader as STD_IN and the given args. // Returns STD_OUT, STD_ERR and the error from the execution. func Exec(t testing.TB, cmd *cobra.Command, stdIn io.Reader, args ...string) (string, string, error) { return ExecCtx(t.Context(), cmd, stdIn, args...) } func ExecCtx(ctx context.Context, cmd *cobra.Command, stdIn io.Reader, args ...string) (string, string, error) { stdOut, stdErr := &bytes.Buffer{}, &bytes.Buffer{} prepareCmd(cmd, stdIn, stdOut, stdErr, args) // needs to be on a separate line to ensure that the output buffers are read AFTER the command ran err := cmd.ExecuteContext(ctx) return stdOut.String(), stdErr.String(), err } // ExecNoErr is a helper that assumes a successful run from Exec. // Returns STD_OUT. func ExecNoErr(t testing.TB, cmd *cobra.Command, args ...string) string { return ExecNoErrCtx(t.Context(), t, cmd, args...) } func ExecNoErrCtx(ctx context.Context, t require.TestingT, cmd *cobra.Command, args ...string) string { stdOut, stdErr, err := ExecCtx(ctx, cmd, nil, args...) if err == nil { require.Len(t, stdErr, 0, "std_out: %s\nstd_err: %s", stdOut, stdErr) } else { require.ErrorIsf(t, err, context.Canceled, "std_out: %s\nstd_err: %s", stdOut, stdErr) } return stdOut } // ExecExpectedErr is a helper that assumes a failing run from Exec returning ErrNoPrintButFail // Returns STD_ERR. func ExecExpectedErr(t testing.TB, cmd *cobra.Command, args ...string) string { return ExecExpectedErrCtx(t.Context(), t, cmd, args...) } func ExecExpectedErrCtx(ctx context.Context, t require.TestingT, cmd *cobra.Command, args ...string) string { stdOut, stdErr, err := ExecCtx(ctx, cmd, nil, args...) require.True(t, errors.Is(err, ErrNoPrintButFail), "std_out: %s\nstd_err: %s", stdOut, stdErr) require.Len(t, stdOut, 0, stdErr) return stdErr } type CommandExecuter struct { New func() *cobra.Command Ctx context.Context PersistentArgs []string } func (c *CommandExecuter) Exec(stdin io.Reader, args ...string) (string, string, error) { return ExecCtx(c.Ctx, c.New(), stdin, append(c.PersistentArgs, args...)...) } func (c *CommandExecuter) ExecBackground(stdin io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group { return ExecBackgroundCtx(c.Ctx, c.New(), stdin, stdOut, stdErr, append(c.PersistentArgs, args...)...) } func (c *CommandExecuter) ExecNoErr(t require.TestingT, args ...string) string { return ExecNoErrCtx(c.Ctx, t, c.New(), append(c.PersistentArgs, args...)...) } func (c *CommandExecuter) ExecExpectedErr(t require.TestingT, args ...string) string { return ExecExpectedErrCtx(c.Ctx, t, c.New(), append(c.PersistentArgs, args...)...) } type URL struct { url.URL } var _ pflag.Value = (*URL)(nil) func (u *URL) Set(s string) error { uu, err := url.Parse(s) if err != nil { return err } u.URL = *uu return nil } func (*URL) Type() string { return "url" } ================================================ FILE: oryx/cmdx/http.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "crypto/tls" "fmt" "net/http" "net/url" "os" "strings" "time" "github.com/hashicorp/go-retryablehttp" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/ory/x/httpx" ) const ( envKeyEndpoint = "ORY_SDK_URL" FlagEndpoint = "endpoint" FlagSkipTLSVerify = "skip-tls-verify" FlagHeaders = "http-header" ) // Remote returns the remote endpoint for the given command. func Remote(cmd *cobra.Command) (string, error) { endpoint, err := cmd.Flags().GetString(FlagEndpoint) if err != nil { return "", errors.WithStack(err) } if endpoint != "" { return strings.TrimRight(endpoint, "/"), nil } else if endpoint := os.Getenv("ORY_SDK_URL"); endpoint != "" { return strings.TrimRight(endpoint, "/"), nil } _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "To execute this command, the endpoint URL must point to the URL where Ory is located. To set the endpoint URL, use flag `--endpoint` or environment variable `ORY_SDK_URL`.") return "", FailSilently(cmd) } // RemoteURI returns the remote URI for the given command. func RemoteURI(cmd *cobra.Command) (*url.URL, error) { remote, err := Remote(cmd) if err != nil { return nil, err } endpoint, err := url.ParseRequestURI(remote) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse endpoint URL: %s", err) return nil, err } return endpoint, nil } // NewClient creates a new HTTP client. func NewClient(cmd *cobra.Command) (*http.Client, *url.URL, error) { endpoint, err := cmd.Flags().GetString(FlagEndpoint) if err != nil { return nil, nil, errors.WithStack(err) } if endpoint == "" { endpoint = os.Getenv(envKeyEndpoint) } if endpoint == "" { return nil, nil, errors.Errorf("you have to set the remote endpoint, try --help for details") } u, err := url.Parse(strings.TrimRight(endpoint, "/")) if err != nil { return nil, nil, errors.Wrapf(err, `could not parse the endpoint URL "%s"`, endpoint) } hc := retryablehttp.NewClient().StandardClient() hc.Timeout = time.Second * 10 rawHeaders, err := cmd.Flags().GetStringSlice(FlagHeaders) if err != nil { return nil, nil, errors.WithStack(err) } header := http.Header{} for _, h := range rawHeaders { parts := strings.Split(h, ":") if len(parts) != 2 { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Unable to parse `--http-header` flag. Format of flag value is a `: ` delimited string like `--http-header 'Some-Header: some-values; other values`. Received: %v", rawHeaders) return nil, nil, FailSilently(cmd) } for k := range parts { parts[k] = strings.TrimSpace(parts[k]) } header.Add(parts[0], parts[1]) } skipVerify, err := cmd.Flags().GetBool(FlagSkipTLSVerify) if err != nil { return nil, nil, errors.WithStack(err) } rt := httpx.NewTransportWithHeader(header) rt.RoundTripper = &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: skipVerify, //nolint:gosec // This is a false positive }, } hc.Transport = rt return hc, u, nil } // RegisterHTTPClientFlags registers HTTP client configuration flags. func RegisterHTTPClientFlags(flags *pflag.FlagSet) { flags.StringP(FlagEndpoint, FlagEndpoint[:1], "", fmt.Sprintf("The API URL this command should target. Alternatively set using the %s environmental variable.", envKeyEndpoint)) flags.Bool(FlagSkipTLSVerify, false, "Do not verify TLS certificates. Useful when dealing with self-signed certificates. Do not use in production!") flags.StringSliceP(FlagHeaders, "H", []string{}, "A list of additional HTTP headers to set. HTTP headers is separated by a `: `, for example: `-H 'Authorization: bearer some-token'`.") } ================================================ FILE: oryx/cmdx/noise_printer.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "fmt" "io" "github.com/spf13/cobra" "github.com/spf13/pflag" ) type ConditionalPrinter struct { w io.Writer print bool } const ( FlagQuiet = "quiet" ) func RegisterNoiseFlags(flags *pflag.FlagSet) { flags.BoolP(FlagQuiet, FlagQuiet[:1], false, "Be quiet with output printing.") } // NewLoudOutPrinter returns a ConditionalPrinter that // only prints to cmd.OutOrStdout when --quiet is not set func NewLoudOutPrinter(cmd *cobra.Command) *ConditionalPrinter { quiet, err := cmd.Flags().GetBool(FlagQuiet) if err != nil { Fatalf(err.Error()) } return &ConditionalPrinter{ w: cmd.OutOrStdout(), print: !quiet, } } // NewQuietOutPrinter returns a ConditionalPrinter that // only prints to cmd.OutOrStdout when --quiet is set func NewQuietOutPrinter(cmd *cobra.Command) *ConditionalPrinter { quiet, err := cmd.Flags().GetBool(FlagQuiet) if err != nil { Fatalf(err.Error()) } return &ConditionalPrinter{ w: cmd.OutOrStdout(), print: quiet, } } // NewLoudErrPrinter returns a ConditionalPrinter that // only prints to cmd.ErrOrStderr when --quiet is not set func NewLoudErrPrinter(cmd *cobra.Command) *ConditionalPrinter { quiet, err := cmd.Flags().GetBool(FlagQuiet) if err != nil { Fatalf(err.Error()) } return &ConditionalPrinter{ w: cmd.ErrOrStderr(), print: !quiet, } } // NewQuietErrPrinter returns a ConditionalPrinter that // only prints to cmd.ErrOrStderr when --quiet is set func NewQuietErrPrinter(cmd *cobra.Command) *ConditionalPrinter { quiet, err := cmd.Flags().GetBool(FlagQuiet) if err != nil { Fatalf(err.Error()) } return &ConditionalPrinter{ w: cmd.ErrOrStderr(), print: quiet, } } // NewLoudPrinter returns a ConditionalPrinter that // only prints to w when --quiet is not set func NewLoudPrinter(cmd *cobra.Command, w io.Writer) *ConditionalPrinter { quiet, err := cmd.Flags().GetBool(FlagQuiet) if err != nil { Fatalf(err.Error()) } return &ConditionalPrinter{ w: w, print: !quiet, } } // NewQuietPrinter returns a ConditionalPrinter that // only prints to w when --quiet is set func NewQuietPrinter(cmd *cobra.Command, w io.Writer) *ConditionalPrinter { quiet, err := cmd.Flags().GetBool(FlagQuiet) if err != nil { Fatalf(err.Error()) } return &ConditionalPrinter{ w: w, print: quiet, } } func NewConditionalPrinter(w io.Writer, print bool) *ConditionalPrinter { return &ConditionalPrinter{ w: w, print: print, } } func (p *ConditionalPrinter) Println(a ...interface{}) (n int, err error) { if p.print { return fmt.Fprintln(p.w, a...) } return } func (p *ConditionalPrinter) Print(a ...interface{}) (n int, err error) { if p.print { return fmt.Fprint(p.w, a...) } return } func (p *ConditionalPrinter) Printf(format string, a ...interface{}) (n int, err error) { if p.print { return fmt.Fprintf(p.w, format, a...) } return } ================================================ FILE: oryx/cmdx/output.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import "strconv" type ( // OutputIder outputs an ID OutputIder string // OutputIderCollection outputs a list of IDs OutputIderCollection struct { Items []OutputIder } ) func (OutputIder) Header() []string { return []string{"ID"} } func (i OutputIder) Columns() []string { return []string{string(i)} } func (i OutputIder) Interface() interface{} { return i } func (OutputIderCollection) Header() []string { return []string{"ID"} } func (c OutputIderCollection) Table() [][]string { rows := make([][]string, len(c.Items)) for i, ident := range c.Items { rows[i] = []string{string(ident)} } return rows } func (c OutputIderCollection) Interface() interface{} { return c.Items } func (c OutputIderCollection) Len() int { return len(c.Items) } type PaginatedList struct { Collection interface { Table IDs() []string } `json:"-"` Items []interface{} `json:"items"` NextPageToken string `json:"next_page_token"` IsLastPage bool `json:"is_last_page"` } func (r *PaginatedList) Header() []string { return r.Collection.Header() } func (r *PaginatedList) Table() [][]string { return append( r.Collection.Table(), []string{}, []string{"NEXT PAGE TOKEN", r.NextPageToken}, []string{"IS LAST PAGE", strconv.FormatBool(r.IsLastPage)}, ) } func (r *PaginatedList) Interface() interface{} { return r } func (r *PaginatedList) Len() int { return r.Collection.Len() + 3 } func (r *PaginatedList) IDs() []string { return r.Collection.IDs() } var _ Table = (*PaginatedList)(nil) ================================================ FILE: oryx/cmdx/pagination.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "fmt" "strconv" "github.com/spf13/cobra" ) const ( FlagPageSize = "page-size" FlagPageToken = "page-token" ) func RegisterTokenPaginationFlags(cmd *cobra.Command) (pageSize int, pageToken string) { cmd.Flags().StringVar(&pageToken, FlagPageToken, "", "page token acquired from a previous response") cmd.Flags().IntVar(&pageSize, FlagPageSize, 100, "maximum number of items to return") return } // ParsePaginationArgs parses pagination arguments from the command line. func ParsePaginationArgs(cmd *cobra.Command, pageArg, perPageArg string) (page, perPage int64, err error) { if len(pageArg+perPageArg) > 0 { page, err = strconv.ParseInt(pageArg, 0, 64) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse page argument\"%s\": %s", pageArg, err) return 0, 0, FailSilently(cmd) } perPage, err = strconv.ParseInt(perPageArg, 0, 64) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse per-page argument\"%s\": %s", perPageArg, err) return 0, 0, FailSilently(cmd) } } return } // ParseTokenPaginationArgs parses token-based pagination arguments from the command line. func ParseTokenPaginationArgs(cmd *cobra.Command) (page string, perPage int, err error) { pageArg, err := cmd.Flags().GetString(FlagPageToken) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse %s argument \"%s\": %s", FlagPageToken, pageArg, err) return "", 0, FailSilently(cmd) } perPageArg, err := cmd.Flags().GetInt(FlagPageSize) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse %s argument \"%d\": %s", FlagPageSize, perPageArg, err) return "", 0, FailSilently(cmd) } return pageArg, perPageArg, nil } ================================================ FILE: oryx/cmdx/printing.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "encoding/json" "errors" "fmt" "io" "os" "strings" "text/tabwriter" "github.com/go-openapi/jsonpointer" "github.com/goccy/go-yaml" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/tidwall/gjson" ) type ( TableHeader interface { Header() []string } TableRow interface { TableHeader Columns() []string Interface() interface{} } Table interface { TableHeader Table() [][]string Interface() interface{} Len() int } Nil struct{} Format string ) const ( FormatQuiet Format = "quiet" FormatTable Format = "table" FormatJSON Format = "json" FormatJSONPath Format = "jsonpath" FormatJSONPointer Format = "jsonpointer" FormatJSONPretty Format = "json-pretty" FormatYAML Format = "yaml" FormatDefault Format = "default" FlagFormat = "format" None = "" ) func (Nil) String() string { return "null" } func (Nil) Interface() interface{} { return nil } type printOptions struct { format string } type PrintOption func(*printOptions) func WithFormat(v string) PrintOption { return func(o *printOptions) { o.format = v } } func PrintErrors(cmd *cobra.Command, errs map[string]error) { for src, err := range errs { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s: %s\n", src, err.Error()) } } func PrintRow(cmd *cobra.Command, row TableRow) { PrintRowf(cmd.OutOrStdout(), row, WithFormat(getFormatValue(cmd))) } func PrintRowf(w io.Writer, row TableRow, opts ...PrintOption) { o := &printOptions{} for _, fn := range opts { fn(o) } switch parseFormat(o.format) { case FormatQuiet: if idAble, ok := row.(interface{ ID() string }); ok { _, _ = fmt.Fprintln(w, idAble.ID()) break } _, _ = fmt.Fprintln(w, row.Columns()[0]) case FormatJSON: printJSON(w, row.Interface(), false, "") case FormatYAML: printYAML(w, row.Interface()) case FormatJSONPretty: printJSON(w, row.Interface(), true, "") case FormatJSONPath: printJSON(w, row.Interface(), true, getPath(o.format)) case FormatJSONPointer: printJSON(w, filterJSONPointer(o.format, row.Interface()), true, "") case FormatTable, FormatDefault: w := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0) fields := row.Columns() for i, h := range row.Header() { _, _ = fmt.Fprintf(w, "%s\t%s\t\n", h, fields[i]) } _ = w.Flush() } } func filterJSONPointer(f string, data any) any { _, jsonptr, found := strings.Cut(f, "=") if !found { _, _ = fmt.Fprintf(os.Stderr, "Format %s is missing a JSON pointer, e.g., --%s=%s=. The path syntax is described at https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-07.", f, FlagFormat, f) os.Exit(1) } ptr, err := jsonpointer.New(jsonptr) Must(err, "invalid JSON pointer: %s", err) result, _, err := ptr.Get(data) Must(err, "failed to apply JSON pointer: %s", err) return result } func PrintTable(cmd *cobra.Command, table Table) { PrintTablef(cmd.OutOrStdout(), table, WithFormat(getFormatValue(cmd))) } func PrintTablef(w io.Writer, table Table, opts ...PrintOption) { o := &printOptions{} for _, fn := range opts { fn(o) } switch parseFormat(o.format) { case FormatQuiet: if table.Len() == 0 { fmt.Fprintln(w) } if idAble, ok := table.(interface{ IDs() []string }); ok { for _, row := range idAble.IDs() { fmt.Fprintln(w, row) } break } for _, row := range table.Table() { fmt.Fprintln(w, row[0]) } case FormatJSON: printJSON(w, table.Interface(), false, "") case FormatJSONPretty: printJSON(w, table.Interface(), true, "") case FormatJSONPath: printJSON(w, table.Interface(), true, getPath(o.format)) case FormatJSONPointer: printJSON(w, filterJSONPointer(o.format, table.Interface()), true, "") case FormatYAML: printYAML(w, table.Interface()) default: w := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0) for _, h := range table.Header() { fmt.Fprintf(w, "%s\t", h) } fmt.Fprintln(w) for _, row := range table.Table() { fmt.Fprintln(w, strings.Join(row, "\t")+"\t") } _ = w.Flush() } } type interfacer interface{ Interface() interface{} } func PrintJSONAble(cmd *cobra.Command, d interface{ String() string }) { PrintJSONAblef(cmd.OutOrStdout(), d, WithFormat(getFormatValue(cmd))) } func PrintJSONAblef(w io.Writer, d interface{ String() string }, opts ...PrintOption) { if d == nil { d = Nil{} } o := &printOptions{} for _, fn := range opts { fn(o) } var path string switch parseFormat(o.format) { default: _, _ = fmt.Fprint(w, d.String()) case FormatJSON: var v interface{} = d if i, ok := d.(interfacer); ok { v = i } printJSON(w, v, false, "") case FormatJSONPath: path = getPath(o.format) fallthrough case FormatJSONPretty: var v interface{} = d if i, ok := d.(interfacer); ok { v = i } printJSON(w, v, true, path) case FormatJSONPointer: var v interface{} = d if i, ok := d.(interfacer); ok { v = i } printJSON(w, filterJSONPointer(o.format, v), true, "") case FormatYAML: var v interface{} = d if i, ok := d.(interfacer); ok { v = i } printYAML(w, v) } } func getQuiet(cmd *cobra.Command) bool { // ignore the error here as we use this function also when the flag might not be registered q, _ := cmd.Flags().GetBool(FlagQuiet) return q } func getFormatValue(cmd *cobra.Command) string { if getQuiet(cmd) { return string(FormatQuiet) } f, _ := cmd.Flags().GetString(FlagFormat) return f } func parseFormat(f string) Format { switch { case f == string(FormatQuiet): return FormatQuiet case f == string(FormatTable): return FormatTable case f == string(FormatJSON): return FormatJSON case strings.HasPrefix(f, string(FormatJSONPath)): return FormatJSONPath case strings.HasPrefix(f, string(FormatJSONPointer)): return FormatJSONPointer case f == string(FormatJSONPretty): return FormatJSONPretty case f == string(FormatYAML): return FormatYAML default: return FormatDefault } } func getPath(f string) string { _, path, found := strings.Cut(f, "=") if !found { _, _ = fmt.Fprintf(os.Stderr, "Format %s is missing a path, e.g., --%s=%s=. The path syntax is described at https://github.com/tidwall/gjson/blob/master/SYNTAX.md", f, FlagFormat, f) os.Exit(1) } return path } func printJSON(w io.Writer, v interface{}, pretty bool, path string) { if path != "" { temp, err := json.Marshal(v) Must(err, "Error encoding JSON: %s", err) v = gjson.GetBytes(temp, path).Value() } e := json.NewEncoder(w) if pretty { e.SetIndent("", " ") } err := e.Encode(v) // unexpected error Must(err, "Error encoding JSON: %s", err) } func printYAML(w io.Writer, v interface{}) { j, err := json.Marshal(v) Must(err, "Error encoding JSON: %s", err) e, err := yaml.JSONToYAML(j) Must(err, "Error encoding YAML: %s", err) _, _ = w.Write(e) } func RegisterJSONFormatFlags(flags *pflag.FlagSet) { flags.String(FlagFormat, string(FormatDefault), fmt.Sprintf("Set the output format. One of %s, %s, %s, %s, %s and %s.", FormatDefault, FormatJSON, FormatYAML, FormatJSONPretty, FormatJSONPath, FormatJSONPointer)) } func RegisterFormatFlags(flags *pflag.FlagSet) { RegisterNoiseFlags(flags) flags.String(FlagFormat, string(FormatTable), fmt.Sprintf("Set the output format. One of %s, %s, %s, %s, %s and %s.", FormatTable, FormatJSON, FormatYAML, FormatJSONPretty, FormatJSONPath, FormatJSONPointer)) } func PrintOpenAPIError(cmd *cobra.Command, err error) error { if err == nil { return nil } var be interface { Body() []byte } if !errors.As(err, &be) { return err } body := be.Body() didPrettyPrint := false if message := gjson.GetBytes(body, "error.message"); message.Exists() { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\n", message.String()) didPrettyPrint = true } if reason := gjson.GetBytes(body, "error.reason"); reason.Exists() { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\n", reason.String()) didPrettyPrint = true } if didPrettyPrint { return FailSilently(cmd) } if body, err := json.MarshalIndent(json.RawMessage(body), "", " "); err == nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\nFailed to execute API request, see error above.\n", body) return FailSilently(cmd) } return err } ================================================ FILE: oryx/cmdx/usage.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "bytes" "text/template" "github.com/Masterminds/sprig/v3" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var usageTemplateFuncs = sprig.TxtFuncMap() // AddUsageTemplateFunc adds a template function to the usage template. func AddUsageTemplateFunc(name string, f interface{}) { usageTemplateFuncs[name] = f } const ( helpTemplate = `{{insertTemplate . (or .Long .Short) | trimTrailingWhitespaces}} {{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` usageTemplate = `Usage:{{if .Runnable}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} Aliases: {{.NameAndAliases}}{{end}}{{if .HasExample}} Examples: {{insertTemplate . .Example}}{{end}}{{if .HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} Global Flags: {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} ` ) // EnableUsageTemplating enables gotemplates for usage strings, i.e. cmd.Short, cmd.Long, and cmd.Example. // The data for the template is the command itself. Especially useful are `.Root.Name` and `.CommandPath`. // This will be inherited by all subcommands, so enabling it on the root command is sufficient. func EnableUsageTemplating(cmds ...*cobra.Command) { cobra.AddTemplateFunc("insertTemplate", TemplateCommandField) for _, cmd := range cmds { cmd.SetHelpTemplate(helpTemplate) cmd.SetUsageTemplate(usageTemplate) } } func TemplateCommandField(cmd *cobra.Command, field string) (string, error) { t := template.New("") t.Funcs(usageTemplateFuncs) t, err := t.Parse(field) if err != nil { return "", err } var out bytes.Buffer if err := t.Execute(&out, cmd); err != nil { return "", err } return out.String(), nil } // DisableUsageTemplating resets the commands usage template to the default. // This can be used to undo the effects of EnableUsageTemplating, specifically for a subcommand. func DisableUsageTemplating(cmds ...*cobra.Command) { defaultCmd := new(cobra.Command) for _, cmd := range cmds { cmd.SetHelpTemplate(defaultCmd.HelpTemplate()) cmd.SetUsageTemplate(defaultCmd.UsageTemplate()) } } // AssertUsageTemplates asserts that the usage string of the commands are properly templated. func AssertUsageTemplates(t require.TestingT, cmd *cobra.Command) { var usage, help string require.NotPanics(t, func() { usage = cmd.UsageString() out, err := cmd.OutOrStdout(), cmd.ErrOrStderr() bb := new(bytes.Buffer) cmd.SetOut(bb) cmd.SetErr(bb) require.NoError(t, cmd.Help()) help = bb.String() cmd.SetOut(out) cmd.SetErr(err) }) assert.NotContains(t, usage, "{{") assert.NotContains(t, help, "{{") for _, child := range cmd.Commands() { AssertUsageTemplates(t, child) } } ================================================ FILE: oryx/cmdx/user_input.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "bufio" "fmt" "io" "os" "strings" "github.com/pkg/errors" ) // asks for confirmation with the question string s and reads the answer // pass nil to use os.Stdin and os.Stdout func AskForConfirmation(s string, stdin io.Reader, stdout io.Writer) bool { if stdin == nil { stdin = os.Stdin } if stdout == nil { stdout = os.Stdout } ok, err := AskScannerForConfirmation(s, bufio.NewReader(stdin), stdout) if err != nil { Must(err, "Unable to confirm: %s", err) } return ok } func AskScannerForConfirmation(s string, reader *bufio.Reader, stdout io.Writer) (bool, error) { if stdout == nil { stdout = os.Stdout } for { _, err := fmt.Fprintf(stdout, "%s [y/n]: ", s) if err != nil { return false, errors.Wrap(err, "unable to print to stdout") } response, err := reader.ReadString('\n') if err != nil { return false, errors.Wrap(err, "unable to read from stdin") } response = strings.ToLower(strings.TrimSpace(response)) if response == "y" || response == "yes" { return true, nil } else if response == "n" || response == "no" { return false, nil } } } ================================================ FILE: oryx/cmdx/version.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package cmdx import ( "fmt" "os" "github.com/spf13/cobra" ) // Version returns a *cobra.Command that handles the `version` command. func Version(gitTag, gitHash, buildTime *string) *cobra.Command { return &cobra.Command{ Use: "version", Short: "Show the build version, build time, and git hash", Run: func(cmd *cobra.Command, args []string) { if len(*gitTag) == 0 { fmt.Fprintln(os.Stderr, "Unable to determine version because the build process did not properly configure it.") } else { fmt.Printf("Version: %s\n", *gitTag) } if len(*gitHash) == 0 { fmt.Fprintln(os.Stderr, "Unable to determine build commit because the build process did not properly configure it.") } else { fmt.Printf("Build Commit: %s\n", *gitHash) } if len(*buildTime) == 0 { fmt.Fprintln(os.Stderr, "Unable to determine build timestamp because the build process did not properly configure it.") } else { fmt.Printf("Build Timestamp: %s\n", *buildTime) } }, } } ================================================ FILE: oryx/configx/.snapshots/TestKoanfSchemaDefaults.json ================================================ {} ================================================ FILE: oryx/configx/context.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import "context" type contextKey int const configContextKey contextKey = iota + 1 func ContextWithConfigOptions(ctx context.Context, opts ...OptionModifier) context.Context { return context.WithValue(ctx, configContextKey, opts) } func ConfigOptionsFromContext(ctx context.Context) []OptionModifier { opts, ok := ctx.Value(configContextKey).([]OptionModifier) if !ok { return []OptionModifier{} } return opts } ================================================ FILE: oryx/configx/cors.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( _ "embed" "github.com/rs/cors" ) const CORSConfigSchemaID = "ory://cors-config" //go:embed cors.schema.json var CORSConfigSchema []byte func (p *Provider) CORS(prefix string, defaults cors.Options) (cors.Options, bool) { prefix = cleanPrefix(prefix) return cors.Options{ AllowedOrigins: p.StringsF(prefix+"cors.allowed_origins", defaults.AllowedOrigins), AllowedMethods: p.StringsF(prefix+"cors.allowed_methods", defaults.AllowedMethods), AllowedHeaders: p.StringsF(prefix+"cors.allowed_headers", defaults.AllowedHeaders), ExposedHeaders: p.StringsF(prefix+"cors.exposed_headers", defaults.ExposedHeaders), AllowCredentials: p.BoolF(prefix+"cors.allow_credentials", defaults.AllowCredentials), OptionsPassthrough: p.BoolF(prefix+"cors.options_passthrough", defaults.OptionsPassthrough), MaxAge: p.IntF(prefix+"cors.max_age", defaults.MaxAge), Debug: p.BoolF(prefix+"cors.debug", defaults.Debug), }, p.Bool(prefix + "cors.enabled") } ================================================ FILE: oryx/configx/cors.schema.json ================================================ { "$id": "https://github.com/ory/x/configx/cors.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "CORS", "description": "Configures Cross Origin Resource Sharing for this endpoint.", "properties": { "enabled": { "type": "boolean", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: https://*.example.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "matches all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "type": "string", "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non-simple headers the client is allowed to use with cross-domain requests.", "examples": [ [ "Authorization", "Content-Type", "Max-Age", "X-Session-Token", "X-XSRF-TOKEN", "X-CSRF-TOKEN" ] ], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug CORS issues.", "default": false } } } ================================================ FILE: oryx/configx/error.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "fmt" "github.com/pkg/errors" ) type ImmutableError struct { From interface{} To interface{} Key string error } func NewImmutableError(key string, from, to interface{}) error { return &ImmutableError{ From: from, To: to, Key: key, error: errors.Errorf("immutable configuration key \"%s\" was changed from \"%v\" to \"%v\"", key, from, to), } } func (e *ImmutableError) Error() string { return fmt.Sprintf("immutable configuration key \"%s\" was changed from \"%v\" to \"%v\"", e.Key, e.From, e.To) } ================================================ FILE: oryx/configx/helpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "bytes" "fmt" "io" "strings" "github.com/spf13/pflag" ) // RegisterFlags registers the config file flag. func RegisterFlags(flags *pflag.FlagSet) { flags.StringSliceP("config", "c", []string{}, "Path to one or more .json, .yaml, .yml, .toml config files. Values are loaded in the order provided, meaning that the last config file overwrites values from the previous config file.") } // host = unix:/path/to/socket => port is discarded, otherwise format as host:port func GetAddress(host string, port int) string { if strings.HasPrefix(host, "unix:") { return host } return fmt.Sprintf("%s:%d", host, port) } func (s *Serve) GetAddress() string { return GetAddress(s.Host, s.Port) } // AddSchemaResources adds the config schema partials to the compiler. // The interface is specified instead of `jsonschema.Compiler` to allow the use of any jsonschema library fork or version. func AddSchemaResources(c interface { AddResource(url string, r io.Reader) error }) error { if err := c.AddResource(TLSConfigSchemaID, bytes.NewReader(TLSConfigSchema)); err != nil { return err } if err := c.AddResource(ServeConfigSchemaID, bytes.NewReader(ServeConfigSchema)); err != nil { return err } return c.AddResource(CORSConfigSchemaID, bytes.NewReader(CORSConfigSchema)) } func cleanPrefix(prefix string) string { if len(prefix) > 0 { prefix = strings.TrimRight(prefix, ".") + "." } return prefix } ================================================ FILE: oryx/configx/koanf_confmap.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "bytes" "encoding/json" "errors" "github.com/knadh/koanf/maps" "github.com/tidwall/gjson" ) // KoanfConfmap implements a raw map[string]interface{} provider. type KoanfConfmap struct { tuples []tuple } // Provider returns a confmap Provider that takes a flat or nested // map[string]interface{}. If a delim is provided, it indicates that the // keys are flat and the map needs to be unflatted by delim. func NewKoanfConfmap(tuples []tuple) *KoanfConfmap { return &KoanfConfmap{tuples: jsonify(tuples)} } func jsonify(tuples []tuple) []tuple { for k, t := range tuples { var parsed interface{} switch vt := t.Value.(type) { case string: if gjson.Valid(vt) && json.NewDecoder(bytes.NewBufferString(vt)).Decode(&parsed) == nil { tuples[k].Value = parsed } continue case []byte: if gjson.ValidBytes(vt) && json.NewDecoder(bytes.NewBuffer(vt)).Decode(&parsed) == nil { tuples[k].Value = parsed } continue case json.RawMessage: if gjson.ValidBytes(vt) && json.NewDecoder(bytes.NewBuffer(vt)).Decode(&parsed) == nil { tuples[k].Value = parsed } continue } } return tuples } // ReadBytes is not supported by the env provider. func (e *KoanfConfmap) ReadBytes() ([]byte, error) { return nil, errors.New("confmap provider does not support this method") } // Read returns the loaded map[string]interface{}. func (e *KoanfConfmap) Read() (map[string]interface{}, error) { values := map[string]interface{}{} for _, t := range e.tuples { values[t.Key] = t.Value } // Ensure any nested values are properly converted as well cp := maps.Copy(values) maps.IntfaceKeysToStrings(cp) cp = maps.Unflatten(cp, Delimiter) return cp, nil } ================================================ FILE: oryx/configx/koanf_env.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "encoding/json" "os" "regexp" "strings" "github.com/pkg/errors" "github.com/tidwall/sjson" "github.com/ory/jsonschema/v3" "github.com/spf13/cast" "github.com/tidwall/gjson" "github.com/ory/x/castx" "github.com/ory/x/jsonschemax" ) var isNumRegex = regexp.MustCompile("^[0-9]+$") func NewKoanfEnv(prefix string, rawSchema []byte, schema *jsonschema.Schema) (*Env, error) { paths, err := getSchemaPaths(rawSchema, schema) if err != nil { return nil, err } return &Env{ paths: paths, prefix: prefix, }, nil } // Env implements an environment variables provider. type Env struct { prefix string paths []jsonschemax.Path } // ReadBytes is not supported by the env provider. func (e *Env) ReadBytes() ([]byte, error) { return nil, errors.New("env provider does not support this method") } // Read reads all available environment variables into a key:value map // and returns it. func (e *Env) Read() (map[string]interface{}, error) { // Collect the environment variable keys. var keys []string for _, k := range os.Environ() { if e.prefix != "" { if strings.HasPrefix(k, e.prefix) { keys = append(keys, k) } } else { keys = append(keys, k) } } raw := "{}" var err error for _, k := range keys { parts := strings.SplitN(k, "=", 2) key, value := e.extract(parts[0], parts[1]) // If the callback blanked the key, it should be omitted if key == "" { continue } raw, err = sjson.Set(raw, key, value) if err != nil { return nil, errors.WithStack(err) } } var m map[string]interface{} if err := json.Unmarshal([]byte(raw), &m); err != nil { return nil, errors.WithStack(err) } return m, nil } // Watch is not supported. func (e *Env) Watch(cb func(event interface{}, err error)) error { return errors.New("env provider does not support this method") } func (e *Env) extract(key string, value string) (string, interface{}) { key = strings.Replace(strings.ToLower(strings.TrimPrefix(key, e.prefix)), "_", ".", -1) for _, path := range e.paths { normalized := strings.Replace(path.Name, "_", ".", -1) name := path.Name // Crazy hack to get arrays working. var indices []string searchParts := strings.Split(normalized, ".") keyParts := strings.Split(key, ".") if len(searchParts) == len(keyParts) { for k, search := range searchParts { if search != keyParts[k] { indices = nil } if search != "#" { continue } if !isNumRegex.MatchString(keyParts[k]) { continue } searchParts[k] = keyParts[k] indices = append(indices, keyParts[k]) } } if len(indices) > 0 { normalized = strings.Join(searchParts, ".") for _, index := range indices { name = strings.Replace(name, "#", index, 1) } } if normalized == key { switch path.TypeHint { case jsonschemax.String: return name, cast.ToString(value) case jsonschemax.Float: return name, cast.ToFloat64(value) case jsonschemax.Int: return name, cast.ToInt64(value) case jsonschemax.Bool: return name, cast.ToBool(value) case jsonschemax.Nil: return name, nil case jsonschemax.BoolSlice: if !gjson.Valid(value) { return name, cast.ToBoolSlice(value) } fallthrough case jsonschemax.StringSlice: if !gjson.Valid(value) { return name, castx.ToStringSlice(value) } fallthrough case jsonschemax.IntSlice: if !gjson.Valid(value) { return name, cast.ToIntSlice(value) } fallthrough case jsonschemax.FloatSlice: if !gjson.Valid(value) { return name, castx.ToFloatSlice(value) } fallthrough case jsonschemax.JSON: return name, decode(value) default: return name, value } } } return "", nil } func decode(value string) (v interface{}) { b := []byte(value) var arr []interface{} if err := json.Unmarshal(b, &arr); err == nil { return &arr } h := map[string]interface{}{} if err := json.Unmarshal(b, &h); err == nil { return &h } return nil } ================================================ FILE: oryx/configx/koanf_file.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "context" "os" "path/filepath" "strings" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/v2" "github.com/pkg/errors" "github.com/ory/x/watcherx" ) // KoanfFile implements a KoanfFile provider. type KoanfFile struct { subKey string path string parser koanf.Parser } // NewKoanfFile returns a file provider. func NewKoanfFile(path string) (*KoanfFile, error) { return NewKoanfFileSubKey(path, "") } func NewKoanfFileSubKey(path, subKey string) (*KoanfFile, error) { kf := &KoanfFile{ path: filepath.Clean(path), subKey: subKey, } switch e := filepath.Ext(path); e { case ".toml": kf.parser = toml.Parser() case ".json": kf.parser = json.Parser() case ".yaml", ".yml": kf.parser = yaml.Parser() default: return nil, errors.Errorf("unknown config file extension: %s", e) } return kf, nil } // ReadBytes is not supported by KoanfFile. func (f *KoanfFile) ReadBytes() ([]byte, error) { return nil, errors.New("file provider does not support this method") } // Read reads the file and returns the parsed configuration. func (f *KoanfFile) Read() (map[string]interface{}, error) { //#nosec G304 -- false positive fc, err := os.ReadFile(f.path) if err != nil { return nil, errors.WithStack(err) } v, err := f.parser.Unmarshal(fc) if err != nil { return nil, errors.WithStack(err) } if f.subKey == "" { return v, nil } path := strings.Split(f.subKey, Delimiter) for i := range path { v = map[string]interface{}{ path[len(path)-1-i]: v, } } return v, nil } // WatchChannel watches the file and triggers a callback when it changes. It is a // blocking function that internally spawns a goroutine to watch for changes. func (f *KoanfFile) WatchChannel(ctx context.Context, c watcherx.EventChannel) (watcherx.Watcher, error) { return watcherx.WatchFile(ctx, f.path, c) } ================================================ FILE: oryx/configx/koanf_full_merge.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "encoding/json" "github.com/pkg/errors" "github.com/tidwall/sjson" "github.com/ory/x/jsonx" ) func MergeAllTypes(src, dst map[string]interface{}) error { rawSrc, err := json.Marshal(src) if err != nil { return errors.WithStack(err) } dstSrc, err := json.Marshal(dst) if err != nil { return errors.WithStack(err) } keys := jsonx.Flatten(rawSrc) for key, value := range keys { dstSrc, err = sjson.SetBytes(dstSrc, key, value) if err != nil { return errors.WithStack(err) } } return errors.WithStack(json.Unmarshal(dstSrc, &dst)) } ================================================ FILE: oryx/configx/koanf_memory.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "context" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/v2" "github.com/pkg/errors" stdjson "encoding/json" ) // KoanfMemory implements a KoanfMemory provider. type KoanfMemory struct { doc stdjson.RawMessage ctx context.Context parser koanf.Parser } // NewKoanfMemory returns a file provider. func NewKoanfMemory(ctx context.Context, doc stdjson.RawMessage) *KoanfMemory { return &KoanfMemory{ ctx: ctx, doc: doc, parser: json.Parser(), } } func (f *KoanfMemory) SetDoc(doc stdjson.RawMessage) { f.doc = doc } // ReadBytes reads the contents of a file on disk and returns the bytes. func (f *KoanfMemory) ReadBytes() ([]byte, error) { return nil, errors.New("file provider does not support this method") } // Read is not supported by the file provider. func (f *KoanfMemory) Read() (map[string]interface{}, error) { v, err := f.parser.Unmarshal(f.doc) if err != nil { return nil, errors.WithStack(err) } return v, nil } ================================================ FILE: oryx/configx/koanf_schema_defaults.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "strings" "github.com/knadh/koanf/maps" "github.com/pkg/errors" "github.com/ory/jsonschema/v3" "github.com/ory/x/jsonschemax" ) type KoanfSchemaDefaults struct { keys []jsonschemax.Path } func NewKoanfSchemaDefaults(rawSchema []byte, schema *jsonschema.Schema) (*KoanfSchemaDefaults, error) { keys, err := getSchemaPaths(rawSchema, schema) if err != nil { return nil, err } return &KoanfSchemaDefaults{keys: keys}, nil } func (k *KoanfSchemaDefaults) ReadBytes() ([]byte, error) { return nil, errors.New("schema defaults provider does not support this method") } func (k *KoanfSchemaDefaults) Read() (map[string]interface{}, error) { values := map[string]interface{}{} for _, key := range k.keys { // It's an array! if strings.Contains(key.Name, "#") { continue } if key.Default != nil { values[key.Name] = key.Default } } return maps.Unflatten(values, "."), nil } ================================================ FILE: oryx/configx/options.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "context" "errors" "fmt" "io" "os" "github.com/spf13/pflag" "github.com/ory/jsonschema/v3" "github.com/ory/x/logrusx" "github.com/knadh/koanf/v2" "github.com/ory/x/watcherx" ) type ( OptionModifier func(p *Provider) ) func WithContext(ctx context.Context) OptionModifier { return func(p *Provider) { for _, o := range ConfigOptionsFromContext(ctx) { o(p) } } } func WithConfigFiles(files ...string) OptionModifier { return func(p *Provider) { p.files = append(p.files, files...) } } func WithImmutables(immutables ...string) OptionModifier { return func(p *Provider) { p.immutables = append(p.immutables, immutables...) } } func WithExceptImmutables(exceptImmutables ...string) OptionModifier { return func(p *Provider) { p.exceptImmutables = append(p.exceptImmutables, exceptImmutables...) } } func WithFlags(flags *pflag.FlagSet) OptionModifier { return func(p *Provider) { p.flags = flags } } func WithLogger(l *logrusx.Logger) OptionModifier { return func(p *Provider) { p.logger = l } } func SkipValidation() OptionModifier { return func(p *Provider) { p.skipValidation = true } } func DisableEnvLoading() OptionModifier { return func(p *Provider) { p.disableEnvLoading = true } } func WithValue(key string, value interface{}) OptionModifier { return func(p *Provider) { p.forcedValues = append(p.forcedValues, tuple{Key: key, Value: value}) } } func WithValues(values map[string]interface{}) OptionModifier { return func(p *Provider) { for key, value := range values { p.forcedValues = append(p.forcedValues, tuple{Key: key, Value: value}) } } } func WithBaseValues(values map[string]interface{}) OptionModifier { return func(p *Provider) { for key, value := range values { p.baseValues = append(p.baseValues, tuple{Key: key, Value: value}) } } } func WithUserProviders(providers ...koanf.Provider) OptionModifier { return func(p *Provider) { p.userProviders = providers } } // DEPRECATED without replacement. This option is a no-op. func OmitKeysFromTracing(keys ...string) OptionModifier { return func(*Provider) {} } func AttachWatcher(watcher func(event watcherx.Event, err error)) OptionModifier { return func(p *Provider) { p.onChanges = append(p.onChanges, watcher) } } func WithLogrusWatcher(l *logrusx.Logger) OptionModifier { return AttachWatcher(LogrusWatcher(l)) } func LogrusWatcher(l *logrusx.Logger) func(e watcherx.Event, err error) { return func(e watcherx.Event, err error) { l.WithField("file", e.Source()). WithField("event_type", fmt.Sprintf("%T", e)). Info("A change to a configuration file was detected.") if et := new(jsonschema.ValidationError); errors.As(err, &et) { l.WithField("event", fmt.Sprintf("%#v", et)). Errorf("The changed configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process.") } else if et := new(ImmutableError); errors.As(err, &et) { l.WithError(err). WithField("key", et.Key). WithField("old_value", fmt.Sprintf("%v", et.From)). WithField("new_value", fmt.Sprintf("%v", et.To)). Errorf("A configuration value marked as immutable has changed. Rolling back to the last working configuration revision. To reload the values please restart the process.") } else if err != nil { l.WithError(err).Errorf("An error occurred while watching config file %s", e.Source()) } else { l.WithField("file", e.Source()). WithField("event_type", fmt.Sprintf("%T", e)). Info("Configuration change processed successfully.") } } } func WithStderrValidationReporter() OptionModifier { return func(p *Provider) { p.onValidationError = func(k *koanf.Koanf, err error) { p.printHumanReadableValidationErrors(k, os.Stderr, err) } } } func WithStandardValidationReporter(w io.Writer) OptionModifier { return func(p *Provider) { p.onValidationError = func(k *koanf.Koanf, err error) { p.printHumanReadableValidationErrors(k, w, err) } } } ================================================ FILE: oryx/configx/permission.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "os" "os/user" "strconv" ) type UnixPermission struct { Owner string Group string Mode os.FileMode } func (p *UnixPermission) SetPermission(file string) error { var e error e = os.Chmod(file, p.Mode) if e != nil { return e } gid := -1 uid := -1 if p.Owner != "" { var userObj *user.User userObj, e = user.Lookup(p.Owner) if e != nil { return e } uid, e = strconv.Atoi(userObj.Uid) if e != nil { return e } } if p.Group != "" { var group *user.Group group, e := user.LookupGroup(p.Group) if e != nil { return e } gid, e = strconv.Atoi(group.Gid) if e != nil { return e } } e = os.Chown(file, uid, gid) if e != nil { return e } return nil } ================================================ FILE: oryx/configx/pflag.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "strings" "github.com/knadh/koanf/providers/posflag" "github.com/knadh/koanf/v2" "github.com/pkg/errors" "github.com/spf13/pflag" "github.com/ory/jsonschema/v3" "github.com/ory/x/jsonschemax" ) type PFlagProvider struct { p *posflag.Posflag paths []jsonschemax.Path } func NewPFlagProvider(rawSchema []byte, schema *jsonschema.Schema, f *pflag.FlagSet, k *koanf.Koanf) (*PFlagProvider, error) { paths, err := getSchemaPaths(rawSchema, schema) if err != nil { return nil, err } return &PFlagProvider{ p: posflag.Provider(f, ".", k), paths: paths, }, nil } func (p *PFlagProvider) ReadBytes() ([]byte, error) { return nil, errors.New("pflag provider does not support this method") } func (p *PFlagProvider) Read() (map[string]interface{}, error) { all, err := p.p.Read() if err != nil { return nil, errors.WithStack(err) } knownFlags := make(map[string]interface{}, len(all)) for k, v := range all { k = strings.ReplaceAll(k, ".", "-") for _, path := range p.paths { normalized := strings.ReplaceAll(path.Name, ".", "-") if k == normalized { knownFlags[k] = v break } } } return knownFlags, nil } var _ koanf.Provider = (*PFlagProvider)(nil) ================================================ FILE: oryx/configx/provider.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "bytes" "context" "fmt" "io" "net/url" "os" "reflect" "sync" "time" "github.com/inhies/go-bytesize" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/providers/posflag" "github.com/knadh/koanf/v2" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/pflag" "github.com/ory/jsonschema/v3" "github.com/ory/x/jsonschemax" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/watcherx" ) type tuple struct { Key string Value interface{} } type Provider struct { l sync.RWMutex *koanf.Koanf immutables, exceptImmutables []string schema []byte flags *pflag.FlagSet validator *jsonschema.Schema onChanges []func(watcherx.Event, error) onValidationError func(k *koanf.Koanf, err error) forcedValues []tuple baseValues []tuple files []string skipValidation bool disableEnvLoading bool logger *logrusx.Logger providers []koanf.Provider userProviders []koanf.Provider } const ( FlagConfig = "config" Delimiter = "." ) // RegisterConfigFlag registers the "--config" flag on pflag.FlagSet. func RegisterConfigFlag(flags *pflag.FlagSet, fallback []string) { flags.StringSliceP(FlagConfig, "c", fallback, "Config files to load, overwriting in the order specified.") } // New creates a new provider instance or errors. // Configuration values are loaded in the following order: // // 1. Defaults from the JSON Schema // 2. Config files (yaml, yml, toml, json) // 3. Command line flags // 4. Environment variables // // There will also be file-watchers started for all config files. To cancel the // watchers, cancel the context. func New(ctx context.Context, schema []byte, modifiers ...OptionModifier) (*Provider, error) { validator, err := getSchema(ctx, schema) if err != nil { return nil, err } l := logrus.New() l.Out = io.Discard p := &Provider{ schema: schema, validator: validator, onValidationError: func(k *koanf.Koanf, err error) {}, logger: logrusx.New("discarding config logger", "", logrusx.UseLogger(l)), Koanf: koanf.NewWithConf(koanf.Conf{Delim: Delimiter, StrictMerge: true}), } for _, m := range modifiers { m(p) } providers, err := p.createProviders(ctx) if err != nil { return nil, err } p.providers = providers k, err := p.newKoanf() if err != nil { return nil, err } p.replaceKoanf(k) return p, nil } func (p *Provider) SkipValidation() bool { return p.skipValidation } func (p *Provider) createProviders(ctx context.Context) (providers []koanf.Provider, err error) { defaultsProvider, err := NewKoanfSchemaDefaults(p.schema, p.validator) if err != nil { return nil, err } providers = append(providers, defaultsProvider) // Workaround for https://github.com/knadh/koanf/pull/47 for _, t := range p.baseValues { providers = append(providers, NewKoanfConfmap([]tuple{t})) } paths := p.files if p.flags != nil { p, _ := p.flags.GetStringSlice(FlagConfig) paths = append(paths, p...) } p.logger.WithField("files", paths).Debug("Adding config files.") c := make(watcherx.EventChannel) defer func() { if err == nil && len(paths) > 0 { go p.watchForFileChanges(ctx, c) } }() for _, path := range paths { fp, err := NewKoanfFile(path) if err != nil { return nil, err } if _, err := fp.WatchChannel(ctx, c); err != nil { return nil, err } providers = append(providers, fp) } providers = append(providers, p.userProviders...) if p.flags != nil { pp, err := NewPFlagProvider(p.schema, p.validator, p.flags, p.Koanf) if err != nil { return nil, err } providers = append(providers, pp) } if !p.disableEnvLoading { envProvider, err := NewKoanfEnv("", p.schema, p.validator) if err != nil { return nil, err } providers = append(providers, envProvider) } // Workaround for https://github.com/knadh/koanf/pull/47 for _, t := range p.forcedValues { providers = append(providers, NewKoanfConfmap([]tuple{t})) } return providers, nil } func (p *Provider) replaceKoanf(k *koanf.Koanf) { p.Koanf = k } func (p *Provider) validate(k *koanf.Koanf) error { if p.skipValidation { return nil } out, err := k.Marshal(json.Parser()) if err != nil { return errors.WithStack(err) } if err := p.validator.Validate(bytes.NewReader(out)); err != nil { p.onValidationError(k, err) return err } return nil } // newKoanf creates a new koanf instance with all the updated config // // This is unfortunately required due to several limitations / bugs in koanf: // // - https://github.com/knadh/koanf/issues/77 // - https://github.com/knadh/koanf/pull/47 func (p *Provider) newKoanf() (_ *koanf.Koanf, err error) { k := koanf.New(Delimiter) for _, provider := range p.providers { // posflag.Posflag requires access to Koanf instance so we recreate the provider here which is a workaround // for posflag.Provider's API. if _, ok := provider.(*posflag.Posflag); ok { provider = posflag.Provider(p.flags, ".", k) } var opts []koanf.Option if _, ok := provider.(*Env); ok { opts = append(opts, koanf.WithMergeFunc(MergeAllTypes)) } if err := k.Load(provider, nil, opts...); err != nil { return nil, err } } if err := p.validate(k); err != nil { return nil, err } return k, nil } // SetTracer does nothing. DEPRECATED without replacement. func (p *Provider) SetTracer(_ context.Context, _ *otelx.Tracer) { } func (p *Provider) runOnChanges(e watcherx.Event, err error) { for k := range p.onChanges { p.onChanges[k](e, err) } } func deleteOtherKeys(k *koanf.Koanf, keys []string) { outer: for _, key := range k.Keys() { for _, ik := range keys { if key == ik { continue outer } } k.Delete(key) } } func (p *Provider) reload(e watcherx.Event) { p.l.Lock() var err error defer func() { // we first want to unlock and then runOnChanges, so that the callbacks can actually use the Provider p.l.Unlock() p.runOnChanges(e, err) }() nk, err := p.newKoanf() if err != nil { return // unlocks & runs changes in defer } oldImmutables, newImmutables := p.Koanf.Copy(), nk.Copy() deleteOtherKeys(oldImmutables, p.immutables) deleteOtherKeys(newImmutables, p.immutables) for _, key := range p.exceptImmutables { oldImmutables.Delete(key) newImmutables.Delete(key) } if !reflect.DeepEqual(oldImmutables.Raw(), newImmutables.Raw()) { for _, key := range p.immutables { if !reflect.DeepEqual(oldImmutables.Get(key), newImmutables.Get(key)) { err = NewImmutableError(key, fmt.Sprintf("%v", p.Koanf.Get(key)), fmt.Sprintf("%v", nk.Get(key))) return // unlocks & runs changes in defer } } } p.replaceKoanf(nk) // unlocks & runs changes in defer } func (p *Provider) watchForFileChanges(ctx context.Context, c watcherx.EventChannel) { for { select { case <-ctx.Done(): return case e := <-c: switch et := e.(type) { case *watcherx.ErrorEvent: p.runOnChanges(e, et) default: p.reload(e) } } } } // DirtyPatch patches individual config keys without reloading the full config // // WARNING! This method is only useful to override existing keys in string or number // format. DO NOT use this method to override arrays, maps, or other complex types. // // This method DOES NOT validate the config against the config JSON schema. If you // need to validate the config, use the Set method instead. // // This method can not be used to remove keys from the config as that is not // possible without reloading the full config. func (p *Provider) DirtyPatch(key string, value any) error { p.l.Lock() defer p.l.Unlock() t := tuple{Key: key, Value: value} kc := NewKoanfConfmap([]tuple{t}) p.forcedValues = append(p.forcedValues, t) p.providers = append(p.providers, kc) if err := p.Koanf.Load(kc, nil, []koanf.Option{}...); err != nil { return err } return nil } func (p *Provider) Set(key string, value interface{}) error { p.l.Lock() defer p.l.Unlock() p.forcedValues = append(p.forcedValues, tuple{Key: key, Value: value}) p.providers = append(p.providers, NewKoanfConfmap([]tuple{{Key: key, Value: value}})) k, err := p.newKoanf() if err != nil { return err } p.replaceKoanf(k) return nil } func (p *Provider) BoolF(key string, fallback bool) bool { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } return p.Bool(key) } func (p *Provider) StringF(key string, fallback string) string { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } return p.String(key) } func (p *Provider) StringsF(key string, fallback []string) (val []string) { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } return p.Strings(key) } func (p *Provider) IntF(key string, fallback int) (val int) { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } return p.Int(key) } func (p *Provider) Float64F(key string, fallback float64) (val float64) { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } return p.Float64(key) } func (p *Provider) DurationF(key string, fallback time.Duration) (val time.Duration) { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } return p.Duration(key) } func (p *Provider) ByteSizeF(key string, fallback bytesize.ByteSize) bytesize.ByteSize { p.l.RLock() defer p.l.RUnlock() if !p.Koanf.Exists(key) { return fallback } switch v := p.Koanf.Get(key).(type) { case string: // this type usually comes from user input dec, err := bytesize.Parse(v) if err != nil { p.logger.WithField("key", key).WithField("raw_value", v).WithError(err).Warnf("error parsing byte size value, using fallback of %s", fallback) return fallback } return dec case float64: // this type comes from json.Unmarshal return bytesize.ByteSize(v) case bytesize.ByteSize: return v default: p.logger.WithField("key", key).WithField("raw_type", fmt.Sprintf("%T", v)).WithField("raw_value", fmt.Sprintf("%+v", v)).Errorf("error converting byte size value because of unknown type, using fallback of %s", fallback) return fallback } } func (p *Provider) GetF(key string, fallback interface{}) (val interface{}) { p.l.RLock() defer p.l.RUnlock() if !p.Exists(key) { return fallback } return p.Get(key) } func (p *Provider) TracingConfig(serviceName string) *otelx.Config { return &otelx.Config{ ServiceName: p.StringF("tracing.service_name", serviceName), DeploymentEnvironment: p.StringF("tracing.deployment_environment", ""), Provider: p.String("tracing.provider"), Providers: otelx.ProvidersConfig{ Jaeger: otelx.JaegerConfig{ Sampling: otelx.JaegerSampling{ ServerURL: p.String("tracing.providers.jaeger.sampling.server_url"), TraceIDRatio: p.Float64F("tracing.providers.jaeger.sampling.trace_id_ratio", 1), }, LocalAgentAddress: p.String("tracing.providers.jaeger.local_agent_address"), }, Zipkin: otelx.ZipkinConfig{ ServerURL: p.String("tracing.providers.zipkin.server_url"), Sampling: otelx.ZipkinSampling{ SamplingRatio: p.Float64("tracing.providers.zipkin.sampling.sampling_ratio"), }, }, OTLP: otelx.OTLPConfig{ ServerURL: p.String("tracing.providers.otlp.server_url"), Insecure: p.Bool("tracing.providers.otlp.insecure"), Sampling: otelx.OTLPSampling{ SamplingRatio: p.Float64F("tracing.providers.otlp.sampling.sampling_ratio", 1), }, AuthorizationHeader: p.String("tracing.providers.otlp.authorization_header"), }, }, } } func (p *Provider) RequestURIF(path string, fallback *url.URL) *url.URL { p.l.RLock() defer p.l.RUnlock() switch t := p.Get(path).(type) { case *url.URL: return t case url.URL: return &t case string: if parsed, err := url.ParseRequestURI(t); err == nil { return parsed } } return fallback } func (p *Provider) URIF(path string, fallback *url.URL) *url.URL { p.l.RLock() defer p.l.RUnlock() switch t := p.Get(path).(type) { case *url.URL: return t case url.URL: return &t case string: if parsed, err := url.Parse(t); err == nil { return parsed } } return fallback } // PrintHumanReadableValidationErrors prints human readable validation errors. Duh. func (p *Provider) PrintHumanReadableValidationErrors(w io.Writer, err error) { p.printHumanReadableValidationErrors(p.Koanf, w, err) } func (p *Provider) printHumanReadableValidationErrors(k *koanf.Koanf, w io.Writer, err error) { if err == nil { return } _, _ = fmt.Fprintln(os.Stderr, "") conf, innerErr := k.Marshal(json.Parser()) if innerErr != nil { _, _ = fmt.Fprintf(w, "Unable to unmarshal configuration: %+v", innerErr) } jsonschemax.FormatValidationErrorForCLI(w, conf, err) } ================================================ FILE: oryx/configx/schema.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "bytes" "fmt" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/tidwall/gjson" "github.com/ory/jsonschema/v3" ) func newCompiler(schema []byte) (string, *jsonschema.Compiler, error) { id := gjson.GetBytes(schema, "$id").String() if id == "" { id = fmt.Sprintf("%s.json", uuid.Must(uuid.NewV4()).String()) } compiler := jsonschema.NewCompiler() if err := compiler.AddResource(id, bytes.NewReader(schema)); err != nil { return "", nil, errors.WithStack(err) } // DO NOT REMOVE THIS compiler.ExtractAnnotations = true if err := otelx.AddConfigSchema(compiler); err != nil { return "", nil, err } if err := logrusx.AddConfigSchema(compiler); err != nil { return "", nil, err } if err := AddSchemaResources(compiler); err != nil { return "", nil, err } return id, compiler, nil } ================================================ FILE: oryx/configx/schema_cache.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "context" "crypto/sha256" "github.com/dgraph-io/ristretto/v2" "github.com/ory/jsonschema/v3" ) var schemaCacheConfig = &ristretto.Config[[]byte, *jsonschema.Schema]{ // Hold up to 25 schemas in cache. Usually we only need one. MaxCost: 25, NumCounters: 250, BufferItems: 64, Metrics: false, IgnoreInternalCost: true, Cost: func(*jsonschema.Schema) int64 { return 1 }, } var schemaCache, _ = ristretto.NewCache(schemaCacheConfig) func getSchema(ctx context.Context, schema []byte) (*jsonschema.Schema, error) { key := sha256.Sum256(schema) if val, found := schemaCache.Get(key[:]); found { return val, nil } schemaID, comp, err := newCompiler(schema) if err != nil { return nil, err } validator, err := comp.Compile(ctx, schemaID) if err != nil { return nil, err } schemaCache.Set(key[:], validator, 1) schemaCache.Wait() return validator, nil } ================================================ FILE: oryx/configx/schema_path_cache.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "crypto/sha256" "github.com/ory/x/jsonschemax" "github.com/dgraph-io/ristretto/v2" "github.com/ory/jsonschema/v3" ) var schemaPathCacheConfig = &ristretto.Config[[]byte, []jsonschemax.Path]{ // Hold up to 25 schemas in cache. Usually we only need one. MaxCost: 250, NumCounters: 2500, BufferItems: 64, Metrics: false, IgnoreInternalCost: true, } var schemaPathCache, _ = ristretto.NewCache[[]byte, []jsonschemax.Path](schemaPathCacheConfig) func getSchemaPaths(rawSchema []byte, schema *jsonschema.Schema) ([]jsonschemax.Path, error) { key := sha256.Sum256(rawSchema) if val, found := schemaPathCache.Get(key[:]); found { return val, nil } keys, err := jsonschemax.ListPathsWithInitializedSchemaAndArraysIncluded(schema) if err != nil { return nil, err } schemaPathCache.Set(key[:], keys, 1) schemaPathCache.Wait() return keys, nil } ================================================ FILE: oryx/configx/serve.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx import ( "cmp" "context" "crypto/tls" _ "embed" "fmt" "net/url" "os" "github.com/ory/x/logrusx" "github.com/ory/x/tlsx" ) const ( ServeConfigSchemaID = "ory://serve-config" TLSConfigSchemaID = "ory://tls-config" ) //go:embed serve.schema.json var ServeConfigSchema []byte //go:embed tls.schema.json var TLSConfigSchema []byte type ( Serve struct { Host, WriteListenFile string Port int BaseURL *url.URL Socket UnixPermission TLS TLS RequestLog ServeRequestLog } TLS struct { Enabled bool AllowTerminationFrom []string CertBase64, KeyBase64, CertPath, KeyPath string } ServeRequestLog struct { DisableHealth bool } ) func (p *Provider) Serve(prefix string, isDev bool, defaults Serve) *Serve { prefix = cleanPrefix(prefix) defaults.Socket.Mode = cmp.Or(defaults.Socket.Mode, 0o755) serve := Serve{ Host: p.StringF(prefix+"host", defaults.Host), Port: p.IntF(prefix+"port", defaults.Port), WriteListenFile: p.StringF(prefix+"write_listen_file", defaults.WriteListenFile), BaseURL: p.URIF(prefix+"base_url", defaults.BaseURL), Socket: UnixPermission{ Owner: p.StringF(prefix+"socket.owner", defaults.Socket.Owner), Group: p.StringF(prefix+"socket.group", defaults.Socket.Group), Mode: os.FileMode(p.IntF(prefix+"socket.mode", int(defaults.Socket.Mode))), }, TLS: p.TLS(prefix+"tls", defaults.TLS), RequestLog: ServeRequestLog{ DisableHealth: p.BoolF(prefix+"request_log.disable_for_health", defaults.RequestLog.DisableHealth), }, } if serve.BaseURL == nil { serve.BaseURL = &url.URL{ Scheme: "http", Path: "/", } if !isDev || serve.TLS.Enabled { serve.BaseURL.Scheme = "https" } host := serve.Host if host == "0.0.0.0" || host == "" { var err error host, err = os.Hostname() if err != nil { p.logger.WithError(err).Warn("Unable to get hostname from system, falling back to 127.0.0.1.") host = "127.0.0.1" } } serve.BaseURL.Host = fmt.Sprintf("%s:%d", host, serve.Port) } return &serve } func (p *Provider) TLS(prefix string, defaults TLS) TLS { prefix = cleanPrefix(prefix) return TLS{ Enabled: p.BoolF(prefix+"enabled", defaults.Enabled), AllowTerminationFrom: p.StringsF(prefix+"allow_termination_from", defaults.AllowTerminationFrom), CertBase64: p.StringF(prefix+"cert.base64", defaults.CertBase64), KeyBase64: p.StringF(prefix+"key.base64", defaults.KeyBase64), CertPath: p.StringF(prefix+"cert.path", defaults.CertPath), KeyPath: p.StringF(prefix+"key.path", defaults.KeyPath), } } func (t *TLS) GetCertFunc(ctx context.Context, l *logrusx.Logger, ifaceName string) (tlsx.CertFunc, error) { switch { case t.CertBase64 != "" && t.KeyBase64 != "": cert, err := tlsx.CertificateFromBase64(t.CertBase64, t.KeyBase64) if err != nil { return nil, fmt.Errorf("unable to load TLS certificate for interface %s: %w", ifaceName, err) } l.Infof("Setting up HTTPS for %s", ifaceName) return func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return &cert, nil }, nil case t.CertPath != "" && t.KeyPath != "": errs := make(chan error, 1) getCert, err := tlsx.GetCertificate(ctx, t.CertPath, t.KeyPath, errs) if err != nil { return nil, fmt.Errorf("unable to load TLS certificate for interface %s: %w", ifaceName, err) } go func() { for { select { case <-ctx.Done(): return case err := <-errs: l.WithError(err).Error("Failed to reload TLS certificates, using previous certificates") } } }() l.Infof("Setting up HTTPS for %s (automatic certificate reloading active)", ifaceName) return getCert, nil default: l.Infof("TLS has not been configured for %s, skipping", ifaceName) } return nil, nil } ================================================ FILE: oryx/configx/serve.schema.json ================================================ { "$id": "https://github.com/ory/x/configx/serve.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "request_log": { "type": "object", "properties": { "disable_for_health": { "title": "Disable health endpoints request logging", "description": "Disable request logging for /health/alive and /health/ready endpoints", "type": "boolean", "default": false } }, "additionalProperties": false }, "base_url": { "title": "Base URL", "description": "The URL where the endpoint is exposed at. This domain is used to generate redirects, form URLs, and more.", "type": "string", "format": "uri-reference", "examples": [ "https://my-app.com/", "https://my-app.com/.ory/kratos/public", "https://auth.my-app.com/hydra" ] }, "host": { "title": "Host", "description": "The host (interface) that the endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Port", "description": "The port that the endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535 }, "socket": { "type": "object", "additionalProperties": false, "description": "Sets the permissions of the unix socket", "properties": { "owner": { "type": "string", "description": "Owner of unix socket. If empty, the owner will be the user running the service.", "default": "" }, "group": { "type": "string", "description": "Group of unix socket. If empty, the group will be the primary group of the user running the service.", "default": "" }, "mode": { "type": "integer", "description": "Mode of unix socket in numeric form", "default": 493, "minimum": 0, "maximum": 511 } } }, "tls": { "$ref": "ory://tls-config" } } } ================================================ FILE: oryx/configx/span.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package configx const ( LoadSpanOpName = "config-load" UpdatedSpanOpName = "config-update" SnapshotSpanOpName = "config-snapshot" ) ================================================ FILE: oryx/configx/stub/benchmark/benchmark.yaml ================================================ # Please find the documentation for this file at # https://www.ory.sh/oathkeeper/docs/configuration log: level: debug format: json profiling: cpu serve: proxy: port: 1234 host: 127.0.0.1 timeout: read: 1s write: 2s idle: 3s cors: enabled: true allowed_origins: - https://example.com - https://*.example.com allowed_methods: - POST - GET - PUT - PATCH - DELETE allowed_headers: - Authorization - Content-Type exposed_headers: - Content-Type allow_credentials: true max_age: 10 debug: true tls: key: path: /path/to/key.pem base64: LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3... cert: path: /path/to/cert.pem base64: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr... api: port: 1235 host: 127.0.0.2 timeout: read: 1s write: 2s idle: 3s cors: enabled: true allowed_origins: - https://example.org - https://*.example.org allowed_methods: - GET - PUT - PATCH - DELETE allowed_headers: - Authorization - Content-Type exposed_headers: - Content-Type allow_credentials: true max_age: 10 debug: true tls: key: path: /path/to/key.pem base64: LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3... cert: path: /path/to/cert.pem base64: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr... prometheus: port: 9000 host: localhost metrics_path: /metrics collapse_request_paths: true # Configures Access Rules access_rules: # Locations (list of URLs) where access rules should be fetched from on boot. # It is expected that the documents at those locations return a JSON or YAML Array containing ORY Oathkeeper Access Rules. repositories: # If the URL Scheme is `file://`, the access rules (an array of access rules is expected) will be # fetched from the local file system. - file://path/to/rules.json # If the URL Scheme is `inline://`, the access rules (an array of access rules is expected) # are expected to be a base64 encoded (with padding!) JSON/YAML string (base64_encode(`[{"id":"foo-rule","authenticators":[....]}]`)): - inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d # If the URL Scheme is `http://` or `https://`, the access rules (an array of access rules is expected) will be # fetched from the provided HTTP(s) location. - https://path-to-my-rules/rules.json # Optional fields describing matching strategy, defaults to "regexp". matching_strategy: glob errors: fallback: - json handlers: redirect: enabled: true config: to: http://path-to/redirect json: enabled: true config: verbose: true when: - error: - unauthorized - forbidden - internal_server_error request: header: content_type: - application/json accept: - application/json cidr: - 127.0.0.0/24 # All authenticators can be configured under this configuration key authenticators: # Configures the anonymous authenticator anonymous: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true config: # Sets the anonymous username. Defaults to "anonymous". Common names include "guest", "anon", "anonymous", "unknown". subject: guest # Configures the cookie session authenticator cookie_session: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true config: # Sets the origin to proxy requests to. If the response is a 200 with body `{ "subject": "...", "extra": {} }` # The request will pass the subject through successfully, otherwise it will be marked as unauthorized check_session_url: https://session-store-host # Sets a list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if # none of the passed cookies are set on the request only: - sessionid # Configures the jwt authenticator jwt: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true config: # REQUIRED IF ENABLED - The URL where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web # Token. Usually something like "https://my-keys.com/.well-known/jwks.json". The response of that endpoint must # return a JSON Web Key Set (JWKS). jwks_urls: - https://my-website.com/.well-known/jwks.json - https://my-other-website.com/.well-known/jwks.json - file://path/to/local/jwks.json # Sets the strategy to be used to validate/match the scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults # to "none". scope_strategy: wildcard # Configures the noop authenticator noop: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true # Configures the oauth2_client_credentials authenticator oauth2_client_credentials: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true config: # REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint that will be used to validate the client credentials. token_url: https://my-website.com/oauth2/token # Configures the oauth2_introspection authenticator oauth2_introspection: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true config: # REQUIRED IF ENABLED - The OAuth 2.0 Token Introspection endpoint. introspection_url: https://my-website.com/oauth2/introspection # Sets the strategy to be used to validate/match the token scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults # to "none". scope_strategy: exact # Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer # Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant. pre_authorization: # Enable pre-authorization. Defaults to false. enabled: true # REQUIRED IF ENABLED - The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant. client_id: some_id # REQUIRED IF ENABLED - The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant. client_secret: some_secret # The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant. scope: - foo - bar # REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed. token_url: https://my-website.com/oauth2/token # Configures the unauthorized authenticator unauthorized: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true # All authorizers can be configured under this configuration key authorizers: # Configures the allow authorizer allow: # Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false. enabled: true # Configures the deny authorizer deny: # Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false. enabled: true # Configures the keto_engine_acp_ory authorizer keto_engine_acp_ory: # Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false. enabled: true config: # REQUIRED IF ENABLED - The base URL of ORY Keto, typically something like http(s)://[:]/ base_url: http://my-keto/ required_action: unknown required_resource: unknown # Configures the remote authorizer remote: # Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false. enabled: true config: remote: https://host/path headers: {} # Configures the remote_json authorizer remote_json: # Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false. enabled: true config: remote: https://host/path payload: "{}" # All mutators can be configured under this configuration key mutators: header: enabled: true config: headers: foo: bar # Configures the cookie mutator cookie: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true config: cookies: foo: bar # Configures the hydrator mutator hydrator: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true config: api: url: https://some-url/ # Configures the id_token mutator id_token: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true config: # REQUIRED IF ENABLED - Sets the "iss" value of the ID Token. issuer_url: https://my-oathkeeper/ # REQUIRED IF ENABLED - Sets the URL where keys should be fetched from. Supports remote locations (http, https) as # well as local filesystem paths. jwks_url: https://fetch-keys/from/this/location.json # jwks_url: file:///from/this/absolute/location.json # jwks_url: file://../from/this/relative/location.json # Sets the time-to-live of the ID token. Defaults to one minute. Valid time units are: s (second), m (minute), h (hour). ttl: 1h # Configures the noop mutator noop: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true ================================================ FILE: oryx/configx/stub/benchmark/schema.config.json ================================================ { "log": { "level": "debug", "format": "json" }, "profiling": "cpu", "serve": { "proxy": { "port": 1234, "host": "127.0.0.1", "timeout": { "read": "1s", "write": "2s", "idle": "3s" }, "cors": { "enabled": true, "allowed_origins": ["https://example.com", "https://*.example.com"], "allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"], "allowed_headers": ["Authorization", "Content-Type"], "exposed_headers": ["Content-Type"], "allow_credentials": true, "max_age": 10, "debug": true }, "tls": { "key": { "path": "/path/to/key.pem", "base64": "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3..." }, "cert": { "path": "/path/to/cert.pem", "base64": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." } } }, "api": { "port": 1235, "host": "127.0.0.2", "cors": { "enabled": true, "allowed_origins": ["https://example.org", "https://*.example.org"], "allowed_methods": ["GET", "PUT", "PATCH", "DELETE"], "allowed_headers": ["Authorization", "Content-Type"], "exposed_headers": ["Content-Type"], "allow_credentials": true, "max_age": 10, "debug": true }, "tls": { "key": { "path": "/path/to/key.pem", "base64": "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3..." }, "cert": { "path": "/path/to/cert.pem", "base64": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." } } } }, "access_rules": { "repositories": [ "file://path/to/rules.json", "inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d", "https://path-to-my-rules/rules.json" ], "matching_strategy": "glob" }, "errors": { "fallback": ["json"], "handlers": { "redirect": { "enabled": true, "config": { "to": "http://path-to/redirect" } }, "json": { "enabled": true, "config": { "verbose": true, "when": [ { "error": ["unauthorized", "forbidden", "internal_server_error"], "request": { "header": { "content_type": ["application/json"], "accept": ["application/json"] }, "cidr": ["127.0.0.0/24"] } } ] } } } }, "authenticators": { "anonymous": { "enabled": true, "config": { "subject": "guest" } }, "cookie_session": { "enabled": true, "config": { "check_session_url": "https://session-store-host", "only": ["sessionid"] } }, "jwt": { "enabled": true, "config": { "jwks_urls": [ "https://my-website.com/.well-known/jwks.json", "https://my-other-website.com/.well-known/jwks.json", "file://path/to/local/jwks.json" ], "scope_strategy": "wildcard" } }, "noop": { "enabled": true }, "oauth2_client_credentials": { "enabled": true, "config": { "token_url": "https://my-website.com/oauth2/token" } }, "oauth2_introspection": { "enabled": true, "config": { "introspection_url": "https://my-website.com/oauth2/introspection", "scope_strategy": "exact", "pre_authorization": { "enabled": true, "client_id": "some_id", "client_secret": "some_secret", "scope": ["foo", "bar"], "token_url": "https://my-website.com/oauth2/token" } } }, "unauthorized": { "enabled": true } }, "authorizers": { "allow": { "enabled": true }, "deny": { "enabled": true }, "keto_engine_acp_ory": { "enabled": true, "config": { "base_url": "http://my-keto/", "required_action": "unknown", "required_resource": "unknown" } } }, "mutators": { "header": { "enabled": false, "config": { "headers": { "foo": "bar" } } }, "cookie": { "enabled": true, "config": { "cookies": { "foo": "bar" } } }, "hydrator": { "enabled": true, "config": { "api": { "url": "https://some-url/" } } }, "id_token": { "enabled": true, "config": { "issuer_url": "https://my-oathkeeper/", "jwks_url": "https://fetch-keys/from/this/location.json", "ttl": "1h" } }, "noop": { "enabled": true } } } ================================================ FILE: oryx/configx/stub/domain-aliases/config.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "domain_aliases": { "title": "Domain Aliases", "description": "Adds an alias domain. If a request with the hostname (FQDN) matching the hostname in the alias is found, that URL is used as the base URL.", "type": "array", "items": [ { "additionalProperties": false, "type": "object", "required": ["match_domain", "base_path", "scheme"], "properties": { "match_domain": { "minLength": 1, "title": "Matching Domain", "description": "Sets the matching domain. If the domain matches with this entry, the accompanying base_url will be used.", "type": "string", "examples": ["localhost", "my-domain.com"] }, "scheme": { "title": "Scheme", "description": "Sets the scheme, for example https or http.", "type": "string", "enum": ["http", "https"] }, "base_path": { "minLength": 1, "title": "Base Path", "description": "Sets the base path for the matched domain.", "type": "string", "default": "/", "pattern": "^/.*$", "examples": ["/", "/.ory/kratos"] } } } ] } } } ================================================ FILE: oryx/configx/stub/from-files/a.yaml ================================================ version: v0.5.3-alpha.1 dsn: memory serve: public: base_url: http://127.0.0.1:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://127.0.0.1:4455/ whitelisted_return_urls: - http://127.0.0.1:4455 methods: password: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings ================================================ FILE: oryx/configx/stub/from-files/b.yaml ================================================ selfservice: flows: settings: privileged_session_max_age: 15m recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery verification: enabled: true ui_url: http://127.0.0.1:4455/verify after: default_browser_return_url: http://127.0.0.1:4455/ logout: after: default_browser_return_url: http://127.0.0.1:4455/auth/login login: ui_url: http://127.0.0.1:4455/auth/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/auth/registration after: password: hooks: - hook: session log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE hashers: argon2: parallelism: 1 memory: 131072 iterations: 2 salt_length: 16 key_length: 16 identity: default_schema_url: file:///etc/config/kratos/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: oryx/configx/stub/from-files/config.schema.json ================================================ { "$id": "https://github.com/ory/kratos/.schema/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "ORY Kratos Configuration", "type": "object", "definitions": { "defaultReturnTo": { "title": "Redirect browsers to set URL per default", "description": "ORY Kratos redirects to this URL per default on completion of self-service flows and other browser interaction. Read this [article for more information on browser redirects](https://www.ory.sh/kratos/docs/concepts/browser-redirect-flow-completion).", "type": "string", "format": "uri-reference", "minLength": 1, "examples": ["https://my-app.com/dashboard", "/dashboard"] }, "selfServiceSessionRevokerHook": { "type": "object", "properties": { "hook": { "const": "revoke_active_sessions" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceVerifyHook": { "type": "object", "properties": { "hook": { "const": "verify" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceSessionIssuerHook": { "type": "object", "properties": { "hook": { "const": "session" } }, "additionalProperties": false, "required": ["hook"] }, "OIDCClaims": { "title": "OpenID Connect claims", "description": "The OpenID Connect claims and optionally their properties which should be included in the id_token or returned from the UserInfo Endpoint.", "type": "object", "examples": [ { "id_token": { "email": null, "email_verified": null } }, { "userinfo": { "given_name": { "essential": true }, "nickname": null, "email": { "essential": true }, "email_verified": { "essential": true }, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": { "essential": true }, "acr": { "values": ["urn:mace:incommon:iap:silver"] } } } ], "patternProperties": { "^userinfo$|^id_token$": { "type": "object", "additionalProperties": false, "patternProperties": { ".*": { "oneOf": [ { "const": null, "description": "Indicates that this Claim is being requested in the default manner." }, { "type": "object", "additionalProperties": false, "properties": { "essential": { "description": "Indicates whether the Claim being requested is an Essential Claim.", "type": "boolean" }, "value": { "description": "Requests that the Claim be returned with a particular value.", "$comment": "There seem to be no constrains on value" }, "values": { "description": "Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.", "type": "array", "items": { "$comment": "There seem to be no constrains on individual items" } } } } ] } } } } }, "selfServiceOIDCProvider": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, gitlab, generic, google, microsoft, discord.", "type": "string", "enum": [ "github", "gitlab", "generic", "google", "microsoft", "discord" ], "examples": ["google"] }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] }, "requested_claims": { "$ref": "#/definitions/OIDCClaims" } }, "additionalProperties": false, "required": [ "id", "provider", "client_id", "client_secret", "mapper_url" ], "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["tenant"] }, "else": { "not": { "properties": { "tenant": {} }, "required": ["tenant"] } } }, "selfServiceAfterSettingsMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceVerifyHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterRegistrationMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionIssuerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterSettings": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterSettingsMethod" }, "profile": { "$ref": "#/definitions/selfServiceAfterSettingsMethod" } } }, "selfServiceAfterLogin": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterLoginMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterLoginMethod" } } }, "selfServiceAfterRegistration": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" } } } }, "properties": { "selfservice": { "type": "object", "additionalProperties": false, "required": ["default_browser_return_url"], "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "whitelisted_return_urls": { "title": "Whitelisted Return To URLs", "description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "examples": [ [ "https://app.my-app.com/dashboard", "/dashboard", "https://www.my-app.com/" ] ], "uniqueItems": true }, "flows": { "type": "object", "additionalProperties": false, "properties": { "settings": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "URL of the Settings page.", "description": "URL where the Settings UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/user/settings"], "default": "https://www.ory.sh/kratos/docs/fallback/settings" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "privileged_session_max_age": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterSettings" } } }, "logout": { "type": "object", "additionalProperties": false, "properties": { "after": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } } } } }, "registration": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Registration UI URL", "description": "URL where the Registration UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/signup"], "default": "https://www.ory.sh/kratos/docs/fallback/registration" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterRegistration" } } }, "login": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Login UI URL", "description": "URL where the Login UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/login"], "default": "https://www.ory.sh/kratos/docs/fallback/login" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterLogin" } } }, "verification": { "title": "Email and Phone Verification and Account Activation Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Email/Phone Verification", "description": "If set to true will enable [Email and Phone Verification and Account Activation](https://www.ory.sh/kratos/docs/self-service/flows/verify-email-account-activation/).", "default": false }, "ui_url": { "title": "Verify UI URL", "description": "URL where the ORY Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/verification" }, "after": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } }, "additionalProperties": false }, "lifespan": { "title": "Self-Service Verification Request Lifespan", "description": "Sets how long the verification request (for the UI interaction) is valid.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] } } }, "recovery": { "title": "Account Recovery Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Account Recovery", "description": "If set to true will enable [Account Recovery](https://www.ory.sh/kratos/docs/self-service/flows/password-reset-account-recovery/).", "default": false }, "ui_url": { "title": "Recovery UI URL", "description": "URL where the ORY Recovery UI is hosted. This is the page where users request and complete account recovery. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/recovery" }, "after": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } }, "additionalProperties": false }, "lifespan": { "title": "Self-Service Recovery Request Lifespan", "description": "Sets how long the recovery request is valid. If expired, the user has to redo the flow.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] } } }, "error": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "ORY Kratos Error UI URL", "description": "URL where the ORY Kratos Error UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/kratos-error"], "default": "https://www.ory.sh/kratos/docs/fallback/error" } } } } }, "methods": { "type": "object", "additionalProperties": false, "properties": { "profile": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Profile Management Method", "default": true } } }, "link": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Link Method", "default": true } } }, "password": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Username/Email and Password Method", "default": true } } }, "oidc": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables OpenID Connect Method", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.", "type": "array", "items": { "$ref": "#/definitions/selfServiceOIDCProvider" } } } } } } } } } }, "dsn": { "type": "string", "title": "Data Source Name", "description": "DSN is used to specify the database credentials as a connection URI.", "examples": [ "postgres://user: password@postgresd:5432/database?sslmode=disable&max_conns=20&max_idle_conns=4", "mysql://user:secret@tcp(mysqld:3306)/database?max_conns=20&max_idle_conns=4", "cockroach://user@cockroachdb:26257/database?sslmode=disable&max_conns=20&max_idle_conns=4", "sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc" ] }, "courier": { "type": "object", "title": "Courier configuration", "description": "The courier is responsible for sending and delivering messages over email, sms, and other means.", "properties": { "template_override_path": { "type": "string", "title": "Override message templates", "description": "You can override certain or all message templates by pointing this key to the path where the templates are located.", "examples": ["/conf/courier-templates"] }, "smtp": { "title": "SMTP Configuration", "description": "Configures outgoing emails using the SMTP protocol.", "type": "object", "properties": { "connection_uri": { "title": "SMTP connection string", "description": "This URI will be used to connect to the SMTP server. Use the query parameter to allow (`?skip_ssl_verify=true`) or disallow (`?skip_ssl_verify=false`) self-signed TLS certificates. Please keep in mind that any host other than localhost / 127.0.0.1 must use smtp over TLS (smtps) or the connection will not be possible.", "examples": [ "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=false" ], "type": "string", "format": "uri" }, "from_address": { "title": "SMTP Sender Address", "description": "The recipient of an email will see this as the sender address.", "type": "string", "format": "email", "default": "no-reply@ory.kratos.sh" } }, "required": ["connection_uri"], "additionalProperties": false } }, "required": ["smtp"], "additionalProperties": false }, "serve": { "type": "object", "properties": { "admin": { "type": "object", "properties": { "base_url": { "title": "Admin Base URL", "description": "The URL where the admin endpoint is exposed at.", "type": "string", "format": "uri", "examples": ["https://kratos.private-network:4434/"] }, "host": { "title": "Admin Host", "description": "The host (interface) kratos' admin endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Admin Port", "description": "The port kratos' admin endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4434], "default": 4434 } }, "additionalProperties": false }, "public": { "type": "object", "properties": { "cors": { "type": "object", "additionalProperties": false, "description": "Configures Cross Origin Resource Sharing for public endpoints.", "properties": { "enabled": { "type": "boolean", "description": "Sets whether CORS is enabled.", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "does match all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "default": ["*"], "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "default": [ "Authorization", "Content-Type", "X-Session-Token" ], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "default": ["Content-Type"], "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug server side CORS issues.", "default": false } } }, "base_url": { "title": "Public Base URL", "description": "The URL where the public endpoint is exposed at.", "type": "string", "format": "uri-reference", "examples": [ "https://my-app.com/.ory/kratos/public", "/.ory/kratos/public/" ] }, "host": { "title": "Public Host", "description": "The host (interface) kratos' public endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Public Port", "description": "The port kratos' public endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4433], "default": 4433 } }, "additionalProperties": false } }, "additionalProperties": false }, "log": { "type": "object", "properties": { "level": { "type": "string", "enum": [ "trace", "debug", "info", "warning", "error", "fatal", "panic" ] }, "leak_sensitive_values": { "type": "boolean", "title": "Leak Sensitive Log Values", "description": "If set will leak sensitive values (e.g. emails) in the logs." }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "format": { "type": "string", "enum": ["json", "text"] } }, "additionalProperties": false }, "identity": { "type": "object", "properties": { "default_schema_url": { "title": "JSON Schema URL for default identity traits", "description": "Path to the JSON Schema which describes a default identity's traits.", "type": "string", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json" ] }, "schemas": { "type": "array", "title": "Additional JSON Schemas for Identity Traits", "examples": [ [ { "id": "customer", "url": "https://foo.bar.com/path/to/customer.traits.schema.json" }, { "id": "employee", "url": "https://foo.bar.com/path/to/employee.traits.schema.json" }, { "id": "employee-v2", "url": "https://foo.bar.com/path/to/employee.v2.traits.schema.json" } ] ], "items": { "type": "object", "properties": { "id": { "title": "The schema's ID.", "type": "string", "examples": ["employee"] }, "url": { "type": "string", "title": "Path to the JSON Schema", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json" ] } }, "required": ["id", "url"], "not": { "type": "object", "properties": { "id": { "const": "default" } }, "additionalProperties": true } } } }, "required": ["default_schema_url"], "additionalProperties": false }, "secrets": { "type": "object", "properties": { "default": { "type": "array", "title": "Default Encryption Signing Secrets", "description": "The first secret in the array is used for singing and encrypting things while all other keys are used to verify and decrypt older things that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "cookie": { "type": "array", "title": "Singing Keys for Cookies", "description": "The first secret in the array is used for encrypting cookies while all other keys are used to decrypt older cookies that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true } }, "additionalProperties": false }, "hashers": { "title": "Hashing Algorithm Configuration", "type": "object", "properties": { "argon2": { "title": "Configuration for the Argon2id hasher.", "type": "object", "properties": { "memory": { "type": "integer", "minimum": 16384 }, "iterations": { "type": "integer", "minimum": 1 }, "parallelism": { "type": "integer", "minimum": 1 }, "salt_length": { "type": "integer", "minimum": 16 }, "key_length": { "type": "integer", "minimum": 16 } }, "additionalProperties": false } }, "additionalProperties": false }, "session": { "type": "object", "additionalProperties": false, "properties": { "lifespan": { "title": "Session Lifespan", "description": "Defines how long a session is active. Once that lifespan has been reached, the user needs to sign in again.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "24h", "examples": ["1h", "1m", "1s"] }, "cookie": { "type": "object", "properties": { "domain": { "title": "Session Cookie Domain", "description": "Sets the session cookie domain. Useful when dealing with subdomains. Use with care!", "type": "string" }, "persistent": { "title": "Make Session Cookie Persistent", "description": "If set to true will persist the cookie in the end-user's browser using the `max-age` parameter which is set to the `session.lifespan` value. Persistent cookies are not deleted when the browser is closed (e.g. on reboot or alt+f4).", "type": "boolean", "default": true }, "path": { "title": "Session Cookie Path", "description": "Sets the session cookie path. Use with care!", "type": "string", "default": "/" }, "same_site": { "title": "Cookie Same Site Configuration", "type": "string", "enum": ["Strict", "Lax", "None"], "default": "Lax" } }, "additionalProperties": false } } }, "version": { "title": "The kratos version this config is written for.", "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "type": "string", "pattern": "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "examples": ["v0.5.0-alpha.1"] } }, "allOf": [ { "if": { "properties": { "selfservice": { "properties": { "flows": { "oneOf": [ { "properties": { "verification": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["verification"] }, { "properties": { "recovery": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["recovery"] } ] } }, "required": ["flows"] } }, "required": ["selfservice"] }, "then": { "required": ["courier"] } } ], "required": ["identity", "dsn", "selfservice"] } ================================================ FILE: oryx/configx/stub/from-files/expected.json ================================================ { "courier": { "smtp": { "connection_uri": "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true", "from_address": "no-reply@ory.kratos.sh" } }, "dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true", "hashers": { "argon2": { "iterations": 2, "key_length": 16, "memory": 131072, "parallelism": 1, "salt_length": 16 } }, "identity": { "default_schema_url": "file:///etc/config/kratos/identity.schema.json" }, "log": { "format": "text", "leak_sensitive_values": true, "level": "debug" }, "secrets": { "cookie": ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"] }, "selfservice": { "default_browser_return_url": "http://127.0.0.1:4455/", "flows": { "error": { "ui_url": "http://127.0.0.1:4455/error" }, "login": { "lifespan": "10m", "ui_url": "http://127.0.0.1:4455/auth/login" }, "logout": { "after": { "default_browser_return_url": "http://127.0.0.1:4455/auth/login" } }, "recovery": { "enabled": true, "lifespan": "1h", "ui_url": "http://127.0.0.1:4455/recovery" }, "registration": { "after": { "password": { "hooks": [ { "hook": "session" } ] } }, "lifespan": "10m", "ui_url": "http://127.0.0.1:4455/auth/registration" }, "settings": { "lifespan": "1h", "privileged_session_max_age": "15m", "ui_url": "http://127.0.0.1:4455/settings" }, "verification": { "after": { "default_browser_return_url": "http://127.0.0.1:4455/" }, "enabled": true, "lifespan": "1h", "ui_url": "http://127.0.0.1:4455/verify" } }, "methods": { "link": { "enabled": true }, "oidc": { "enabled": false }, "password": { "enabled": true }, "profile": { "enabled": true } }, "whitelisted_return_urls": ["http://127.0.0.1:4455"] }, "serve": { "admin": { "base_url": "http://kratos:4434/", "host": "0.0.0.0", "port": 4434 }, "public": { "base_url": "http://127.0.0.1:4433/", "cors": { "allow_credentials": true, "allowed_headers": ["Authorization", "Content-Type", "X-Session-Token"], "allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"], "allowed_origins": ["*"], "debug": false, "enabled": true, "exposed_headers": ["Content-Type"], "max_age": 0, "options_passthrough": false }, "host": "0.0.0.0", "port": 4433 } }, "session": { "cookie": { "path": "/", "persistent": true, "same_site": "Lax" }, "lifespan": "24h" }, "version": "v0.5.3-alpha.1" } ================================================ FILE: oryx/configx/stub/hydra/config.schema.json ================================================ { "$id": "https://github.com/ory/hydra/docs/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "ORY Hydra Configuration", "type": "object", "definitions": { "http_method": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] }, "port_number": { "type": "integer", "description": "The port to listen on.", "minimum": 1, "maximum": 65535 }, "socket": { "type": "object", "additionalProperties": false, "description": "Sets the permissions of the unix socket", "properties": { "owner": { "type": "string", "description": "Owner of unix socket. If empty, the owner will be the user running hydra.", "default": "" }, "group": { "type": "string", "description": "Group of unix socket. If empty, the group will be the primary group of the user running hydra.", "default": "" }, "mode": { "type": "integer", "description": "Mode of unix socket in numeric form", "default": 493, "minimum": 0, "maximum": 511 } } }, "cors": { "type": "object", "additionalProperties": false, "description": "Configures Cross Origin Resource Sharing for public endpoints.", "properties": { "enabled": { "type": "boolean", "description": "Sets whether CORS is enabled.", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "does match all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "default": ["*"], "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "default": ["Authorization", "Content-Type"], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "default": ["Content-Type"], "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug server side CORS issues.", "default": false } } }, "pem_file": { "type": "object", "oneOf": [ { "properties": { "path": { "type": "string", "description": "The path to the pem file.", "examples": ["/path/to/file.pem"] } }, "additionalProperties": false, "required": ["path"] }, { "properties": { "base64": { "type": "string", "description": "The base64 encoded string (without padding).", "contentEncoding": "base64", "contentMediaType": "application/x-pem-file", "examples": ["b3J5IGh5ZHJhIGlzIGF3ZXNvbWUK"] } }, "additionalProperties": false, "required": ["base64"] } ] }, "duration": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "examples": ["1h"] } }, "properties": { "log": { "type": "object", "additionalProperties": false, "description": "Configures the logger", "properties": { "level": { "type": "string", "description": "Sets the log level.", "enum": ["panic", "fatal", "error", "warn", "info", "debug", "trace"], "default": "info" }, "leak_sensitive_values": { "type": "boolean", "description": "Logs sensitive values such as cookie and URL parameter.", "default": false }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "format": { "type": "string", "description": "Sets the log format.", "enum": ["json", "json_pretty", "text"], "default": "text" } } }, "serve": { "type": "object", "additionalProperties": false, "description": "Controls the configuration for the http(s) daemon(s).", "properties": { "public": { "type": "object", "additionalProperties": false, "description": "Controls the public daemon serving public API endpoints like /oauth2/auth, /oauth2/token, /.well-known/jwks.json", "properties": { "port": { "default": 4444, "allOf": [ { "$ref": "#/definitions/port_number" } ] }, "host": { "type": "string", "description": "The interface or unix socket ORY Hydra should listen and handle public API requests on. Use the prefix \"unix:\" to specify a path to a unix socket. Leave empty to listen on all interfaces.", "default": "", "examples": ["localhost"] }, "cors": { "$ref": "#/definitions/cors" }, "socket": { "$ref": "#/definitions/socket" }, "access_log": { "type": "object", "additionalProperties": false, "description": "Access Log configuration for public server.", "properties": { "disable_for_health": { "type": "boolean", "description": "Disable access log for health endpoints.", "default": false } } } } }, "admin": { "type": "object", "additionalProperties": false, "properties": { "port": { "default": 4445, "allOf": [ { "$ref": "#/definitions/port_number" } ] }, "host": { "type": "string", "description": "The interface or unix socket ORY Hydra should listen and handle administrative API requests on. Use the prefix \"unix:\" to specify a path to a unix socket. Leave empty to listen on all interfaces.", "default": "", "examples": ["localhost"] }, "cors": { "$ref": "#/definitions/cors" }, "socket": { "$ref": "#/definitions/socket" }, "access_log": { "type": "object", "additionalProperties": false, "description": "Access Log configuration for admin server.", "properties": { "disable_for_health": { "type": "boolean", "description": "Disable access log for health endpoints.", "default": false } } } } }, "tls": { "type": "object", "additionalProperties": false, "description": "Configures HTTPS (HTTP over TLS). If configured, the server automatically supports HTTP/2.", "properties": { "key": { "description": "Configures the private key (pem encoded).", "allOf": [ { "$ref": "#/definitions/pem_file" } ] }, "cert": { "description": "Configures the private key (pem encoded).", "allOf": [ { "$ref": "#/definitions/pem_file" } ] }, "allow_termination_from": { "type": "array", "description": "Whitelist one or multiple CIDR address ranges and allow them to terminate TLS connections. Be aware that the X-Forwarded-Proto header must be set and must never be modifiable by anyone but your proxy / gateway / load balancer. Supports ipv4 and ipv6. Hydra serves http instead of https when this option is set.", "items": { "type": "string", "oneOf": [ { "pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, { "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$" } ], "examples": ["127.0.0.1/32"] } } } }, "cookies": { "type": "object", "additionalProperties": false, "properties": { "same_site_mode": { "type": "string", "description": "Specify the SameSite mode that cookies should be sent with.", "enum": ["Strict", "Lax", "None"], "default": "None" }, "same_site_legacy_workaround": { "type": "boolean", "description": "Some older browser versions don’t work with SameSite=None. This option enables the workaround defined in https://web.dev/samesite-cookie-recipes/ which essentially stores a second cookie without SameSite as a fallback.", "default": false, "examples": [true] } } } } }, "dsn": { "type": "string", "description": "Sets the data source name. This configures the backend where ORY Hydra persists data. If dsn is \"memory\", data will be written to memory and is lost when you restart this instance. ORY Hydra supports popular SQL databases. For more detailed configuration information go to: https://www.ory.sh/docs/hydra/dependencies-environment#sql" }, "webfinger": { "type": "object", "additionalProperties": false, "description": "Configures ./well-known/ settings.", "properties": { "jwks": { "type": "object", "additionalProperties": false, "description": "Configures the /.well-known/jwks.json endpoint.", "properties": { "broadcast_keys": { "type": "array", "description": "A list of JSON Web Keys that should be exposed at that endpoint. This is usually the public key for verifying OpenID Connect ID Tokens. However, you might want to add additional keys here as well.", "items": { "type": "string" }, "default": ["hydra.openid.id-token"], "examples": ["hydra.jwt.access-token"] } } }, "oidc_discovery": { "type": "object", "additionalProperties": false, "description": "Configures OpenID Connect Discovery (/.well-known/openid-configuration).", "properties": { "jwks_url": { "type": "string", "description": "Overwrites the JWKS URL", "format": "uri", "examples": ["https://my-service.com/.well-known/jwks.json"] }, "token_url": { "type": "string", "description": "Overwrites the OAuth2 Token URL", "format": "uri", "examples": ["https://my-service.com/oauth2/token"] }, "auth_url": { "type": "string", "description": "Overwrites the OAuth2 Auth URL", "format": "uri", "examples": ["https://my-service.com/oauth2/auth"] }, "client_registration_url": { "description": "Sets the OpenID Connect Dynamic Client Registration Endpoint", "type": "string", "format": "uri", "examples": ["https://my-service.com/clients"] }, "supported_claims": { "type": "array", "description": "A list of supported claims to be broadcasted. Claim \"sub\" is always included.", "items": { "type": "string" }, "examples": [["email", "username"]] }, "supported_scope": { "type": "array", "description": "The scope OAuth 2.0 Clients may request. Scope `offline`, `offline_access`, and `openid` are always included.", "items": { "type": "string" }, "examples": [["email", "whatever", "read.photos"]] }, "userinfo_url": { "type": "string", "description": "A URL of the userinfo endpoint to be advertised at the OpenID Connect Discovery endpoint /.well-known/openid-configuration. Defaults to ORY Hydra's userinfo endpoint at /userinfo. Set this value if you want to handle this endpoint yourself.", "format": "uri", "examples": ["https://example.org/my-custom-userinfo-endpoint"] } } } } }, "oidc": { "type": "object", "additionalProperties": false, "description": "Configures OpenID Connect features.", "properties": { "subject_identifiers": { "type": "object", "additionalProperties": false, "description": "Configures the Subject Identifier algorithm. For more information please head over to the documentation: https://www.ory.sh/docs/hydra/advanced#subject-identifier-algorithms", "properties": { "enabled": { "type": "array", "description": "A list of algorithms to enable.", "items": { "type": "string", "enum": ["public", "pairwise"] } }, "pairwise": { "type": "object", "additionalProperties": false, "description": "Configures the pairwise algorithm.", "properties": { "salt": { "type": "string" } }, "required": ["salt"] } }, "if": { "properties": { "enabled": { "contains": { "const": "pairwise" } } } }, "then": { "required": ["pairwise"] }, "else": { "properties": { "pairwise": { "$comment": "This enforces pairwise to not be set if 'enabled' does not contain 'pairwise'", "not": {} } } }, "examples": [ { "enabled": ["public", "pairwise"], "pairwise": { "salt": "some-random-salt" } } ] }, "dynamic_client_registration": { "type": "object", "additionalProperties": false, "description": "Configures OpenID Connect Dynamic Client Registration (exposed as admin endpoints /clients/...).", "properties": { "default_scope": { "type": "array", "description": "The OpenID Connect Dynamic Client Registration specification has no concept of whitelisting OAuth 2.0 Scope. If you want to expose Dynamic Client Registration, you should set the default scope enabled for newly registered clients. Keep in mind that users can overwrite this default by setting the \"scope\" key in the registration payload, effectively disabling the concept of whitelisted scopes.", "items": { "type": "string" }, "examples": [["openid", "offline", "offline_access"]] } } } } }, "urls": { "type": "object", "additionalProperties": false, "properties": { "self": { "type": "object", "additionalProperties": false, "properties": { "issuer": { "type": "string", "description": "This value will be used as the \"issuer\" in access and ID tokens. It must be specified and using HTTPS protocol, unless --dangerous-force-http is set. This should typically be equal to the public value.", "format": "uri", "examples": ["https://localhost:4444/"] }, "public": { "type": "string", "description": "This is the base location of the public endpoints of your ORY Hydra installation. This should typically be equal to the issuer value. If left unspecified, it falls back to the issuer value.", "format": "uri", "examples": ["https://localhost:4444/"] } } }, "login": { "type": "string", "description": "Sets the login endpoint of the User Login & Consent flow. Defaults to an internal fallback URL showing an error.", "format": "uri", "examples": ["https://my-login.app/login"] }, "consent": { "type": "string", "description": "Sets the consent endpoint of the User Login & Consent flow. Defaults to an internal fallback URL showing an error.", "format": "uri", "examples": ["https://my-consent.app/consent"] }, "logout": { "type": "string", "description": "Sets the logout endpoint. Defaults to an internal fallback URL showing an error.", "format": "uri", "examples": ["https://my-logout.app/logout"] }, "error": { "type": "string", "description": "Sets the error endpoint. The error ui will be shown when an OAuth2 error occurs that which can not be sent back to the client. Defaults to an internal fallback URL showing an error.", "format": "uri", "examples": ["https://my-error.app/error"] }, "post_logout_redirect": { "type": "string", "description": "When a user agent requests to logout, it will be redirected to this url afterwards per default.", "format": "uri", "examples": ["https://my-example.app/logout-successful"] } } }, "strategies": { "type": "object", "additionalProperties": false, "properties": { "scope": { "type": "string", "description": "Defines how scopes are matched. For more details have a look at https://github.com/ory/fosite#scopes", "enum": [ "exact", "wildcard", "DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY" ], "default": "wildcard" }, "access_token": { "type": "string", "description": "Defines access token type. jwt is a bad idea, see https://www.ory.sh/docs/hydra/advanced#json-web-tokens", "enum": ["opaque", "jwt"] } } }, "ttl": { "type": "object", "additionalProperties": false, "description": "Configures time to live.", "properties": { "login_consent_request": { "description": "Configures how long a user login and consent flow may take.", "default": "1h", "allOf": [ { "$ref": "#/definitions/duration" } ] }, "access_token": { "description": "Configures how long access tokens are valid.", "default": "1h", "allOf": [ { "$ref": "#/definitions/duration" } ] }, "refresh_token": { "description": "Configures how long refresh tokens are valid. Set to -1 for refresh tokens to never expire.", "default": "720h", "oneOf": [ { "$ref": "#/definitions/duration" }, { "enum": ["-1", -1] } ] }, "id_token": { "description": "Configures how long id tokens are valid.", "default": "1h", "allOf": [ { "$ref": "#/definitions/duration" } ] }, "auth_code": { "description": "Configures how long auth codes are valid.", "default": "10m", "allOf": [ { "$ref": "#/definitions/duration" } ] } } }, "oauth2": { "type": "object", "additionalProperties": false, "properties": { "expose_internal_errors": { "type": "boolean", "description": "Set this to true if you want to share error debugging information with your OAuth 2.0 clients. Keep in mind that debug information is very valuable when dealing with errors, but might also expose database error codes and similar errors.", "default": false, "examples": [true] }, "session": { "type": "object", "properties": { "encrypt_at_rest": { "type": "boolean", "default": true, "title": "Encrypt OAuth2 Session", "description": "If set to true (default) ORY Hydra encrypt OAuth2 and OpenID Connect session data using AES-GCM and the system secret before persisting it in the database." } } }, "include_legacy_error_fields": { "type": "boolean", "description": "Set this to true if you want to include the `error_hint` and `error_debug` legacy fields in error responses. We recommend to set this to `false` unless you have clients using these fields.", "default": false, "examples": [true] }, "hashers": { "type": "object", "additionalProperties": false, "description": "Configures hashing algorithms. Supports only BCrypt at the moment.", "properties": { "bcrypt": { "type": "object", "additionalProperties": false, "description": "Configures the BCrypt hashing algorithm used for hashing Client Secrets.", "properties": { "cost": { "type": "integer", "description": "Sets the BCrypt cost. The higher the value, the more CPU time is being used to generate hashes.", "default": 10, "minimum": 4, "maximum": 31 } } } } }, "pkce": { "type": "object", "additionalProperties": false, "properties": { "enforced": { "type": "boolean", "description": "Sets whether PKCE should be enforced for all clients.", "examples": [true] }, "enforced_for_public_clients": { "type": "boolean", "description": "Sets whether PKCE should be enforced for public clients.", "examples": [true] } } }, "client_credentials": { "type": "object", "additionalProperties": false, "properties": { "default_grant_allowed_scope": { "type": "boolean", "description": "Defines how scopes are added if the request doesn't contains any scope", "examples": [false] } } } } }, "secrets": { "type": "object", "additionalProperties": false, "description": "The secrets section configures secrets used for encryption and signing of several systems. All secrets can be rotated, for more information on this topic go to: https://www.ory.sh/docs/hydra/advanced#rotation-of-hmac-token-signing-and-database-and-cookie-encryption-keys", "properties": { "system": { "description": "The system secret must be at least 16 characters long. If none is provided, one will be generated. They key is used to encrypt sensitive data using AES-GCM (256 bit) and validate HMAC signatures. The first item in the list is used for signing and encryption. The whole list is used for verifying signatures and decryption.", "type": "array", "items": { "type": "string", "minLength": 16 }, "examples": [ [ "this-is-the-primary-secret", "this-is-an-old-secret", "this-is-another-old-secret" ] ] }, "cookie": { "type": "array", "description": "A secret that is used to encrypt cookie sessions. Defaults to secrets.system. It is recommended to use a separate secret in production. The first item in the list is used for signing and encryption. The whole list is used for verifying signatures and decryption.", "items": { "type": "string", "minLength": 16 }, "examples": [ [ "this-is-the-primary-secret", "this-is-an-old-secret", "this-is-another-old-secret" ] ] } } }, "profiling": { "type": "string", "description": "Enables profiling if set. For more details on profiling, head over to: https://blog.golang.org/profiling-go-programs", "enum": ["cpu", "mem"], "examples": ["cpu"] }, "tracing": { "$ref": "ory://tracing-config" }, "version": { "type": "string", "title": "The Hydra version this config is written for.", "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "pattern": "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, "cgroups": { "type": "object", "additionalProperties": false, "description": "ORY Hydra can respect Linux container CPU quota", "properties": { "v1": { "type": "object", "additionalProperties": false, "description": "Configures parameters using cgroups v1 hierarchy", "properties": { "auto_max_procs_enabled": { "type": "boolean", "description": "Set GOMAXPROCS automatically according to cgroups limits", "default": false, "examples": [true] } } } } } }, "required": ["dsn"] } ================================================ FILE: oryx/configx/stub/hydra/expected.json ================================================ { "cgroups": { "v1": { "auto_max_procs_enabled": false } }, "dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true", "log": { "format": "text", "leak_sensitive_values": false, "level": "info" }, "oauth2": { "expose_internal_errors": false, "hashers": { "bcrypt": { "cost": 10 } }, "include_legacy_error_fields": false, "session": { "encrypt_at_rest": true } }, "oidc": { "subject_identifiers": { "enabled": ["pairwise", "public"], "pairwise": { "salt": "youReallyNeedToChangeThis" } } }, "secrets": { "system": ["youReallyNeedToChangeThis"] }, "serve": { "admin": { "access_log": { "disable_for_health": false }, "cors": { "allow_credentials": true, "allowed_headers": ["Authorization", "Content-Type"], "allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"], "allowed_origins": ["*"], "debug": false, "enabled": false, "exposed_headers": ["Content-Type"], "max_age": 0, "options_passthrough": false }, "host": "", "port": 4445, "socket": { "group": "", "mode": 493, "owner": "" } }, "cookies": { "same_site_legacy_workaround": false, "same_site_mode": "Lax" }, "public": { "access_log": { "disable_for_health": false }, "cors": { "allow_credentials": true, "allowed_headers": ["Authorization", "Content-Type"], "allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"], "allowed_origins": ["*"], "debug": false, "enabled": false, "exposed_headers": ["Content-Type"], "max_age": 0, "options_passthrough": false }, "host": "", "port": 4444, "socket": { "group": "", "mode": 493, "owner": "" } } }, "strategies": { "scope": "wildcard" }, "tracing": { "provider": "jaeger", "providers": { "jaeger": { "local_agent_address": "jaeger:6831", "sampling": { "server_url": "http://jaeger:5778/sampling" } } } }, "ttl": { "access_token": "1h", "auth_code": "10m", "id_token": "1h", "login_consent_request": "1h", "refresh_token": "720h" }, "urls": { "consent": "http://127.0.0.1:3000/consent", "login": "http://127.0.0.1:3000/login", "logout": "http://127.0.0.1:3000/logout", "self": { "issuer": "http://127.0.0.1:4444" } }, "webfinger": { "jwks": { "broadcast_keys": ["hydra.openid.id-token"] } } } ================================================ FILE: oryx/configx/stub/hydra/hydra.yaml ================================================ serve: cookies: same_site_mode: Lax urls: self: issuer: http://127.0.0.1:4444 consent: http://127.0.0.1:3000/consent login: http://127.0.0.1:3000/login logout: http://127.0.0.1:3000/logout secrets: system: - youReallyNeedToChangeThis oidc: subject_identifiers: enabled: - pairwise - public pairwise: salt: youReallyNeedToChangeThis ================================================ FILE: oryx/configx/stub/kratos/config.schema.json ================================================ { "$id": "https://github.com/ory/kratos/.schema/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "ORY Kratos Configuration", "type": "object", "definitions": { "defaultReturnTo": { "title": "Redirect browsers to set URL per default", "description": "ORY Kratos redirects to this URL per default on completion of self-service flows and other browser interaction. Read this [article for more information on browser redirects](https://www.ory.sh/kratos/docs/concepts/browser-redirect-flow-completion).", "type": "string", "format": "uri-reference", "minLength": 1, "examples": ["https://my-app.com/dashboard", "/dashboard"] }, "selfServiceSessionRevokerHook": { "type": "object", "properties": { "hook": { "const": "revoke_active_sessions" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceVerifyHook": { "type": "object", "properties": { "hook": { "const": "verify" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceSessionIssuerHook": { "type": "object", "properties": { "hook": { "const": "session" } }, "additionalProperties": false, "required": ["hook"] }, "OIDCClaims": { "title": "OpenID Connect claims", "description": "The OpenID Connect claims and optionally their properties which should be included in the id_token or returned from the UserInfo Endpoint.", "type": "object", "examples": [ { "id_token": { "email": null, "email_verified": null } }, { "userinfo": { "given_name": { "essential": true }, "nickname": null, "email": { "essential": true }, "email_verified": { "essential": true }, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": { "essential": true }, "acr": { "values": ["urn:mace:incommon:iap:silver"] } } } ], "patternProperties": { "^userinfo$|^id_token$": { "type": "object", "additionalProperties": false, "patternProperties": { ".*": { "oneOf": [ { "const": null, "description": "Indicates that this Claim is being requested in the default manner." }, { "type": "object", "additionalProperties": false, "properties": { "essential": { "description": "Indicates whether the Claim being requested is an Essential Claim.", "type": "boolean" }, "value": { "description": "Requests that the Claim be returned with a particular value.", "$comment": "There seem to be no constrains on value" }, "values": { "description": "Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.", "type": "array", "items": { "$comment": "There seem to be no constrains on individual items" } } } } ] } } } } }, "selfServiceOIDCProvider": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, gitlab, generic, google, microsoft, discord.", "type": "string", "enum": [ "github", "gitlab", "generic", "google", "microsoft", "discord" ], "examples": ["google"] }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] }, "requested_claims": { "$ref": "#/definitions/OIDCClaims" } }, "additionalProperties": false, "required": [ "id", "provider", "client_id", "client_secret", "mapper_url" ], "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["tenant"] }, "else": { "not": { "properties": { "tenant": {} }, "required": ["tenant"] } } }, "selfServiceAfterSettingsMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceVerifyHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterRegistrationMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionIssuerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterSettings": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterSettingsMethod" }, "profile": { "$ref": "#/definitions/selfServiceAfterSettingsMethod" } } }, "selfServiceAfterLogin": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterLoginMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterLoginMethod" } } }, "selfServiceAfterRegistration": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" } } } }, "properties": { "selfservice": { "type": "object", "additionalProperties": false, "required": ["default_browser_return_url"], "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "whitelisted_return_urls": { "title": "Whitelisted Return To URLs", "description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "examples": [ [ "https://app.my-app.com/dashboard", "/dashboard", "https://www.my-app.com/" ] ], "uniqueItems": true }, "flows": { "type": "object", "additionalProperties": false, "properties": { "settings": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "URL of the Settings page.", "description": "URL where the Settings UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/user/settings"], "default": "https://www.ory.sh/kratos/docs/fallback/settings" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "privileged_session_max_age": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterSettings" } } }, "logout": { "type": "object", "additionalProperties": false, "properties": { "after": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } } } } }, "registration": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Registration UI URL", "description": "URL where the Registration UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/signup"], "default": "https://www.ory.sh/kratos/docs/fallback/registration" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterRegistration" } } }, "login": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Login UI URL", "description": "URL where the Login UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/login"], "default": "https://www.ory.sh/kratos/docs/fallback/login" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterLogin" } } }, "verification": { "title": "Email and Phone Verification and Account Activation Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Email/Phone Verification", "description": "If set to true will enable [Email and Phone Verification and Account Activation](https://www.ory.sh/kratos/docs/self-service/flows/verify-email-account-activation/).", "default": false }, "ui_url": { "title": "Verify UI URL", "description": "URL where the ORY Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/verification" }, "after": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } }, "additionalProperties": false }, "lifespan": { "title": "Self-Service Verification Request Lifespan", "description": "Sets how long the verification request (for the UI interaction) is valid.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] } } }, "recovery": { "title": "Account Recovery Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Account Recovery", "description": "If set to true will enable [Account Recovery](https://www.ory.sh/kratos/docs/self-service/flows/password-reset-account-recovery/).", "default": false }, "ui_url": { "title": "Recovery UI URL", "description": "URL where the ORY Recovery UI is hosted. This is the page where users request and complete account recovery. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/recovery" }, "after": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } }, "additionalProperties": false }, "lifespan": { "title": "Self-Service Recovery Request Lifespan", "description": "Sets how long the recovery request is valid. If expired, the user has to redo the flow.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] } } }, "error": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "ORY Kratos Error UI URL", "description": "URL where the ORY Kratos Error UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/kratos-error"], "default": "https://www.ory.sh/kratos/docs/fallback/error" } } } } }, "methods": { "type": "object", "additionalProperties": false, "properties": { "profile": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Profile Management Method", "default": true } } }, "link": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Link Method", "default": true } } }, "password": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Username/Email and Password Method", "default": true } } }, "oidc": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables OpenID Connect Method", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.", "type": "array", "items": { "$ref": "#/definitions/selfServiceOIDCProvider" } } } } } } } } } }, "dsn": { "type": "string", "title": "Data Source Name", "description": "DSN is used to specify the database credentials as a connection URI.", "examples": [ "postgres://user: password@postgresd:5432/database?sslmode=disable&max_conns=20&max_idle_conns=4", "mysql://user:secret@tcp(mysqld:3306)/database?max_conns=20&max_idle_conns=4", "cockroach://user@cockroachdb:26257/database?sslmode=disable&max_conns=20&max_idle_conns=4", "sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc" ] }, "courier": { "type": "object", "title": "Courier configuration", "description": "The courier is responsible for sending and delivering messages over email, sms, and other means.", "properties": { "template_override_path": { "type": "string", "title": "Override message templates", "description": "You can override certain or all message templates by pointing this key to the path where the templates are located.", "examples": ["/conf/courier-templates"] }, "smtp": { "title": "SMTP Configuration", "description": "Configures outgoing emails using the SMTP protocol.", "type": "object", "properties": { "connection_uri": { "title": "SMTP connection string", "description": "This URI will be used to connect to the SMTP server. Use the query parameter to allow (`?skip_ssl_verify=true`) or disallow (`?skip_ssl_verify=false`) self-signed TLS certificates. Please keep in mind that any host other than localhost / 127.0.0.1 must use smtp over TLS (smtps) or the connection will not be possible.", "examples": [ "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=false" ], "type": "string", "format": "uri" }, "from_address": { "title": "SMTP Sender Address", "description": "The recipient of an email will see this as the sender address.", "type": "string", "format": "email", "default": "no-reply@ory.kratos.sh" } }, "required": ["connection_uri"], "additionalProperties": false } }, "required": ["smtp"], "additionalProperties": false }, "serve": { "type": "object", "properties": { "admin": { "type": "object", "properties": { "base_url": { "title": "Admin Base URL", "description": "The URL where the admin endpoint is exposed at.", "type": "string", "format": "uri", "examples": ["https://kratos.private-network:4434/"] }, "host": { "title": "Admin Host", "description": "The host (interface) kratos' admin endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Admin Port", "description": "The port kratos' admin endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4434], "default": 4434 } }, "additionalProperties": false }, "public": { "type": "object", "properties": { "cors": { "type": "object", "additionalProperties": false, "description": "Configures Cross Origin Resource Sharing for public endpoints.", "properties": { "enabled": { "type": "boolean", "description": "Sets whether CORS is enabled.", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "does match all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "default": ["*"], "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "default": [ "Authorization", "Content-Type", "X-Session-Token" ], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "default": ["Content-Type"], "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug server side CORS issues.", "default": false } } }, "base_url": { "title": "Public Base URL", "description": "The URL where the public endpoint is exposed at.", "type": "string", "format": "uri-reference", "examples": [ "https://my-app.com/.ory/kratos/public", "/.ory/kratos/public/" ] }, "host": { "title": "Public Host", "description": "The host (interface) kratos' public endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Public Port", "description": "The port kratos' public endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4433], "default": 4433 } }, "additionalProperties": false } }, "additionalProperties": false }, "log": { "type": "object", "properties": { "level": { "type": "string", "enum": [ "trace", "debug", "info", "warning", "error", "fatal", "panic" ] }, "leak_sensitive_values": { "type": "boolean", "title": "Leak Sensitive Log Values", "description": "If set will leak sensitive values (e.g. emails) in the logs." }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "format": { "type": "string", "enum": ["json", "text"] } }, "additionalProperties": false }, "identity": { "type": "object", "properties": { "default_schema_url": { "title": "JSON Schema URL for default identity traits", "description": "Path to the JSON Schema which describes a default identity's traits.", "type": "string", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json" ] }, "schemas": { "type": "array", "title": "Additional JSON Schemas for Identity Traits", "examples": [ [ { "id": "customer", "url": "https://foo.bar.com/path/to/customer.traits.schema.json" }, { "id": "employee", "url": "https://foo.bar.com/path/to/employee.traits.schema.json" }, { "id": "employee-v2", "url": "https://foo.bar.com/path/to/employee.v2.traits.schema.json" } ] ], "items": { "type": "object", "properties": { "id": { "title": "The schema's ID.", "type": "string", "examples": ["employee"] }, "url": { "type": "string", "title": "Path to the JSON Schema", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json" ] } }, "required": ["id", "url"], "not": { "type": "object", "properties": { "id": { "const": "default" } }, "additionalProperties": true } } } }, "required": ["default_schema_url"], "additionalProperties": false }, "secrets": { "type": "object", "properties": { "default": { "type": "array", "title": "Default Encryption Signing Secrets", "description": "The first secret in the array is used for singing and encrypting things while all other keys are used to verify and decrypt older things that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "cookie": { "type": "array", "title": "Singing Keys for Cookies", "description": "The first secret in the array is used for encrypting cookies while all other keys are used to decrypt older cookies that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true } }, "additionalProperties": false }, "hashers": { "title": "Hashing Algorithm Configuration", "type": "object", "properties": { "argon2": { "title": "Configuration for the Argon2id hasher.", "type": "object", "properties": { "memory": { "type": "integer", "minimum": 16384 }, "iterations": { "type": "integer", "minimum": 1 }, "parallelism": { "type": "integer", "minimum": 1 }, "salt_length": { "type": "integer", "minimum": 16 }, "key_length": { "type": "integer", "minimum": 16 } }, "additionalProperties": false } }, "additionalProperties": false }, "session": { "type": "object", "additionalProperties": false, "properties": { "lifespan": { "title": "Session Lifespan", "description": "Defines how long a session is active. Once that lifespan has been reached, the user needs to sign in again.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "24h", "examples": ["1h", "1m", "1s"] }, "cookie": { "type": "object", "properties": { "domain": { "title": "Session Cookie Domain", "description": "Sets the session cookie domain. Useful when dealing with subdomains. Use with care!", "type": "string" }, "persistent": { "title": "Make Session Cookie Persistent", "description": "If set to true will persist the cookie in the end-user's browser using the `max-age` parameter which is set to the `session.lifespan` value. Persistent cookies are not deleted when the browser is closed (e.g. on reboot or alt+f4).", "type": "boolean", "default": true }, "path": { "title": "Session Cookie Path", "description": "Sets the session cookie path. Use with care!", "type": "string", "default": "/" }, "same_site": { "title": "Cookie Same Site Configuration", "type": "string", "enum": ["Strict", "Lax", "None"], "default": "Lax" } }, "additionalProperties": false } } }, "version": { "title": "The kratos version this config is written for.", "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "type": "string", "pattern": "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "examples": ["v0.5.0-alpha.1"] } }, "allOf": [ { "if": { "properties": { "selfservice": { "properties": { "flows": { "oneOf": [ { "properties": { "verification": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["verification"] }, { "properties": { "recovery": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["recovery"] } ] } }, "required": ["flows"] } }, "required": ["selfservice"] }, "then": { "required": ["courier"] } } ], "required": ["identity", "dsn", "selfservice"] } ================================================ FILE: oryx/configx/stub/kratos/expected.json ================================================ { "courier": { "smtp": { "connection_uri": "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true", "from_address": "no-reply@ory.kratos.sh" } }, "dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true", "hashers": { "argon2": { "iterations": 2, "key_length": 16, "memory": 131072, "parallelism": 1, "salt_length": 16 } }, "identity": { "default_schema_url": "file:///etc/config/kratos/identity.schema.json" }, "log": { "format": "text", "leak_sensitive_values": true, "level": "debug" }, "secrets": { "cookie": ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"] }, "selfservice": { "default_browser_return_url": "http://127.0.0.1:4455/", "flows": { "error": { "ui_url": "http://127.0.0.1:4455/error" }, "login": { "lifespan": "10m", "ui_url": "http://127.0.0.1:4455/auth/login" }, "logout": { "after": { "default_browser_return_url": "http://127.0.0.1:4455/auth/login" } }, "recovery": { "enabled": true, "lifespan": "1h", "ui_url": "http://127.0.0.1:4455/recovery" }, "registration": { "after": { "password": { "hooks": [ { "hook": "session" } ] } }, "lifespan": "10m", "ui_url": "http://127.0.0.1:4455/auth/registration" }, "settings": { "lifespan": "1h", "privileged_session_max_age": "15m", "ui_url": "http://127.0.0.1:4455/settings" }, "verification": { "after": { "default_browser_return_url": "http://127.0.0.1:4455/" }, "enabled": true, "lifespan": "1h", "ui_url": "http://127.0.0.1:4455/verify" } }, "methods": { "link": { "enabled": true }, "oidc": { "enabled": true, "config": { "providers": [ { "id": "google", "provider": "google", "mapper_url": "file:///etc/config/kratos/oidc.google.jsonnet", "client_id": "client@example.com", "client_secret": "secret" } ] } }, "password": { "enabled": true }, "profile": { "enabled": true } }, "whitelisted_return_urls": ["http://127.0.0.1:4455"] }, "serve": { "admin": { "base_url": "http://kratos:4434/", "host": "0.0.0.0", "port": 4434 }, "public": { "base_url": "http://127.0.0.1:4433/", "cors": { "allow_credentials": true, "allowed_headers": ["Authorization", "Content-Type", "X-Session-Token"], "allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"], "allowed_origins": ["*"], "debug": false, "enabled": true, "exposed_headers": ["Content-Type"], "max_age": 0, "options_passthrough": false }, "host": "0.0.0.0", "port": 4433 } }, "session": { "cookie": { "path": "/", "persistent": true, "same_site": "Lax" }, "lifespan": "24h" }, "version": "v0.5.3-alpha.1" } ================================================ FILE: oryx/configx/stub/kratos/kratos.yaml ================================================ version: v0.5.3-alpha.1 dsn: memory serve: public: base_url: http://127.0.0.1:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://127.0.0.1:4455/ whitelisted_return_urls: - http://127.0.0.1:4455 methods: password: enabled: true oidc: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings privileged_session_max_age: 15m recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery verification: enabled: true ui_url: http://127.0.0.1:4455/verify after: default_browser_return_url: http://127.0.0.1:4455/ logout: after: default_browser_return_url: http://127.0.0.1:4455/auth/login login: ui_url: http://127.0.0.1:4455/auth/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/auth/registration log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE hashers: argon2: parallelism: 1 memory: 131072 iterations: 2 salt_length: 16 key_length: 16 identity: default_schema_url: file:///etc/config/kratos/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: oryx/configx/stub/multi/a.yaml ================================================ version: v0.5.3-alpha.1 dsn: memory serve: public: base_url: http://127.0.0.1:4433/ cors: enabled: true admin: base_url: http://kratos:4434/ selfservice: default_browser_return_url: http://127.0.0.1:4455/ whitelisted_return_urls: - http://127.0.0.1:4455 methods: password: enabled: true flows: error: ui_url: http://127.0.0.1:4455/error settings: ui_url: http://127.0.0.1:4455/settings ================================================ FILE: oryx/configx/stub/multi/b.yaml ================================================ selfservice: flows: settings: privileged_session_max_age: 15m recovery: enabled: true ui_url: http://127.0.0.1:4455/recovery verification: enabled: true ui_url: http://127.0.0.1:4455/verify after: default_browser_return_url: http://127.0.0.1:4455/ logout: after: default_browser_return_url: http://127.0.0.1:4455/auth/login login: ui_url: http://127.0.0.1:4455/auth/login lifespan: 10m registration: lifespan: 10m ui_url: http://127.0.0.1:4455/auth/registration after: password: hooks: - hook: session log: level: debug format: text leak_sensitive_values: true secrets: cookie: - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE hashers: argon2: parallelism: 1 memory: 131072 iterations: 2 salt_length: 16 key_length: 16 identity: default_schema_url: file:///etc/config/kratos/identity.schema.json courier: smtp: connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true ================================================ FILE: oryx/configx/stub/multi/config.schema.json ================================================ { "$id": "https://github.com/ory/kratos/.schema/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "ORY Kratos Configuration", "type": "object", "definitions": { "defaultReturnTo": { "title": "Redirect browsers to set URL per default", "description": "ORY Kratos redirects to this URL per default on completion of self-service flows and other browser interaction. Read this [article for more information on browser redirects](https://www.ory.sh/kratos/docs/concepts/browser-redirect-flow-completion).", "type": "string", "format": "uri-reference", "minLength": 1, "examples": ["https://my-app.com/dashboard", "/dashboard"] }, "selfServiceSessionRevokerHook": { "type": "object", "properties": { "hook": { "const": "revoke_active_sessions" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceVerifyHook": { "type": "object", "properties": { "hook": { "const": "verify" } }, "additionalProperties": false, "required": ["hook"] }, "selfServiceSessionIssuerHook": { "type": "object", "properties": { "hook": { "const": "session" } }, "additionalProperties": false, "required": ["hook"] }, "OIDCClaims": { "title": "OpenID Connect claims", "description": "The OpenID Connect claims and optionally their properties which should be included in the id_token or returned from the UserInfo Endpoint.", "type": "object", "examples": [ { "id_token": { "email": null, "email_verified": null } }, { "userinfo": { "given_name": { "essential": true }, "nickname": null, "email": { "essential": true }, "email_verified": { "essential": true }, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": { "essential": true }, "acr": { "values": ["urn:mace:incommon:iap:silver"] } } } ], "patternProperties": { "^userinfo$|^id_token$": { "type": "object", "additionalProperties": false, "patternProperties": { ".*": { "oneOf": [ { "const": null, "description": "Indicates that this Claim is being requested in the default manner." }, { "type": "object", "additionalProperties": false, "properties": { "essential": { "description": "Indicates whether the Claim being requested is an Essential Claim.", "type": "boolean" }, "value": { "description": "Requests that the Claim be returned with a particular value.", "$comment": "There seem to be no constrains on value" }, "values": { "description": "Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.", "type": "array", "items": { "$comment": "There seem to be no constrains on individual items" } } } } ] } } } } }, "selfServiceOIDCProvider": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, gitlab, generic, google, microsoft, discord.", "type": "string", "enum": [ "github", "gitlab", "generic", "google", "microsoft", "discord" ], "examples": ["google"] }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] }, "requested_claims": { "$ref": "#/definitions/OIDCClaims" } }, "additionalProperties": false, "required": [ "id", "provider", "client_id", "client_secret", "mapper_url" ], "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["tenant"] }, "else": { "not": { "properties": { "tenant": {} }, "required": ["tenant"] } } }, "selfServiceAfterSettingsMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceVerifyHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterLoginMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionRevokerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterRegistrationMethod": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "hooks": { "type": "array", "items": { "anyOf": [ { "$ref": "#/definitions/selfServiceSessionIssuerHook" } ] }, "uniqueItems": true, "additionalItems": false } } }, "selfServiceAfterSettings": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterSettingsMethod" }, "profile": { "$ref": "#/definitions/selfServiceAfterSettingsMethod" } } }, "selfServiceAfterLogin": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterLoginMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterLoginMethod" } } }, "selfServiceAfterRegistration": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "password": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" }, "oidc": { "$ref": "#/definitions/selfServiceAfterRegistrationMethod" } } } }, "properties": { "selfservice": { "type": "object", "additionalProperties": false, "required": ["default_browser_return_url"], "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" }, "whitelisted_return_urls": { "title": "Whitelisted Return To URLs", "description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.", "type": "array", "items": { "type": "string", "format": "uri-reference" }, "examples": [ [ "https://app.my-app.com/dashboard", "/dashboard", "https://www.my-app.com/" ] ], "uniqueItems": true }, "flows": { "type": "object", "additionalProperties": false, "properties": { "settings": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "URL of the Settings page.", "description": "URL where the Settings UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/user/settings"], "default": "https://www.ory.sh/kratos/docs/fallback/settings" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "privileged_session_max_age": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterSettings" } } }, "logout": { "type": "object", "additionalProperties": false, "properties": { "after": { "type": "object", "additionalProperties": false, "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } } } } }, "registration": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Registration UI URL", "description": "URL where the Registration UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/signup"], "default": "https://www.ory.sh/kratos/docs/fallback/registration" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterRegistration" } } }, "login": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "Login UI URL", "description": "URL where the Login UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/login"], "default": "https://www.ory.sh/kratos/docs/fallback/login" }, "lifespan": { "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] }, "after": { "$ref": "#/definitions/selfServiceAfterLogin" } } }, "verification": { "title": "Email and Phone Verification and Account Activation Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Email/Phone Verification", "description": "If set to true will enable [Email and Phone Verification and Account Activation](https://www.ory.sh/kratos/docs/self-service/flows/verify-email-account-activation/).", "default": false }, "ui_url": { "title": "Verify UI URL", "description": "URL where the ORY Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/verification" }, "after": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } }, "additionalProperties": false }, "lifespan": { "title": "Self-Service Verification Request Lifespan", "description": "Sets how long the verification request (for the UI interaction) is valid.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] } } }, "recovery": { "title": "Account Recovery Configuration", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enable Account Recovery", "description": "If set to true will enable [Account Recovery](https://www.ory.sh/kratos/docs/self-service/flows/password-reset-account-recovery/).", "default": false }, "ui_url": { "title": "Recovery UI URL", "description": "URL where the ORY Recovery UI is hosted. This is the page where users request and complete account recovery. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/verify"], "default": "https://www.ory.sh/kratos/docs/fallback/recovery" }, "after": { "type": "object", "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" } }, "additionalProperties": false }, "lifespan": { "title": "Self-Service Recovery Request Lifespan", "description": "Sets how long the recovery request is valid. If expired, the user has to redo the flow.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1h", "examples": ["1h", "1m", "1s"] } } }, "error": { "type": "object", "additionalProperties": false, "properties": { "ui_url": { "title": "ORY Kratos Error UI URL", "description": "URL where the ORY Kratos Error UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", "examples": ["https://my-app.com/kratos-error"], "default": "https://www.ory.sh/kratos/docs/fallback/error" } } } } }, "methods": { "type": "object", "additionalProperties": false, "properties": { "profile": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Profile Management Method", "default": true } } }, "link": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Link Method", "default": true } } }, "password": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables Username/Email and Password Method", "default": true } } }, "oidc": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean", "title": "Enables OpenID Connect Method", "default": false }, "config": { "type": "object", "additionalProperties": false, "properties": { "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.", "type": "array", "items": { "$ref": "#/definitions/selfServiceOIDCProvider" } } } } } } } } } }, "dsn": { "type": "string", "title": "Data Source Name", "description": "DSN is used to specify the database credentials as a connection URI.", "examples": [ "postgres://user: password@postgresd:5432/database?sslmode=disable&max_conns=20&max_idle_conns=4", "mysql://user:secret@tcp(mysqld:3306)/database?max_conns=20&max_idle_conns=4", "cockroach://user@cockroachdb:26257/database?sslmode=disable&max_conns=20&max_idle_conns=4", "sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc" ] }, "courier": { "type": "object", "title": "Courier configuration", "description": "The courier is responsible for sending and delivering messages over email, sms, and other means.", "properties": { "template_override_path": { "type": "string", "title": "Override message templates", "description": "You can override certain or all message templates by pointing this key to the path where the templates are located.", "examples": ["/conf/courier-templates"] }, "smtp": { "title": "SMTP Configuration", "description": "Configures outgoing emails using the SMTP protocol.", "type": "object", "properties": { "connection_uri": { "title": "SMTP connection string", "description": "This URI will be used to connect to the SMTP server. Use the query parameter to allow (`?skip_ssl_verify=true`) or disallow (`?skip_ssl_verify=false`) self-signed TLS certificates. Please keep in mind that any host other than localhost / 127.0.0.1 must use smtp over TLS (smtps) or the connection will not be possible.", "examples": [ "smtps://foo:bar@my-mailserver:1234/?skip_ssl_verify=false" ], "type": "string", "format": "uri" }, "from_address": { "title": "SMTP Sender Address", "description": "The recipient of an email will see this as the sender address.", "type": "string", "format": "email", "default": "no-reply@ory.kratos.sh" } }, "required": ["connection_uri"], "additionalProperties": false } }, "required": ["smtp"], "additionalProperties": false }, "serve": { "type": "object", "properties": { "admin": { "type": "object", "properties": { "base_url": { "title": "Admin Base URL", "description": "The URL where the admin endpoint is exposed at.", "type": "string", "format": "uri", "examples": ["https://kratos.private-network:4434/"] }, "host": { "title": "Admin Host", "description": "The host (interface) kratos' admin endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Admin Port", "description": "The port kratos' admin endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4434], "default": 4434 } }, "additionalProperties": false }, "public": { "type": "object", "properties": { "cors": { "type": "object", "additionalProperties": false, "description": "Configures Cross Origin Resource Sharing for public endpoints.", "properties": { "enabled": { "type": "boolean", "description": "Sets whether CORS is enabled.", "default": false }, "allowed_origins": { "type": "array", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.", "items": { "type": "string", "minLength": 1, "not": { "type": "string", "description": "does match all strings that contain two or more (*)", "pattern": ".*\\*.*\\*.*" }, "anyOf": [ { "format": "uri" }, { "const": "*" } ] }, "uniqueItems": true, "default": ["*"], "examples": [ [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] ] }, "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], "items": { "type": "string", "enum": [ "POST", "GET", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE" ] } }, "allowed_headers": { "type": "array", "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "default": [ "Authorization", "Content-Type", "X-Session-Token" ], "items": { "type": "string" } }, "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", "default": ["Content-Type"], "items": { "type": "string" } }, "allow_credentials": { "type": "boolean", "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "default": true }, "options_passthrough": { "type": "boolean", "description": "TODO", "default": false }, "max_age": { "type": "integer", "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.", "default": 0, "minimum": 0 }, "debug": { "type": "boolean", "description": "Adds additional log output to debug server side CORS issues.", "default": false } } }, "base_url": { "title": "Public Base URL", "description": "The URL where the public endpoint is exposed at.", "type": "string", "format": "uri-reference", "examples": [ "https://my-app.com/.ory/kratos/public", "/.ory/kratos/public/" ] }, "host": { "title": "Public Host", "description": "The host (interface) kratos' public endpoint listens on.", "type": "string", "default": "0.0.0.0" }, "port": { "title": "Public Port", "description": "The port kratos' public endpoint listens on.", "type": "integer", "minimum": 1, "maximum": 65535, "examples": [4433], "default": 4433 } }, "additionalProperties": false } }, "additionalProperties": false }, "log": { "type": "object", "properties": { "level": { "type": "string", "enum": [ "trace", "debug", "info", "warning", "error", "fatal", "panic" ] }, "leak_sensitive_values": { "type": "boolean", "title": "Leak Sensitive Log Values", "description": "If set will leak sensitive values (e.g. emails) in the logs." }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "format": { "type": "string", "enum": ["json", "text"] } }, "additionalProperties": false }, "identity": { "type": "object", "properties": { "default_schema_url": { "title": "JSON Schema URL for default identity traits", "description": "Path to the JSON Schema which describes a default identity's traits.", "type": "string", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json" ] }, "schemas": { "type": "array", "title": "Additional JSON Schemas for Identity Traits", "examples": [ [ { "id": "customer", "url": "https://foo.bar.com/path/to/customer.traits.schema.json" }, { "id": "employee", "url": "https://foo.bar.com/path/to/employee.traits.schema.json" }, { "id": "employee-v2", "url": "https://foo.bar.com/path/to/employee.v2.traits.schema.json" } ] ], "items": { "type": "object", "properties": { "id": { "title": "The schema's ID.", "type": "string", "examples": ["employee"] }, "url": { "type": "string", "title": "Path to the JSON Schema", "format": "uri", "examples": [ "file://path/to/identity.traits.schema.json", "https://foo.bar.com/path/to/identity.traits.schema.json" ] } }, "required": ["id", "url"], "not": { "type": "object", "properties": { "id": { "const": "default" } }, "additionalProperties": true } } } }, "required": ["default_schema_url"], "additionalProperties": false }, "secrets": { "type": "object", "properties": { "default": { "type": "array", "title": "Default Encryption Signing Secrets", "description": "The first secret in the array is used for singing and encrypting things while all other keys are used to verify and decrypt older things that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true }, "cookie": { "type": "array", "title": "Singing Keys for Cookies", "description": "The first secret in the array is used for encrypting cookies while all other keys are used to decrypt older cookies that were signed with that old secret.", "items": { "type": "string", "minLength": 16 }, "uniqueItems": true } }, "additionalProperties": false }, "hashers": { "title": "Hashing Algorithm Configuration", "type": "object", "properties": { "argon2": { "title": "Configuration for the Argon2id hasher.", "type": "object", "properties": { "memory": { "type": "integer", "minimum": 16384 }, "iterations": { "type": "integer", "minimum": 1 }, "parallelism": { "type": "integer", "minimum": 1 }, "salt_length": { "type": "integer", "minimum": 16 }, "key_length": { "type": "integer", "minimum": 16 } }, "additionalProperties": false } }, "additionalProperties": false }, "session": { "type": "object", "additionalProperties": false, "properties": { "lifespan": { "title": "Session Lifespan", "description": "Defines how long a session is active. Once that lifespan has been reached, the user needs to sign in again.", "type": "string", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "24h", "examples": ["1h", "1m", "1s"] }, "cookie": { "type": "object", "properties": { "domain": { "title": "Session Cookie Domain", "description": "Sets the session cookie domain. Useful when dealing with subdomains. Use with care!", "type": "string" }, "persistent": { "title": "Make Session Cookie Persistent", "description": "If set to true will persist the cookie in the end-user's browser using the `max-age` parameter which is set to the `session.lifespan` value. Persistent cookies are not deleted when the browser is closed (e.g. on reboot or alt+f4).", "type": "boolean", "default": true }, "path": { "title": "Session Cookie Path", "description": "Sets the session cookie path. Use with care!", "type": "string", "default": "/" }, "same_site": { "title": "Cookie Same Site Configuration", "type": "string", "enum": ["Strict", "Lax", "None"], "default": "Lax" } }, "additionalProperties": false } } }, "version": { "title": "The kratos version this config is written for.", "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "type": "string", "pattern": "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "examples": ["v0.5.0-alpha.1"] } }, "allOf": [ { "if": { "properties": { "selfservice": { "properties": { "flows": { "oneOf": [ { "properties": { "verification": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["verification"] }, { "properties": { "recovery": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] } }, "required": ["recovery"] } ] } }, "required": ["flows"] } }, "required": ["selfservice"] }, "then": { "required": ["courier"] } } ], "required": ["identity", "dsn", "selfservice"] } ================================================ FILE: oryx/configx/stub/multi/expected.json ================================================ { "courier": { "smtp": { "connection_uri": "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true", "from_address": "no-reply@ory.kratos.sh" } }, "dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true", "hashers": { "argon2": { "iterations": 2, "key_length": 16, "memory": 131072, "parallelism": 1, "salt_length": 16 } }, "identity": { "default_schema_url": "file:///etc/config/kratos/identity.schema.json" }, "log": { "format": "text", "leak_sensitive_values": true, "level": "debug" }, "secrets": { "cookie": ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"] }, "selfservice": { "default_browser_return_url": "http://127.0.0.1:4455/", "flows": { "error": { "ui_url": "http://127.0.0.1:4455/error" }, "login": { "lifespan": "10m", "ui_url": "http://127.0.0.1:4455/auth/login" }, "logout": { "after": { "default_browser_return_url": "http://127.0.0.1:4455/auth/login" } }, "recovery": { "enabled": true, "lifespan": "1h", "ui_url": "http://127.0.0.1:4455/recovery" }, "registration": { "after": { "password": { "hooks": [ { "hook": "session" } ] } }, "lifespan": "10m", "ui_url": "http://127.0.0.1:4455/auth/registration" }, "settings": { "lifespan": "1h", "privileged_session_max_age": "15m", "ui_url": "http://127.0.0.1:4455/settings" }, "verification": { "after": { "default_browser_return_url": "http://127.0.0.1:4455/" }, "enabled": true, "lifespan": "1h", "ui_url": "http://127.0.0.1:4455/verify" } }, "methods": { "link": { "enabled": true }, "oidc": { "enabled": false }, "password": { "enabled": true }, "profile": { "enabled": true } }, "whitelisted_return_urls": ["http://127.0.0.1:4455"] }, "serve": { "admin": { "base_url": "http://kratos:4434/", "host": "0.0.0.0", "port": 4434 }, "public": { "base_url": "http://127.0.0.1:4433/", "cors": { "allow_credentials": true, "allowed_headers": ["Authorization", "Content-Type", "X-Session-Token"], "allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"], "allowed_origins": ["*"], "debug": false, "enabled": true, "exposed_headers": ["Content-Type"], "max_age": 0, "options_passthrough": false }, "host": "0.0.0.0", "port": 4433 } }, "session": { "cookie": { "path": "/", "persistent": true, "same_site": "Lax" }, "lifespan": "24h" }, "version": "v0.5.3-alpha.1" } ================================================ FILE: oryx/configx/stub/nested-array/config.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.", "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, gitlab, generic, google, microsoft, discord.", "type": "string", "enum": [ "github", "gitlab", "generic", "google", "microsoft", "discord" ], "examples": ["google"] }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] } }, "additionalProperties": false, "required": [], "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["tenant"] }, "else": { "not": { "properties": { "tenant": {} }, "required": ["tenant"] } } } } } } ================================================ FILE: oryx/configx/stub/nested-array/expected.json ================================================ { "providers": [ { "id": "google", "client_id": "client@example.com" }, { "client_id": "some@example.com" } ] } ================================================ FILE: oryx/configx/stub/nested-array/kratos.yaml ================================================ providers: - id: google ================================================ FILE: oryx/configx/stub/watch/config.schema.json ================================================ { "$id": "https://example.com/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "config", "type": "object", "properties": { "dsn": { "type": "string" }, "foo": { "const": "bar" }, "bar": { "type": "string", "enum": ["foo", "bar", "baz"] } }, "required": ["dsn"] } ================================================ FILE: oryx/configx/tls.schema.json ================================================ { "$id": "https://github.com/ory/x/tlsx/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "HTTPS", "description": "Configure HTTP over TLS (HTTPS).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "key": { "title": "Private Key (PEM)", "$ref": "#/definitions/source" }, "cert": { "title": "TLS Certificate (PEM)", "$ref": "#/definitions/source" }, "allow_termination_from": { "type": "array", "description": "Allow-list one or multiple CIDR address ranges and allow them to terminate TLS connections. Be aware that the X-Forwarded-Proto header must be set and must never be modifiable by anyone but your proxy / gateway / load balancer. Supports ipv4 and ipv6. The service serves http instead of https when this option is set.", "items": { "description": "CIDR address range.", "type": "string", "oneOf": [ { "pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, { "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$" } ], "examples": ["127.0.0.1/32"] } } }, "definitions": { "source": { "type": "object", "oneOf": [ { "properties": { "path": { "title": "Path to PEM-encoded File", "type": "string", "examples": ["path/to/file.pem"] } }, "additionalProperties": false }, { "properties": { "base64": { "title": "Base64 Encoded Inline", "description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "type": "string", "examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ] } }, "additionalProperties": false } ] } } } ================================================ FILE: oryx/contextx/contextual.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package contextx import ( "context" "github.com/ory/x/configx" "github.com/gofrs/uuid" ) type ( Contextualizer interface { // Network returns the network id for the given context. Network(ctx context.Context, network uuid.UUID) uuid.UUID // Config returns the config for the given context. Config(ctx context.Context, config *configx.Provider) *configx.Provider } Provider interface { Contextualizer() Contextualizer } Static struct { NID uuid.UUID C *configx.Provider } ) func (d *Static) Network(_ context.Context, nid uuid.UUID) uuid.UUID { if d.NID == uuid.Nil { return nid } return d.NID } func (d *Static) Config(_ context.Context, c *configx.Provider) *configx.Provider { if d.C == nil { return c } return d.C } ================================================ FILE: oryx/contextx/contextual_mock.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package contextx import ( "context" "github.com/ory/x/configx" "github.com/gofrs/uuid" ) // TestContextualizer is a mock implementation of the Contextualizer interface. type TestContextualizer struct{} type contextKeyFake int // fakeNIDContext is a test key for NID. const fakeNIDContext contextKeyFake = 1 // SetNIDContext sets the nid for the given context. func SetNIDContext(ctx context.Context, nid uuid.UUID) context.Context { return context.WithValue(ctx, fakeNIDContext, nid) //nolint:staticcheck } // Network returns the network id for the given context. func (d *TestContextualizer) Network(ctx context.Context, network uuid.UUID) uuid.UUID { nid, ok := ctx.Value(fakeNIDContext).(uuid.UUID) if !ok { return network } return nid } // Config returns the config for the given context. func (d *TestContextualizer) Config(ctx context.Context, config *configx.Provider) *configx.Provider { return config } ================================================ FILE: oryx/contextx/default.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package contextx import ( "context" "github.com/gofrs/uuid" "github.com/ory/x/configx" ) type Default struct{} var _ Contextualizer = (*Default)(nil) func (d *Default) Network(_ context.Context, network uuid.UUID) uuid.UUID { if network == uuid.Nil { panic("nid must be not nil") } return network } func (d *Default) Config(_ context.Context, config *configx.Provider) *configx.Provider { return config } ================================================ FILE: oryx/contextx/testhelpers.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package contextx import ( "context" "net/http" "net/http/httptest" "github.com/gofrs/uuid" "github.com/ory/x/configx" ) type ( TestConfigProvider struct { ConfigSchema []byte Options []configx.OptionModifier } contextKey int ) func NewTestConfigProvider(schema []byte, opts ...configx.OptionModifier) *TestConfigProvider { return &TestConfigProvider{ ConfigSchema: schema, Options: opts, } } func (t *TestConfigProvider) Network(ctx context.Context, network uuid.UUID) uuid.UUID { return (&Default{}).Network(ctx, network) } func (t *TestConfigProvider) Config(ctx context.Context, config *configx.Provider) *configx.Provider { values, ok := ctx.Value(contextConfigKey).([]map[string]any) if !ok { return config } opts := make([]configx.OptionModifier, 1, 1+len(values)) opts[0] = configx.WithValues(config.All()) for _, v := range values { opts = append(opts, configx.WithValues(v)) } config, err := configx.New(ctx, t.ConfigSchema, append(t.Options, opts...)...) if err != nil { // This is not production code. The provider is only used in tests. panic(err) } return config } const contextConfigKey contextKey = 1 var ( _ Contextualizer = (*TestConfigProvider)(nil) ) func WithConfigValue(ctx context.Context, key string, value any) context.Context { return WithConfigValues(ctx, map[string]any{key: value}) } func WithConfigValues(ctx context.Context, setValues ...map[string]any) context.Context { values, ok := ctx.Value(contextConfigKey).([]map[string]any) if !ok { values = make([]map[string]any, 0) } newValues := make([]map[string]any, len(values), len(values)+len(setValues)) copy(newValues, values) newValues = append(newValues, setValues...) return context.WithValue(ctx, contextConfigKey, newValues) } type ConfigurableTestHandler struct { configs map[uuid.UUID][]map[string]any handler http.Handler } func NewConfigurableTestHandler(h http.Handler) *ConfigurableTestHandler { return &ConfigurableTestHandler{ configs: make(map[uuid.UUID][]map[string]any), handler: h, } } func (t *ConfigurableTestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { cID := r.Header.Get("Test-Config-Id") if config, ok := t.configs[uuid.FromStringOrNil(cID)]; ok { r = r.WithContext(WithConfigValues(r.Context(), config...)) } t.handler.ServeHTTP(w, r) } func (t *ConfigurableTestHandler) RegisterConfig(config ...map[string]any) uuid.UUID { id := uuid.Must(uuid.NewV4()) t.configs[id] = config return id } func (t *ConfigurableTestHandler) UseConfig(r *http.Request, id uuid.UUID) *http.Request { r.Header.Set("Test-Config-Id", id.String()) return r } func (t *ConfigurableTestHandler) UseConfigValues(r *http.Request, values ...map[string]any) *http.Request { return t.UseConfig(r, t.RegisterConfig(values...)) } type ConfigurableTestServer struct { *httptest.Server handler *ConfigurableTestHandler transport http.RoundTripper } func NewConfigurableTestServer(h http.Handler) *ConfigurableTestServer { handler := NewConfigurableTestHandler(h) server := httptest.NewServer(handler) t := server.Client().Transport cts := &ConfigurableTestServer{ handler: handler, Server: server, transport: t, } server.Client().Transport = cts return cts } func (t *ConfigurableTestServer) RoundTrip(r *http.Request) (*http.Response, error) { config, ok := r.Context().Value(contextConfigKey).([]map[string]any) if ok && config != nil { r = t.handler.UseConfigValues(r, config...) } return t.transport.RoundTrip(r) } type AutoContextClient struct { *http.Client transport http.RoundTripper ctx context.Context } func (t *ConfigurableTestServer) Client(ctx context.Context) *AutoContextClient { baseClient := *t.Server.Client() autoClient := &AutoContextClient{ Client: &baseClient, transport: t, ctx: ctx, } baseClient.Transport = autoClient return autoClient } func (c *AutoContextClient) RoundTrip(r *http.Request) (*http.Response, error) { return c.transport.RoundTrip(r.WithContext(c.ctx)) } ================================================ FILE: oryx/contextx/tree.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package contextx import ( "context" "testing" ) type ContextKey int const ( ValidContextKey ContextKey = iota + 1 ) var RootContext = context.WithValue(context.Background(), ValidContextKey, true) func TestRootContext(t testing.TB) context.Context { return context.WithValue(t.Context(), ValidContextKey, true) } func IsRootContext(ctx context.Context) bool { is, ok := ctx.Value(ValidContextKey).(bool) return is && ok } ================================================ FILE: oryx/corsx/check_origin.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package corsx import "strings" // CheckOrigin is a function that can be used well with cors.Options.AllowOriginRequestFunc. // It checks whether the origin is allowed following the same behavior as github.com/rs/cors. // // Recommended usage for hot-reloadable origins: // // func (p *Config) cors(ctx context.Context, prefix string) (cors.Options, bool) { // opts, enabled := p.GetProvider(ctx).CORS(prefix, cors.Options{ // AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, // AllowedHeaders: []string{"Authorization", "Content-Type", "Cookie"}, // ExposedHeaders: []string{"Content-Type", "Set-Cookie"}, // AllowCredentials: true, // }) // opts.AllowOriginRequestFunc = func(r *http.Request, origin string) bool { // // load the origins from the config on every request to allow hot-reloading // allowedOrigins := p.GetProvider(r.Context()).Strings(prefix + ".cors.allowed_origins") // return corsx.CheckOrigin(allowedOrigins, origin) // } // return opts, enabled // } func CheckOrigin(allowedOrigins []string, origin string) bool { if len(allowedOrigins) == 0 { return true } for _, o := range allowedOrigins { if o == "*" { // allow all origins return true } // Note: for origins and methods matching, the spec requires a case-sensitive matching. // As it may be error-prone, we chose to ignore the spec here. // https://github.com/rs/cors/blob/066574eebbd0f5f1b6cd1154a160cc292ac1835e/cors.go#L132-L133 o = strings.ToLower(o) prefix, suffix, found := strings.Cut(o, "*") if !found { // not a pattern, check for equality if o == origin { return true } continue } // inspired by https://github.com/rs/cors/blob/066574eebbd0f5f1b6cd1154a160cc292ac1835e/utils.go#L15 if len(origin) >= len(prefix)+len(suffix) && strings.HasPrefix(origin, prefix) && strings.HasSuffix(origin, suffix) { return true } } return false } ================================================ FILE: oryx/corsx/cmd.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package corsx // HelpMessage returns a string containing information on setting up this CORS middleware. func HelpMessage() string { return `- CORS_ENABLED: Switch CORS support on (true) or off (false). Default is off (false). Example: CORS_ENABLED=true - CORS_ALLOWED_ORIGINS: A list of origins (comma separated values) a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin. The default value is *. Example: CORS_ALLOWED_ORIGINS=http://*.domain.com,http://*.domain2.com - CORS_ALLOWED_METHODS: A list of methods (comma separated values) the client is allowed to use with cross-domain requests. Default value is simple methods (GET and POST). Example: CORS_ALLOWED_METHODS=POST,GET,PUT - CORS_ALLOWED_CREDENTIALS: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. Default: CORS_ALLOWED_CREDENTIALS=false Example: CORS_ALLOWED_CREDENTIALS=true - CORS_DEBUG: Debugging flag adds additional output to debug server side CORS issues. Default: CORS_DEBUG=false Example: CORS_DEBUG=true - CORS_MAX_AGE: Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0 which stands for no max age. Default: CORS_MAX_AGE=0 Example: CORS_MAX_AGE=10 - CORS_ALLOWED_HEADERS: A list of non simple headers (comma separated values) the client is allowed to use with cross-domain requests. - CORS_EXPOSED_HEADERS: Indicates which headers (comma separated values) are safe to expose to the API of a CORS API specification.` } ================================================ FILE: oryx/corsx/defaults.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package corsx // CORSRequestHeadersSafelist We add the safe list cors accept headers // https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header var CORSRequestHeadersSafelist = []string{"Accept", "Content-Type", "Content-Length", "Accept-Language", "Content-Language"} // CORSResponseHeadersSafelist We add the safe list cors expose headers // https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header var CORSResponseHeadersSafelist = []string{"Set-Cookie", "Cache-Control", "Expires", "Last-Modified", "Pragma", "Content-Length", "Content-Language", "Content-Type"} // CORSDefaultAllowedMethods Default allowed methods var CORSDefaultAllowedMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE"} // CORSRequestHeadersExtended Extended list of request headers // these will be concatenated with the safelist var CORSRequestHeadersExtended = []string{"Authorization", "X-CSRF-TOKEN"} // CORSResponseHeadersExtended Extended list of response headers // these will be concatenated with the safelist var CORSResponseHeadersExtended = []string{} // CORSDefaultMaxAge max age for cache of preflight request result // default is 5 seconds // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age var CORSDefaultMaxAge = 5 // CORSAllowCredentials default value for allow credentials // this is required for cookies to be sent by the browser // we always want this since we are using cookies for authentication most of the time var CORSAllowCredentials = true ================================================ FILE: oryx/corsx/middleware.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package corsx import ( "context" "net/http" "github.com/rs/cors" "github.com/urfave/negroni" ) // ContextualizedMiddleware is a context-aware CORS middleware. It allows hot-reloading CORS configuration using // the HTTP request context. // // n := negroni.New() // n.UseFunc(ContextualizedMiddleware(func(context.Context) (opts cors.Options, enabled bool) { // panic("implement me") // }) // // ... // // Deprecated: because this is not really practical to use, you should use CheckOrigin as the cors.Options.AllowOriginRequestFunc instead. func ContextualizedMiddleware(provider func(context.Context) (opts cors.Options, enabled bool)) negroni.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { options, enabled := provider(r.Context()) if !enabled { next(rw, r) return } cors.New(options).Handler(next).ServeHTTP(rw, r) } } ================================================ FILE: oryx/corsx/normalize.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package corsx import "net/url" // NormalizeOrigins normalizes the CORS origins. func NormalizeOrigins(origins []url.URL) []string { results := make([]string, len(origins)) for k, o := range origins { results[k] = o.Scheme + "://" + o.Host } return results } // NormalizeOriginStrings normalizes the CORS origins from string representation func NormalizeOriginStrings(origins []string) ([]string, error) { results := make([]string, len(origins)) for k, o := range origins { u, err := url.ParseRequestURI(o) if err != nil { return nil, err } results[k] = u.Scheme + "://" + u.Host } return results, nil } ================================================ FILE: oryx/crdbx/readonly.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package crdbx import ( "github.com/ory/pop/v6" "github.com/ory/x/dbal" "github.com/ory/x/sqlcon" ) // SetTransactionReadOnly sets the transaction to read only for CockroachDB. func SetTransactionReadOnly(c *pop.Connection) error { if c.Dialect.Name() != dbal.DriverCockroachDB { // Only CockroachDB supports this. return nil } return sqlcon.HandleError(c.RawQuery("SET TRANSACTION READ ONLY").Exec()) } ================================================ FILE: oryx/crdbx/staleness.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package crdbx import ( "net/http" "github.com/ory/x/dbal" "github.com/ory/pop/v6" "github.com/ory/x/sqlcon" ) // Control API consistency guarantees // // swagger:model consistencyRequestParameters type ConsistencyRequestParameters struct { // Read Consistency Level (preview) // // The read consistency level determines the consistency guarantee for reads: // // - strong (slow): The read is guaranteed to return the most recent data committed at the start of the read. // - eventual (very fast): The result will return data that is about 4.8 seconds old. // // The default consistency guarantee can be changed in the Ory Network Console or using the Ory CLI with // `ory patch project --replace '/previews/default_read_consistency_level="strong"'`. // // Setting the default consistency level to `eventual` may cause regressions in the future as we add consistency // controls to more APIs. Currently, the following APIs will be affected by this setting: // // - `GET /admin/identities` // // This feature is in preview and only available in Ory Network. // // required: false // in: query Consistency ConsistencyLevel `json:"consistency"` } // ConsistencyLevel is the consistency level. // swagger:enum ConsistencyLevel type ConsistencyLevel string const ( // ConsistencyLevelUnset is the unset / default consistency level. ConsistencyLevelUnset ConsistencyLevel = "" // ConsistencyLevelStrong is the strong consistency level. ConsistencyLevelStrong ConsistencyLevel = "strong" // ConsistencyLevelEventual is the eventual consistency level using follower read timestamps. ConsistencyLevelEventual ConsistencyLevel = "eventual" ) // ConsistencyLevelFromRequest extracts the consistency level from a request. func ConsistencyLevelFromRequest(r *http.Request) ConsistencyLevel { return ConsistencyLevelFromString(r.URL.Query().Get("consistency")) } // ConsistencyLevelFromString converts a string to a ConsistencyLevel. // If the string is not recognized or unset, ConsistencyLevelStrong is returned. func ConsistencyLevelFromString(in string) ConsistencyLevel { switch in { case string(ConsistencyLevelStrong): return ConsistencyLevelStrong case string(ConsistencyLevelEventual): return ConsistencyLevelEventual case string(ConsistencyLevelUnset): return ConsistencyLevelUnset } return ConsistencyLevelStrong } // SetTransactionConsistency sets the transaction consistency level for CockroachDB. func SetTransactionConsistency(c *pop.Connection, level ConsistencyLevel, fallback ConsistencyLevel) error { q := getTransactionConsistencyQuery(c.Dialect.Name(), level, fallback) if len(q) == 0 { return nil } return sqlcon.HandleError(c.RawQuery(q).Exec()) } const transactionFollowerReadTimestamp = "SET TRANSACTION AS OF SYSTEM TIME follower_read_timestamp()" func getTransactionConsistencyQuery(dialect string, level ConsistencyLevel, fallback ConsistencyLevel) string { if dialect != dbal.DriverCockroachDB { // Only CockroachDB supports this. return "" } switch level { case ConsistencyLevelStrong: // Nothing to do return "" case ConsistencyLevelEventual: // Jumps to end of function case ConsistencyLevelUnset: fallthrough default: if fallback != ConsistencyLevelEventual { // Nothing to do return "" } // Jumps to end of function } return transactionFollowerReadTimestamp } ================================================ FILE: oryx/dbal/canonicalize.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package dbal const ( DriverMySQL = "mysql" DriverCockroachDB = "cockroach" ) ================================================ FILE: oryx/dbal/driver.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package dbal import ( "strings" ) // IsSQLite returns true if the connection is a SQLite string. func IsSQLite(dsn string) bool { scheme := strings.Split(dsn, "://")[0] return scheme == "sqlite" || scheme == "sqlite3" } ================================================ FILE: oryx/dbal/dsn.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package dbal import ( "fmt" "os" "regexp" "testing" "github.com/stretchr/testify/require" ) var sqliteMemoryRegexp = regexp.MustCompile(`^sqlite://file:.+\?.*&?mode=memory($|&.*)|sqlite://(file:)?:memory:\?.*$|^(:memory:|memory)$`) // IsMemorySQLite returns true if a given DSN string is pointing to a SQLite database. // // SQLite can be written in different styles depending on the use case // - just in memory // - shared connection // - shared but unique in the same process // see: https://sqlite.org/inmemorydb.html func IsMemorySQLite(dsn string) bool { return sqliteMemoryRegexp.MatchString(dsn) } // NewSQLiteTestDatabase creates a new, unique SQLite database // which is shared amongst all callers and identified by an individual file name. // The database file is created in the system's temporary directory, and not actively // removed to allow debugging in case of test failures. func NewSQLiteTestDatabase(t testing.TB) string { fn, err := os.MkdirTemp("", "sqlite-test-db-*") require.NoError(t, err) return NewSQLiteDatabase(fn) } // NewSQLiteInMemoryDatabase creates a new in-memory, unique SQLite database // which is shared amongst all callers and identified by an individual file name. func NewSQLiteInMemoryDatabase(name string) string { return fmt.Sprintf("sqlite://file:%s?_fk=true&mode=memory&cache=shared&_busy_timeout=100000", name) } // NewSQLiteDatabase creates a new on-disk, unique SQLite database // which is shared amongst all callers and identified by an individual file name. // This is sometimes necessary over a in-memory database, for example when multiple tests/goroutines run in parallel // and access the same table. // This would result in a locking error from SQLite when running in-memory. // Additionally, shared cache mode is deprecated and discouraged, and the problem is better solved with the WAL, // according to official docs. func NewSQLiteDatabase(name string) string { return fmt.Sprintf("sqlite://file:%s/db.sqlite?_fk=true&_journal_mode=WAL&_busy_timeout=100000", name) } ================================================ FILE: oryx/dbal/testhelpers.go ================================================ // Copyright © 2026 Ory Corp // SPDX-License-Identifier: Apache-2.0 package dbal import ( "bytes" "encoding/hex" "fmt" "io/fs" "os" "path/filepath" "regexp" "testing" "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/ory/pop/v6" "github.com/ory/x/fsx" "github.com/ory/x/sqlcon/dockertest" ) var hashDumpRegex = regexp.MustCompile(`-- migrations hash: ([^\n]+)\n`) func RestoreFromSchemaDump(t testing.TB, c *pop.Connection, migrations fs.FS, writeTo string) func(testing.TB) { newHash, err := fsx.DirHash(migrations) require.NoError(t, err) dumpFilename := filepath.Join(writeTo, c.Dialect.Name()+"_dump.sql") updateDump := func(t testing.TB) { dump := dockertest.DumpSchema(t, c) f, err := os.OpenFile(dumpFilename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) require.NoError(t, err) defer f.Close() _, _ = fmt.Fprintf(f, "-- migrations hash: %x\n\n%s", newHash, dump) t.Fatal("database schema restored from migrations and dump updated, please re-run the test") } dump, err := os.ReadFile(dumpFilename) if errors.Is(err, fs.ErrNotExist) { return updateDump } require.NoError(t, err) matches := hashDumpRegex.FindSubmatch(dump) if len(matches) != 2 { return updateDump } currentHash, err := hex.DecodeString(string(matches[1])) require.NoError(t, err) if !bytes.Equal(newHash, currentHash) { return updateDump } require.NoError(t, c.RawQuery(string(dump)).Exec()) return nil } ================================================ FILE: oryx/decoderx/http.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package decoderx import ( "bytes" "context" "crypto/sha256" "encoding/json" "fmt" "io" "net/http" "net/url" "slices" "strconv" "strings" "github.com/pkg/errors" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "github.com/ory/jsonschema/v3" "github.com/ory/herodot" "github.com/ory/x/httpx" "github.com/ory/x/jsonschemax" ) type ( httpDecoderOptions struct { keepRequestBody bool allowedContentTypes []string allowedHTTPMethods []string jsonSchemaRef string jsonSchemaCompiler *jsonschema.Compiler jsonSchemaValidate bool maxCircularReferenceDepth uint8 handleParseErrors parseErrorStrategy expectJSONFlattened bool queryAndBody bool } // HTTPDecoderOption configures the HTTP decoder. HTTPDecoderOption func(*httpDecoderOptions) parseErrorStrategy uint8 ) const ( httpContentTypeMultipartForm = "multipart/form-data" httpContentTypeURLEncodedForm = "application/x-www-form-urlencoded" httpContentTypeJSON = "application/json" ) const ( // ParseErrorIgnoreConversionErrors will ignore any errors caused by strconv.Parse* and use the // raw form field value, which is a string, when such a parse error occurs. // // If the JSON Schema defines `{"ratio": {"type": "number"}}` but `ratio=foobar` then field // `ratio` will be handled as a string. If the destination struct is a `json.RawMessage`, then // the output will be `{"ratio": "foobar"}`. ParseErrorIgnoreConversionErrors parseErrorStrategy = iota + 1 // ParseErrorUseEmptyValueOnConversionErrors will ignore any parse errors caused by strconv.Parse* and use the // default value of the type to be casted, e.g. float64(0), string(""). // // If the JSON Schema defines `{"ratio": {"type": "number"}}` but `ratio=foobar` then field // `ratio` will receive the default value for the primitive type (here `0.0` for `number`). // If the destination struct is a `json.RawMessage`, then the output will be `{"ratio": 0.0}`. ParseErrorUseEmptyValueOnConversionErrors // ParseErrorReturnOnConversionErrors will abort and return with an error if strconv.Parse* returns // an error. // // If the JSON Schema defines `{"ratio": {"type": "number"}}` but `ratio=foobar` the parser aborts // and returns an error, here: `strconv.ParseFloat: parsing "foobar"`. ParseErrorReturnOnConversionErrors ) var errKeyNotFound = errors.New("key not found") // HTTPFormDecoder configures the HTTP decoder to only accept form-data // (application/x-www-form-urlencoded, multipart/form-data) func HTTPFormDecoder() HTTPDecoderOption { return func(o *httpDecoderOptions) { o.allowedContentTypes = []string{httpContentTypeMultipartForm, httpContentTypeURLEncodedForm} } } // HTTPJSONDecoder configures the HTTP decoder to only accept JSON data // (application/json). func HTTPJSONDecoder() HTTPDecoderOption { return func(o *httpDecoderOptions) { o.allowedContentTypes = []string{httpContentTypeJSON} } } // HTTPKeepRequestBody configures the HTTP decoder to allow other // HTTP request body readers to read the body as well by keeping // the data in memory. func HTTPKeepRequestBody(keep bool) HTTPDecoderOption { return func(o *httpDecoderOptions) { o.keepRequestBody = keep } } // HTTPDecoderSetValidatePayloads sets if payloads should be validated or not. func HTTPDecoderSetValidatePayloads(validate bool) HTTPDecoderOption { return func(o *httpDecoderOptions) { o.jsonSchemaValidate = validate o.keepRequestBody = true } } // HTTPDecoderJSONFollowsFormFormat if set tells the decoder that JSON follows the same conventions // as the form decoder, meaning `{"foo.bar": "..."}` is translated to `{"foo": {"bar": "..."}}`. func HTTPDecoderJSONFollowsFormFormat() HTTPDecoderOption { return func(o *httpDecoderOptions) { o.expectJSONFlattened = true o.keepRequestBody = true } } // HTTPDecoderAllowedMethods sets the allowed HTTP methods. Defaults are POST, PUT, PATCH. func HTTPDecoderAllowedMethods(method ...string) HTTPDecoderOption { return func(o *httpDecoderOptions) { o.allowedHTTPMethods = method } } // HTTPDecoderUseQueryAndBody will check both the HTTP body and the HTTP query params when decoding. // Only relevant for non-GET operations. func HTTPDecoderUseQueryAndBody() HTTPDecoderOption { return func(o *httpDecoderOptions) { o.queryAndBody = true } } // HTTPDecoderSetIgnoreParseErrorsStrategy sets a strategy for dealing with strconv.Parse* errors: // // - decoderx.ParseErrorIgnoreConversionErrors will ignore any parse errors caused by strconv.Parse* and use the // raw form field value, which is a string, when such a parse error occurs. (default) // - decoderx.ParseErrorUseEmptyValueOnConversionErrors will ignore any parse errors caused by strconv.Parse* and use the // default value of the type to be casted, e.g. float64(0), string(""). // - decoderx.ParseErrorReturnOnConversionErrors will abort and return with an error if strconv.Parse* returns // an error. func HTTPDecoderSetIgnoreParseErrorsStrategy(strategy parseErrorStrategy) HTTPDecoderOption { return func(o *httpDecoderOptions) { o.handleParseErrors = strategy } } // HTTPDecoderSetMaxCircularReferenceDepth sets the maximum recursive reference resolution depth. func HTTPDecoderSetMaxCircularReferenceDepth(depth uint8) HTTPDecoderOption { return func(o *httpDecoderOptions) { o.maxCircularReferenceDepth = depth } } // HTTPJSONSchemaCompiler sets a JSON schema to be used for validation and type assertion of // incoming requests. func HTTPJSONSchemaCompiler(ref string, compiler *jsonschema.Compiler) HTTPDecoderOption { return func(o *httpDecoderOptions) { if compiler == nil { compiler = jsonschema.NewCompiler() } compiler.ExtractAnnotations = true o.jsonSchemaCompiler = compiler o.jsonSchemaRef = ref o.jsonSchemaValidate = true } } // HTTPRawJSONSchemaCompiler uses a JSON Schema Compiler with the provided JSON Schema in raw byte form. func HTTPRawJSONSchemaCompiler(raw []byte) (HTTPDecoderOption, error) { compiler := jsonschema.NewCompiler() id := fmt.Sprintf("%x.json", sha256.Sum256(raw)) if err := compiler.AddResource(id, bytes.NewReader(raw)); err != nil { return nil, err } compiler.ExtractAnnotations = true return func(o *httpDecoderOptions) { o.jsonSchemaCompiler = compiler o.jsonSchemaRef = id o.jsonSchemaValidate = true }, nil } // MustHTTPRawJSONSchemaCompiler uses HTTPRawJSONSchemaCompiler and panics on error. func MustHTTPRawJSONSchemaCompiler(raw []byte) HTTPDecoderOption { f, err := HTTPRawJSONSchemaCompiler(raw) if err != nil { panic(err) } return f } func newHTTPDecoderOptions(fs []HTTPDecoderOption) *httpDecoderOptions { o := &httpDecoderOptions{ allowedContentTypes: []string{ httpContentTypeMultipartForm, httpContentTypeURLEncodedForm, httpContentTypeJSON, }, allowedHTTPMethods: []string{"POST", "PUT", "PATCH"}, maxCircularReferenceDepth: 5, handleParseErrors: ParseErrorIgnoreConversionErrors, } for _, f := range fs { f(o) } return o } func validateRequest(r *http.Request, c *httpDecoderOptions) error { method := strings.ToUpper(r.Method) if !slices.Contains(c.allowedHTTPMethods, method) { return errors.WithStack(herodot.ErrBadRequest.WithReasonf(`Unable to decode body because HTTP Request Method was "%s" but only %v are supported.`, method, c.allowedHTTPMethods)) } if method != "GET" { if r.ContentLength == 0 { return errors.WithStack(herodot.ErrBadRequest.WithReasonf(`Unable to decode HTTP Request Body because its HTTP Header "Content-Length" is zero.`)) } if !httpx.HasContentType(r, c.allowedContentTypes...) { return errors.WithStack(herodot.ErrBadRequest.WithReasonf(`HTTP %s Request used unknown HTTP Header "Content-Type: %s", only %v are supported.`, method, r.Header.Get("Content-Type"), c.allowedContentTypes)) } } return nil } func validatePayload(ctx context.Context, raw json.RawMessage, c *httpDecoderOptions) error { if !c.jsonSchemaValidate { return nil } if c.jsonSchemaCompiler == nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("JSON Schema Validation is required but no compiler was provided.")) } schema, err := c.jsonSchemaCompiler.Compile(ctx, c.jsonSchemaRef) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to load JSON Schema from location: %s", c.jsonSchemaRef).WithDebug(err.Error())) } if err := schema.Validate(bytes.NewBuffer(raw)); err != nil { if errors.As(err, new(*jsonschema.ValidationError)) { return errors.WithStack(err) } return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to process JSON Schema and input: %s", err).WithDebug(err.Error())) } return nil } // Decode takes an HTTP Request Body and decodes it into destination. func Decode(r *http.Request, destination any, opts ...HTTPDecoderOption) error { c := newHTTPDecoderOptions(opts) if err := validateRequest(r, c); err != nil { return err } if r.Method == "GET" { return decodeForm(r, destination, c) } else if httpx.HasContentType(r, httpContentTypeJSON) { if c.expectJSONFlattened { return decodeJSONForm(r, destination, c) } return decodeJSON(r, destination, c) } else if httpx.HasContentType(r, httpContentTypeMultipartForm, httpContentTypeURLEncodedForm) { return decodeForm(r, destination, c) } return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to determine decoder for content type: %s", r.Header.Get("Content-Type"))) } func requestBody(r *http.Request, o *httpDecoderOptions) (reader io.ReadCloser, err error) { if strings.ToUpper(r.Method) == "GET" { return io.NopCloser(bytes.NewBufferString(r.URL.Query().Encode())), nil } if !o.keepRequestBody { return r.Body, nil } bodyBytes, err := io.ReadAll(r.Body) if err != nil { return nil, errors.Wrapf(err, "unable to read body") } _ = r.Body.Close() // must close r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) return io.NopCloser(bytes.NewBuffer(bodyBytes)), nil } func decodeJSONForm(r *http.Request, destination interface{}, o *httpDecoderOptions) error { if o.jsonSchemaCompiler == nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode HTTP Form Body because no validation schema was provided. This is a code bug.")) } paths, err := jsonschemax.ListPathsWithRecursion(r.Context(), o.jsonSchemaRef, o.jsonSchemaCompiler, o.maxCircularReferenceDepth) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithTrace(err).WithReasonf("Unable to prepare JSON Schema for HTTP Post Body Form parsing: %s", err).WithDebugf("%+v", err)) } reader, err := requestBody(r, o) if err != nil { return err } var interim json.RawMessage if err := json.NewDecoder(reader).Decode(&interim); err != nil { return errors.WithStack(herodot.ErrBadRequest.WithError(err.Error()).WithReason("Unable to decode form as JSON.")) } parsed := gjson.ParseBytes(interim) if !parsed.IsObject() { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("Expected JSON sent in request body to be an object but got: %s", parsed.Type.String())) } values := url.Values{} parsed.ForEach(func(k, v gjson.Result) bool { values.Set(k.String(), v.String()) return true }) if o.queryAndBody { _ = r.ParseForm() for k := range r.Form { values.Set(k, r.Form.Get(k)) } } raw, err := decodeURLValues(values, paths, o) if err != nil { return err } if err := json.Unmarshal(raw, destination); err != nil { return errors.WithStack(err) } return validatePayload(r.Context(), raw, o) } func decodeForm(r *http.Request, destination interface{}, o *httpDecoderOptions) error { if o.jsonSchemaCompiler == nil { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode HTTP Form Body because no validation schema was provided. This is a code bug.")) } reader, err := requestBody(r, o) if err != nil { return err } defer func() { r.Body = reader }() if err := r.ParseForm(); err != nil { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode HTTP %s form body: %s", strings.ToUpper(r.Method), err).WithDebug(err.Error())) } paths, err := jsonschemax.ListPathsWithRecursion(r.Context(), o.jsonSchemaRef, o.jsonSchemaCompiler, o.maxCircularReferenceDepth) if err != nil { return errors.WithStack(herodot.ErrInternalServerError.WithTrace(err).WithReasonf("Unable to prepare JSON Schema for HTTP Post Body Form parsing: %s", err).WithDebugf("%+v", err)) } values := r.PostForm if r.Method == "GET" || o.queryAndBody { values = r.Form } raw, err := decodeURLValues(values, paths, o) if err != nil && !errors.Is(err, errKeyNotFound) { return err } if err := json.NewDecoder(bytes.NewReader(raw)).Decode(destination); err != nil { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode JSON payload: %s", err)) } return validatePayload(r.Context(), raw, o) } func decodeURLValues(values url.Values, paths []jsonschemax.Path, o *httpDecoderOptions) (json.RawMessage, error) { raw := json.RawMessage(`{}`) for key := range values { for _, path := range paths { if key == path.Name { var err error switch path.Type.(type) { case []string: raw, err = sjson.SetBytes(raw, path.Name, values[key]) case []float64: for k, v := range values[key] { var f float64 if f, err = strconv.ParseFloat(v, 64); err != nil { switch o.handleParseErrors { case ParseErrorIgnoreConversionErrors: raw, err = sjson.SetBytes(raw, path.Name+"."+strconv.Itoa(k), v) case ParseErrorUseEmptyValueOnConversionErrors: raw, err = sjson.SetBytes(raw, path.Name+"."+strconv.Itoa(k), f) case ParseErrorReturnOnConversionErrors: return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Expected value to be a number."). WithDetail("parse_error", err.Error()). WithDetail("name", key). WithDetailf("index", "%d", k). WithDetail("value", v)) } } else { raw, err = sjson.SetBytes(raw, path.Name+"."+strconv.Itoa(k), f) } } case []bool: for k, v := range values[key] { var b bool if b, err = strconv.ParseBool(v); err != nil { switch o.handleParseErrors { case ParseErrorIgnoreConversionErrors: raw, err = sjson.SetBytes(raw, path.Name+"."+strconv.Itoa(k), v) case ParseErrorUseEmptyValueOnConversionErrors: raw, err = sjson.SetBytes(raw, path.Name+"."+strconv.Itoa(k), b) case ParseErrorReturnOnConversionErrors: return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Expected value to be a boolean."). WithDetail("parse_error", err.Error()). WithDetail("name", key). WithDetailf("index", "%d", k). WithDetail("value", v)) } } else { raw, err = sjson.SetBytes(raw, path.Name+"."+strconv.Itoa(k), b) } } case []interface{}: raw, err = sjson.SetBytes(raw, path.Name, values[key]) case bool: v := values[key][len(values[key])-1] if len(v) == 0 { if !path.Required { continue } v = "false" } var b bool if b, err = strconv.ParseBool(v); err != nil { switch o.handleParseErrors { case ParseErrorIgnoreConversionErrors: raw, err = sjson.SetBytes(raw, path.Name, v) case ParseErrorUseEmptyValueOnConversionErrors: raw, err = sjson.SetBytes(raw, path.Name, b) case ParseErrorReturnOnConversionErrors: return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Expected value to be a boolean."). WithDetail("parse_error", err.Error()). WithDetail("name", key). WithDetail("value", values.Get(key))) } } else { raw, err = sjson.SetBytes(raw, path.Name, b) } case float64: v := values.Get(key) if len(v) == 0 { if !path.Required { continue } v = "0.0" } var f float64 if f, err = strconv.ParseFloat(v, 64); err != nil { switch o.handleParseErrors { case ParseErrorIgnoreConversionErrors: raw, err = sjson.SetBytes(raw, path.Name, v) case ParseErrorUseEmptyValueOnConversionErrors: raw, err = sjson.SetBytes(raw, path.Name, f) case ParseErrorReturnOnConversionErrors: return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Expected value to be a number."). WithDetail("parse_error", err.Error()). WithDetail("name", key). WithDetail("value", values.Get(key))) } } else { raw, err = sjson.SetBytes(raw, path.Name, f) } case string: v := values.Get(key) if len(v) == 0 { continue } raw, err = sjson.SetBytes(raw, path.Name, v) case map[string]interface{}: v := values.Get(key) if len(v) == 0 && !path.Required { continue } raw, err = sjson.SetRawBytes(raw, path.Name, []byte(v)) case []map[string]interface{}: raw, err = sjson.SetBytes(raw, path.Name, values[key]) } if err != nil { return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to type assert values from HTTP Post Body: %s", err)) } break } } } for _, path := range paths { if path.TypeHint != jsonschemax.JSON { continue } if !gjson.GetBytes(raw, path.Name).Exists() { var err error raw, err = sjson.SetRawBytes(raw, path.Name, []byte(`{}`)) if err != nil { return nil, errors.WithStack(err) } } } return raw, nil } func decodeJSON(r *http.Request, destination interface{}, o *httpDecoderOptions) error { reader, err := requestBody(r, o) if err != nil { return err } raw, err := io.ReadAll(reader) if err != nil { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to read HTTP POST body: %s", err)) } dc := json.NewDecoder(bytes.NewReader(raw)) if err := dc.Decode(destination); err != nil { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode JSON payload: %s", err).WithDebugf("Received request body: %s", string(raw))) } if err := validatePayload(r.Context(), raw, o); err != nil { if o.expectJSONFlattened && strings.Contains(err.Error(), "json: unknown field") { return decodeJSONForm(r, destination, o) } return err } return nil } ================================================ FILE: oryx/decoderx/stub/consent.json ================================================ { "$id": "https://example.com/ory.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "traits": { "additionalProperties": false, "properties": { "consent": { "additionalProperties": false, "properties": { "tos": { "description": "yyyymmdd of when this was accepted", "title": "I accept the Terms of Service https://www.ory.sh/ptos", "const": true, "maxLength": 30 }, "inner": { "type": "object", "properties": { "foo": { "type": "string" } }, "required": ["foo"] } }, "required": ["tos"], "title": "Consent", "type": "object" }, "notrequired": { "additionalProperties": false, "properties": { "tos": { "description": "yyyymmdd of when this was accepted", "title": "I accept the Terms of Service https://www.ory.sh/ptos", "const": true, "maxLength": 30 } }, "required": ["tos"], "title": "Consent", "type": "object" } }, "required": ["consent"], "type": "object" } }, "title": "Person", "type": "object" } ================================================ FILE: oryx/decoderx/stub/dynamic-object.json ================================================ { "$id": "https://example.com/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "name": { "type": "object", "properties": { "first": { "type": "string" }, "last": { "type": "string" } } }, "dynamic_object": { "type": "object", "additionalProperties": true } } } ================================================ FILE: oryx/decoderx/stub/nested.json ================================================ { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "required": ["node"], "properties": { "node": { "type": "object", "required": ["node"], "properties": { "node": { "type": "object", "properties": { "node": { "type": "object", "properties": { "leaf": { "type": "string" } }, "required": ["leaf"] }, "leaf": { "type": "string" } }, "required": ["leaf"] }, "leaf": { "type": "string" } } } } } ================================================ FILE: oryx/decoderx/stub/person.json ================================================ { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "name": { "type": "object", "properties": { "first": { "type": "string" }, "last": { "type": "string" } } }, "age": { "type": "integer" }, "ratio": { "type": "number" }, "consent": { "type": "boolean" }, "newsletter": { "type": "boolean" } } } ================================================ FILE: oryx/decoderx/stub/required-defaults.json ================================================ { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "name": { "type": "object", "properties": { "first": { "type": "string" }, "last": { "type": "string" } }, "required": ["first"] }, "name2": { "type": "object", "properties": { "first": { "type": "string" }, "last": { "type": "string" } }, "required": ["first"] }, "age": { "type": "integer" }, "age2": { "type": "integer" }, "ratio": { "type": "number" }, "ratio2": { "type": "number" }, "consent": { "type": "boolean" }, "consent2": { "type": "boolean" }, "newsletter": { "type": "boolean" }, "newsletter2": { "type": "boolean" } }, "required": ["age2", "ratio2", "consent2", "newsletter2", "name2"] } ================================================ FILE: oryx/decoderx/stub/schema.json ================================================ { "$id": "https://example.com/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["foo"], "properties": { "foo": { "type": "string" } } } ================================================ FILE: oryx/errorsx/errors.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package errorsx import ( "github.com/pkg/errors" "github.com/ory/herodot" ) // Cause returns the underlying cause of the error, if possible. // An error value has a cause if it implements the following // interface: // // type causer interface { // Cause() error // } // // If the error does not implement Cause, the original error will // be returned. If the error is nil, nil will be returned without further // investigation. // Deprecated: you should probably use errors.As instead. func Cause(err error) error { type causer interface { Cause() error } for err != nil { cause, ok := err.(causer) if !ok || cause.Cause() == nil { break } err = cause.Cause() } return err } // WithStack mirror pkg/errors.WithStack but does not wrap existing stack // traces. // Deprecated: you should probably use errors.WithStack instead and only annotate stacks when it makes sense. func WithStack(err error) error { if e, ok := err.(StackTracer); ok && len(e.StackTrace()) > 0 { return err } return errors.WithStack(err) } // StatusCodeCarrier can be implemented by an error to support setting status codes in the error itself. type StatusCodeCarrier interface { // StatusCode returns the status code of this error. StatusCode() int } // RequestIDCarrier can be implemented by an error to support error contexts. type RequestIDCarrier interface { // RequestID returns the ID of the request that caused the error, if applicable. RequestID() string } // ReasonCarrier can be implemented by an error to support error contexts. type ReasonCarrier interface { // Reason returns the reason for the error, if applicable. Reason() string } // DebugCarrier can be implemented by an error to support error contexts. type DebugCarrier interface { // Debug returns debugging information for the error, if applicable. Debug() string } // StatusCarrier can be implemented by an error to support error contexts. type StatusCarrier interface { // ID returns the error id, if applicable. Status() string } // DetailsCarrier can be implemented by an error to support error contexts. type DetailsCarrier interface { // Details returns details on the error, if applicable. Details() map[string]interface{} } // IDCarrier can be implemented by an error to support error contexts. type IDCarrier interface { // ID returns application error ID on the error, if applicable. ID() string } type StackTracer interface { StackTrace() errors.StackTrace } func GetCodeFromHerodotError(err error) (code int, ok bool) { herodotErr := &herodot.DefaultError{} isHerodot := errors.As(err, &herodotErr) return herodotErr.CodeField, isHerodot } ================================================ FILE: oryx/fetcher/fetcher.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fetcher import ( "bytes" "context" "crypto/sha256" "encoding/base64" stderrors "errors" "io" "net/http" "os" "strings" "time" "github.com/dgraph-io/ristretto/v2" "github.com/hashicorp/go-retryablehttp" "github.com/pkg/errors" "github.com/ory/x/httpx" "github.com/ory/x/stringsx" ) // Fetcher is able to load file contents from http, https, file, and base64 locations. type Fetcher struct { hc *retryablehttp.Client limit int64 cache *ristretto.Cache[[]byte, []byte] ttl time.Duration } type opts struct { hc *retryablehttp.Client limit int64 cache *ristretto.Cache[[]byte, []byte] ttl time.Duration } var ErrUnknownScheme = stderrors.New("unknown scheme") // WithClient sets the http.Client the fetcher uses. func WithClient(hc *retryablehttp.Client) Modifier { return func(o *opts) { o.hc = hc } } // WithMaxHTTPMaxBytes reads at most limit bytes from the HTTP response body, // returning bytes.ErrToLarge if the limit would be exceeded. func WithMaxHTTPMaxBytes(limit int64) Modifier { return func(o *opts) { o.limit = limit } } func WithCache(cache *ristretto.Cache[[]byte, []byte], ttl time.Duration) Modifier { return func(o *opts) { if ttl < 0 { return } o.cache = cache o.ttl = ttl } } func newOpts() *opts { return &opts{ hc: httpx.NewResilientClient(), } } type Modifier func(*opts) // NewFetcher creates a new fetcher instance. func NewFetcher(opts ...Modifier) *Fetcher { o := newOpts() for _, f := range opts { f(o) } return &Fetcher{hc: o.hc, limit: o.limit, cache: o.cache, ttl: o.ttl} } // FetchContext fetches the file contents from the source and allows to pass a // context that is used for HTTP requests. func (f *Fetcher) FetchContext(ctx context.Context, source string) (*bytes.Buffer, error) { b, err := f.FetchBytes(ctx, source) if err != nil { return nil, err } return bytes.NewBuffer(b), nil } // FetchBytes fetches the file contents from the source and allows to pass a // context that is used for HTTP requests. func (f *Fetcher) FetchBytes(ctx context.Context, source string) ([]byte, error) { switch s := stringsx.SwitchPrefix(source); { case s.HasPrefix("http://", "https://"): return f.fetchRemote(ctx, source) case s.HasPrefix("file://"): return f.fetchFile(strings.TrimPrefix(source, "file://")) case s.HasPrefix("base64://"): src, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(source, "base64://")) if err != nil { return nil, errors.Wrapf(err, "base64decode: %s", source) } return src, nil default: return nil, errors.Wrap(ErrUnknownScheme, s.ToUnknownPrefixErr().Error()) } } func (f *Fetcher) fetchRemote(ctx context.Context, source string) (b []byte, err error) { if f.cache != nil { cacheKey := sha256.Sum256([]byte(source)) if v, ok := f.cache.Get(cacheKey[:]); ok { b = make([]byte, len(v)) copy(b, v) return b, nil } defer func() { if err == nil && len(b) > 0 { toCache := make([]byte, len(b)) copy(toCache, b) f.cache.SetWithTTL(cacheKey[:], toCache, int64(len(toCache)), f.ttl) } }() } req, err := retryablehttp.NewRequestWithContext(ctx, http.MethodGet, source, nil) if err != nil { return nil, errors.Wrapf(err, "new request: %s", source) } res, err := f.hc.Do(req) if err != nil { return nil, errors.Wrap(err, source) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, errors.Errorf("expected http response status code 200 but got %d when fetching: %s", res.StatusCode, source) } if f.limit > 0 { var buf bytes.Buffer n, err := io.Copy(&buf, io.LimitReader(res.Body, f.limit+1)) if n > f.limit { return nil, bytes.ErrTooLarge } if err != nil { return nil, err } return buf.Bytes(), nil } return io.ReadAll(res.Body) } func (f *Fetcher) fetchFile(source string) ([]byte, error) { fp, err := os.Open(source) // #nosec:G304 if err != nil { return nil, errors.Wrapf(err, "unable to open file: %s", source) } defer fp.Close() b, err := io.ReadAll(fp) if err != nil { return nil, errors.Wrapf(err, "unable to read file: %s", source) } if err := fp.Close(); err != nil { return nil, errors.Wrapf(err, "unable to close file: %s", source) } return b, nil } ================================================ FILE: oryx/flagx/flagx.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package flagx import ( "time" "github.com/spf13/pflag" "github.com/spf13/cobra" "github.com/ory/x/cmdx" ) func NewFlagSet(name string) *pflag.FlagSet { return pflag.NewFlagSet(name, pflag.ContinueOnError) } // MustGetBool returns a bool flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetBool(cmd *cobra.Command, name string) bool { ok, err := cmd.Flags().GetBool(name) if err != nil { cmdx.Fatalf(err.Error()) } return ok } // MustGetString returns a string flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetString(cmd *cobra.Command, name string) string { s, err := cmd.Flags().GetString(name) if err != nil { cmdx.Fatalf(err.Error()) } return s } // MustGetDuration returns a time.Duration flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetDuration(cmd *cobra.Command, name string) time.Duration { d, err := cmd.Flags().GetDuration(name) if err != nil { cmdx.Fatalf(err.Error()) } return d } // MustGetStringSlice returns a []string flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetStringSlice(cmd *cobra.Command, name string) []string { ss, err := cmd.Flags().GetStringSlice(name) if err != nil { cmdx.Fatalf(err.Error()) } return ss } // MustGetStringArray returns a []string flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetStringArray(cmd *cobra.Command, name string) []string { ss, err := cmd.Flags().GetStringArray(name) if err != nil { cmdx.Fatalf(err.Error()) } return ss } // MustGetStringToStringMap returns a map[string]string flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetStringToStringMap(cmd *cobra.Command, name string) map[string]string { ss, err := cmd.Flags().GetStringToString(name) if err != nil { cmdx.Fatalf(err.Error()) } return ss } // MustGetInt returns a int flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetInt(cmd *cobra.Command, name string) int { ss, err := cmd.Flags().GetInt(name) if err != nil { cmdx.Fatalf(err.Error()) } return ss } // MustGetUint8 returns a uint8 flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetUint8(cmd *cobra.Command, name string) uint8 { v, err := cmd.Flags().GetUint8(name) if err != nil { cmdx.Fatalf(err.Error()) } return v } // MustGetUint32 returns a uint32 flag or fatals if an error occurs. // Deprecated: just handle the error properly, this breaks command testing func MustGetUint32(cmd *cobra.Command, name string) uint32 { v, err := cmd.Flags().GetUint32(name) if err != nil { cmdx.Fatalf(err.Error()) } return v } ================================================ FILE: oryx/fsx/dirhash.go ================================================ // Copyright © 2026 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fsx import ( "crypto/sha512" "io" "io/fs" ) // DirHash computes a directory hash from all files contained in any subdirectories. func DirHash(dir fs.FS) ([]byte, error) { hash := sha512.New() err := fs.WalkDir(dir, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } _, _ = io.WriteString(hash, path) // hash write never errors f, err := dir.Open(path) if err != nil { return err } _, _ = io.Copy(hash, f) // hash write never errors if err = f.Close(); err != nil { return err } return nil }) return hash.Sum(nil), err } ================================================ FILE: oryx/fsx/merge.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package fsx import ( "io" "io/fs" "sort" "time" "github.com/pkg/errors" ) type ( mergedFS []fs.FS mergedFile struct { files []fs.File unprocessedDirEntries dirEntries } mergedFileInfo []fs.FileInfo dirEntries []fs.DirEntry ) var ( _ fs.StatFS = (mergedFS)(nil) _ fs.ReadDirFS = (mergedFS)(nil) _ fs.ReadDirFile = (*mergedFile)(nil) _ fs.FileInfo = (mergedFileInfo)(nil) _ sort.Interface = (dirEntries)(nil) ) // Merge multiple filesystems. Later file systems are shadowed by previous ones. func Merge(fss ...fs.FS) fs.FS { return mergedFS(fss) } func (m mergedFS) Open(name string) (fs.File, error) { var file mergedFile for _, fsys := range m { f, err := fsys.Open(name) if errors.Is(err, fs.ErrNotExist) { continue } if err != nil { return nil, errors.WithStack(err) } file.files = append(file.files, f) } if len(file.files) == 0 { return nil, errors.WithStack(fs.ErrNotExist) } return &file, nil } func (m mergedFS) Stat(name string) (fs.FileInfo, error) { for i, fsys := range m { info, err := fs.Stat(fsys, name) if errors.Is(err, fs.ErrNotExist) { continue } switch { case err != nil: return nil, errors.WithStack(err) case info.IsDir(): dirs := mergedFileInfo{info} for j := i + 1; j < len(m); j++ { info, err := fs.Stat(m[j], name) if errors.Is(err, fs.ErrNotExist) { continue } if err != nil { return nil, err } dirs = append(dirs, info) } return dirs, nil default: return info, nil } } return nil, errors.WithStack(fs.ErrNotExist) } func (m mergedFS) ReadDir(name string) ([]fs.DirEntry, error) { var entries dirEntries for _, fsys := range m { e, err := fs.ReadDir(fsys, name) if errors.Is(err, fs.ErrNotExist) { continue } if err != nil { return nil, err } entries = append(entries, e...) } if len(entries) == 0 { return nil, errors.WithStack(fs.ErrNotExist) } entries.clean() return entries, nil } func (m mergedFileInfo) Name() string { return m[0].Name() } func (m mergedFileInfo) Size() int64 { return m[0].Size() } func (m mergedFileInfo) Mode() fs.FileMode { return m[0].Mode() } func (m mergedFileInfo) ModTime() time.Time { return m[0].ModTime() } func (m mergedFileInfo) IsDir() bool { return m[0].IsDir() } func (m mergedFileInfo) Sys() interface{} { return m } func (d dirEntries) Len() int { return len(d) } func (d dirEntries) Less(i, j int) bool { return d[i].Name() < d[j].Name() } func (d dirEntries) Swap(i, j int) { d[i], d[j] = d[j], d[i] } func (d *dirEntries) clean() { sort.Sort(d) for i := 1; i < len(*d); i++ { if (*d)[i-1].Name() == (*d)[i].Name() { if len(*d)-i == 1 { // remove the last entry; we're done *d = (*d)[:i] return } // remove the duplicate entry at index i *d = append((*d)[:i], (*d)[i+1:]...) // need to check the same index again i-- } } } func (m *mergedFile) Stat() (fs.FileInfo, error) { return m.files[0].Stat() } func (m *mergedFile) Read(bytes []byte) (int, error) { return m.files[0].Read(bytes) } func (m *mergedFile) Close() error { var firstErr error for _, f := range m.files { if err := f.Close(); err != nil { if firstErr == nil { firstErr = errors.WithStack(err) } } } return firstErr } func (m *mergedFile) ReadDir(n int) ([]fs.DirEntry, error) { if m.unprocessedDirEntries != nil { if n <= 0 { entries := m.unprocessedDirEntries m.unprocessedDirEntries = nil return entries, nil } if n >= len(m.unprocessedDirEntries) { entries := m.unprocessedDirEntries m.unprocessedDirEntries = nil return entries, io.EOF } var entries dirEntries entries, m.unprocessedDirEntries = m.unprocessedDirEntries[:n], m.unprocessedDirEntries[n:] return entries, nil } var entries dirEntries for _, f := range m.files { if f, ok := f.(fs.ReadDirFile); ok { e, err := f.ReadDir(-1) if err != nil && !errors.Is(err, fs.ErrNotExist) { return nil, err } entries = append(entries, e...) } } if entries == nil { if n > 0 { return nil, io.EOF } return nil, nil } entries.clean() if n <= 0 { return entries, nil } if n >= len(entries) { return entries, io.EOF } entries, m.unprocessedDirEntries = entries[:n], entries[n:] return entries, nil } ================================================ FILE: oryx/go.mod ================================================ module github.com/ory/x go 1.26 require ( code.dny.dev/ssrf v0.2.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/auth0/go-jwt-middleware/v2 v2.3.0 github.com/avast/retry-go/v4 v4.6.1 github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 github.com/cockroachdb/cockroach-go/v2 v2.4.1 github.com/dgraph-io/ristretto/v2 v2.2.0 github.com/docker/docker v28.3.3+incompatible github.com/evanphx/json-patch/v5 v5.9.11 github.com/fsnotify/fsnotify v1.9.0 github.com/ghodss/yaml v1.0.0 github.com/go-jose/go-jose/v3 v3.0.4 github.com/go-openapi/jsonpointer v0.21.2 github.com/go-openapi/runtime v0.28.0 github.com/go-sql-driver/mysql v1.9.3 github.com/gobuffalo/httptest v1.5.2 github.com/gobwas/glob v0.2.3 github.com/goccy/go-yaml v1.18.0 github.com/gofrs/uuid v4.4.0+incompatible github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/go-jsonnet v0.21.0 github.com/gorilla/websocket v1.5.3 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/jackc/pgconn v1.14.3 github.com/jackc/pgx/v5 v5.7.5 github.com/jackc/puddle/v2 v2.2.2 github.com/jmoiron/sqlx v1.4.0 github.com/knadh/koanf/maps v0.1.2 github.com/knadh/koanf/parsers/json v0.1.0 github.com/knadh/koanf/parsers/toml v0.1.0 github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/providers/posflag v0.1.0 github.com/knadh/koanf/providers/rawbytes v0.1.0 github.com/knadh/koanf/v2 v2.2.2 github.com/laher/mergefs v0.1.1 github.com/lestrrat-go/jwx v1.2.31 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.32 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/analytics-go/v5 v5.0.1 github.com/ory/dockertest/v4 v4.0.0-beta.4 github.com/ory/herodot v0.10.7 github.com/ory/jsonschema/v3 v3.0.9-0.20250317235931-280c5fc7bf0e github.com/ory/pop/v6 v6.3.2-0.20251203152233-a32233875f7e github.com/pelletier/go-toml v1.9.5 github.com/peterhellberg/link v1.2.0 github.com/pkg/errors v0.9.1 github.com/pkg/profile v1.7.0 github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.65.0 github.com/rakutentech/jwk-go v1.2.0 github.com/rs/cors v1.11.1 github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cast v1.9.2 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/ssoready/hyrumtoken v1.0.0 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/urfave/negroni v1.0.0 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 go.opentelemetry.io/contrib/propagators/b3 v1.37.0 go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 go.opentelemetry.io/contrib/samplers/jaegerremote v0.31.0 go.opentelemetry.io/otel v1.40.0 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 go.opentelemetry.io/otel/exporters/zipkin v1.37.0 go.opentelemetry.io/otel/sdk v1.40.0 go.opentelemetry.io/otel/trace v1.40.0 go.opentelemetry.io/proto/otlp v1.7.1 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.5.2 golang.org/x/crypto v0.46.0 golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.19.0 google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.10 ) require ( dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.2.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/XSAM/otelsql v0.39.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/errors v0.22.2 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobuffalo/envy v1.10.2 // indirect github.com/gobuffalo/fizz v1.14.4 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gobuffalo/github_flavored_markdown v1.1.4 // indirect github.com/gobuffalo/helpers v0.6.10 // indirect github.com/gobuffalo/nulls v0.4.2 // indirect github.com/gobuffalo/plush/v4 v4.1.22 // indirect github.com/gobuffalo/plush/v5 v5.0.7 // indirect github.com/gobuffalo/tags/v3 v3.1.4 // indirect github.com/gobuffalo/validate/v3 v3.3.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect github.com/jandelgado/gcov2lcov v1.1.1 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/moby/api v1.54.0 // indirect github.com/moby/moby/client v0.3.0 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nyaruka/phonenumbers v1.6.5 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/segmentio/backo-go v1.1.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.39.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) tool ( github.com/jandelgado/gcov2lcov go.uber.org/mock/mockgen golang.org/x/tools/cmd/goimports ) ================================================ FILE: oryx/go.sum ================================================ code.dny.dev/ssrf v0.2.0 h1:wCBP990rQQ1CYfRpW+YK1+8xhwUjv189AQ3WMo1jQaI= code.dny.dev/ssrf v0.2.0/go.mod h1:B+91l25OnyaLIeCx0WRJN5qfJ/4/ZTZxRXgm0lj/2w8= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/XSAM/otelsql v0.39.0 h1:4o374mEIMweaeevL7fd8Q3C710Xi2Jh/c8G4Qy9bvCY= github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/auth0/go-jwt-middleware/v2 v2.3.0 h1:4QREj6cS3d8dS05bEm443jhnqQF97FX9sMBeWqnNRzE= github.com/auth0/go-jwt-middleware/v2 v2.3.0/go.mod h1:dL4ObBs1/dj4/W4cYxd8rqAdDGXYyd5rqbpMIxcbVrU= github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk= github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cockroachdb/cockroach-go/v2 v2.4.1 h1:ACVT/zXsuK6waRPVYtDQpsM8pPA7IA/3fkgA02RR/Gw= github.com/cockroachdb/cockroach-go/v2 v2.4.1/go.mod h1:9U179XbCx4qFWtNhc7BiWLPfuyMVQ7qdAhfrwLz1vH0= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg= github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= github.com/gobuffalo/fizz v1.14.4 h1:8uume7joF6niTNWN582IQ2jhGTUoa9g1fiV/tIoGdBs= github.com/gobuffalo/fizz v1.14.4/go.mod h1:9/2fGNXNeIFOXEEgTPJwiK63e44RjG+Nc4hfMm1ArGM= github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= github.com/gobuffalo/github_flavored_markdown v1.1.4 h1:WacrEGPXUDX+BpU1GM/Y0ADgMzESKNWls9hOTG1MHVs= github.com/gobuffalo/github_flavored_markdown v1.1.4/go.mod h1:Vl9686qrVVQou4GrHRK/KOG3jCZOKLUqV8MMOAYtlso= github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= github.com/gobuffalo/helpers v0.6.10 h1:puKDCOrJ0EIq5ScnTRgKyvEZ05xQa+gwRGCpgoh6Ek8= github.com/gobuffalo/helpers v0.6.10/go.mod h1:r52L6VSnByLJFOmURp1irvzgSakk7RodChi1YbGwk8I= github.com/gobuffalo/httptest v1.5.2 h1:GpGy520SfY1QEmyPvaqmznTpG4gEQqQ82HtHqyNEreM= github.com/gobuffalo/httptest v1.5.2/go.mod h1:FA23yjsWLGj92mVV74Qtc8eqluc11VqcWr8/C1vxt4g= github.com/gobuffalo/nulls v0.4.2 h1:GAqBR29R3oPY+WCC7JL9KKk9erchaNuV6unsOSZGQkw= github.com/gobuffalo/nulls v0.4.2/go.mod h1:EElw2zmBYafU2R9W4Ii1ByIj177wA/pc0JdjtD0EsH8= github.com/gobuffalo/plush/v4 v4.1.16/go.mod h1:6t7swVsarJ8qSLw1qyAH/KbrcSTwdun2ASEQkOznakg= github.com/gobuffalo/plush/v4 v4.1.22 h1:bPQr5PsiTg54UGMsfvnIAvFmUfxzD/ri+wbpu7PlmTM= github.com/gobuffalo/plush/v4 v4.1.22/go.mod h1:WiKHJx3qBvfaDVlrv8zT7NCd3dEMaVR/fVxW4wqV17M= github.com/gobuffalo/plush/v5 v5.0.7 h1:nI8sIt5tZAN2tCZHeaXkH7HAvxvvk3sJHG2TtrKeSHM= github.com/gobuffalo/plush/v5 v5.0.7/go.mod h1:C08u/VEqzzPBXFF/yqs40P/5Cvc/zlZsMzhCxXyWJmU= github.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM= github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4= github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-jsonnet v0.21.0 h1:43Bk3K4zMRP/aAZm9Po2uSEjY6ALCkYUVIcz9HLGMvA= github.com/google/go-jsonnet v0.21.0/go.mod h1:tCGAu8cpUpEZcdGMmdOu37nh8bGgqubhI5v2iSk3KJQ= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= github.com/jandelgado/gcov2lcov v1.1.1 h1:CHUNoAglvb34DqmMoZchnzDbA3yjpzT8EoUvVqcAY+s= github.com/jandelgado/gcov2lcov v1.1.1/go.mod h1:tMVUlMVtS1po2SB8UkADWhOT5Y5Q13XOce2AYU69JuI= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY= github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI= github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18= github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U= github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A= github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/laher/mergefs v0.1.1 h1:nV2bTS57vrmbMxeR6uvJpI8LyGl3QHj4bLBZO3aUV58= github.com/laher/mergefs v0.1.1/go.mod h1:FSY1hYy94on4Tz60waRMGdO1awwS23BacqJlqf9lJ9Q= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/moby/api v1.54.0 h1:7kbUgyiKcoBhm0UrWbdrMs7RX8dnwzURKVbZGy2GnL0= github.com/moby/moby/api v1.54.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= github.com/moby/moby/client v0.3.0 h1:UUGL5okry+Aomj3WhGt9Aigl3ZOxZGqR7XPo+RLPlKs= github.com/moby/moby/client v0.3.0/go.mod h1:HJgFbJRvogDQjbM8fqc1MCEm4mIAGMLjXbgwoZp6jCQ= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nyaruka/phonenumbers v1.6.5 h1:aBCaUhfpRA7hU6fsXk+p7KF1aNx4nQlq9hGeo2qdFg8= github.com/nyaruka/phonenumbers v1.6.5/go.mod h1:7gjs+Lchqm49adhAKB5cdcng5ZXgt6x7Jgvi0ZorUtU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/ory/analytics-go/v5 v5.0.1 h1:LX8T5B9FN8KZXOtxgN+R3I4THRRVB6+28IKgKBpXmAM= github.com/ory/analytics-go/v5 v5.0.1/go.mod h1:lWCiCjAaJkKfgR/BN5DCLMol8BjKS1x+4jxBxff/FF0= github.com/ory/dockertest/v4 v4.0.0-beta.4 h1:QcrNrobOP+5IjSDmS4//EuBtwiFuznQhi5xTe8oFSoM= github.com/ory/dockertest/v4 v4.0.0-beta.4/go.mod h1:p9kfE14tzK8+WU4F9YbIZlzhCzQ2pH7H1KIfBKrF3DM= github.com/ory/herodot v0.10.7 h1:CETBRP4LboLlQCSVTkyQix/a2bVh1rmNhhfxd45khCI= github.com/ory/herodot v0.10.7/go.mod h1:j6i246U6iX8TStYNKIVQxb2waweQvtOLi+b/9q+OULg= github.com/ory/jsonschema/v3 v3.0.9-0.20250317235931-280c5fc7bf0e h1:4tUrC7x4YWRVMFp+c64KACNSGchW1zXo4l6Pa9/1hA8= github.com/ory/jsonschema/v3 v3.0.9-0.20250317235931-280c5fc7bf0e/go.mod h1:XWLxVK4un/iuIcrw+6lCeanbF3NZwO5k6RdLeu/loQk= github.com/ory/pop/v6 v6.3.2-0.20251203152233-a32233875f7e h1:gsbAteu8HZYnkIF4WVBaxklvF/s5IbcxYcCi6qX93ms= github.com/ory/pop/v6 v6.3.2-0.20251203152233-a32233875f7e/go.mod h1:PEqjxMcIV87rBhlyDDha76I7/w2W/FHenSq3V3X1A/A= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rakutentech/jwk-go v1.2.0 h1:vNJwedPkRR+32V5WGNj0JP4COes93BGERvzQLBjLy4c= github.com/rakutentech/jwk-go v1.2.0/go.mod h1:pI0bYVntqaJ27RCpaC75MTUacheW0Rk4+8XzWWe1OWM= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 h1:0b8DF5kR0PhRoRXDiEEdzrgBc8UqVY4JWLkQJCRsLME= github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761/go.mod h1:/THDZYi7F/BsVEcYzYPqdcWFQ+1C2InkawTKfLOAnzg= github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/segmentio/backo-go v1.1.0 h1:cJIfHQUdmLsd8t9IXqf5J8SdrOMn9vMa7cIvOavHAhc= github.com/segmentio/backo-go v1.1.0/go.mod h1:ckenwdf+v/qbyhVdNPWHnqh2YdJBED1O9cidYyM5J18= github.com/segmentio/conf v1.2.0/go.mod h1:Y3B9O/PqqWqjyxyWWseyj/quPEtMu1zDp/kVbSWWaB0= github.com/segmentio/go-snakecase v1.1.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/ssoready/hyrumtoken v1.0.0 h1:N/JPJDOuYS7qPSnOvZpPxNVXwtlT3kfzAMEcPrH8ywQ= github.com/ssoready/hyrumtoken v1.0.0/go.mod h1:h8q768r5Uv6iJKOwsNENIWWUP9kvmLykQox5m3SCpqc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 h1:wCeciVlAfb5DC8MQl/DlmAv/FVPNpQgFvI/71+hatuc= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0/go.mod h1:WfEApdZDMlLUAev/0QQpr8EJ/z0VWDKYZ5tF5RH5T1U= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/b3 v1.37.0 h1:0aGKdIuVhy5l4GClAjl72ntkZJhijf2wg1S7b5oLoYA= go.opentelemetry.io/contrib/propagators/b3 v1.37.0/go.mod h1:nhyrxEJEOQdwR15zXrCKI6+cJK60PXAkJ/jRyfhr2mg= go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 h1:pW+qDVo0jB0rLsNeaP85xLuz20cvsECUcN7TE+D8YTM= go.opentelemetry.io/contrib/propagators/jaeger v1.37.0/go.mod h1:x7bd+t034hxLTve1hF9Yn9qQJlO/pP8H5pWIt7+gsFM= go.opentelemetry.io/contrib/samplers/jaegerremote v0.31.0 h1:l8XCsDh7L6Z7PB+vlw1s4ufNab+ayT2RMNdvDE/UyPc= go.opentelemetry.io/contrib/samplers/jaegerremote v0.31.0/go.mod h1:XAOSk4bqj5vtoiY08bexeiafzxdXeLlxKFnwscvn8Fc= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= go.opentelemetry.io/otel/exporters/zipkin v1.37.0 h1:Z2apuaRnHEjzDAkpbWNPiksz1R0/FCIrJSjiMA43zwI= go.opentelemetry.io/otel/exporters/zipkin v1.37.0/go.mod h1:ofGu/7fG+bpmjZoiPUUmYDJ4vXWxMT57HmGoegx49uw= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/mold.v2 v2.2.0/go.mod h1:XMyyRsGtakkDPbxXbrA5VODo6bUXyvoDjLd5l3T0XoA= gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= ================================================ FILE: oryx/hasherx/hash_comparator.go ================================================ package hasherx import ( "context" "crypto/subtle" "encoding/base64" "fmt" "math" "regexp" "strings" "github.com/pkg/errors" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/pbkdf2" ) var ErrUnknownHashAlgorithm = errors.New("unknown hash algorithm") // Compare the given password with the given hash. func Compare(ctx context.Context, password []byte, hash []byte) error { switch { case IsBcryptHash(hash): return CompareBcrypt(ctx, password, hash) case IsArgon2idHash(hash): return CompareArgon2id(ctx, password, hash) case IsArgon2iHash(hash): return CompareArgon2i(ctx, password, hash) case IsPbkdf2Hash(hash): return ComparePbkdf2(ctx, password, hash) default: return errors.WithStack(ErrUnknownHashAlgorithm) } } func CompareBcrypt(_ context.Context, password []byte, hash []byte) error { if err := validateBcryptPasswordLength(password); err != nil { return err } err := bcrypt.CompareHashAndPassword(hash, password) if err != nil { return err } return nil } func CompareArgon2id(_ context.Context, password []byte, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodeArgon2idHash(string(hash)) if err != nil { return err } mem := uint64(p.Memory) if mem > math.MaxUint32 { return errors.WithStack(ErrInvalidHash) } // Derive the key from the other password using the same parameters. otherHash := argon2.IDKey(password, salt, p.Iterations, uint32(mem), p.Parallelism, p.KeyLength) // Check that the contents of the hashed passwords are identical. Note // that we are using the subtle.ConstantTimeCompare() function for this // to help prevent timing attacks. if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return nil } return errors.WithStack(ErrMismatchedHashAndPassword) } func CompareArgon2i(_ context.Context, password []byte, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodeArgon2idHash(string(hash)) if err != nil { return err } mem := uint64(p.Memory) if mem > math.MaxUint32 { return errors.WithStack(ErrInvalidHash) } // Derive the key from the other password using the same parameters. otherHash := argon2.Key(password, salt, p.Iterations, uint32(mem), p.Parallelism, p.KeyLength) // Check that the contents of the hashed passwords are identical. Note // that we are using the subtle.ConstantTimeCompare() function for this // to help prevent timing attacks. if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return nil } return errors.WithStack(ErrMismatchedHashAndPassword) } func ComparePbkdf2(_ context.Context, password []byte, hash []byte) error { // Extract the parameters, salt and derived key from the encoded password // hash. p, salt, hash, err := decodePbkdf2Hash(string(hash)) if err != nil { return err } // Derive the key from the other password using the same parameters. otherHash := pbkdf2.Key(password, salt, int(p.Iterations), int(p.KeyLength), getPseudorandomFunctionForPbkdf2(p.Algorithm)) // Check that the contents of the hashed passwords are identical. Note // that we are using the subtle.ConstantTimeCompare() function for this // to help prevent timing attacks. if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return nil } return errors.WithStack(ErrMismatchedHashAndPassword) } var ( isBcryptHash = regexp.MustCompile(`^\$2[abzy]?\$`) isArgon2idHash = regexp.MustCompile(`^\$argon2id\$`) isArgon2iHash = regexp.MustCompile(`^\$argon2i\$`) isPbkdf2Hash = regexp.MustCompile(`^\$pbkdf2-sha[0-9]{1,3}\$`) ) func IsBcryptHash(hash []byte) bool { return isBcryptHash.Match(hash) } func IsArgon2idHash(hash []byte) bool { return isArgon2idHash.Match(hash) } func IsArgon2iHash(hash []byte) bool { return isArgon2iHash.Match(hash) } func IsPbkdf2Hash(hash []byte) bool { return isPbkdf2Hash.Match(hash) } func decodeArgon2idHash(encodedHash string) (p *Argon2Config, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 6 { return nil, nil, nil, ErrInvalidHash } var version int _, err = fmt.Sscanf(parts[2], "v=%d", &version) if err != nil { return nil, nil, nil, err } if version != argon2.Version { return nil, nil, nil, ErrIncompatibleVersion } p = new(Argon2Config) _, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &p.Memory, &p.Iterations, &p.Parallelism) if err != nil { return nil, nil, nil, err } salt, err = base64.RawStdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, err } saltLength := uint(len(salt)) if saltLength > math.MaxUint32 { return nil, nil, nil, ErrInvalidHash } p.SaltLength = uint32(saltLength) hash, err = base64.RawStdEncoding.Strict().DecodeString(parts[5]) if err != nil { return nil, nil, nil, err } keyLength := uint(len(hash)) if keyLength > math.MaxUint32 { return nil, nil, nil, ErrInvalidHash } p.KeyLength = uint32(keyLength) return p, salt, hash, nil } // decodePbkdf2Hash decodes PBKDF2 encoded password hash. // format: $pbkdf2-$i=,l=$$ func decodePbkdf2Hash(encodedHash string) (p *PBKDF2Config, salt, hash []byte, err error) { parts := strings.Split(encodedHash, "$") if len(parts) != 5 { return nil, nil, nil, ErrInvalidHash } p = new(PBKDF2Config) digestParts := strings.SplitN(parts[1], "-", 2) if len(digestParts) != 2 { return nil, nil, nil, ErrInvalidHash } p.Algorithm = digestParts[1] _, err = fmt.Sscanf(parts[2], "i=%d,l=%d", &p.Iterations, &p.KeyLength) if err != nil { return nil, nil, nil, err } salt, err = base64.RawStdEncoding.Strict().DecodeString(parts[3]) if err != nil { return nil, nil, nil, err } saltLength := uint(len(salt)) if saltLength > math.MaxUint32 { return nil, nil, nil, ErrInvalidHash } p.SaltLength = uint32(saltLength) hash, err = base64.RawStdEncoding.Strict().DecodeString(parts[4]) if err != nil { return nil, nil, nil, err } keyLength := uint(len(hash)) if keyLength > math.MaxUint32 { return nil, nil, nil, ErrInvalidHash } p.KeyLength = uint32(keyLength) return p, salt, hash, nil } ================================================ FILE: oryx/hasherx/hasher.go ================================================ package hasherx import ( "context" ) // Hasher provides methods for generating and comparing password hashes. type Hasher interface { // Generate returns a hash derived from the password or an error if the hash method failed. Generate(ctx context.Context, password []byte) ([]byte, error) // Understands returns whether the given hash can be understood by this hasher. Understands(hash []byte) bool } type HashProvider interface { Hasher() Hasher } const tracingComponent = "github.com/ory/kratos/hash" ================================================ FILE: oryx/hasherx/hasher_argon2.go ================================================ package hasherx import ( "bytes" "context" "crypto/rand" "encoding/base64" "fmt" "math" "time" "github.com/ory/x/otelx" "github.com/inhies/go-bytesize" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "github.com/pkg/errors" "golang.org/x/crypto/argon2" ) var ( ErrInvalidHash = errors.New("the encoded hash is not in the correct format") ErrIncompatibleVersion = errors.New("incompatible version of argon2") ErrMismatchedHashAndPassword = errors.New("passwords do not match") ) type ( // Argon2Config is the configuration for a Argon2 hasher. Argon2Config struct { // Memory is the amount of memory to use. Memory bytesize.ByteSize `json:"memory"` // Iterations is the number of iterations to use. Iterations uint32 `json:"iterations"` // Parallelism is the number of threads to use. Parallelism uint8 `json:"parallelism"` // SaltLength is the length of the salt to use. SaltLength uint32 `json:"salt_length"` // KeyLength is the length of the key to use. KeyLength uint32 `json:"key_length"` // ExpectedDuration is the expected duration of the hash. ExpectedDuration time.Duration `json:"expected_duration"` // ExpectedDeviation is the expected deviation of the hash. ExpectedDeviation time.Duration `json:"expected_deviation"` // DedicatedMemory is the amount of dedicated memory to use. DedicatedMemory bytesize.ByteSize `json:"dedicated_memory"` } // Argon2 is a hasher that uses the Argon2 algorithm. Argon2 struct { c Argon2Configurator } // Argon2Configurator is a function that returns the Argon2 configuration. Argon2Configurator interface { HasherArgon2Config(ctx context.Context) *Argon2Config } ) func NewHasherArgon2(c Argon2Configurator) *Argon2 { return &Argon2{c: c} } func toKB(mem bytesize.ByteSize) (uint32, error) { kb := uint64(mem / bytesize.KB) if kb > math.MaxUint32 { return 0, errors.Errorf("memory %v is too large", mem) } return uint32(kb), nil } // Generate generates a hash for the given password. func (h *Argon2) Generate(ctx context.Context, password []byte) (_ []byte, err error) { ctx, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Argon2.Generate") defer otelx.End(span, &err) p := h.c.HasherArgon2Config(ctx) span.SetAttributes(attribute.String("argon2.config", fmt.Sprintf("#%v", p))) salt := make([]byte, p.SaltLength) if _, err := rand.Read(salt); err != nil { return nil, err } mem, err := toKB(p.Memory) if err != nil { return nil, err } // Pass the plaintext password, salt and parameters to the argon2.IDKey // function. This will generate a hash of the password using the Argon2id // variant. hash := argon2.IDKey(password, salt, p.Iterations, mem, p.Parallelism, p.KeyLength) var b bytes.Buffer if _, err := fmt.Fprintf( &b, "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, mem, p.Iterations, p.Parallelism, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash), ); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return nil, errors.WithStack(err) } return b.Bytes(), nil } // Understands checks if the given hash is in the correct format. func (h *Argon2) Understands(hash []byte) bool { return IsArgon2idHash(hash) } ================================================ FILE: oryx/hasherx/hasher_bcrypt.go ================================================ package hasherx import ( "context" "github.com/pkg/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "golang.org/x/crypto/bcrypt" "github.com/ory/x/otelx" ) // ErrBcryptPasswordLengthReached is returned when the password is longer than 72 bytes. var ErrBcryptPasswordLengthReached = errors.Errorf("passwords are limited to a maximum length of 72 characters") type ( // Bcrypt is a hasher that uses the bcrypt algorithm. Bcrypt struct { c BCryptConfigurator } // BCryptConfig is the configuration for the bcrypt hasher. BCryptConfig struct { Cost uint32 `json:"cost"` } // BCryptConfigurator is the interface that must be implemented by a configuration provider for the bcrypt hasher. BCryptConfigurator interface { HasherBcryptConfig(ctx context.Context) *BCryptConfig } ) func NewHasherBcrypt(c BCryptConfigurator) *Bcrypt { return &Bcrypt{c: c} } // Generate generates a hash for the given password. func (h *Bcrypt) Generate(ctx context.Context, password []byte) (hash []byte, err error) { ctx, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Bcrypt.Generate") defer otelx.End(span, &err) if err := validateBcryptPasswordLength(password); err != nil { return nil, err } cost := int(h.c.HasherBcryptConfig(ctx).Cost) span.SetAttributes(attribute.Int("bcrypt.cost", cost)) hash, err = bcrypt.GenerateFromPassword(password, cost) if err != nil { return nil, err } return hash, nil } func validateBcryptPasswordLength(password []byte) error { // Bcrypt truncates the password to the first 72 bytes, following the OpenBSD implementation, // so if password is longer than 72 bytes, function returns an error // See https://en.wikipedia.org/wiki/Bcrypt#User_input if len(password) > 72 { return ErrBcryptPasswordLengthReached } return nil } // Understands checks if the given hash is in the correct format. func (h *Bcrypt) Understands(hash []byte) bool { return IsBcryptHash(hash) } ================================================ FILE: oryx/hasherx/hasher_pbkdf2.go ================================================ package hasherx import ( "bytes" "context" "crypto/rand" "crypto/sha1" // #nosec G505 - compatibility for imported passwords "crypto/sha256" "crypto/sha512" "encoding/base64" "fmt" "hash" "github.com/pkg/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/sha3" "github.com/ory/x/otelx" ) type ( // PBKDF2 is a PBKDF2 hasher. PBKDF2 struct { c PBKDF2Configurator } // PBKDF2Config is the configuration for a PBKDF2 hasher. PBKDF2Config struct { // Algorithm can be one of sha1, sha224, sha256, sha384, sha512 Algorithm string // Iterations is the number of iterations to use. Iterations uint32 // KeyLength is the length of the salt. SaltLength uint32 // KeyLength is the length of the key. KeyLength uint32 } // PBKDF2Configurator is a configurator for a PBKDF2 hasher. PBKDF2Configurator interface { HasherPBKDF2Config(ctx context.Context) *PBKDF2Config } ) // NewHasherPBKDF2 creates a new PBKDF2 hasher. func NewHasherPBKDF2(c PBKDF2Configurator) *PBKDF2 { return &PBKDF2{c: c} } // Generate generates a hash for the given password. func (h *PBKDF2) Generate(ctx context.Context, password []byte) (hash []byte, err error) { ctx, span := otel.GetTracerProvider().Tracer("").Start(ctx, "hash.PBKDF2.Generate") defer otelx.End(span, &err) conf := h.c.HasherPBKDF2Config(ctx) salt := make([]byte, conf.SaltLength) if _, err := rand.Read(salt); err != nil { return nil, err } key := pbkdf2.Key(password, salt, int(conf.Iterations), int(conf.KeyLength), getPseudorandomFunctionForPbkdf2(conf.Algorithm)) var b bytes.Buffer if _, err := fmt.Fprintf( &b, "$pbkdf2-%s$i=%d,l=%d$%s$%s", conf.Algorithm, conf.Iterations, conf.KeyLength, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(key), ); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return nil, errors.WithStack(err) } return b.Bytes(), nil } // Understands checks if the given hash is in the correct format. func (h *PBKDF2) Understands(hash []byte) bool { return IsPbkdf2Hash(hash) } func getPseudorandomFunctionForPbkdf2(alg string) func() hash.Hash { switch alg { case "sha1": return sha1.New case "sha224": return sha3.New224 case "sha256": return sha256.New case "sha384": return sha3.New384 case "sha512": return sha512.New default: return sha256.New } } ================================================ FILE: oryx/healthx/doc.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package healthx providers helpers for returning health status information via HTTP. package healthx import "strings" // The health status of the service. // // swagger:model healthStatus type swaggerHealthStatus struct { // Status always contains "ok". Status string `json:"status"` } // The not ready status of the service. // // swagger:model healthNotReadyStatus type swaggerNotReadyStatus struct { // Errors contains a list of errors that caused the not ready status. Errors map[string]string `json:"errors"` } func (s swaggerNotReadyStatus) Error() string { var errs []string for _, err := range s.Errors { errs = append(errs, err) } return strings.Join(errs, "; ") } // swagger:model version type swaggerVersion struct { // Version is the service's version. Version string `json:"version"` } ================================================ FILE: oryx/healthx/handler.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package healthx import ( "net/http" "github.com/ory/herodot" ) const ( // AliveCheckPath is the path where information about the life state of the instance is provided. AliveCheckPath = "/health/alive" // ReadyCheckPath is the path where information about the ready state of the instance is provided. ReadyCheckPath = "/health/ready" // VersionPath is the path where information about the software version of the instance is provided. VersionPath = "/version" ) // RoutesToObserve returns a string of all the available routes of this module. func RoutesToObserve() []string { return []string{ AliveCheckPath, ReadyCheckPath, VersionPath, } } // ReadyChecker should return an error if the component is not ready yet. type ReadyChecker func(r *http.Request) error // ReadyCheckers is a map of ReadyCheckers. type ReadyCheckers map[string]ReadyChecker // NoopReadyChecker is always ready. func NoopReadyChecker() error { return nil } // Handler handles HTTP requests to health and version endpoints. type Handler struct { H herodot.Writer VersionString string ReadyChecks ReadyCheckers } type options struct { middleware func(http.Handler) http.Handler } type Options func(*options) // NewHandler instantiates a handler. func NewHandler( h herodot.Writer, version string, readyChecks ReadyCheckers, ) *Handler { return &Handler{ H: h, VersionString: version, ReadyChecks: readyChecks, } } type router interface { Handler(method, path string, handler http.Handler) } // SetHealthRoutes registers this handler's routes for health checking. func (h *Handler) SetHealthRoutes(r router, shareErrors bool, opts ...Options) { o := &options{} aliveHandler := h.Alive() readyHandler := h.Ready(shareErrors) for _, opt := range opts { opt(o) } if o.middleware != nil { aliveHandler = o.middleware(aliveHandler) readyHandler = o.middleware(readyHandler) } r.Handler("GET", AliveCheckPath, aliveHandler) r.Handler("GET", ReadyCheckPath, readyHandler) } // SetVersionRoutes registers this handler's routes for health checking. func (h *Handler) SetVersionRoutes(r router, opts ...Options) { o := &options{} versionHandler := h.Version() for _, opt := range opts { opt(o) } if o.middleware != nil { versionHandler = o.middleware(versionHandler) } r.Handler("GET", VersionPath, versionHandler) } // Alive returns an ok status if the instance is ready to handle HTTP requests. // // swagger:route GET /health/alive health isInstanceAlive // // # Check alive status // // This endpoint returns a 200 status code when the HTTP server is up running. // This status does currently not include checks whether the database connection is working. // // If the service supports TLS Edge Termination, this endpoint does not require the // `X-Forwarded-Proto` header to be set. // // Be aware that if you are running multiple nodes of this service, the health status will never // refer to the cluster state, only to a single instance. // // Produces: // - application/json // - text/plain // // Responses: // 200: healthStatus // default: unexpectedError func (h *Handler) Alive() http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { h.H.Write(rw, r, &swaggerHealthStatus{ Status: "ok", }) }) } // swagger:model unexpectedError // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type unexpectedError string // Ready returns an ok status if the instance is ready to handle HTTP requests and all ReadyCheckers are ok. // // swagger:route GET /health/ready health isInstanceReady // // # Check readiness status // // This endpoint returns a 200 status code when the HTTP server is up running and the environment dependencies (e.g. // the database) are responsive as well. // // If the service supports TLS Edge Termination, this endpoint does not require the // `X-Forwarded-Proto` header to be set. // // Be aware that if you are running multiple nodes of this service, the health status will never // refer to the cluster state, only to a single instance. // // Produces: // - application/json // - text/plain // // Responses: // 200: healthStatus // 503: healthNotReadyStatus // default: unexpectedError func (h *Handler) Ready(shareErrors bool) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { var notReady = swaggerNotReadyStatus{ Errors: map[string]string{}, } for n, c := range h.ReadyChecks { if err := c(r); err != nil { if shareErrors { notReady.Errors[n] = err.Error() } else { notReady.Errors[n] = "error may contain sensitive information and was obfuscated" } } } if len(notReady.Errors) > 0 { h.H.WriteErrorCode(rw, r, http.StatusServiceUnavailable, ¬Ready) return } h.H.Write(rw, r, &swaggerHealthStatus{ Status: "ok", }) }) } // Version returns this service's versions. // // swagger:route GET /version version getVersion // // # Get service version // // This endpoint returns the service version typically notated using semantic versioning. // // If the service supports TLS Edge Termination, this endpoint does not require the // `X-Forwarded-Proto` header to be set. // // Be aware that if you are running multiple nodes of this service, the health status will never // refer to the cluster state, only to a single instance. // // Produces: // - application/json // // Responses: // 200: version func (h *Handler) Version() http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { h.H.Write(rw, r, &swaggerVersion{ Version: h.VersionString, }) }) } // WithMiddleware accepts a http.Handler to be run on the // route handlers func WithMiddleware(h func(http.Handler) http.Handler) Options { return func(o *options) { o.middleware = h } } ================================================ FILE: oryx/healthx/openapi/patch.yaml ================================================ - op: replace path: /paths/~1health~1alive value: get: description: |- This endpoint returns a HTTP 200 status code when {{.ProjectHumanName}} is accepting incoming HTTP requests. This status does currently not include checks whether the database connection is working. If the service supports TLS Edge Termination, this endpoint does not require the `X-Forwarded-Proto` header to be set. Be aware that if you are running multiple nodes of this service, the health status will never refer to the cluster state, only to a single instance. operationId: isAlive responses: '200': content: application/json: schema: required: - status type: object properties: status: description: Always "ok". type: string description: '{{.ProjectHumanName}} is ready to accept connections.' default: content: text/plain: schema: type: string description: Unexpected error summary: Check HTTP Server Status tags: {{ .HealthPathTags | toJson }} - op: replace path: /paths/~1health~1ready value: get: operationId: isReady description: |- This endpoint returns a HTTP 200 status code when {{.ProjectHumanName}} is up running and the environment dependencies (e.g. the database) are responsive as well. If the service supports TLS Edge Termination, this endpoint does not require the `X-Forwarded-Proto` header to be set. Be aware that if you are running multiple nodes of {{.ProjectHumanName}}, the health status will never refer to the cluster state, only to a single instance. responses: '200': content: application/json: schema: required: - status type: object properties: status: description: Always "ok". type: string description: '{{.ProjectHumanName}} is ready to accept requests.' '503': content: application/json: schema: required: - errors properties: errors: additionalProperties: type: string description: Errors contains a list of errors that caused the not ready status. type: object type: object description: Ory Kratos is not yet ready to accept requests. default: content: text/plain: schema: type: string description: Unexpected error summary: Check HTTP Server and Database Status tags: {{ .HealthPathTags | toJson }} - op: replace path: /paths/~1version value: get: description: |- This endpoint returns the version of {{.ProjectHumanName}}. If the service supports TLS Edge Termination, this endpoint does not require the `X-Forwarded-Proto` header to be set. Be aware that if you are running multiple nodes of this service, the version will never refer to the cluster state, only to a single instance. operationId: getVersion responses: '200': content: application/json: schema: type: object required: - version properties: version: description: The version of {{.ProjectHumanName}}. type: string description: Returns the {{.ProjectHumanName}} version. summary: Return Running Software Version. tags: {{ .HealthPathTags | toJson }} ================================================ FILE: oryx/httprouterx/router.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httprouterx import ( "net/http" "path" "strings" "testing" "github.com/ory/x/prometheusx" ) const AdminPrefix = "/admin" type ( router struct { mux *http.ServeMux prefix string metrics *prometheusx.HTTPMetrics } RouterAdmin struct{ router } RouterPublic struct{ router } Router interface { http.Handler GET(route string, handle http.HandlerFunc) HEAD(route string, handle http.HandlerFunc) POST(route string, handle http.HandlerFunc) PUT(route string, handle http.HandlerFunc) PATCH(route string, handle http.HandlerFunc) DELETE(route string, handle http.HandlerFunc) Handler(method, route string, handler http.Handler) } ) func newRouter(metrics *prometheusx.HTTPMetrics) *router { return &router{ mux: http.NewServeMux(), metrics: metrics, } } // NewRouter creates a new general purpose router. It should only be used when neither the admin nor the public router is applicable. func NewRouter(metrics *prometheusx.HTTPMetrics) Router { return newRouter(metrics) } // NewRouterAdmin creates a new admin router. func NewRouterAdmin(metrics *prometheusx.HTTPMetrics) *RouterAdmin { return &RouterAdmin{router: *newRouter(metrics)} } func NewTestRouterAdmin(_ testing.TB) *RouterAdmin { return NewRouterAdmin(nil) } func NewTestRouterAdminWithPrefix(_ testing.TB) *RouterAdmin { return NewRouterAdminWithPrefix(nil) } func NewTestRouterPublic(_ testing.TB) *RouterPublic { return NewRouterPublic(nil) } func (r *RouterAdmin) ToPublic() *RouterPublic { return &RouterPublic{router: router{ mux: r.mux, metrics: r.metrics, prefix: "", // do not copy the admin prefix }} } // NewRouterPublic returns a public router. func NewRouterPublic(metrics *prometheusx.HTTPMetrics) *RouterPublic { return &RouterPublic{router: *newRouter(metrics)} } // NewRouterAdminWithPrefix creates a new router with the admin prefix. func NewRouterAdminWithPrefix(metricsManager *prometheusx.HTTPMetrics) *RouterAdmin { r := NewRouterAdmin(metricsManager) r.prefix = AdminPrefix return r } func (r *router) GET(route string, handle http.HandlerFunc) { r.handle(http.MethodGet, route, handle) } func (r *router) HEAD(route string, handle http.HandlerFunc) { r.handle(http.MethodHead, route, handle) } func (r *router) POST(route string, handle http.HandlerFunc) { r.handle(http.MethodPost, route, handle) } func (r *router) PUT(route string, handle http.HandlerFunc) { r.handle(http.MethodPut, route, handle) } func (r *router) PATCH(route string, handle http.HandlerFunc) { r.handle(http.MethodPatch, route, handle) } func (r *router) DELETE(route string, handle http.HandlerFunc) { r.handle(http.MethodDelete, route, handle) } func (r *router) Handler(method, route string, handler http.Handler) { r.handle(method, route, handler) } func (r *router) handle(method string, route string, handler http.Handler) { route = path.Join(r.prefix, route) if r.metrics != nil { handler = r.metrics.Instrument(handler, prometheusx.GetLabelForPattern(route)) } r.mux.Handle(method+" "+route, handler) } func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.mux.ServeHTTP(w, req) } func TrimTrailingSlashNegroni(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { r.URL.Path = strings.TrimSuffix(r.URL.Path, "/") next(rw, r) } func NoCacheNegroni(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if r.Method == "GET" { rw.Header().Set("Cache-Control", "private, no-cache, no-store, must-revalidate") } next(rw, r) } func AddAdminPrefixIfNotPresentNegroni(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if !strings.HasPrefix(r.URL.Path, AdminPrefix) { r.URL.Path = path.Join(AdminPrefix, r.URL.Path) } next(rw, r) } ================================================ FILE: oryx/httpx/assert.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "net/http" ) func GetResponseMeta(w http.ResponseWriter) (status, size int) { switch t := w.(type) { case interface{ Status() int }: status = t.Status() } switch t := w.(type) { case interface{ Size() int }: size = t.Size() case interface{ Written() int64 }: size = int(t.Written()) } return } ================================================ FILE: oryx/httpx/chan_handler.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import "net/http" type chanHandler <-chan http.HandlerFunc var _ http.Handler = chanHandler(nil) func (c chanHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { (<-c)(w, r) } // NewChanHandler returns a new handler and corresponding channel for sending handler funcs. // Useful for testing. The argument buf specifies the channel capacity, so pass 0 for a sync handler. func NewChanHandler(buf int) (http.Handler, chan<- http.HandlerFunc) { c := make(chan http.HandlerFunc, buf) return chanHandler(c), c } ================================================ FILE: oryx/httpx/client_info.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "net" "net/http" "strconv" "strings" ) type GeoLocation struct { City string Region string Country string Latitude *float64 Longitude *float64 } func GetClientIPAddressesWithoutInternalIPs(ipAddresses []string) (string, error) { var res string for i := len(ipAddresses) - 1; i >= 0; i-- { ip := strings.TrimSpace(ipAddresses[i]) if !net.ParseIP(ip).IsPrivate() { res = ip break } } return res, nil } func ClientIP(r *http.Request) string { if trueClientIP := r.Header.Get("True-Client-IP"); trueClientIP != "" { return trueClientIP } else if cfConnectingIP := r.Header.Get("Cf-Connecting-IP"); cfConnectingIP != "" { return cfConnectingIP } else if realClientIP := r.Header.Get("X-Real-IP"); realClientIP != "" { return realClientIP } else if forwardedIP := r.Header.Get("X-Forwarded-For"); forwardedIP != "" { ip, _ := GetClientIPAddressesWithoutInternalIPs(strings.Split(forwardedIP, ",")) return ip } else { return r.RemoteAddr } } func parseFloatHeaderValue(headerValue string) *float64 { if headerValue == "" { return nil } val, err := strconv.ParseFloat(headerValue, 64) if err != nil { return nil } return &val } func ClientGeoLocation(r *http.Request) *GeoLocation { return &GeoLocation{ City: r.Header.Get("Cf-Ipcity"), Region: r.Header.Get("Cf-Region-Code"), Country: r.Header.Get("Cf-Ipcountry"), Longitude: parseFloatHeaderValue(r.Header.Get("Cf-Iplongitude")), Latitude: parseFloatHeaderValue(r.Header.Get("Cf-Iplatitude")), } } ================================================ FILE: oryx/httpx/content_type.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "mime" "net/http" "slices" "strings" ) // HasContentType determines whether the request `content-type` includes a // server-acceptable mime-type // // Failure should yield an HTTP 415 (`http.StatusUnsupportedMediaType`) func HasContentType(r *http.Request, mimetypes ...string) bool { contentType := r.Header.Get("Content-Type") if contentType == "" { return slices.Contains(mimetypes, "application/octet-stream") } mediaType, _, err := mime.ParseMediaType(strings.TrimSpace(contentType)) if err != nil { return false } return slices.Contains(mimetypes, mediaType) } ================================================ FILE: oryx/httpx/gzip_server.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "compress/gzip" "fmt" "io" "net/http" "strings" ) type CompressionRequestReader struct { ErrHandler func(w http.ResponseWriter, r *http.Request, err error) } func defaultCompressionErrorHandler(w http.ResponseWriter, r *http.Request, err error) { http.Error(w, err.Error(), http.StatusBadRequest) } func NewCompressionRequestReader(eh func(w http.ResponseWriter, r *http.Request, err error)) *CompressionRequestReader { if eh == nil { eh = defaultCompressionErrorHandler } return &CompressionRequestReader{ ErrHandler: eh, } } func (c *CompressionRequestReader) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { for _, enc := range strings.Split(r.Header.Get("Content-Encoding"), ",") { switch enc = strings.TrimSpace(enc); enc { case "gzip": reader, err := gzip.NewReader(r.Body) if err != nil { c.ErrHandler(w, r, err) return } r.Body = io.NopCloser(reader) case "identity", "": // nothing to do default: c.ErrHandler(w, r, fmt.Errorf("%s content encoding not supported", enc)) } } next(w, r) } ================================================ FILE: oryx/httpx/private_ip_validator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "fmt" "net" "net/netip" "net/url" "code.dny.dev/ssrf" "github.com/pkg/errors" ) // ErrPrivateIPAddressDisallowed is returned when a private IP address is disallowed. type ErrPrivateIPAddressDisallowed error // DisallowIPPrivateAddresses returns nil for a domain (with NS lookup), IP, or IPv6 address if it // does not resolve to a private IP subnet. This is a first level of defense against // SSRF attacks by disallowing any domain or IP to resolve to a private network range. // // Please keep in mind that validations for domains is valid only when looking up. // A malicious actor could easily update the DSN record post validation to point // to an internal IP func DisallowIPPrivateAddresses(ipOrHostnameOrURL string) error { lookup := func(hostname string) ([]net.IP, error) { lookup, err := net.LookupIP(hostname) if err != nil { if dnsErr := new(net.DNSError); errors.As(err, &dnsErr) && (dnsErr.IsNotFound || dnsErr.IsTemporary) { // If the hostname does not resolve, we can't validate it. So yeah, // I guess we're allowing it. return nil, nil } return nil, errors.WithStack(err) } return lookup, nil } var ips []net.IP ip := net.ParseIP(ipOrHostnameOrURL) if ip == nil { if result, err := lookup(ipOrHostnameOrURL); err != nil { return err } else if result != nil { ips = append(ips, result...) } if parsed, err := url.Parse(ipOrHostnameOrURL); err == nil { if result, err := lookup(parsed.Hostname()); err != nil { return err } else if result != nil { ips = append(ips, result...) } } } else { ips = append(ips, ip) } for _, ip := range ips { ip, err := netip.ParseAddr(ip.String()) if err != nil { return ErrPrivateIPAddressDisallowed(errors.WithStack(err)) // should be unreacheable } if ip.Is4() { for _, deny := range ssrf.IPv4DeniedPrefixes { if deny.Contains(ip) { return ErrPrivateIPAddressDisallowed(fmt.Errorf("%s is not a public IP address", ip)) } } } else { if !ssrf.IPv6GlobalUnicast.Contains(ip) { return ErrPrivateIPAddressDisallowed(fmt.Errorf("%s is not a public IP address", ip)) } for _, net := range ssrf.IPv6DeniedPrefixes { if net.Contains(ip) { return ErrPrivateIPAddressDisallowed(fmt.Errorf("%s is not a public IP address", ip)) } } } } return nil } ================================================ FILE: oryx/httpx/request.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "bytes" "encoding/json" "io" "net/http" "net/url" "strings" "github.com/pkg/errors" ) // NewRequestJSON returns a new JSON *http.Request. func NewRequestJSON(method, url string, data interface{}) (*http.Request, error) { var b bytes.Buffer if err := json.NewEncoder(&b).Encode(data); err != nil { return nil, errors.WithStack(err) } req, err := http.NewRequest(method, url, &b) if err != nil { return nil, errors.WithStack(err) } req.Header.Set("Content-Type", "application/json") return req, nil } // NewRequestForm returns a new POST Form *http.Request. func NewRequestForm(method, url string, data url.Values) (*http.Request, error) { req, err := http.NewRequest(method, url, strings.NewReader(data.Encode())) if err != nil { return nil, errors.WithStack(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return req, nil } // MustNewRequest returns a new *http.Request or fatals. func MustNewRequest(method, url string, body io.Reader, contentType string) *http.Request { req, err := http.NewRequest(method, url, body) if err != nil { panic(err) } if contentType != "" { req.Header.Set("Content-Type", contentType) } return req } ================================================ FILE: oryx/httpx/resilient_client.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "context" "io" "log" "net/http" "time" "github.com/hashicorp/go-retryablehttp" "golang.org/x/oauth2" "github.com/ory/herodot" "github.com/ory/x/logrusx" ) type resilientOptions struct { c *http.Client l interface{} retryWaitMin time.Duration retryWaitMax time.Duration retryMax int noInternalIPs bool internalIPExceptions []string } func newResilientOptions() *resilientOptions { return &resilientOptions{ c: &http.Client{Timeout: time.Minute}, retryWaitMin: 1 * time.Second, retryWaitMax: 30 * time.Second, retryMax: 4, l: log.New(io.Discard, "", log.LstdFlags), } } // ResilientOptions is a set of options for the ResilientClient. type ResilientOptions func(o *resilientOptions) // ResilientClientWithMaxRetry sets the maximum number of retries. func ResilientClientWithMaxRetry(retryMax int) ResilientOptions { return func(o *resilientOptions) { o.retryMax = retryMax } } // ResilientClientWithMinxRetryWait sets the minimum wait time between retries. func ResilientClientWithMinxRetryWait(retryWaitMin time.Duration) ResilientOptions { return func(o *resilientOptions) { o.retryWaitMin = retryWaitMin } } // ResilientClientWithMaxRetryWait sets the maximum wait time for a retry. func ResilientClientWithMaxRetryWait(retryWaitMax time.Duration) ResilientOptions { return func(o *resilientOptions) { o.retryWaitMax = retryWaitMax } } // ResilientClientWithConnectionTimeout sets the connection timeout for the client. func ResilientClientWithConnectionTimeout(connTimeout time.Duration) ResilientOptions { return func(o *resilientOptions) { o.c.Timeout = connTimeout } } // ResilientClientWithLogger sets the logger to be used by the client. func ResilientClientWithLogger(l *logrusx.Logger) ResilientOptions { return func(o *resilientOptions) { o.l = l } } // ResilientClientDisallowInternalIPs disallows internal IPs from being used. func ResilientClientDisallowInternalIPs() ResilientOptions { return func(o *resilientOptions) { o.noInternalIPs = true } } // ResilientClientAllowInternalIPRequestsTo allows requests to the glob-matching URLs even // if they are internal IPs. func ResilientClientAllowInternalIPRequestsTo(urlGlobs ...string) ResilientOptions { return func(o *resilientOptions) { o.internalIPExceptions = urlGlobs } } // NewResilientClient creates a new ResilientClient. func NewResilientClient(opts ...ResilientOptions) *retryablehttp.Client { o := newResilientOptions() for _, f := range opts { f(o) } if o.noInternalIPs { o.c.Transport = &noInternalIPRoundTripper{ onWhitelist: allowInternalAllowIPv6, notOnWhitelist: prohibitInternalAllowIPv6, internalIPExceptions: o.internalIPExceptions, } } else { o.c.Transport = allowInternalAllowIPv6 } cl := retryablehttp.NewClient() cl.HTTPClient = o.c cl.Logger = o.l cl.RetryWaitMin = o.retryWaitMin cl.RetryWaitMax = o.retryWaitMax cl.RetryMax = o.retryMax cl.CheckRetry = retryablehttp.DefaultRetryPolicy cl.Backoff = retryablehttp.DefaultBackoff return cl } // SetOAuth2 modifies the given client to enable OAuth2 authentication. Requests // with the client should always use the returned context. // // client := http.NewResilientClient(opts...) // ctx, client = httpx.SetOAuth2(ctx, client, oauth2Config, oauth2Token) // req, err := retryablehttp.NewRequestWithContext(ctx, ...) // if err != nil { /* ... */ } // res, err := client.Do(req) func SetOAuth2(ctx context.Context, cl *retryablehttp.Client, c OAuth2Config, t *oauth2.Token) (context.Context, *retryablehttp.Client) { ctx = context.WithValue(ctx, oauth2.HTTPClient, cl.HTTPClient) cl.HTTPClient = c.Client(ctx, t) return ctx, cl } type ( OAuth2Config interface { Client(context.Context, *oauth2.Token) *http.Client } ClientProvider interface { HTTPClient(ctx context.Context, opts ...ResilientOptions) *retryablehttp.Client } WriterProvider interface { Writer() herodot.Writer } ) ================================================ FILE: oryx/httpx/ssrf.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "context" "net" "net/http" "net/http/httptrace" "net/netip" "time" "code.dny.dev/ssrf" "github.com/gobwas/glob" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) var _ http.RoundTripper = (*noInternalIPRoundTripper)(nil) type noInternalIPRoundTripper struct { onWhitelist, notOnWhitelist http.RoundTripper internalIPExceptions []string } // RoundTrip implements http.RoundTripper. func (n noInternalIPRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { incoming := IncomingRequestURL(request) incoming.RawQuery = "" incoming.RawFragment = "" for _, exception := range n.internalIPExceptions { compiled, err := glob.Compile(exception, '.', '/') if err != nil { return nil, err } if compiled.Match(incoming.String()) { return n.onWhitelist.RoundTrip(request) } } return n.notOnWhitelist.RoundTrip(request) } var ( prohibitInternalAllowIPv6 http.RoundTripper allowInternalAllowIPv6 http.RoundTripper ) func init() { t, d := newDefaultTransport() d.Control = ssrf.New( ssrf.WithAnyPort(), ssrf.WithNetworks("tcp4", "tcp6"), ).Safe prohibitInternalAllowIPv6 = OTELTraceTransport(t) } func init() { t, d := newDefaultTransport() d.Control = ssrf.New( ssrf.WithAnyPort(), ssrf.WithNetworks("tcp4"), ).Safe t.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return d.DialContext(ctx, "tcp4", addr) } } func init() { t, d := newDefaultTransport() d.Control = ssrf.New( ssrf.WithAnyPort(), ssrf.WithNetworks("tcp4", "tcp6"), ssrf.WithAllowedV4Prefixes( netip.MustParsePrefix("10.0.0.0/8"), // Private-Use (RFC 1918) netip.MustParsePrefix("127.0.0.0/8"), // Loopback (RFC 1122, Section 3.2.1.3)) netip.MustParsePrefix("169.254.0.0/16"), // Link Local (RFC 3927) netip.MustParsePrefix("172.16.0.0/12"), // Private-Use (RFC 1918) netip.MustParsePrefix("192.168.0.0/16"), // Private-Use (RFC 1918) ), ssrf.WithAllowedV6Prefixes( netip.MustParsePrefix("::1/128"), // Loopback (RFC 4193) netip.MustParsePrefix("fc00::/7"), // Unique Local (RFC 4193) ), ).Safe allowInternalAllowIPv6 = OTELTraceTransport(t) } func init() { t, d := newDefaultTransport() d.Control = ssrf.New( ssrf.WithAnyPort(), ssrf.WithNetworks("tcp4"), ssrf.WithAllowedV4Prefixes( netip.MustParsePrefix("10.0.0.0/8"), // Private-Use (RFC 1918) netip.MustParsePrefix("127.0.0.0/8"), // Loopback (RFC 1122, Section 3.2.1.3)) netip.MustParsePrefix("169.254.0.0/16"), // Link Local (RFC 3927) netip.MustParsePrefix("172.16.0.0/12"), // Private-Use (RFC 1918) netip.MustParsePrefix("192.168.0.0/16"), // Private-Use (RFC 1918) ), ssrf.WithAllowedV6Prefixes( netip.MustParsePrefix("::1/128"), // Loopback (RFC 4193) netip.MustParsePrefix("fc00::/7"), // Unique Local (RFC 4193) ), ).Safe t.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return d.DialContext(ctx, "tcp4", addr) } } func newDefaultTransport() (*http.Transport, *net.Dialer) { dialer := net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, } return &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: dialer.DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, &dialer } // OTELTraceTransport wraps the given http.Transport with OpenTelemetry instrumentation. func OTELTraceTransport(t *http.Transport) http.RoundTripper { return otelhttp.NewTransport(t, otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { return otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutHeaders(), otelhttptrace.WithoutSubSpans()) })) } ================================================ FILE: oryx/httpx/transports.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import "net/http" // WrapTransportWithHeader wraps a http.Transport to always use the values from the given header. func WrapTransportWithHeader(parent http.RoundTripper, h http.Header) *TransportWithHeader { return &TransportWithHeader{ RoundTripper: parent, h: h, } } // NewTransportWithHeader returns a new http.Transport that always uses the values from the given header. func NewTransportWithHeader(h http.Header) *TransportWithHeader { return &TransportWithHeader{ RoundTripper: http.DefaultTransport, h: h, } } // TransportWithHeader is an http.RoundTripper that always uses the values from the given header. type TransportWithHeader struct { http.RoundTripper h http.Header } // RoundTrip implements http.RoundTripper. func (ct *TransportWithHeader) RoundTrip(req *http.Request) (*http.Response, error) { for k := range ct.h { req.Header.Set(k, ct.h.Get(k)) } return ct.RoundTripper.RoundTrip(req) } // NewTransportWithHost returns a new http.Transport that always uses the given host. func NewTransportWithHost(host string) *TransportWithHost { return &TransportWithHost{ RoundTripper: http.DefaultTransport, host: host, } } // WrapRoundTripperWithHost wraps a http.RoundTripper that always uses the given host. func WrapRoundTripperWithHost(parent http.RoundTripper, host string) *TransportWithHost { return &TransportWithHost{ RoundTripper: parent, host: host, } } // TransportWithHost is an http.RoundTripper that always uses the given host. type TransportWithHost struct { http.RoundTripper host string } // RoundTrip implements http.RoundTripper. func (ct *TransportWithHost) RoundTrip(req *http.Request) (*http.Response, error) { req.Host = ct.host return ct.RoundTripper.RoundTrip(req) } ================================================ FILE: oryx/httpx/url.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "cmp" "net/http" "net/url" ) // IncomingRequestURL returns the URL of the incoming HTTP request by looking at the host, TLS, and X-Forwarded-* headers. func IncomingRequestURL(r *http.Request) *url.URL { source := *r.URL source.Host = cmp.Or(source.Host, r.Header.Get("X-Forwarded-Host"), r.Host) if proto := r.Header.Get("X-Forwarded-Proto"); len(proto) > 0 { source.Scheme = proto } if source.Scheme == "" { source.Scheme = "https" if r.TLS == nil { source.Scheme = "http" } } return &source } ================================================ FILE: oryx/httpx/wait_for.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package httpx import ( "context" "io" "net/http" "time" "github.com/avast/retry-go/v4" "github.com/pkg/errors" "github.com/tidwall/gjson" ) // WaitForEndpoint waits for the endpoint to be available. func WaitForEndpoint(ctx context.Context, endpoint string, opts ...retry.Option) error { return WaitForEndpointWithClient(ctx, http.DefaultClient, endpoint, opts...) } // WaitForEndpointWithClient waits for the endpoint to be available while using the given http.Client. func WaitForEndpointWithClient(ctx context.Context, client *http.Client, endpoint string, opts ...retry.Option) error { return retry.Do(func() error { req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { return err } res, err := client.Do(req) if err != nil { return err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return err } if gjson.GetBytes(body, "status").String() != "ok" { return errors.Errorf("status is not yet ok: %s", body) } return nil }, append([]retry.Option{ retry.DelayType(retry.BackOffDelay), retry.Delay(time.Second), retry.MaxDelay(time.Second * 2), retry.Attempts(20), }, opts...)...) } ================================================ FILE: oryx/ioutilx/pkger.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package ioutilx import ( "io" ) // MustReadAll reads a reader or panics. func MustReadAll(r io.Reader) []byte { all, err := io.ReadAll(r) if err != nil { panic(err) } return all } ================================================ FILE: oryx/ipx/cidr.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package ipx import ( "iter" "net/netip" ) func Hosts(prefix netip.Prefix) iter.Seq[netip.Addr] { prefix = prefix.Masked() return func(yield func(netip.Addr) bool) { if !prefix.IsValid() { return } for addr := prefix.Addr().Next(); prefix.Contains(addr); addr = addr.Next() { if !yield(addr) { return } } } } ================================================ FILE: oryx/ipx/ip_validator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package ipx import ( "context" "net" "net/url" "time" "golang.org/x/sync/errgroup" "github.com/pkg/errors" ) // IsAssociatedIPAllowedWhenSet is a wrapper for IsAssociatedIPAllowed which returns valid // when ipOrHostnameOrURL is empty. func IsAssociatedIPAllowedWhenSet(ipOrHostnameOrURL string) error { if ipOrHostnameOrURL == "" { return nil } return IsAssociatedIPAllowed(ipOrHostnameOrURL) } // AreAllAssociatedIPsAllowed fails if one of the pairs is failing. func AreAllAssociatedIPsAllowed(pairs map[string]string) error { g := new(errgroup.Group) for key, ipOrHostnameOrURL := range pairs { key := key ipOrHostnameOrURL := ipOrHostnameOrURL g.Go(func() error { return errors.Wrapf(IsAssociatedIPAllowed(ipOrHostnameOrURL), "key %s validation is failing", key) }) } return g.Wait() } // IsAssociatedIPAllowed returns nil for a domain (with NS lookup), IP, or IPv6 address if it // does not resolve to a private IP subnet. This is a first level of defense against // SSRF attacks by disallowing any domain or IP to resolve to a private network range. // // Please keep in mind that validations for domains is valid only when looking up. // A malicious actor could easily update the DSN record post validation to point // to an internal IP func IsAssociatedIPAllowed(ipOrHostnameOrURL string) error { lookup := func(hostname string) []net.IP { ctx, cancel := context.WithTimeoutCause(context.Background(), 2*time.Second, errors.Errorf("failed to resolve %s within 2s", ipOrHostnameOrURL)) defer cancel() lookup, err := net.DefaultResolver.LookupIPAddr(ctx, hostname) if err != nil { return nil } ips := make([]net.IP, len(lookup)) for i, ip := range lookup { ips[i] = ip.IP } return ips } var ips []net.IP ip := net.ParseIP(ipOrHostnameOrURL) if ip == nil { if result := lookup(ipOrHostnameOrURL); result != nil { ips = append(ips, result...) } if parsed, err := url.Parse(ipOrHostnameOrURL); err == nil { if result := lookup(parsed.Hostname()); result != nil { ips = append(ips, result...) } } } else { ips = append(ips, ip) } for _, disabled := range []string{ "127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd47:1ed0:805d:59f0::/64", "fc00::/7", "::1/128", } { _, cidr, err := net.ParseCIDR(disabled) if err != nil { return err } for _, ip := range ips { if cidr.Contains(ip) { return errors.Errorf("ip %s is in the %s range", ip, disabled) } } } return nil } ================================================ FILE: oryx/josex/encoding.go ================================================ /*- * Copyright 2019 Square Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package josex import "io" // Base64Reader wraps an input stream consisting of either standard or url-safe // base64 data, and maps it to a raw (unpadded) standard encoding. This can be used // to read any base64-encoded data as input, whether padded, unpadded, standard or // url-safe. type Base64Reader struct { In io.Reader } func (r Base64Reader) Read(p []byte) (n int, err error) { n, err = r.In.Read(p) if err != nil { return } for i := range n { switch p[i] { // Map - to + case 0x2D: p[i] = 0x2B // Map _ to / case 0x5F: p[i] = 0x2F // Strip = case 0x3D: n = i default: } } if n == 0 { err = io.EOF } return } ================================================ FILE: oryx/josex/generate.go ================================================ /*- * Copyright 2019 Square Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package josex import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "errors" "fmt" "github.com/go-jose/go-jose/v3" ) // NewSigningKey generates a keypair for corresponding SignatureAlgorithm. func NewSigningKey(alg jose.SignatureAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) { switch alg { case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA: keylen := map[jose.SignatureAlgorithm]int{ jose.ES256: 256, jose.ES384: 384, jose.ES512: 521, // sic! jose.EdDSA: 256, } if bits != 0 && bits != keylen[alg] { return nil, nil, errors.New("invalid elliptic curve key size, this algorithm does not support arbitrary size") } case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512: if bits == 0 { bits = 2048 } if bits < 2048 { return nil, nil, errors.New("invalid key size for RSA key, 2048 or more is required") } } switch alg { case jose.ES256: key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } return key.Public(), key, err case jose.ES384: key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { return nil, nil, err } return key.Public(), key, err case jose.ES512: key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) if err != nil { return nil, nil, err } return key.Public(), key, err case jose.EdDSA: pub, key, err := ed25519.GenerateKey(rand.Reader) return pub, key, err case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512: key, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { return nil, nil, err } return key.Public(), key, err default: return nil, nil, fmt.Errorf("unknown algorithm %s for signing key", alg) } } // NewEncryptionKey generates a keypair for corresponding KeyAlgorithm. func NewEncryptionKey(alg jose.KeyAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) { switch alg { case jose.RSA1_5, jose.RSA_OAEP, jose.RSA_OAEP_256: if bits == 0 { bits = 2048 } if bits < 2048 { return nil, nil, errors.New("invalid key size for RSA key, 2048 or more is required") } key, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { return nil, nil, err } return key.Public(), key, err case jose.ECDH_ES, jose.ECDH_ES_A128KW, jose.ECDH_ES_A192KW, jose.ECDH_ES_A256KW: var crv elliptic.Curve switch bits { case 0, 256: crv = elliptic.P256() case 384: crv = elliptic.P384() case 521: crv = elliptic.P521() default: return nil, nil, errors.New("invalid elliptic curve key size, use one of 256, 384, or 521") } key, err := ecdsa.GenerateKey(crv, rand.Reader) if err != nil { return nil, nil, err } return key.Public(), key, err default: return nil, nil, fmt.Errorf("unknown algorithm %s for encryption key", alg) } } ================================================ FILE: oryx/josex/public.go ================================================ package josex import ( "crypto" "github.com/go-jose/go-jose/v3" ) // ToPublicKey returns the public key of the given private key. func ToPublicKey(k *jose.JSONWebKey) jose.JSONWebKey { if key := k.Public(); key.Key != nil { return key } // HSM workaround - jose does not understand crypto.Signer / HSM so we need to manually // extract the public key. switch key := k.Key.(type) { case crypto.Signer: newKey := *k newKey.Key = key.Public() return newKey case jose.OpaqueSigner: newKey := *k newKey.Key = key.Public().Key return newKey } return jose.JSONWebKey{} } ================================================ FILE: oryx/josex/utils.go ================================================ /*- * Copyright 2019 Square Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package josex import ( "crypto/x509" "encoding/pem" "errors" "fmt" "github.com/go-jose/go-jose/v3" ) // LoadJSONWebKey returns a *jose.JSONWebKey for a given JSON string. func LoadJSONWebKey(json []byte, pub bool) (*jose.JSONWebKey, error) { var jwk jose.JSONWebKey err := jwk.UnmarshalJSON(json) if err != nil { return nil, err } if !jwk.Valid() { return nil, errors.New("invalid JWK key") } if jwk.IsPublic() != pub { return nil, errors.New("priv/pub JWK key mismatch") } return &jwk, nil } // LoadPublicKey loads a public key from PEM/DER/JWK-encoded data. func LoadPublicKey(data []byte) (interface{}, error) { input := data block, _ := pem.Decode(data) if block != nil { input = block.Bytes } // Try to load SubjectPublicKeyInfo pub, err0 := x509.ParsePKIXPublicKey(input) if err0 == nil { return pub, nil } cert, err1 := x509.ParseCertificate(input) if err1 == nil { return cert.PublicKey, nil } jwk, err2 := LoadJSONWebKey(data, true) if err2 == nil { return jwk, nil } return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s' and '%s'", err0, err1, err2) } // LoadPrivateKey loads a private key from PEM/DER/JWK-encoded data. func LoadPrivateKey(data []byte) (interface{}, error) { input := data block, _ := pem.Decode(data) if block != nil { input = block.Bytes } var priv interface{} priv, err0 := x509.ParsePKCS1PrivateKey(input) if err0 == nil { return priv, nil } priv, err1 := x509.ParsePKCS8PrivateKey(input) if err1 == nil { return priv, nil } priv, err2 := x509.ParseECPrivateKey(input) if err2 == nil { return priv, nil } jwk, err3 := LoadJSONWebKey(input, false) if err3 == nil { return jwk, nil } return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s', '%s' and '%s'", err0, err1, err2, err3) } ================================================ FILE: oryx/jsonnetsecure/cmd/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "os" "github.com/ory/x/jsonnetsecure" ) func main() { if err := jsonnetsecure.NewJsonnetCmd().ExecuteContext(context.Background()); err != nil { fmt.Println(err) os.Exit(-1) } } ================================================ FILE: oryx/jsonnetsecure/cmd.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetsecure import ( "bufio" "fmt" "io" "github.com/pkg/errors" "github.com/spf13/cobra" ) const ( GiB uint64 = 1024 * 1024 * 1024 // Generous limit on virtual memory including the peak memory allocated by the Go runtime, the Jsonnet VM, // and the Jsonnet script. // This number was acquired by running: // Found by trial and error with: // `ulimit -Sv 1048576 && echo '{"Snippet": "{user_id: std.repeat(\'a\', 1000)}"}' | kratos jsonnet -0` // NOTE: Ideally we'd like to limit RSS but that is not possible on Linux with `ulimit/setrlimit(2)` - only with cgroups. virtualMemoryLimitBytes = 2 * GiB ) func NewJsonnetCmd() *cobra.Command { var null bool cmd := &cobra.Command{ Use: "jsonnet", Short: "Run Jsonnet as a CLI command", Hidden: true, RunE: func(cmd *cobra.Command, args []string) error { // This could fail because current limits are lower than what we tried to set, // so we still continue in this case. SetVirtualMemoryLimit(virtualMemoryLimitBytes) if null { return scan(cmd.OutOrStdout(), cmd.InOrStdin()) } input, err := io.ReadAll(cmd.InOrStdin()) if err != nil { return errors.Wrap(err, "failed to read from stdin") } json, err := eval(input) if err != nil { return errors.Wrap(err, "failed to evaluate jsonnet") } if _, err := io.WriteString(cmd.OutOrStdout(), json); err != nil { return errors.Wrap(err, "failed to write json output") } return nil }, } cmd.Flags().BoolVarP(&null, "null", "0", false, `Read multiple snippets and parameters from stdin separated by null bytes. Output will be in the same order as inputs, separated by null bytes. Evaluation errors will also be reported to stdout, separated by null bytes. Non-recoverable errors are written to stderr and the program will terminate with a non-zero exit code.`) return cmd } func scan(w io.Writer, r io.Reader) error { scanner := bufio.NewScanner(r) scanner.Split(splitNull) for scanner.Scan() { json, err := eval(scanner.Bytes()) if err != nil { json = fmt.Sprintf("ERROR: %s", err) } if _, err := fmt.Fprintf(w, "%s%c", json, 0); err != nil { return errors.Wrap(err, "failed to write json output") } } return errors.Wrap(scanner.Err(), "failed to read from stdin") } func eval(input []byte) (json string, err error) { var params processParameters if err := params.Decode(input); err != nil { return "", err } vm := MakeSecureVM() for _, it := range params.ExtCodes { vm.ExtCode(it.Key, it.Value) } for _, it := range params.ExtVars { vm.ExtVar(it.Key, it.Value) } for _, it := range params.TLACodes { vm.TLACode(it.Key, it.Value) } for _, it := range params.TLAVars { vm.TLAVar(it.Key, it.Value) } return vm.EvaluateAnonymousSnippet(params.Filename, params.Snippet) } ================================================ FILE: oryx/jsonnetsecure/jsonnet.go ================================================ package jsonnetsecure import ( "bytes" "context" "encoding/json" "fmt" "io" "os" "os/exec" "path" "runtime" "testing" "github.com/google/go-jsonnet" ) type ( VM interface { EvaluateAnonymousSnippet(filename string, snippet string) (json string, formattedErr error) ExtCode(key string, val string) ExtVar(key string, val string) TLACode(key string, val string) TLAVar(key string, val string) } kv struct { Key, Value string } processParameters struct { Filename, Snippet string TLACodes, TLAVars, ExtCodes, ExtVars []kv } vmOptions struct { jsonnetBinaryPath string args []string ctx context.Context pool *pool } Option func(o *vmOptions) ) func (pp *processParameters) EncodeTo(w io.Writer) error { return json.NewEncoder(w).Encode(pp) } func (pp *processParameters) Decode(d []byte) error { return json.Unmarshal(d, pp) } func newVMOptions() *vmOptions { jsonnetBinaryPath, _ := os.Executable() return &vmOptions{ jsonnetBinaryPath: jsonnetBinaryPath, ctx: context.Background(), } } func WithContext(ctx context.Context) Option { return func(o *vmOptions) { o.ctx = ctx } } func WithProcessPool(p Pool) Option { return func(o *vmOptions) { pool, _ := p.(*pool) o.pool = pool } } func WithJsonnetBinary(jsonnetBinaryPath string) Option { return func(o *vmOptions) { o.jsonnetBinaryPath = jsonnetBinaryPath } } func WithProcessArgs(args ...string) Option { return func(o *vmOptions) { o.args = args } } func MakeSecureVM(opts ...Option) VM { options := newVMOptions() for _, o := range opts { o(options) } if options.pool != nil { return NewProcessPoolVM(options) } else { vm := jsonnet.MakeVM() vm.Importer(new(ErrorImporter)) return vm } } // ErrorImporter errors when calling "import". type ErrorImporter struct{} // Import fetches data from a map entry. // All paths are treated as absolute keys. func (importer *ErrorImporter) Import(importedFrom, importedPath string) (contents jsonnet.Contents, foundAt string, err error) { return jsonnet.Contents{}, "", fmt.Errorf("import not available %v", importedPath) } func JsonnetTestBinary(t testing.TB) string { t.Helper() // We can force the usage of a given jsonnet executable. // Useful to test different versions, or run the tests under wine. if s := os.Getenv("ORY_JSONNET_PATH"); s != "" { return s } var stderr bytes.Buffer // Using `t.TempDir()` results in permissions errors on Windows, sometimes. outPath := path.Join(os.TempDir(), "jsonnet") if runtime.GOOS == "windows" { outPath = outPath + ".exe" } cmd := exec.Command("go", "build", "-o", outPath, "github.com/ory/x/jsonnetsecure/cmd") cmd.Stderr = &stderr if err := cmd.Run(); err != nil || stderr.Len() != 0 { t.Fatalf("building the Go binary returned error: %v\n%s", err, stderr.String()) } return outPath } ================================================ FILE: oryx/jsonnetsecure/jsonnet_pool.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetsecure // Known limitations/edge cases: // - The child process exiting early (e.g. crashing) or getting killed (e.g. reaching some OS limit) // is not detected and no error will be returned in this case from `eval()`. // - Misbehaving jsonnet scripts in the middle of a batch being passed to the child process for evaluation may result in // no error (as mentioned above), and other valid scripts in this batch may result // in an error (because the output from the child process is truncated). // // Possible remediations: // - Do not pass a batch of scripts to a worker, only pass one script at a time (to isolate misbehaving scripts) // - Validate that the output is valid JSON (to detect truncated output) // - Detect the child process exiting (to return an error) import ( "bufio" "context" "encoding/json" "io" "math" "os/exec" "strings" "time" "github.com/jackc/puddle/v2" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" "go.opentelemetry.io/otel/trace" "github.com/ory/x/otelx" ) const ( KiB = 1024 jsonnetOutputLimit = 512 * KiB jsonnetErrLimit = 1 * KiB ) type ( processPoolVM struct { path string args []string ctx context.Context params processParameters pool *pool } Pool interface { Close() private() } pool struct { puddle *puddle.Pool[worker] } worker struct { cmd *exec.Cmd stdin chan<- []byte stdout <-chan string stderr <-chan string } contextKeyType string ) var ( ErrProcessPoolClosed = errors.New("jsonnetsecure: process pool closed") _ VM = (*processPoolVM)(nil) _ Pool = (*pool)(nil) contextValuePath contextKeyType = "argc" contextValueArgs contextKeyType = "argv" ) func NewProcessPool(size int) Pool { size = max(5, min(size, math.MaxInt32)) pud, err := puddle.NewPool(&puddle.Config[worker]{ MaxSize: int32(size), //nolint:gosec // disable G115 // because of the previous min/max, 5 <= size <= math.MaxInt32 Constructor: newWorker, Destructor: worker.destroy, }) if err != nil { panic(err) // this should never happen, see implementation of puddle.NewPool } for range size { // warm pool go pud.CreateResource(context.Background()) } go func() { for { time.Sleep(10 * time.Second) for _, proc := range pud.AcquireAllIdle() { if proc.Value().cmd.ProcessState != nil { proc.Destroy() } else { proc.Release() } } } }() return &pool{pud} } func (*pool) private() {} func (p *pool) Close() { p.puddle.Close() } func newWorker(ctx context.Context) (_ worker, err error) { tracer := trace.SpanFromContext(ctx).TracerProvider().Tracer("") ctx, span := tracer.Start(ctx, "jsonnetsecure.newWorker") defer otelx.End(span, &err) path, _ := ctx.Value(contextValuePath).(string) if path == "" { return worker{}, errors.New("newWorker: missing binary path in context") } args, _ := ctx.Value(contextValueArgs).([]string) cmd := exec.Command(path, append(args, "-0")...) cmd.Env = []string{"GOMAXPROCS=1"} cmd.WaitDelay = 100 * time.Millisecond span.SetAttributes(semconv.ProcessCommand(cmd.Path), semconv.ProcessCommandArgs(cmd.Args...)) stdin, err := cmd.StdinPipe() if err != nil { return worker{}, errors.Wrap(err, "newWorker: failed to create stdin pipe") } in := make(chan []byte, 1) go func(c <-chan []byte) { for input := range c { if _, err := stdin.Write(append(input, 0)); err != nil { stdin.Close() return } } }(in) stdout, err := cmd.StdoutPipe() if err != nil { return worker{}, errors.Wrap(err, "newWorker: failed to create stdout pipe") } stderr, err := cmd.StderrPipe() if err != nil { return worker{}, errors.Wrap(err, "newWorker: failed to create stderr pipe") } if err := cmd.Start(); err != nil { return worker{}, errors.Wrap(err, "newWorker: failed to start process") } span.SetAttributes(semconv.ProcessPID(cmd.Process.Pid)) scan := func(c chan<- string, r io.Reader) { defer close(c) // NOTE: `bufio.Scanner` has its own internal limit of 64 KiB. scanner := bufio.NewScanner(r) scanner.Split(splitNull) for scanner.Scan() { c <- scanner.Text() } if err := scanner.Err(); err != nil { c <- "ERROR: scan: " + err.Error() } } out := make(chan string, 1) go scan(out, stdout) errs := make(chan string, 1) go scan(errs, stderr) w := worker{ cmd: cmd, stdin: in, stdout: out, stderr: errs, } _, err = w.eval(ctx, []byte("{}")) // warm up if err != nil { w.destroy() return worker{}, errors.Wrap(err, "newWorker: warm up failed") } return w, nil } func (w worker) destroy() { close(w.stdin) w.cmd.Process.Kill() w.cmd.Wait() } func (w worker) eval(ctx context.Context, processParams []byte) (output string, err error) { tracer := trace.SpanFromContext(ctx).TracerProvider().Tracer("") ctx, span := tracer.Start(ctx, "jsonnetsecure.worker.eval", trace.WithAttributes( semconv.ProcessPID(w.cmd.Process.Pid))) defer otelx.End(span, &err) select { case <-ctx.Done(): return "", ctx.Err() case w.stdin <- processParams: break } select { case <-ctx.Done(): return "", ctx.Err() case output := <-w.stdout: return output, nil case err := <-w.stderr: return "", errors.New(err) } } func (vm *processPoolVM) EvaluateAnonymousSnippet(filename string, snippet string) (_ string, err error) { tracer := trace.SpanFromContext(vm.ctx).TracerProvider().Tracer("") ctx, span := tracer.Start(vm.ctx, "jsonnetsecure.processPoolVM.EvaluateAnonymousSnippet", trace.WithAttributes(attribute.String("filename", filename))) defer otelx.End(span, &err) params := vm.params params.Filename = filename params.Snippet = snippet pp, err := json.Marshal(params) if err != nil { return "", errors.Wrap(err, "jsonnetsecure: marshal") } ctx = context.WithValue(ctx, contextValuePath, vm.path) ctx = context.WithValue(ctx, contextValueArgs, vm.args) worker, err := vm.pool.puddle.Acquire(ctx) if err != nil { return "", errors.Wrap(err, "jsonnetsecure: acquire") } ctx, cancel := context.WithTimeoutCause(ctx, 1*time.Second, errors.Errorf("failed to run jsonnet within 1s: filename=%s", filename)) defer cancel() result, err := worker.Value().eval(ctx, pp) if err != nil { worker.Destroy() return "", errors.Wrap(err, "jsonnetsecure: eval") } else { worker.Release() } if strings.HasPrefix(result, "ERROR: ") { return "", errors.New("jsonnetsecure: " + result) } return result, nil } func NewProcessPoolVM(opts *vmOptions) VM { ctx := opts.ctx if ctx == nil { ctx = context.Background() } return &processPoolVM{ path: opts.jsonnetBinaryPath, args: opts.args, ctx: ctx, pool: opts.pool, } } func (vm *processPoolVM) ExtCode(key string, val string) { vm.params.ExtCodes = append(vm.params.ExtCodes, kv{key, val}) } func (vm *processPoolVM) ExtVar(key string, val string) { vm.params.ExtVars = append(vm.params.ExtVars, kv{key, val}) } func (vm *processPoolVM) TLACode(key string, val string) { vm.params.TLACodes = append(vm.params.TLACodes, kv{key, val}) } func (vm *processPoolVM) TLAVar(key string, val string) { vm.params.TLAVars = append(vm.params.TLAVars, kv{key, val}) } ================================================ FILE: oryx/jsonnetsecure/limit_unix.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build !windows package jsonnetsecure import ( "fmt" "runtime/debug" "syscall" "github.com/pkg/errors" ) func SetVirtualMemoryLimit(limitBytes uint64) error { // Tell the Go runtime about the limit. debug.SetMemoryLimit(int64(limitBytes)) //nolint:gosec // The number is a compile-time constant. lim := syscall.Rlimit{ Cur: limitBytes, Max: limitBytes, } err := syscall.Setrlimit(syscall.RLIMIT_AS, &lim) if err != nil { return errors.WithStack(fmt.Errorf("failed to set virtual memory limit: %v", err)) } return nil } ================================================ FILE: oryx/jsonnetsecure/limit_windows.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build windows package jsonnetsecure import "runtime/debug" func SetVirtualMemoryLimit(limitBytes uint64) error { // Tell the Go runtime about the limit. debug.SetMemoryLimit(int64(limitBytes)) //nolint:gosec // The number is a compile-time constant. // TODO No OS limit for now. Apparently there is a Windows-specific equivalent (Job control)? return nil } ================================================ FILE: oryx/jsonnetsecure/null.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetsecure import "bytes" func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) { // Look for a null byte; if found, return the position after it, // the data before it, and no error. if i := bytes.IndexByte(data, 0); i >= 0 { return i + 1, data[0:i], nil } // If we're at EOF, we have a final, non-terminated word. Return it. if atEOF && len(data) != 0 { return len(data), data, nil } // Request more data. return 0, nil, nil } ================================================ FILE: oryx/jsonnetsecure/provider.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetsecure import ( "context" "os" "runtime" "testing" ) type ( VMProvider interface { // JsonnetVM creates a new secure process-isolated Jsonnet VM whose // execution is bound to the provided context, i.e., // cancelling the context will terminate the VM process. JsonnetVM(context.Context) (VM, error) } // TestProvider provides a secure VM by running go build on github.com/ory/x/jsonnetsecure/cmd TestProvider struct { jsonnetBinary string pool Pool } // DefaultProvider provides a secure VM by calling the currently // running the current binary with the provided subcommand. DefaultProvider struct { Subcommand string Pool Pool } ) func NewTestProvider(t testing.TB) *TestProvider { pool := NewProcessPool(runtime.GOMAXPROCS(0)) t.Cleanup(pool.Close) return &TestProvider{JsonnetTestBinary(t), pool} } func (p *TestProvider) JsonnetVM(ctx context.Context) (VM, error) { return MakeSecureVM( WithContext(ctx), WithProcessPool(p.pool), WithJsonnetBinary(p.jsonnetBinary), ), nil } func (p *DefaultProvider) JsonnetVM(ctx context.Context) (VM, error) { self, err := os.Executable() if err != nil { return nil, err } return MakeSecureVM( WithContext(ctx), WithJsonnetBinary(self), WithProcessArgs(p.Subcommand), WithProcessPool(p.Pool), ), nil } ================================================ FILE: oryx/jsonnetsecure/stub/import.jsonnet ================================================ { foo: 'bar' } ================================================ FILE: oryx/jsonnetx/format.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetx import ( "fmt" "os" "github.com/bmatcuk/doublestar/v2" "github.com/google/go-jsonnet/formatter" "github.com/spf13/cobra" "github.com/ory/x/cmdx" ) func NewFormatCommand() *cobra.Command { var verbose, write bool cmd := &cobra.Command{ Use: "format path/to/files/*.jsonnet [more/files.jsonnet, [supports/**/{foo,bar}.jsonnet]]", Long: `Formats JSONNet files using the official JSONNet formatter. Use -w or --write to write output back to files instead of stdout. ` + GlobHelp, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { for _, pattern := range args { files, err := doublestar.Glob(pattern) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Glob pattern %q is not valid: %s\n", pattern, err) return cmdx.FailSilently(cmd) } for _, file := range files { if fi, err := os.Stat(file); err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Matching file %q could not be opened: %s\n", file, err) return cmdx.FailSilently(cmd) } else if fi.IsDir() { continue } if verbose { fmt.Printf("Processing file: %s\n", file) } //#nosec G304 -- false positive content, err := os.ReadFile(file) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unable to read file %q: %s\n", file, err) return cmdx.FailSilently(cmd) } output, err := formatter.Format(file, string(content), formatter.DefaultOptions()) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "File %q could not be formatted: %s", file, err) } if write { err := os.WriteFile(file, []byte(output), 0o644) // #nosec if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unable to write file %q: %s\n", file, err) return cmdx.FailSilently(cmd) } } else { fmt.Println(output) } } } return nil }, } cmd.Flags().BoolVarP(&write, "write", "w", false, "Write formatted output back to file.") cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output.") return cmd } ================================================ FILE: oryx/jsonnetx/lint.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetx import ( "fmt" "os" "github.com/bmatcuk/doublestar/v2" "github.com/google/go-jsonnet" "github.com/google/go-jsonnet/linter" "github.com/spf13/cobra" "github.com/ory/x/cmdx" ) func NewLintCommand() *cobra.Command { var verbose bool cmd := &cobra.Command{ Use: "lint path/to/files/*.jsonnet [more/files.jsonnet, [supports/**/{foo,bar}.jsonnet]]", Long: `Lints JSONNet files using the official JSONNet linter and exits with a status code of 1 when issues are detected. ` + GlobHelp, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { for _, pattern := range args { files, err := doublestar.Glob(pattern) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Glob pattern %q is not valid: %s\n", pattern, err) return cmdx.FailSilently(cmd) } for _, file := range files { if fi, err := os.Stat(file); err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Matching file %q could not be opened: %s\n", file, err) return cmdx.FailSilently(cmd) } else if fi.IsDir() { continue } if verbose { fmt.Printf("Processing file: %s\n", file) } //#nosec G304 -- false positive content, err := os.ReadFile(file) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unable to read file %q: %s\n", file, err) return cmdx.FailSilently(cmd) } if linter.LintSnippet(jsonnet.MakeVM(), cmd.ErrOrStderr(), []linter.Snippet{{FileName: file, Code: string(content)}}) { return cmdx.FailSilently(cmd) } } } return nil }, } cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output.") return cmd } ================================================ FILE: oryx/jsonnetx/root.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonnetx import ( "github.com/spf13/cobra" ) const GlobHelp = `Glob patterns supports the following special terms in the patterns: Special Terms | Meaning ------------- | ------- '*' | matches any sequence of non-path-separators '**' | matches any sequence of characters, including path separators '?' | matches any single non-path-separator character '[class]' | matches any single non-path-separator character against a class of characters ([see below](#character-classes)) '{alt1,...}' | matches a sequence of characters if one of the comma-separated alternatives matches Any character with a special meaning can be escaped with a backslash ('\'). #### Character Classes Character classes support the following: Class | Meaning ---------- | ------- '[abc]' | matches any single character within the set '[a-z]' | matches any single character in the range '[^class]' | matches any single character which does *not* match the class ` // RootCommand represents the jsonnet command // Deprecated: use NewRootCommand instead. var RootCommand = &cobra.Command{ Use: "jsonnet", Short: "Helpers for linting and formatting JSONNet code", } // RegisterCommandRecursive adds all jsonnet helpers to the RootCommand // Deprecated: use NewRootCommand instead. func RegisterCommandRecursive(parent *cobra.Command) { parent.AddCommand(RootCommand) RootCommand.AddCommand(NewFormatCommand()) RootCommand.AddCommand(NewLintCommand()) } func NewRootCommand() *cobra.Command { cmd := &cobra.Command{ Use: "jsonnet", Short: "Helpers for linting and formatting JSONNet code", } cmd.AddCommand(NewFormatCommand(), NewLintCommand()) return cmd } ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=0.json ================================================ [ { "Title": "Access Rules", "Description": "Configure access rules. All sub-keys support configuration reloading without restarting.", "Examples": null, "Name": "access_rules", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Repositories", "Description": "Locations (list of URLs) where access rules should be fetched from on boot. It is expected that the documents at those locations return a JSON or YAML Array containing ORY Oathkeeper Access Rules:\n\n- If the URL Scheme is `file://`, the access rules (an array of access rules is expected) will be fetched from the local file system.\n- If the URL Scheme is `inline://`, the access rules (an array of access rules is expected) are expected to be a base64 encoded (with padding!) JSON/YAML string (base64_encode(`[{\"id\":\"foo-rule\",\"authenticators\":[....]}]`)).\n- If the URL Scheme is `http://` or `https://`, the access rules (an array of access rules is expected) will be fetched from the provided HTTP(s) location.", "Examples": [ "[\"file://path/to/rules.json\",\"inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d\",\"https://path-to-my-rules/rules.json\"]" ], "Name": "access_rules.repositories", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "access_rules.repositories.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Authenticators", "Description": "For more information on authenticators head over to: https://www.ory.sh/docs/oathkeeper/pipeline/authn", "Examples": null, "Name": "authenticators", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Anonymous", "Description": "The [`anonymous` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#anonymous).", "Examples": null, "Name": "authenticators.anonymous", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Anonymous Authenticator Configuration", "Description": "This section is optional when the authenticator is disabled.", "Examples": null, "Name": "authenticators.anonymous.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Anonymous Subject", "Description": "Sets the anonymous username.", "Examples": [ "guest", "anon", "anonymous", "unknown" ], "Name": "authenticators.anonymous.config.subject", "Default": "anonymous", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.anonymous.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Cookie Session", "Description": "The [`cookie_session` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#cookie_session).", "Examples": null, "Name": "authenticators.cookie_session", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Cookie Session Authenticator Configuration", "Description": "This section is optional when the authenticator is disabled.", "Examples": null, "Name": "authenticators.cookie_session.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Session Check URL", "Description": "The origin to proxy requests to. If the response is a 200 with body `{ \"subject\": \"...\", \"extra\": {} }`. The request will pass the subject through successfully, otherwise it will be marked as unauthorized.\n\n\u003eIf this authenticator is enabled, this value is required.", "Examples": [ "https://session-store-host" ], "Name": "authenticators.cookie_session.config.check_session_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Only Cookies", "Description": "A list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if none of the passed cookies are set on the request.", "Examples": null, "Name": "authenticators.cookie_session.config.only", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.cookie_session.config.only.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.cookie_session.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "JSON Web Token (jwt)", "Description": "The [`jwt` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#jwt).", "Examples": null, "Name": "authenticators.jwt", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "JWT Authenticator Configuration", "Description": "This section is optional when the authenticator is disabled.", "Examples": null, "Name": "authenticators.jwt.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.allowed_algorithms", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.allowed_algorithms.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "JSON Web Key URLs", "Description": "URLs where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web Token. Usually something like \"https://my-keys.com/.well-known/jwks.json\". The response of that endpoint must return a JSON Web Key Set (JWKS).\n\n\u003eIf this authenticator is enabled, this value is required.", "Examples": [ "https://my-website.com/.well-known/jwks.json", "https://my-other-website.com/.well-known/jwks.json", "file://path/to/local/jwks.json" ], "Name": "authenticators.jwt.config.jwks_urls", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.jwks_urls.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Required Token Scope", "Description": "An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", "Examples": null, "Name": "authenticators.jwt.config.required_scope", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.required_scope.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Scope Strategy", "Description": "Sets the strategy validation algorithm.", "Examples": null, "Name": "authenticators.jwt.config.scope_strategy", "Default": "none", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "hierarchic", "exact", "wildcard", "none" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Intended Audience", "Description": "An array of audiences that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied.", "Examples": null, "Name": "authenticators.jwt.config.target_audience", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.target_audience.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.token_from", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Header", "Description": "The header (case insensitive) that must contain a token for request authentication. It can't be set along with query_parameter.", "Examples": null, "Name": "authenticators.jwt.config.token_from.header", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Query Parameter", "Description": "The query parameter (case sensitive) that must contain a token for request authentication. It can't be set along with header.", "Examples": null, "Name": "authenticators.jwt.config.token_from.query_parameter", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.trusted_issuers", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.jwt.config.trusted_issuers.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.jwt.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "No Operation (noop)", "Description": "The [`noop` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#noop).", "Examples": null, "Name": "authenticators.noop", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.noop.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Client Credentials", "Description": "The [`oauth2_client_credentials` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#oauth2_client_credentials).", "Examples": null, "Name": "authenticators.oauth2_client_credentials", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Client Credentials Authenticator Configuration", "Description": "This section is optional when the authenticator is disabled.", "Examples": null, "Name": "authenticators.oauth2_client_credentials.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Request Permissions (Token Scope)", "Description": "Scopes is an array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this rule.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", "Examples": null, "Name": "authenticators.oauth2_client_credentials.config.required_scope", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.oauth2_client_credentials.config.required_scope.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "The OAuth 2.0 Token Endpoint that will be used to validate the client credentials.\n\n\u003eIf this authenticator is enabled, this value is required.", "Examples": [ "https://my-website.com/oauth2/token" ], "Name": "authenticators.oauth2_client_credentials.config.token_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.oauth2_client_credentials.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Token Introspection", "Description": "The [`oauth2_introspection` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#oauth2_introspection).", "Examples": null, "Name": "authenticators.oauth2_introspection", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Introspection Authenticator Configuration", "Description": "This section is optional when the authenticator is disabled.", "Examples": null, "Name": "authenticators.oauth2_introspection.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Introspection URL", "Description": "The OAuth 2.0 Token Introspection endpoint URL.\n\n\u003eIf this authenticator is enabled, this value is required.", "Examples": [ "https://my-website.com/oauth2/introspection" ], "Name": "authenticators.oauth2_introspection.config.introspection_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Pre-Authorization", "Description": "Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.pre_authorization", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Client ID", "Description": "The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant.\n\n\u003eIf pre-authorization is enabled, this value is required.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.pre_authorization.client_id", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Client Secret", "Description": "The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant.\n\n\u003eIf pre-authorization is enabled, this value is required.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.pre_authorization.client_secret", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.oauth2_introspection.config.pre_authorization.enabled", "Default": null, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": [ true ], "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Scope", "Description": "The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant.", "Examples": [ [ "[\"foo\", \"bar\"]" ] ], "Name": "authenticators.oauth2_introspection.config.pre_authorization.scope", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.oauth2_introspection.config.pre_authorization.scope.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "OAuth 2.0 Token URL", "Description": "The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed.\n\n\u003eIf pre-authorization is enabled, this value is required.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.pre_authorization.token_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Required Scope", "Description": "An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.required_scope", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.oauth2_introspection.config.required_scope.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Scope Strategy", "Description": "Sets the strategy validation algorithm.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.scope_strategy", "Default": "none", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "hierarchic", "exact", "wildcard", "none" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Target Audience", "Description": "An array of audiences that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.target_audience", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.oauth2_introspection.config.target_audience.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Token From", "Description": "The location of the token.\n If not configured, the token will be received from a default location - 'Authorization' header.\n One and only one location (header or query) must be specified.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.token_from", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Header", "Description": "The header (case insensitive) that must contain a token for request authentication.\n It can't be set along with query_parameter.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.token_from.header", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Query Parameter", "Description": "The query parameter (case sensitive) that must contain a token for request authentication.\n It can't be set along with header.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.token_from.query_parameter", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Trusted Issuers", "Description": "The token must have been issued by one of the issuers listed in this array.", "Examples": null, "Name": "authenticators.oauth2_introspection.config.trusted_issuers", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authenticators.oauth2_introspection.config.trusted_issuers.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.oauth2_introspection.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Unauthorized", "Description": "The [`unauthorized` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#unauthorized).", "Examples": null, "Name": "authenticators.unauthorized", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authenticators.unauthorized.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Authorizers", "Description": "For more information on authorizers head over to: https://www.ory.sh/docs/oathkeeper/pipeline/authz", "Examples": null, "Name": "authorizers", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allow", "Description": "The [`allow` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allow).", "Examples": null, "Name": "authorizers.allow", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authorizers.allow.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Deny", "Description": "The [`deny` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allow).", "Examples": null, "Name": "authorizers.deny", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authorizers.deny.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "ORY Keto Access Control Policies Engine", "Description": "The [`keto_engine_acp_ory` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#keto_engine_acp_ory).", "Examples": null, "Name": "authorizers.keto_engine_acp_ory", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "ORY Keto Access Control Policy Authorizer Configuration", "Description": "This section is optional when the authorizer is disabled.", "Examples": null, "Name": "authorizers.keto_engine_acp_ory.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Base URL", "Description": "The base URL of ORY Keto.\n\n\u003eIf this authorizer is enabled, this value is required.", "Examples": [ "http://my-keto/" ], "Name": "authorizers.keto_engine_acp_ory.config.base_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authorizers.keto_engine_acp_ory.config.flavor", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authorizers.keto_engine_acp_ory.config.required_action", "Default": "unset", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authorizers.keto_engine_acp_ory.config.required_resource", "Default": "unset", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "authorizers.keto_engine_acp_ory.config.subject", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "authorizers.keto_engine_acp_ory.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Log", "Description": "Configure logging using the following options. Logging will always be sent to stdout and stderr.", "Examples": null, "Name": "log", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Format", "Description": "The log format can either be text or JSON.", "Examples": null, "Name": "log.format", "Default": "text", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "text", "json" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Level", "Description": "Debug enables stack traces on errors. Can also be set using environment variable LOG_LEVEL.", "Examples": null, "Name": "log.level", "Default": "info", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "panic", "fatal", "error", "warn", "info", "debug" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Mutators", "Description": "For more information on mutators head over to: https://www.ory.sh/docs/oathkeeper/pipeline/mutator", "Examples": null, "Name": "mutators", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Cookie", "Description": "The [`cookie` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#cookie).", "Examples": null, "Name": "mutators.cookie", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Cookie Mutator Configuration", "Description": "This section is optional when the mutator is disabled.", "Examples": null, "Name": "mutators.cookie.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.cookie.config.cookies", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "mutators.cookie.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Header", "Description": "The [`header` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#header).", "Examples": null, "Name": "mutators.header", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Header Mutator Configuration", "Description": "This section is optional when the mutator is disabled.", "Examples": null, "Name": "mutators.header.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.header.config.headers", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "mutators.header.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Hydrator", "Description": "The [`hydrator` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#hydrator).", "Examples": null, "Name": "mutators.hydrator", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Hydrator Mutator Configuration", "Description": "This section is optional when the mutator is disabled.", "Examples": null, "Name": "mutators.hydrator.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.auth", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.auth.basic", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.auth.basic.password", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.auth.basic.username", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.retry", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.retry.delay_in_milliseconds", "Default": 3, "Type": 0, "TypeHint": 3, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": "0", "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.retry.number_of_retries", "Default": 100, "Type": 0, "TypeHint": 2, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": "0", "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.hydrator.config.api.url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "mutators.hydrator.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "ID Token (JSON Web Token)", "Description": "The [`id_token` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token).", "Examples": null, "Name": "mutators.id_token", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "ID Token Mutator Configuration", "Description": "This section is optional when the mutator is disabled.", "Examples": null, "Name": "mutators.id_token.config", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "mutators.id_token.config.claims", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Issuer URL", "Description": "Sets the \"iss\" value of the ID Token.\n\n\u003eIf this mutator is enabled, this value is required.", "Examples": null, "Name": "mutators.id_token.config.issuer_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "JSON Web Key URL", "Description": "Sets the URL where keys should be fetched from. Supports remote locations (http, https) as well as local filesystem paths.\n\n\u003eIf this mutator is enabled, this value is required.", "Examples": [ "https://fetch-keys/from/this/location.json", "file:///from/this/absolute/location.json", "file://../from/this/relative/location.json" ], "Name": "mutators.id_token.config.jwks_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Expire After", "Description": "Sets the time-to-live of the JSON Web Token.", "Examples": [ "1h", "1m", "30s" ], "Name": "mutators.id_token.config.ttl", "Default": "1m", "Type": "", "TypeHint": 1, "Format": "", "Pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "mutators.id_token.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "No Operation (noop)", "Description": "The [`noop` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#noop).", "Examples": null, "Name": "mutators.noop", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enabled", "Description": "En-/disables this component.", "Examples": [ true ], "Name": "mutators.noop.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Profiling", "Description": "Enables CPU or memory profiling if set. For more details on profiling Go programs read [Profiling Go Programs](https://blog.golang.org/profiling-go-programs).", "Examples": null, "Name": "profiling", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "cpu", "mem" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP(s)", "Description": "", "Examples": null, "Name": "serve", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP REST API", "Description": "", "Examples": null, "Name": "serve.api", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Cross Origin Resource Sharing (CORS)", "Description": "Configure [Cross Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) using the following options.", "Examples": null, "Name": "serve.api.cors", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allow HTTP Credentials", "Description": "Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "Examples": null, "Name": "serve.api.cors.allow_credentials", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed Request HTTP Headers", "Description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "Examples": null, "Name": "serve.api.cors.allowed_headers", "Default": [ "Authorization", "Content-Type" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.api.cors.allowed_headers.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed HTTP Methods", "Description": "A list of methods the client is allowed to use with cross-domain requests.", "Examples": null, "Name": "serve.api.cors.allowed_methods", "Default": [ "GET", "POST", "PUT", "PATCH", "DELETE" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.api.cors.allowed_methods.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "TRACE", "PATCH" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed Origins", "Description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin.", "Examples": [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ], "Name": "serve.api.cors.allowed_origins", "Default": [ "*" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.api.cors.allowed_origins.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enable Debugging", "Description": "Set to true to debug server side CORS issues.", "Examples": null, "Name": "serve.api.cors.debug", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enable CORS", "Description": "If set to true, CORS will be enabled and preflight-requests (OPTION) will be answered.", "Examples": null, "Name": "serve.api.cors.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed Response HTTP Headers", "Description": "Indicates which headers are safe to expose to the API of a CORS API specification", "Examples": null, "Name": "serve.api.cors.exposed_headers", "Default": [ "Content-Type" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.api.cors.exposed_headers.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Maximum Age", "Description": "Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0 which stands for no max age.", "Examples": null, "Name": "serve.api.cors.max_age", "Default": 0, "Type": 0, "TypeHint": 2, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Host", "Description": "The network interface to listen on.", "Examples": [ "localhost", "127.0.0.1" ], "Name": "serve.api.host", "Default": "", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Port", "Description": "The port to listen on.", "Examples": null, "Name": "serve.api.port", "Default": 4456, "Type": 0, "TypeHint": 2, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTPS", "Description": "Configure HTTP over TLS (HTTPS). All options can also be set using environment variables by replacing dots (`.`) with underscores (`_`) and uppercasing the key. For example, `some.prefix.tls.key.path` becomes `export SOME_PREFIX_TLS_KEY_PATH`. If all keys are left undefined, TLS will be disabled.", "Examples": null, "Name": "serve.api.tls", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.api.tls.cert", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Base64 Encoded Inline", "Description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "Examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ], "Name": "serve.api.tls.cert.base64", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Path to PEM-encoded Fle", "Description": "", "Examples": [ "path/to/file.pem" ], "Name": "serve.api.tls.cert.path", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.api.tls.key", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Base64 Encoded Inline", "Description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "Examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ], "Name": "serve.api.tls.key.base64", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Path to PEM-encoded Fle", "Description": "", "Examples": [ "path/to/file.pem" ], "Name": "serve.api.tls.key.path", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Reverse Proxy", "Description": "", "Examples": null, "Name": "serve.proxy", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Cross Origin Resource Sharing (CORS)", "Description": "Configure [Cross Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) using the following options.", "Examples": null, "Name": "serve.proxy.cors", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allow HTTP Credentials", "Description": "Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.", "Examples": null, "Name": "serve.proxy.cors.allow_credentials", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed Request HTTP Headers", "Description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "Examples": null, "Name": "serve.proxy.cors.allowed_headers", "Default": [ "Authorization", "Content-Type" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.proxy.cors.allowed_headers.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed HTTP Methods", "Description": "A list of methods the client is allowed to use with cross-domain requests.", "Examples": null, "Name": "serve.proxy.cors.allowed_methods", "Default": [ "GET", "POST", "PUT", "PATCH", "DELETE" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.proxy.cors.allowed_methods.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "TRACE", "PATCH" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed Origins", "Description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin.", "Examples": [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ], "Name": "serve.proxy.cors.allowed_origins", "Default": [ "*" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.proxy.cors.allowed_origins.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enable Debugging", "Description": "Set to true to debug server side CORS issues.", "Examples": null, "Name": "serve.proxy.cors.debug", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Enable CORS", "Description": "If set to true, CORS will be enabled and preflight-requests (OPTION) will be answered.", "Examples": null, "Name": "serve.proxy.cors.enabled", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Allowed Response HTTP Headers", "Description": "Indicates which headers are safe to expose to the API of a CORS API specification", "Examples": null, "Name": "serve.proxy.cors.exposed_headers", "Default": [ "Content-Type" ], "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.proxy.cors.exposed_headers.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Maximum Age", "Description": "Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0 which stands for no max age.", "Examples": null, "Name": "serve.proxy.cors.max_age", "Default": 0, "Type": 0, "TypeHint": 2, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Host", "Description": "The network interface to listen on. Leave empty to listen on all interfaces.", "Examples": [ "localhost", "127.0.0.1" ], "Name": "serve.proxy.host", "Default": "", "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Port", "Description": "The port to listen on.", "Examples": null, "Name": "serve.proxy.port", "Default": 4455, "Type": 0, "TypeHint": 2, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Timeouts", "Description": "Control the reverse proxy's HTTP timeouts.", "Examples": null, "Name": "serve.proxy.timeout", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Idle Timeout", "Description": " The maximum amount of time to wait for any action of a request session, reading data or writing the response.", "Examples": [ "5s", "5m", "5h" ], "Name": "serve.proxy.timeout.idle", "Default": "120s", "Type": "", "TypeHint": 1, "Format": "", "Pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Read Timeout", "Description": "The maximum duration for reading the entire request, including the body.", "Examples": [ "5s", "5m", "5h" ], "Name": "serve.proxy.timeout.read", "Default": "5s", "Type": "", "TypeHint": 1, "Format": "", "Pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTP Write Timeout", "Description": "The maximum duration before timing out writes of the response. Increase this parameter to prevent unexpected closing a client connection if an upstream request is responding slowly.", "Examples": [ "5s", "5m", "5h" ], "Name": "serve.proxy.timeout.write", "Default": "120s", "Type": "", "TypeHint": 1, "Format": "", "Pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "HTTPS", "Description": "Configure HTTP over TLS (HTTPS). All options can also be set using environment variables by replacing dots (`.`) with underscores (`_`) and uppercasing the key. For example, `some.prefix.tls.key.path` becomes `export SOME_PREFIX_TLS_KEY_PATH`. If all keys are left undefined, TLS will be disabled.", "Examples": null, "Name": "serve.proxy.tls", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.proxy.tls.cert", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Base64 Encoded Inline", "Description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "Examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ], "Name": "serve.proxy.tls.cert.base64", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Path to PEM-encoded Fle", "Description": "", "Examples": [ "path/to/file.pem" ], "Name": "serve.proxy.tls.cert.path", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "serve.proxy.tls.key", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Base64 Encoded Inline", "Description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "Examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ], "Name": "serve.proxy.tls.key.base64", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Path to PEM-encoded Fle", "Description": "", "Examples": [ "path/to/file.pem" ], "Name": "serve.proxy.tls.key.path", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=1.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "providers", "Default": null, "Type": [], "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#.id", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=2.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "dsn", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=3.json ================================================ [ { "Title": "OpenID Connect and OAuth2 Providers", "Description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.", "Examples": null, "Name": "providers", "Default": null, "Type": [], "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": [ "https://accounts.google.com/o/oauth2/v2/auth" ], "Name": "providers.#.auth_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#.client_id", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#.client_secret", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": [ "google" ], "Name": "providers.#.id", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": [ "https://accounts.google.com" ], "Name": "providers.#.issuer_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Jsonnet Mapper URL", "Description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.", "Examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ], "Name": "providers.#.mapper_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Provider", "Description": "Can be one of github, gitlab, generic, google, microsoft, discord.", "Examples": [ "google" ], "Name": "providers.#.provider", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": [ "github", "gitlab", "generic", "google", "microsoft", "discord" ], "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#.scope", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": [ "offline_access", "profile" ], "Name": "providers.#.scope.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "Azure AD Tenant", "Description": "The Azure AD Tenant to use for authentication.", "Examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ], "Name": "providers.#.tenant", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": [ "https://www.googleapis.com/oauth2/v4/token" ], "Name": "providers.#.token_url", "Default": null, "Type": "", "TypeHint": 1, "Format": "uri", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=4.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "bar", "Default": "asdf", "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": true, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "foo", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "list", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "list.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=5.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "bar", "Default": "asdf", "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": true, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "foo", "Default": false, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "list", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "list.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=6.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "bar", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": { "foobar": "bar" } }, { "Title": "", "Description": "", "Examples": null, "Name": "foo", "Default": null, "Type": false, "TypeHint": 4, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=7.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "bar", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=8.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "baz", "Default": null, "Type": [], "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "baz.#", "Default": null, "Type": [], "TypeHint": 8, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "baz.#.#", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPaths-case=9.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "baz", "Default": null, "Type": [], "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "baz.#", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "baz.#.foo", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/.snapshots/TestListPathsWithRecursion-case=0.json ================================================ [ { "Title": "", "Description": "", "Examples": null, "Name": "bar", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar.foo", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar.foo.bar", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar.foo.bar.foo", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar.foo.bar.foos", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": 10, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar.foo.bars", "Default": null, "Type": "", "TypeHint": 1, "Format": "email", "Pattern": ".*", "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bar.foos", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": 10, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foo.bars", "Default": null, "Type": "", "TypeHint": 1, "Format": "email", "Pattern": ".*", "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": true, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "bar.foos", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": 1, "MaxLength": 10, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ================================================ FILE: oryx/jsonschemax/README.md ================================================ # JSON Schema Helpers This package contains utilities for working with JSON Schemas. ## Listing all Possible JSON Schema Paths Using `jsonschemax.ListPaths()` you can get a list of all possible JSON paths in a JSON Schema. ```go package main import ( "bytes" "fmt" "github.com/ory/jsonschema/v3" "github.com/ory/x/jsonschemax" ) var schema = "..." func main() { c := jsonschema.NewCompiler() _ = c.AddResource("test.json", bytes.NewBufferString(schema)) paths, _ := jsonschemax.ListPaths("test.json", c) fmt.Printf("%+v", paths) } ``` All keys are delimited using `.`. Please note that arrays are denoted with `#` when `ListPathsWithArraysIncluded` is used. For example, the JSON Schema ```json { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "providers": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" } } } } } } ``` Results in paths: ```json [ { "Title": "", "Description": "", "Examples": null, "Name": "providers", "Default": null, "Type": [], "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#", "Default": null, "Type": {}, "TypeHint": 5, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null }, { "Title": "", "Description": "", "Examples": null, "Name": "providers.#.id", "Default": null, "Type": "", "TypeHint": 1, "Format": "", "Pattern": null, "Enum": null, "Constant": null, "ReadOnly": false, "MinLength": -1, "MaxLength": -1, "Required": false, "Minimum": null, "Maximum": null, "MultipleOf": null, "CustomProperties": null } ] ``` ================================================ FILE: oryx/jsonschemax/error.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonschemax import ( "github.com/ory/jsonschema/v3" ) // ErrorType is the schema error type. type ErrorType int const ( // ErrorTypeMissing represents a validation that failed because a value is missing. ErrorTypeMissing ErrorType = iota + 1 ) // Error represents a schema error. type Error struct { // Type is the error type. Type ErrorType // DocumentPointer is the JSON Pointer in the document. DocumentPointer string // SchemaPointer is the JSON Pointer in the schema. SchemaPointer string // DocumentFieldName is a pointer to the document in dot-notation: fo.bar.baz DocumentFieldName string } // NewFromSanthoshError converts github.com/santhosh-tekuri/jsonschema.ValidationError to Error. func NewFromSanthoshError(validationError jsonschema.ValidationError) *Error { return &Error{ // DocumentPointer: JSONPointerToDotNotation(validationError.InstancePtr), // SchemaPointer: JSONPointerToDotNotation(validationError.SchemaPtr), // DocumentFieldName: JSONPointerToDotNotation(validationError.InstancePtr), } } ================================================ FILE: oryx/jsonschemax/keys.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonschemax import ( "bytes" "context" "crypto/sha256" "encoding/json" "fmt" "math/big" "regexp" "slices" "sort" "strings" "github.com/pkg/errors" "github.com/ory/jsonschema/v3" ) type ( byName []Path PathEnhancer interface { EnhancePath(Path) map[string]interface{} } TypeHint int ) func (s byName) Len() int { return len(s) } func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byName) Less(i, j int) bool { return s[i].Name < s[j].Name } const ( String TypeHint = iota + 1 Float Int Bool JSON Nil BoolSlice StringSlice IntSlice FloatSlice ) // Path represents a JSON Schema Path. type Path struct { // Title of the path. Title string // Description of the path. Description string // Examples of the path. Examples []interface{} // Name is the JSON path name. Name string // Default is the default value of that path. Default interface{} // Type is a prototype (e.g. float64(0)) of the path type. Type interface{} TypeHint // Format is the format of the path if defined Format string // Pattern is the pattern of the path if defined Pattern *regexp.Regexp // Enum are the allowed enum values Enum []interface{} // first element in slice is constant value. note: slice is used to capture nil constant. Constant []interface{} // ReadOnly is whether the value is readonly ReadOnly bool // -1 if not specified MinLength int MaxLength int // Required if set indicates this field is required. Required bool Minimum *big.Float Maximum *big.Float MultipleOf *big.Float CustomProperties map[string]interface{} } // ListPathsBytes works like ListPathsWithRecursion but prepares the JSON Schema itself. func ListPathsBytes(ctx context.Context, raw json.RawMessage, maxRecursion int16) ([]Path, error) { compiler := jsonschema.NewCompiler() compiler.ExtractAnnotations = true id := fmt.Sprintf("%x.json", sha256.Sum256(raw)) if err := compiler.AddResource(id, bytes.NewReader(raw)); err != nil { return nil, err } compiler.ExtractAnnotations = true return runPathsFromCompiler(ctx, id, compiler, maxRecursion, false) } // ListPathsWithRecursion will follow circular references until maxRecursion is reached, without // returning an error. func ListPathsWithRecursion(ctx context.Context, ref string, compiler *jsonschema.Compiler, maxRecursion uint8) ([]Path, error) { return runPathsFromCompiler(ctx, ref, compiler, int16(maxRecursion), false) } // ListPaths lists all paths of a JSON Schema. Will return an error // if circular references are found. func ListPaths(ctx context.Context, ref string, compiler *jsonschema.Compiler) ([]Path, error) { return runPathsFromCompiler(ctx, ref, compiler, -1, false) } // ListPathsWithArraysIncluded lists all paths of a JSON Schema. Will return an error // if circular references are found. // Includes arrays with `#`. func ListPathsWithArraysIncluded(ctx context.Context, ref string, compiler *jsonschema.Compiler) ([]Path, error) { return runPathsFromCompiler(ctx, ref, compiler, -1, true) } // ListPathsWithInitializedSchema loads the paths from the schema without compiling it. // // You MUST ensure that the compiler was using `ExtractAnnotations = true`. func ListPathsWithInitializedSchema(schema *jsonschema.Schema) ([]Path, error) { return runPaths(schema, -1, false) } // ListPathsWithInitializedSchemaAndArraysIncluded loads the paths from the schema without compiling it. // // You MUST ensure that the compiler was using `ExtractAnnotations = true`. // Includes arrays with `#`. func ListPathsWithInitializedSchemaAndArraysIncluded(schema *jsonschema.Schema) ([]Path, error) { return runPaths(schema, -1, true) } func runPathsFromCompiler(ctx context.Context, ref string, compiler *jsonschema.Compiler, maxRecursion int16, includeArrays bool) ([]Path, error) { if compiler == nil { compiler = jsonschema.NewCompiler() } compiler.ExtractAnnotations = true schema, err := compiler.Compile(ctx, ref) if err != nil { return nil, errors.WithStack(err) } return runPaths(schema, maxRecursion, includeArrays) } func runPaths(schema *jsonschema.Schema, maxRecursion int16, includeArrays bool) ([]Path, error) { pointers := map[string]bool{} paths, err := listPaths(schema, nil, nil, pointers, 0, maxRecursion, includeArrays) if err != nil { return nil, errors.WithStack(err) } sort.Stable(paths) return makeUnique(paths) } func makeUnique(in byName) (byName, error) { cache := make(map[string]Path) for _, p := range in { vc, ok := cache[p.Name] if !ok { cache[p.Name] = p continue } if fmt.Sprintf("%T", p.Type) != fmt.Sprintf("%T", p.Type) { return nil, errors.Errorf("multiple types %+v are not supported for path: %s", []interface{}{p.Type, vc.Type}, p.Name) } if vc.Default == nil { cache[p.Name] = p } } k := 0 out := make([]Path, len(cache)) for _, v := range cache { out[k] = v k++ } paths := byName(out) sort.Sort(paths) return paths, nil } func appendPointer(in map[string]bool, pointer *jsonschema.Schema) map[string]bool { out := make(map[string]bool) for k, v := range in { out[k] = v } out[fmt.Sprintf("%p", pointer)] = true return out } func listPaths(schema *jsonschema.Schema, parent *jsonschema.Schema, parents []string, pointers map[string]bool, currentRecursion int16, maxRecursion int16, includeArrays bool) (byName, error) { var pathType interface{} var pathTypeHint TypeHint var paths []Path _, isCircular := pointers[fmt.Sprintf("%p", schema)] if len(schema.Constant) > 0 { switch schema.Constant[0].(type) { case float64, json.Number: pathType = float64(0) pathTypeHint = Float case int8, int16, int, int64: pathType = int64(0) pathTypeHint = Int case string: pathType = "" pathTypeHint = String case bool: pathType = false pathTypeHint = Bool default: pathType = schema.Constant[0] pathTypeHint = JSON } } else if len(schema.Types) == 1 { switch schema.Types[0] { case "null": pathType = nil pathTypeHint = Nil case "boolean": pathType = false pathTypeHint = Bool case "number": pathType = float64(0) pathTypeHint = Float case "integer": pathType = float64(0) pathTypeHint = Int case "string": pathType = "" pathTypeHint = String case "array": pathType = []interface{}{} if schema.Items != nil { var itemSchemas []*jsonschema.Schema switch t := schema.Items.(type) { case []*jsonschema.Schema: itemSchemas = t case *jsonschema.Schema: itemSchemas = []*jsonschema.Schema{t} } var types []string for _, is := range itemSchemas { types = append(types, is.Types...) if is.Ref != nil { types = append(types, is.Ref.Types...) } } types = slices.Compact(types) if len(types) == 1 { switch types[0] { case "boolean": pathType = []bool{} pathTypeHint = BoolSlice case "number": pathType = []float64{} pathTypeHint = FloatSlice case "integer": pathType = []float64{} pathTypeHint = IntSlice case "string": pathType = []string{} pathTypeHint = StringSlice default: pathType = []interface{}{} pathTypeHint = JSON } } } case "object": pathType = map[string]interface{}{} pathTypeHint = JSON } } else if len(schema.Types) > 2 { pathType = nil pathTypeHint = JSON } var def interface{} = schema.Default if v, ok := def.(json.Number); ok { def, _ = v.Float64() } if (pathType != nil || schema.Default != nil) && len(parents) > 0 { name := parents[len(parents)-1] var required bool if parent != nil { for _, r := range parent.Required { if r == name { required = true break } } } path := Path{ Name: strings.Join(parents, "."), Default: def, Type: pathType, TypeHint: pathTypeHint, Format: schema.Format, Pattern: schema.Pattern, Enum: schema.Enum, Constant: schema.Constant, MinLength: schema.MinLength, MaxLength: schema.MaxLength, Minimum: schema.Minimum, Maximum: schema.Maximum, MultipleOf: schema.MultipleOf, ReadOnly: schema.ReadOnly, Title: schema.Title, Description: schema.Description, Examples: schema.Examples, Required: required, } for _, e := range schema.Extensions { if enhancer, ok := e.(PathEnhancer); ok { path.CustomProperties = enhancer.EnhancePath(path) } } paths = append(paths, path) } if isCircular { if maxRecursion == -1 { return nil, errors.Errorf("detected circular dependency in schema path: %s", strings.Join(parents, ".")) } else if currentRecursion > maxRecursion { return paths, nil } currentRecursion++ } if schema.Ref != nil { path, err := listPaths(schema.Ref, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } if schema.Not != nil { path, err := listPaths(schema.Not, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } if schema.If != nil { path, err := listPaths(schema.If, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } if schema.Then != nil { path, err := listPaths(schema.Then, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } if schema.Else != nil { path, err := listPaths(schema.Else, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } for _, sub := range schema.AllOf { path, err := listPaths(sub, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } for _, sub := range schema.AnyOf { path, err := listPaths(sub, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } for _, sub := range schema.OneOf { path, err := listPaths(sub, schema, parents, appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } for name, sub := range schema.Properties { path, err := listPaths(sub, schema, append(parents, name), appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } if schema.Items != nil && includeArrays { switch t := schema.Items.(type) { case []*jsonschema.Schema: for _, sub := range t { path, err := listPaths(sub, schema, append(parents, "#"), appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } case *jsonschema.Schema: path, err := listPaths(t, schema, append(parents, "#"), appendPointer(pointers, schema), currentRecursion, maxRecursion, includeArrays) if err != nil { return nil, err } paths = append(paths, path...) } } return paths, nil } ================================================ FILE: oryx/jsonschemax/pointer.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonschemax import ( "net/url" "strings" "github.com/pkg/errors" ) // JSONPointerToDotNotation converts JSON Pointer "#/foo/bar" to dot-notation "foo.bar". func JSONPointerToDotNotation(pointer string) (string, error) { if !strings.HasPrefix(pointer, "#/") { return pointer, errors.Errorf("remote JSON pointers are not supported: %s", pointer) } var path []string for _, item := range strings.Split(strings.TrimPrefix(pointer, "#/"), "/") { item = strings.Replace(item, "~1", "/", -1) item = strings.Replace(item, "~0", "~", -1) item, err := url.PathUnescape(item) if err != nil { return "", err } path = append(path, strings.ReplaceAll(item, ".", "\\.")) } return strings.Join(path, "."), nil } ================================================ FILE: oryx/jsonschemax/print.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonschemax import ( "errors" "fmt" "io" "strings" "github.com/tidwall/gjson" "github.com/ory/jsonschema/v3" ) func FormatValidationErrorForCLI(w io.Writer, conf []byte, err error) { if err == nil { return } if e := new(jsonschema.ValidationError); errors.As(err, &e) { _, _ = fmt.Fprintln(w, "The configuration contains values or keys which are invalid:") pointer, validation := FormatError(e) if pointer == "#" { if len(e.Causes) == 0 { _, _ = fmt.Fprintln(w, "(root)") _, _ = fmt.Fprintln(w, "^-- "+validation) _, _ = fmt.Fprintln(w, "") } } else { spaces := make([]string, len(pointer)+3) _, _ = fmt.Fprintf(w, "%s: %+v", pointer, gjson.GetBytes(conf, pointer).Value()) _, _ = fmt.Fprintln(w, "") _, _ = fmt.Fprintf(w, "%s^-- %s", strings.Join(spaces, " "), validation) _, _ = fmt.Fprintln(w, "") _, _ = fmt.Fprintln(w, "") } for _, cause := range e.Causes { FormatValidationErrorForCLI(w, conf, cause) } return } } func FormatError(e *jsonschema.ValidationError) (string, string) { var ( err error pointer string message string ) pointer = e.InstancePtr message = e.Message switch ctx := e.Context.(type) { case *jsonschema.ValidationErrorContextRequired: if len(ctx.Missing) > 0 { message = "one or more required properties are missing" pointer = ctx.Missing[0] } } // We can ignore the error as it will simply echo the pointer. pointer, err = JSONPointerToDotNotation(pointer) if err != nil { pointer = e.InstancePtr } return pointer, message } ================================================ FILE: oryx/jsonschemax/stub/.config.yaml ================================================ dsn: memory items: - id: 1 ================================================ FILE: oryx/jsonschemax/stub/.oathkeeper.schema.json ================================================ { "$id": "https://raw.githubusercontent.com/ory/oathkeeper/v0.32.1-beta.1/.schemas/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "ORY Oathkeeper Configuration", "type": "object", "definitions": { "tlsxSource": { "type": "object", "additionalProperties": false, "properties": { "path": { "title": "Path to PEM-encoded Fle", "type": "string", "examples": ["path/to/file.pem"] }, "base64": { "title": "Base64 Encoded Inline", "description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.", "type": "string", "examples": [ "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..." ] } } }, "tlsx": { "title": "HTTPS", "description": "Configure HTTP over TLS (HTTPS). All options can also be set using environment variables by replacing dots (`.`) with underscores (`_`) and uppercasing the key. For example, `some.prefix.tls.key.path` becomes `export SOME_PREFIX_TLS_KEY_PATH`. If all keys are left undefined, TLS will be disabled.", "type": "object", "additionalProperties": false, "properties": { "key": { "title": "Private Key (PEM)", "allOf": [ { "$ref": "#/definitions/tlsxSource" } ] }, "cert": { "title": "TLS Certificate (PEM)", "allOf": [ { "$ref": "#/definitions/tlsxSource" } ] } } }, "cors": { "title": "Cross Origin Resource Sharing (CORS)", "description": "Configure [Cross Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) using the following options.", "type": "object", "properties": { "enabled": { "type": "boolean", "default": false, "title": "Enable CORS", "description": "If set to true, CORS will be enabled and preflight-requests (OPTION) will be answered." }, "allowed_origins": { "title": "Allowed Origins", "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin.", "type": "array", "items": { "type": "string", "minLength": 1 }, "default": ["*"], "uniqueItems": true, "examples": [ "https://example.com", "https://*.example.com", "https://*.foo.example.com" ] }, "allowed_methods": { "type": "array", "title": "Allowed HTTP Methods", "description": "A list of methods the client is allowed to use with cross-domain requests.", "items": { "type": "string", "enum": [ "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "TRACE", "PATCH" ] }, "uniqueItems": true, "default": ["GET", "POST", "PUT", "PATCH", "DELETE"] }, "allowed_headers": { "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", "title": "Allowed Request HTTP Headers", "type": "array", "items": { "type": "string" }, "minLength": 1, "uniqueItems": true, "default": ["Authorization", "Content-Type"] }, "exposed_headers": { "description": "Indicates which headers are safe to expose to the API of a CORS API specification", "title": "Allowed Response HTTP Headers", "type": "array", "items": { "type": "string" }, "minLength": 1, "uniqueItems": true, "default": ["Content-Type"] }, "allow_credentials": { "type": "boolean", "title": "Allow HTTP Credentials", "default": false, "description": "Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates." }, "max_age": { "type": "number", "default": 0, "title": "Maximum Age", "description": "Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0 which stands for no max age." }, "debug": { "type": "boolean", "default": false, "title": "Enable Debugging", "description": "Set to true to debug server side CORS issues." } }, "additionalProperties": false }, "handlerSwitch": { "title": "Enabled", "type": "boolean", "default": false, "examples": [true], "description": "En-/disables this component." }, "scopeStrategy": { "title": "Scope Strategy", "type": "string", "enum": ["hierarchic", "exact", "wildcard", "none"], "default": "none", "description": "Sets the strategy validation algorithm." }, "configAuthenticatorsAnonymous": { "type": "object", "title": "Anonymous Authenticator Configuration", "description": "This section is optional when the authenticator is disabled.", "properties": { "subject": { "type": "string", "title": "Anonymous Subject", "examples": ["guest", "anon", "anonymous", "unknown"], "default": "anonymous", "description": "Sets the anonymous username." } }, "additionalProperties": false }, "configAuthenticatorsCookieSession": { "type": "object", "title": "Cookie Session Authenticator Configuration", "description": "This section is optional when the authenticator is disabled.", "properties": { "check_session_url": { "title": "Session Check URL", "type": "string", "format": "uri", "description": "The origin to proxy requests to. If the response is a 200 with body `{ \"subject\": \"...\", \"extra\": {} }`. The request will pass the subject through successfully, otherwise it will be marked as unauthorized.\n\n>If this authenticator is enabled, this value is required.", "examples": ["https://session-store-host"] }, "only": { "type": "array", "items": { "type": "string", "additionalItems": false }, "title": "Only Cookies", "description": "A list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if none of the passed cookies are set on the request." } }, "required": ["check_session_url"], "additionalProperties": false }, "configAuthenticatorsJwt": { "type": "object", "title": "JWT Authenticator Configuration", "description": "This section is optional when the authenticator is disabled.", "required": ["jwks_urls"], "properties": { "required_scope": { "type": "array", "title": "Required Token Scope", "description": "An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", "items": { "type": "string" } }, "target_audience": { "title": "Intended Audience", "type": "array", "description": "An array of audiences that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied.", "items": { "type": "string" } }, "trusted_issuers": { "type": "array", "items": { "type": "string" } }, "allowed_algorithms": { "type": "array", "items": { "type": "string" } }, "jwks_urls": { "title": "JSON Web Key URLs", "type": "array", "items": { "type": "string", "format": "uri" }, "description": "URLs where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web Token. Usually something like \"https://my-keys.com/.well-known/jwks.json\". The response of that endpoint must return a JSON Web Key Set (JWKS).\n\n>If this authenticator is enabled, this value is required.", "examples": [ "https://my-website.com/.well-known/jwks.json", "https://my-other-website.com/.well-known/jwks.json", "file://path/to/local/jwks.json" ] }, "scope_strategy": { "$ref": "#/definitions/scopeStrategy" }, "token_from": { "title": "Token From", "description": "The location of the token.\n If not configured, the token will be received from a default location - 'Authorization' header.\n One and only one location (header or query) must be specified.", "oneOf": [ { "type": "object", "required": ["header"], "properties": { "header": { "title": "Header", "type": "string", "description": "The header (case insensitive) that must contain a token for request authentication. It can't be set along with query_parameter." } } }, { "type": "object", "required": ["query_parameter"], "properties": { "query_parameter": { "title": "Query Parameter", "type": "string", "description": "The query parameter (case sensitive) that must contain a token for request authentication. It can't be set along with header." } } } ] } }, "additionalProperties": false }, "configAuthenticatorsOauth2ClientCredentials": { "type": "object", "title": "OAuth 2.0 Client Credentials Authenticator Configuration", "description": "This section is optional when the authenticator is disabled.", "properties": { "token_url": { "type": "string", "description": "The OAuth 2.0 Token Endpoint that will be used to validate the client credentials.\n\n>If this authenticator is enabled, this value is required.", "format": "uri", "examples": ["https://my-website.com/oauth2/token"] }, "required_scope": { "type": "array", "title": "Request Permissions (Token Scope)", "description": "Scopes is an array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this rule.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", "items": { "type": "string" } } }, "required": ["token_url"], "additionalProperties": false }, "configAuthenticatorsOauth2Introspection": { "type": "object", "title": "OAuth 2.0 Introspection Authenticator Configuration", "description": "This section is optional when the authenticator is disabled.", "properties": { "introspection_url": { "type": "string", "format": "uri", "examples": ["https://my-website.com/oauth2/introspection"], "title": "OAuth 2.0 Introspection URL", "description": "The OAuth 2.0 Token Introspection endpoint URL.\n\n>If this authenticator is enabled, this value is required." }, "scope_strategy": { "$ref": "#/definitions/scopeStrategy" }, "pre_authorization": { "title": "Pre-Authorization", "description": "Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant.", "type": "object", "additionalProperties": false, "required": ["client_id", "client_secret", "token_url"], "properties": { "enabled": { "const": true }, "client_id": { "type": "string", "title": "OAuth 2.0 Client ID", "description": "The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant.\n\n>If pre-authorization is enabled, this value is required." }, "client_secret": { "type": "string", "title": "OAuth 2.0 Client Secret", "description": "The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant.\n\n>If pre-authorization is enabled, this value is required." }, "token_url": { "type": "string", "format": "uri", "title": "OAuth 2.0 Token URL", "description": "The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed.\n\n>If pre-authorization is enabled, this value is required." }, "scope": { "type": "array", "items": { "type": "string" }, "title": "OAuth 2.0 Scope", "description": "The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant.", "examples": [["[\"foo\", \"bar\"]"]] } } }, "required_scope": { "title": "Required Scope", "description": "An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", "type": "array", "items": { "type": "string" } }, "target_audience": { "title": "Target Audience", "description": "An array of audiences that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied.", "type": "array", "items": { "type": "string" } }, "trusted_issuers": { "title": "Trusted Issuers", "description": "The token must have been issued by one of the issuers listed in this array.", "type": "array", "items": { "type": "string" } }, "token_from": { "title": "Token From", "description": "The location of the token.\n If not configured, the token will be received from a default location - 'Authorization' header.\n One and only one location (header or query) must be specified.", "type": "object", "oneOf": [ { "required": ["header"], "properties": { "header": { "title": "Header", "type": "string", "description": "The header (case insensitive) that must contain a token for request authentication.\n It can't be set along with query_parameter." } } }, { "required": ["query_parameter"], "properties": { "query_parameter": { "title": "Query Parameter", "type": "string", "description": "The query parameter (case sensitive) that must contain a token for request authentication.\n It can't be set along with header." } } } ] } }, "required": ["introspection_url"], "additionalProperties": false }, "configAuthorizersKetoEngineAcpOry": { "type": "object", "title": "ORY Keto Access Control Policy Authorizer Configuration", "description": "This section is optional when the authorizer is disabled.", "properties": { "base_url": { "title": "Base URL", "type": "string", "format": "uri", "description": "The base URL of ORY Keto.\n\n>If this authorizer is enabled, this value is required.", "examples": ["http://my-keto/"] }, "required_action": { "type": "string", "default": "unset" }, "required_resource": { "type": "string", "default": "unset" }, "subject": { "type": "string" }, "flavor": { "type": "string" } }, "required": ["base_url", "required_action", "required_resource"], "additionalProperties": false }, "configMutatorsCookie": { "type": "object", "title": "Cookie Mutator Configuration", "description": "This section is optional when the mutator is disabled.", "required": ["cookies"], "properties": { "cookies": { "type": "object", "additionalProperties": { "type": "string" } } }, "additionalProperties": false }, "configMutatorsHeader": { "type": "object", "title": "Header Mutator Configuration", "description": "This section is optional when the mutator is disabled.", "required": ["headers"], "properties": { "headers": { "type": "object", "additionalProperties": { "type": "string" } } }, "additionalProperties": false }, "configMutatorsHydrator": { "type": "object", "title": "Hydrator Mutator Configuration", "description": "This section is optional when the mutator is disabled.", "properties": { "api": { "additionalProperties": false, "required": ["url"], "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "auth": { "type": "object", "additionalProperties": false, "properties": { "basic": { "required": ["username", "password"], "type": "object", "additionalProperties": false, "properties": { "username": { "type": "string" }, "password": { "type": "string" } } } } }, "retry": { "type": "object", "additionalProperties": false, "properties": { "number_of_retries": { "type": "number", "minimum": 0, "default": 100 }, "delay_in_milliseconds": { "type": "integer", "minimum": 0, "default": 3 } } } } } }, "required": ["api"], "additionalProperties": false }, "configMutatorsIdToken": { "type": "object", "title": "ID Token Mutator Configuration", "description": "This section is optional when the mutator is disabled.", "required": ["jwks_url", "issuer_url"], "properties": { "claims": { "type": "string" }, "issuer_url": { "type": "string", "title": "Issuer URL", "description": "Sets the \"iss\" value of the ID Token.\n\n>If this mutator is enabled, this value is required." }, "jwks_url": { "type": "string", "format": "uri", "title": "JSON Web Key URL", "description": "Sets the URL where keys should be fetched from. Supports remote locations (http, https) as well as local filesystem paths.\n\n>If this mutator is enabled, this value is required.", "examples": [ "https://fetch-keys/from/this/location.json", "file:///from/this/absolute/location.json", "file://../from/this/relative/location.json" ] }, "ttl": { "type": "string", "title": "Expire After", "description": "Sets the time-to-live of the JSON Web Token.", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "1m", "examples": ["1h", "1m", "30s"] } }, "additionalProperties": false } }, "properties": { "serve": { "title": "HTTP(s)", "additionalProperties": false, "type": "object", "properties": { "api": { "type": "object", "title": "HTTP REST API", "additionalProperties": false, "properties": { "port": { "type": "number", "default": 4456, "title": "Port", "description": "The port to listen on." }, "host": { "type": "string", "default": "", "examples": ["localhost", "127.0.0.1"], "title": "Host", "description": "The network interface to listen on." }, "cors": { "$ref": "#/definitions/cors" }, "tls": { "$ref": "#/definitions/tlsx" } } }, "proxy": { "type": "object", "title": "HTTP Reverse Proxy", "additionalProperties": false, "properties": { "port": { "type": "number", "default": 4455, "title": "Port", "description": "The port to listen on." }, "host": { "type": "string", "default": "", "examples": ["localhost", "127.0.0.1"], "title": "Host", "description": "The network interface to listen on. Leave empty to listen on all interfaces." }, "timeout": { "title": "HTTP Timeouts", "description": "Control the reverse proxy's HTTP timeouts.", "type": "object", "additionalProperties": false, "properties": { "read": { "title": "HTTP Read Timeout", "type": "string", "default": "5s", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "description": "The maximum duration for reading the entire request, including the body.", "examples": ["5s", "5m", "5h"] }, "write": { "title": "HTTP Write Timeout", "type": "string", "default": "120s", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "description": "The maximum duration before timing out writes of the response. Increase this parameter to prevent unexpected closing a client connection if an upstream request is responding slowly.", "examples": ["5s", "5m", "5h"] }, "idle": { "title": "HTTP Idle Timeout", "type": "string", "default": "120s", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "description": " The maximum amount of time to wait for any action of a request session, reading data or writing the response.", "examples": ["5s", "5m", "5h"] } } }, "cors": { "$ref": "#/definitions/cors" }, "tls": { "$ref": "#/definitions/tlsx" } } } } }, "access_rules": { "title": "Access Rules", "description": "Configure access rules. All sub-keys support configuration reloading without restarting.", "type": "object", "additionalProperties": false, "properties": { "repositories": { "title": "Repositories", "description": "Locations (list of URLs) where access rules should be fetched from on boot. It is expected that the documents at those locations return a JSON or YAML Array containing ORY Oathkeeper Access Rules:\n\n- If the URL Scheme is `file://`, the access rules (an array of access rules is expected) will be fetched from the local file system.\n- If the URL Scheme is `inline://`, the access rules (an array of access rules is expected) are expected to be a base64 encoded (with padding!) JSON/YAML string (base64_encode(`[{\"id\":\"foo-rule\",\"authenticators\":[....]}]`)).\n- If the URL Scheme is `http://` or `https://`, the access rules (an array of access rules is expected) will be fetched from the provided HTTP(s) location.", "type": "array", "items": { "type": "string", "format": "uri" }, "examples": [ "[\"file://path/to/rules.json\",\"inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d\",\"https://path-to-my-rules/rules.json\"]" ] } } }, "authenticators": { "title": "Authenticators", "type": "object", "description": "For more information on authenticators head over to: https://www.ory.sh/docs/oathkeeper/pipeline/authn", "additionalProperties": false, "properties": { "anonymous": { "title": "Anonymous", "description": "The [`anonymous` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#anonymous).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configAuthenticatorsAnonymous" } } }, "noop": { "title": "No Operation (noop)", "description": "The [`noop` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#noop).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" } } }, "unauthorized": { "title": "Unauthorized", "description": "The [`unauthorized` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#unauthorized).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" } } }, "cookie_session": { "title": "Cookie Session", "description": "The [`cookie_session` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#cookie_session).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configAuthenticatorsCookieSession" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] }, "jwt": { "title": "JSON Web Token (jwt)", "description": "The [`jwt` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#jwt).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configAuthenticatorsJwt" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] }, "oauth2_client_credentials": { "title": "OAuth 2.0 Client Credentials", "description": "The [`oauth2_client_credentials` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#oauth2_client_credentials).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configAuthenticatorsOauth2ClientCredentials" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] }, "oauth2_introspection": { "title": "OAuth 2.0 Token Introspection", "description": "The [`oauth2_introspection` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#oauth2_introspection).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configAuthenticatorsOauth2Introspection" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] } } }, "authorizers": { "title": "Authorizers", "type": "object", "description": "For more information on authorizers head over to: https://www.ory.sh/docs/oathkeeper/pipeline/authz", "additionalProperties": false, "properties": { "allow": { "title": "Allow", "description": "The [`allow` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allow).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" } } }, "deny": { "title": "Deny", "description": "The [`deny` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allow).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" } } }, "keto_engine_acp_ory": { "title": "ORY Keto Access Control Policies Engine", "description": "The [`keto_engine_acp_ory` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#keto_engine_acp_ory).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configAuthorizersKetoEngineAcpOry" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] } } }, "mutators": { "title": "Mutators", "type": "object", "description": "For more information on mutators head over to: https://www.ory.sh/docs/oathkeeper/pipeline/mutator", "additionalProperties": false, "properties": { "noop": { "title": "No Operation (noop)", "description": "The [`noop` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#noop).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" } } }, "cookie": { "title": "HTTP Cookie", "description": "The [`cookie` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#cookie).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configMutatorsCookie" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] }, "header": { "title": "HTTP Header", "description": "The [`header` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#header).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configMutatorsHeader" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] }, "hydrator": { "title": "Hydrator", "description": "The [`hydrator` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#hydrator).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configMutatorsHydrator" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] }, "id_token": { "title": "ID Token (JSON Web Token)", "description": "The [`id_token` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token).", "type": "object", "additionalProperties": false, "properties": { "enabled": { "$ref": "#/definitions/handlerSwitch" }, "config": { "$ref": "#/definitions/configMutatorsIdToken" } }, "oneOf": [ { "properties": { "enabled": { "const": true } }, "required": ["config"] }, { "properties": { "enabled": { "const": false } } } ] } } }, "log": { "title": "Log", "description": "Configure logging using the following options. Logging will always be sent to stdout and stderr.", "type": "object", "properties": { "level": { "type": "string", "default": "info", "enum": ["panic", "fatal", "error", "warn", "info", "debug"], "title": "Level", "description": "Debug enables stack traces on errors. Can also be set using environment variable LOG_LEVEL." }, "format": { "type": "string", "default": "text", "enum": ["text", "json"], "title": "Format", "description": "The log format can either be text or JSON." } }, "additionalProperties": false }, "profiling": { "title": "Profiling", "description": "Enables CPU or memory profiling if set. For more details on profiling Go programs read [Profiling Go Programs](https://blog.golang.org/profiling-go-programs).", "type": "string", "enum": ["cpu", "mem"] } }, "required": [], "additionalProperties": false } ================================================ FILE: oryx/jsonschemax/stub/config.schema.json ================================================ { "$id": "https://example.com/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "config", "type": "object", "properties": { "dsn": { "type": "string" } }, "required": ["dsn"] } ================================================ FILE: oryx/jsonschemax/stub/json/.project-stub-name.json ================================================ { "serve": { "admin": { "port": 1 } } } ================================================ FILE: oryx/jsonschemax/stub/nested-array.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "providers": { "title": "OpenID Connect and OAuth2 Providers", "description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.", "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "examples": ["google"] }, "provider": { "title": "Provider", "description": "Can be one of github, gitlab, generic, google, microsoft, discord.", "type": "string", "enum": [ "github", "gitlab", "generic", "google", "microsoft", "discord" ], "examples": ["google"] }, "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "issuer_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com"] }, "auth_url": { "type": "string", "format": "uri", "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] }, "token_url": { "type": "string", "format": "uri", "examples": ["https://www.googleapis.com/oauth2/v4/token"] }, "mapper_url": { "title": "Jsonnet Mapper URL", "description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.", "type": "string", "format": "uri", "examples": [ "file://path/to/oidc.jsonnet", "https://foo.bar.com/path/to/oidc.jsonnet", "base64://bG9jYWwgc3ViamVjdCA9I..." ] }, "scope": { "type": "array", "items": { "type": "string", "examples": ["offline_access", "profile"] } }, "tenant": { "title": "Azure AD Tenant", "description": "The Azure AD Tenant to use for authentication.", "type": "string", "examples": [ "common", "organizations", "consumers", "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", "contoso.onmicrosoft.com" ] } }, "additionalProperties": false, "required": [], "if": { "properties": { "provider": { "const": "microsoft" } }, "required": ["provider"] }, "then": { "required": ["tenant"] }, "else": { "not": { "properties": { "tenant": {} }, "required": ["tenant"] } } } } } } ================================================ FILE: oryx/jsonschemax/stub/nested-simple-array.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "providers": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" } } } } } } ================================================ FILE: oryx/jsonschemax/stub/toml/.project-stub-name.toml ================================================ [serve] [serve.admin] port = "2" ================================================ FILE: oryx/jsonschemax/stub/yaml/.project-stub-name.yaml ================================================ # serve controls the configuration for the http(s) daemon serve: admin: port: 3 ================================================ FILE: oryx/jsonschemax/stub/yml/.project-stub-name.yml ================================================ serve: admin: port: 4 ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-fixtures-fixture=1.json.json ================================================ "foo" ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-fixtures-fixture=2.json.json ================================================ { "some": "key" } ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-fixtures-fixture=3.json.json ================================================ { "some_key": 1234 } ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-fixtures-fixture=4.json.json ================================================ { "nested": { "object": { "source": "base64://aGVsbG8gd29ybGQ=" }, "array": [ { "nested": { "source": "base64://aGVsbG8gd29ybGQ=" } }, "base64://aGVsbG8gd29ybGQ=" ] } } ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-fixtures-fixture=5.json.json ================================================ "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-fixtures-fixture=6.json.json ================================================ { "nested": { "object": { "ignore_this_key": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" }, "array": [ { "nested": { "source": "base64://aGVsbG8gd29ybGQ=" } }, "base64://aGVsbG8gd29ybGQ=" ] } } ================================================ FILE: oryx/jsonx/.snapshots/TestEmbedSources-only_embeds_base64.json ================================================ { "key": "https://foobar.com", "bar": "base64://YXNkZg==" } ================================================ FILE: oryx/jsonx/debug.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "encoding/json" "fmt" "slices" ) // Anonymize takes a JSON byte array and anonymizes its content by // recursively replacing all values with a string indicating their type. // // It recurses into nested objects and arrays, but ignores the "schemas" and "id". func Anonymize(data []byte, except ...string) []byte { obj := make(map[string]any) if err := json.Unmarshal(data, &obj); err != nil { return []byte(fmt.Sprintf(`{"error": "invalid JSON", "message": %q}`, err.Error())) } anonymize(obj, except...) out, err := json.MarshalIndent(obj, "", " ") if err != nil { return []byte(fmt.Sprintf(`{"error": "could not marshal JSON shape", "message": %q}`, err.Error())) } return out } func anonymize(obj map[string]any, except ...string) { for k, v := range obj { if slices.Contains(except, k) { continue } switch v := v.(type) { case []any: for elIdx, el := range v { switch el := el.(type) { case map[string]any: anonymize(el) v[elIdx] = el default: v[elIdx] = jsonType(el) } } case map[string]any: anonymize(v) obj[k] = v default: obj[k] = jsonType(v) } } } func jsonType(v any) string { switch v := v.(type) { case string: return "string" case float64: return "number" case bool: return "boolean" case nil: return "null" case []any: return "array" case map[string]any: return "object" default: return fmt.Sprintf("%T", v) } } ================================================ FILE: oryx/jsonx/decoder.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "encoding/json" "io" ) // NewStrictDecoder is a shorthand for json.Decoder.DisallowUnknownFields func NewStrictDecoder(b io.Reader) *json.Decoder { d := json.NewDecoder(b) d.DisallowUnknownFields() return d } ================================================ FILE: oryx/jsonx/embed.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "encoding/base64" "encoding/json" "net/url" "slices" "strconv" "strings" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "github.com/ory/x/osx" ) type options struct { ignoreKeys []string onlySchemes []string } type OptionsModifier func(*options) func newOptions(o []OptionsModifier) *options { opt := &options{} for _, f := range o { f(opt) } return opt } func WithIgnoreKeys(keys ...string) OptionsModifier { return func(o *options) { o.ignoreKeys = keys } } func WithOnlySchemes(scheme ...string) OptionsModifier { return func(o *options) { o.onlySchemes = scheme } } func EmbedSources(in json.RawMessage, opts ...OptionsModifier) (out json.RawMessage, err error) { out = make([]byte, len(in)) copy(out, in) if err := embed(gjson.ParseBytes(in), nil, &out, newOptions(opts)); err != nil { return nil, err } return out, nil } func embed(parsed gjson.Result, parents []string, result *json.RawMessage, o *options) (err error) { if parsed.IsObject() { parsed.ForEach(func(k, v gjson.Result) bool { err = embed(v, append(parents, strings.ReplaceAll(k.String(), ".", "\\.")), result, o) return err == nil }) if err != nil { return err } } else if parsed.IsArray() { for kk, vv := range parsed.Array() { if err = embed(vv, append(parents, strconv.Itoa(kk)), result, o); err != nil { return err } } } else if parsed.Type != gjson.String { return nil } if len(parents) > 0 && slices.Contains(o.ignoreKeys, parents[len(parents)-1]) { return nil } loc, err := url.ParseRequestURI(parsed.String()) if err != nil { // Not a URL, return return nil } if len(o.onlySchemes) == 0 { if loc.Scheme != "file" && loc.Scheme != "http" && loc.Scheme != "https" && loc.Scheme != "base64" { // Not a known pattern, ignore return nil } } else if !slices.Contains(o.onlySchemes, loc.Scheme) { // Not a known pattern, ignore return nil } contents, err := osx.ReadFileFromAllSources(loc.String()) if err != nil { return err } encoded := base64.StdEncoding.EncodeToString(contents) key := strings.Join(parents, ".") if key == "" { key = "@" } interim, err := sjson.SetBytes(*result, key, "base64://"+encoded) if err != nil { return err } *result = interim return } ================================================ FILE: oryx/jsonx/fixture/embed/1.json ================================================ "foo" ================================================ FILE: oryx/jsonx/fixture/embed/2.json ================================================ { "some": "key" } ================================================ FILE: oryx/jsonx/fixture/embed/3.json ================================================ { "some_key": 1234 } ================================================ FILE: oryx/jsonx/fixture/embed/4.json ================================================ { "nested": { "object": { "source": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" }, "array": [ { "nested": { "source": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" } }, "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" ] } } ================================================ FILE: oryx/jsonx/fixture/embed/5.json ================================================ "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" ================================================ FILE: oryx/jsonx/fixture/embed/6.json ================================================ { "nested": { "object": { "ignore_this_key": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" }, "array": [ { "nested": { "source": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" } }, "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt" ] } } ================================================ FILE: oryx/jsonx/flatten.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "encoding/json" "strconv" "strings" "github.com/tidwall/gjson" ) // Flatten flattens a JSON object using dot notation. func Flatten(raw json.RawMessage) map[string]interface{} { parsed := gjson.ParseBytes(raw) if !parsed.IsObject() { return nil } flattened := make(map[string]interface{}) flatten(parsed, nil, flattened) return flattened } func flatten(parsed gjson.Result, parents []string, flattened map[string]interface{}) { if parsed.IsObject() { parsed.ForEach(func(k, v gjson.Result) bool { flatten(v, append(parents, strings.ReplaceAll(k.String(), ".", "\\.")), flattened) return true }) } else if parsed.IsArray() { for kk, vv := range parsed.Array() { flatten(vv, append(parents, strconv.Itoa(kk)), flattened) } } else { flattened[strings.Join(parents, ".")] = parsed.Value() } } ================================================ FILE: oryx/jsonx/get.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "reflect" "strings" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" ) func jsonKey(f reflect.StructField) *string { if jsonTag := f.Tag.Get("json"); jsonTag != "" { if jsonTag == "-" { return nil } return &strings.Split(jsonTag, ",")[0] } else if f.Anonymous { return nil } else if f.IsExported() { return &f.Name } return nil } // AllValidJSONKeys returns all JSON keys from the struct or *struct type. // It does not return keys from nested slices, but embedded/nested structs. func AllValidJSONKeys(s interface{}) (keys []string) { t := reflect.TypeOf(s) v := reflect.ValueOf(s) if t.Kind() == reflect.Pointer { t = t.Elem() v = v.Elem() } for i := range t.NumField() { f := t.Field(i) jKey := jsonKey(f) if k := f.Type.Kind(); k == reflect.Struct || k == reflect.Pointer { subKeys := AllValidJSONKeys(v.Field(i).Interface()) for _, subKey := range subKeys { if jKey != nil { keys = append(keys, *jKey+"."+subKey) } else { keys = append(keys, subKey) } } } else if jKey != nil { keys = append(keys, *jKey) } } return keys } // ParseEnsureKeys returns a result that has the GetRequireValidKey function. func ParseEnsureKeys(original interface{}, raw []byte) *Result { return &Result{ keys: AllValidJSONKeys(original), result: gjson.ParseBytes(raw), } } type Result struct { result gjson.Result keys []string } // GetRequireValidKey ensures that the key is valid before returning the result. func (r *Result) GetRequireValidKey(t require.TestingT, key string) gjson.Result { require.Contains(t, r.keys, key) return r.result.Get(key) } func GetRequireValidKey(t require.TestingT, original interface{}, raw []byte, key string) gjson.Result { return ParseEnsureKeys(original, raw).GetRequireValidKey(t, key) } ================================================ FILE: oryx/jsonx/helpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "encoding/json" "testing" "github.com/stretchr/testify/require" ) func TestMarshalJSONString(t *testing.T, i interface{}) string { out, err := json.Marshal(i) require.NoError(t, err) return string(out) } ================================================ FILE: oryx/jsonx/patch.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jsonx import ( "encoding/json" "fmt" "strconv" "strings" jsonpatch "github.com/evanphx/json-patch/v5" "github.com/gobwas/glob" "github.com/pkg/errors" ) var opAllowList = map[string]struct{}{ "add": {}, "remove": {}, "replace": {}, } func isUnsupported(op jsonpatch.Operation) bool { _, ok := opAllowList[op.Kind()] return !ok } func isElementAccess(path string) bool { if path == "" { return false } elements := strings.Split(path, "/") lastElement := elements[len(elements)-1:][0] if lastElement == "-" { return true } if _, err := strconv.Atoi(lastElement); err == nil { return true } return false } // ApplyJSONPatch applies a JSON patch to an object and returns the modified // object. The original object is not modified. It returns an error if the patch // is invalid or if the patch includes paths that are denied. denyPaths is a // list of path globs (interpreted with [glob.Compile] that are not allowed to // be patched. func ApplyJSONPatch[T any](p json.RawMessage, object T, denyPaths ...string) (result T, err error) { patch, err := jsonpatch.DecodePatch(p) if err != nil { return result, errors.WithStack(err) } denyPattern := fmt.Sprintf("{%s}", strings.ToLower(strings.Join(denyPaths, ","))) matcher, err := glob.Compile(denyPattern, '/') if err != nil { return result, errors.WithStack(err) } for _, op := range patch { // Some operations are buggy, see https://github.com/evanphx/json-patch/pull/158 if isUnsupported(op) { return result, errors.Errorf("unsupported operation: %s", op.Kind()) } path, err := op.Path() if err != nil { return result, errors.Errorf("error parsing patch operations: %v", err) } if matcher.Match(strings.ToLower(path)) { return result, errors.Errorf("patch includes denied path: %s", path) } // JSON patch officially rejects replacing paths that don't exist, but we want to be more tolerant. // Therefore, we will ensure that all paths that we want to replace exist in the original document. if op.Kind() == "replace" && !isElementAccess(path) { op["op"] = new(json.RawMessage(`"add"`)) } } original, err := json.Marshal(object) if err != nil { return result, errors.WithStack(err) } options := jsonpatch.NewApplyOptions() options.EnsurePathExistsOnAdd = true modified, err := patch.ApplyWithOptions(original, options) if err != nil { return result, errors.WithStack(err) } err = json.Unmarshal(modified, &result) return result, errors.WithStack(err) } ================================================ FILE: oryx/jsonx/stub/random.json ================================================ { "floating": [ -1273085434, 953442581, { "ready": "silent", "worker": "situation", "joy": "difference", "probably": -413625494, "gray": { "parent": "pull", "shore": -738396277, "usually": 1050049449, "hold": [ [ 181518765, [ { "steam": { "box": false, "cry": 1463961818, "appropriate": 1249911539, "through": 695239749, "ago": true, "entirely": -851427469 }, "leather": "across", "flies": -1571371799, "over": 512666854, "thank": true, "shaking": true }, "hit", -648178744.4899056, 1225027271, -1481507228, true ], -2114582277, 1390060204.9360588, 1615602630.9049141, "darkness" ], 63427197.713988304, -580344963.961421, "stems", 1016960217.612642, 1240918909 ], "buy": true, "wonder": false }, "little": "cloud" }, "grade", false, "thou" ], "wagon": -1722583702, "shop": 1294397217, "spend": "greatest", "product": "whale", "fall": "to" } ================================================ FILE: oryx/jwksx/.snapshots/TestFetcherNext-case=resolve_multiple_source_urls-case=succeeds_with_forced_kid.json ================================================ { "alg": "HS256", "k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU", "kid": "8d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8", "kty": "oct", "use": "sig" } ================================================ FILE: oryx/jwksx/.snapshots/TestFetcherNext-case=resolve_single_source_url-case=with_cache.json ================================================ { "alg": "HS256", "k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU", "kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8", "kty": "oct", "use": "sig" } ================================================ FILE: oryx/jwksx/.snapshots/TestFetcherNext-case=resolve_single_source_url-case=with_cache_and_TTL.json ================================================ { "alg": "HS256", "k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU", "kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8", "kty": "oct", "use": "sig" } ================================================ FILE: oryx/jwksx/.snapshots/TestFetcherNext-case=resolve_single_source_url-case=with_forced_key.json ================================================ { "alg": "HS256", "k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU", "kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8", "kty": "oct", "use": "sig" } ================================================ FILE: oryx/jwksx/.snapshots/TestFetcherNext-case=resolve_single_source_url-case=without_cache.json ================================================ { "alg": "HS256", "k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU", "kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8", "kty": "oct", "use": "sig" } ================================================ FILE: oryx/jwksx/fetcher.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwksx import ( "encoding/json" "net/http" "sync" "github.com/go-jose/go-jose/v3" "github.com/pkg/errors" ) // Fetcher is a small helper for fetching JSON Web Keys from remote endpoints. // // DEPRECATED: Use FetcherNext instead. type Fetcher struct { sync.RWMutex remote string c *http.Client keys map[string]jose.JSONWebKey } // NewFetcher returns a new fetcher that can download JSON Web Keys from remote endpoints. // // DEPRECATED: Use FetcherNext instead. func NewFetcher(remote string) *Fetcher { return &Fetcher{ remote: remote, c: http.DefaultClient, keys: make(map[string]jose.JSONWebKey), } } // GetKey retrieves a JSON Web Key from the cache, fetches it from a remote if it is not yet cached or returns an error. // // DEPRECATED: Use FetcherNext instead. func (f *Fetcher) GetKey(kid string) (*jose.JSONWebKey, error) { f.RLock() if k, ok := f.keys[kid]; ok { f.RUnlock() return &k, nil } f.RUnlock() res, err := f.c.Get(f.remote) if err != nil { return nil, errors.WithStack(err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, errors.Errorf("expected status code 200 but got %d when requesting %s", res.StatusCode, f.remote) } var set jose.JSONWebKeySet if err := json.NewDecoder(res.Body).Decode(&set); err != nil { return nil, errors.WithStack(err) } for _, k := range set.Keys { f.Lock() f.keys[k.KeyID] = k f.Unlock() } f.RLock() defer f.RUnlock() if k, ok := f.keys[kid]; ok { return &k, nil } return nil, errors.Errorf("unable to find JSON Web Key with ID: %s", kid) } ================================================ FILE: oryx/jwksx/fetcher_v2.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwksx import ( "context" "crypto/sha256" "time" "github.com/ory/herodot" "github.com/hashicorp/go-retryablehttp" "github.com/ory/x/fetcher" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/ory/x/otelx" "github.com/dgraph-io/ristretto/v2" "github.com/lestrrat-go/jwx/jwk" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) var ErrUnableToFindKeyID = errors.New("specified JWK kid can not be found in the JWK sets") type ( fetcherNextOptions struct { forceKID string cacheTTL time.Duration useCache bool httpClient *retryablehttp.Client } // FetcherNext is a JWK fetcher that can be used to fetch JWKs from multiple locations. FetcherNext struct { cache *ristretto.Cache[[]byte, jwk.Set] } // FetcherNextOption is a functional option for the FetcherNext. FetcherNextOption func(*fetcherNextOptions) ) // NewFetcherNext returns a new FetcherNext instance. func NewFetcherNext(cache *ristretto.Cache[[]byte, jwk.Set]) *FetcherNext { return &FetcherNext{ cache: cache, } } // WithForceKID forces the key ID to be used. Required when multiple JWK sets are configured. func WithForceKID(kid string) FetcherNextOption { return func(o *fetcherNextOptions) { o.forceKID = kid } } // WithCacheTTL sets the cache TTL. If not set, the TTL is unlimited. func WithCacheTTL(ttl time.Duration) FetcherNextOption { return func(o *fetcherNextOptions) { o.cacheTTL = ttl } } // WithCacheEnabled enables the cache. func WithCacheEnabled() FetcherNextOption { return func(o *fetcherNextOptions) { o.useCache = true } } // WithHTTPClient will use the given HTTP client to fetch the JSON Web Keys. func WithHTTPClient(c *retryablehttp.Client) FetcherNextOption { return func(o *fetcherNextOptions) { o.httpClient = c } } func (f *FetcherNext) ResolveKey(ctx context.Context, locations string, modifiers ...FetcherNextOption) (jwk.Key, error) { return f.ResolveKeyFromLocations(ctx, []string{locations}, modifiers...) } func (f *FetcherNext) ResolveKeyFromLocations(ctx context.Context, locations []string, modifiers ...FetcherNextOption) (jwk.Key, error) { opts := new(fetcherNextOptions) for _, m := range modifiers { m(opts) } if len(locations) > 1 && opts.forceKID == "" { return nil, errors.Errorf("a key ID must be specified when multiple JWK sets are configured") } set := jwk.NewSet() eg := new(errgroup.Group) for k := range locations { location := locations[k] eg.Go(func() error { remoteSet, err := f.fetch(ctx, location, opts) if err != nil { return err } iterator := remoteSet.Iterate(ctx) for iterator.Next(ctx) { // Pair().Value is always of type jwk.Key when generated by Iterate. set.Add(iterator.Pair().Value.(jwk.Key)) } return nil }) } if err := eg.Wait(); err != nil { return nil, err } if opts.forceKID != "" { key, found := set.LookupKeyID(opts.forceKID) if !found { return nil, errors.WithStack(ErrUnableToFindKeyID) } return key, nil } // No KID was forced? Use the first key we can find. key, found := set.Get(0) if !found { return nil, errors.WithStack(ErrUnableToFindKeyID) } return key, nil } // fetch fetches the JWK set from the given location and if enabled, may use the cache to look up the JWK set. func (f *FetcherNext) fetch(ctx context.Context, location string, opts *fetcherNextOptions) (_ jwk.Set, err error) { tracer := trace.SpanFromContext(ctx).TracerProvider().Tracer("") ctx, span := tracer.Start(ctx, "jwksx.FetcherNext.fetch", trace.WithAttributes(attribute.String("location", location))) defer otelx.End(span, &err) cacheKey := sha256.Sum256([]byte(location)) if opts.useCache { if result, found := f.cache.Get(cacheKey[:]); found { return result, nil } } var fopts []fetcher.Modifier if opts.httpClient != nil { fopts = append(fopts, fetcher.WithClient(opts.httpClient)) } result, err := fetcher.NewFetcher(fopts...).FetchContext(ctx, location) if err != nil { return nil, err } set, err := jwk.ParseReader(result) if err != nil { return nil, errors.WithStack(herodot.ErrBadRequest.WithReason("failed to parse JWK set").WithWrap(err)) } if opts.useCache { f.cache.SetWithTTL(cacheKey[:], set, 1, opts.cacheTTL) } return set, nil } ================================================ FILE: oryx/jwksx/generator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwksx import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "io" "github.com/go-jose/go-jose/v3" "github.com/gofrs/uuid" "github.com/pkg/errors" "golang.org/x/crypto/ed25519" ) // GenerateSigningKeys generates a JSON Web Key Set for signing. func GenerateSigningKeys(id, alg string, bits int) (*jose.JSONWebKeySet, error) { if id == "" { id = uuid.Must(uuid.NewV4()).String() } key, err := generate(jose.SignatureAlgorithm(alg), bits) if err != nil { return nil, err } return &jose.JSONWebKeySet{ Keys: []jose.JSONWebKey{ { Algorithm: alg, Use: "sig", Key: key, KeyID: id, Certificates: []*x509.Certificate{}, }, }, }, nil } // GenerateSigningKeysAvailableAlgorithms lists available algorithms that are supported by GenerateSigningKeys. func GenerateSigningKeysAvailableAlgorithms() []string { return []string{ string(jose.HS256), string(jose.HS384), string(jose.HS512), string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.EdDSA), string(jose.RS256), string(jose.RS384), string(jose.RS512), string(jose.PS256), string(jose.PS384), string(jose.PS512), } } // generate generates keypair for corresponding SignatureAlgorithm. func generate(alg jose.SignatureAlgorithm, bits int) (crypto.PrivateKey, error) { switch alg { case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA: keylen := map[jose.SignatureAlgorithm]int{ jose.ES256: 256, jose.ES384: 384, jose.ES512: 521, // sic! jose.EdDSA: 256, } if bits != 0 && bits != keylen[alg] { return nil, errors.Errorf(`jwksx: "%s" does not support arbitrary key length`, alg) } case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512: if bits == 0 { bits = 2048 } if bits < 2048 { return nil, errors.Errorf(`jwksx: key size must be at least 2048 bit for algorithm "%s"`, alg) } case jose.HS256: if bits == 0 { bits = 256 } if bits < 256 { return nil, errors.Errorf(`jwksx: key size must be at least 256 bit for algorithm "%s"`, alg) } case jose.HS384: if bits == 0 { bits = 384 } if bits < 384 { return nil, errors.Errorf(`jwksx: key size must be at least 2038448 bit for algorithm "%s"`, alg) } case jose.HS512: if bits == 0 { bits = 1024 } if bits < 512 { return nil, errors.Errorf(`jwksx: key size must be at least 512 bit for algorithm "%s"`, alg) } } switch alg { case jose.ES256: // The cryptographic operations are implemented using constant-time algorithms. key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) return key, errors.Wrapf(err, "jwks: unable to generate key") case jose.ES384: // NB: The cryptographic operations do not use constant-time algorithms. key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) return key, errors.Wrapf(err, "jwks: unable to generate key") case jose.ES512: // NB: The cryptographic operations do not use constant-time algorithms. key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) return key, errors.Wrapf(err, "jwks: unable to generate key") case jose.EdDSA: _, key, err := ed25519.GenerateKey(rand.Reader) return key, errors.Wrapf(err, "jwks: unable to generate key") case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512: key, err := rsa.GenerateKey(rand.Reader, bits) return key, errors.Wrapf(err, "jwks: unable to generate key") case jose.HS256, jose.HS384, jose.HS512: if bits%8 != 0 { return nil, errors.Errorf(`jwksx: key size must be a multiple of 8 for algorithm "%s" but got: %d`, alg, bits) } key := make([]byte, bits/8) if _, err := io.ReadFull(rand.Reader, key); err != nil { return nil, errors.Wrapf(err, "jwks: unable to generate key") } return key, nil default: return nil, errors.Errorf(`jwksx: available algorithms are "%+v" but unknown algorithm was requested: "%s"`, GenerateSigningKeysAvailableAlgorithms(), alg) } } ================================================ FILE: oryx/jwtmiddleware/middleware.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwtmiddleware import ( "context" "encoding/json" "fmt" "net/http" "strings" "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" "github.com/ory/herodot" jwtmiddleware "github.com/auth0/go-jwt-middleware/v2" "github.com/urfave/negroni" "github.com/ory/x/jwksx" ) type Middleware struct { o *middlewareOptions wku string jm *jwtmiddleware.JWTMiddleware } type middlewareOptions struct { Debug bool ExcludePaths []string SigningMethod jwt.SigningMethod ErrorWriter herodot.Writer } type MiddlewareOption func(*middlewareOptions) func SessionFromContext(ctx context.Context) (json.RawMessage, error) { raw := ctx.Value(jwtmiddleware.ContextKey{}) if raw == nil { return nil, errors.WithStack(herodot.ErrUnauthorized.WithReasonf("Could not find credentials in the request.")) } token, ok := raw.(*jwt.Token) if !ok { return nil, errors.WithStack(herodot.ErrInternalServerError.WithDebugf(`Expected context key "%T" to transport value of type *jwt.MapClaims but got type: %T`, jwtmiddleware.ContextKey{}, raw)) } session, err := json.Marshal(token.Claims) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithDebugf("Unable to encode session data: %s", err)) } return session, nil } func MiddlewareDebugEnabled() MiddlewareOption { return func(o *middlewareOptions) { o.Debug = true } } func MiddlewareExcludePaths(paths ...string) MiddlewareOption { return func(o *middlewareOptions) { o.ExcludePaths = append(o.ExcludePaths, paths...) } } func MiddlewareAllowSigningMethod(method jwt.SigningMethod) MiddlewareOption { return func(o *middlewareOptions) { o.SigningMethod = method } } func MiddlewareErrorWriter(w herodot.Writer) MiddlewareOption { return func(o *middlewareOptions) { o.ErrorWriter = w } } func NewMiddleware( wellKnownURL string, opts ...MiddlewareOption, ) *Middleware { c := &middlewareOptions{ SigningMethod: jwt.SigningMethodES256, ErrorWriter: herodot.NewJSONWriter(nil), } for _, o := range opts { o(c) } jc := jwksx.NewFetcher(wellKnownURL) return &Middleware{ o: c, wku: wellKnownURL, jm: jwtmiddleware.New( func(ctx context.Context, rawToken string) (any, error) { return jwt.NewParser( jwt.WithValidMethods([]string{c.SigningMethod.Alg()}), ).Parse(rawToken, func(token *jwt.Token) (interface{}, error) { if raw, ok := token.Header["kid"]; !ok { return nil, errors.New(`jwt from authorization HTTP header is missing value for "kid" in token header`) } else if kid, ok := raw.(string); !ok { return nil, fmt.Errorf(`jwt from authorization HTTP header is expecting string value for "kid" in tokenWithoutKid header but got: %T`, raw) } else if k, err := jc.GetKey(kid); err != nil { return nil, err } else { return k.Key, nil } }) }, jwtmiddleware.WithCredentialsOptional(false), jwtmiddleware.WithTokenExtractor(func(r *http.Request) (string, error) { // wrapping the extractor to get a herodot.ErrorContainer token, err := jwtmiddleware.AuthHeaderTokenExtractor(r) if err != nil { return "", herodot.ErrUnauthorized.WithReason(err.Error()) } return token, nil }), jwtmiddleware.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) { switch { case errors.Is(err, jwtmiddleware.ErrJWTInvalid): reason := "The token is invalid or expired." if err := errors.Unwrap(err); err != nil { reason = err.Error() } c.ErrorWriter.WriteError(w, r, errors.WithStack(herodot.ErrUnauthorized.WithReason(reason))) case errors.Is(err, jwtmiddleware.ErrJWTMissing): c.ErrorWriter.WriteError(w, r, errors.WithStack(herodot.ErrUnauthorized.WithReason("The token is missing."))) default: c.ErrorWriter.WriteError(w, r, err) } }), ), } } // Deprecated: use Middleware as a negroni.Handler directly instead. func (h *Middleware) NegroniHandler() negroni.Handler { return negroni.HandlerFunc(h.ServeHTTP) } func (h *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { for _, excluded := range h.o.ExcludePaths { if strings.HasPrefix(r.URL.Path, excluded) { next(w, r) return } } h.jm.CheckJWT(next).ServeHTTP(w, r) } ================================================ FILE: oryx/jwtmiddleware/stub/jwks.json ================================================ { "use": "sig", "kty": "EC", "kid": "b71ff5bd-a016-4ac0-9f3f-a172552578ea", "crv": "P-256", "alg": "ES256", "x": "7fVj_SeCx3TnkHANRWrpEho9BcYkU953LHUvKsSF5Wo", "y": "2A9D_AAFPiJQLSJQ_h600Fy9jUrg9Q88gNPPZwHDb7o", "d": "sRl-e-tGEVsNBF8FgEado9NAEipxhAFXGMryWDgbUMo" } ================================================ FILE: oryx/jwtx/claims.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package jwtx import ( "time" "github.com/pkg/errors" "github.com/ory/x/mapx" ) // Claims represents a JSON Web Token's standard claims. type Claims struct { // Audience identifies the recipients that the JWT is intended for. Audience []string `json:"aud"` // Issuer identifies the principal that issued the JWT. Issuer string `json:"iss"` // Subject identifies the principal that is the subject of the JWT. Subject string `json:"sub"` // ExpiresAt identifies the expiration time on or after which the JWT most not be accepted for processing. ExpiresAt time.Time `json:"exp"` // IssuedAt identifies the time at which the JWT was issued. IssuedAt time.Time `json:"iat"` // NotBefore identifies the time before which the JWT must not be accepted for processing. NotBefore time.Time `json:"nbf"` // JTI provides a unique identifier for the JWT. JTI string `json:"jti"` } // ParseMapStringInterfaceClaims converts map[string]interface{} to *Claims. func ParseMapStringInterfaceClaims(claims map[string]interface{}) *Claims { c := make(map[interface{}]interface{}) for k, v := range claims { c[k] = v } return ParseMapInterfaceInterfaceClaims(c) } // ParseMapInterfaceInterfaceClaims converts map[interface{}]interface{} to *Claims. func ParseMapInterfaceInterfaceClaims(claims map[interface{}]interface{}) *Claims { result := &Claims{ Issuer: mapx.GetStringDefault(claims, "iss", ""), Subject: mapx.GetStringDefault(claims, "sub", ""), JTI: mapx.GetStringDefault(claims, "jti", ""), } if aud, err := mapx.GetString(claims, "aud"); err == nil { result.Audience = []string{aud} } else if errors.Is(err, mapx.ErrKeyCanNotBeTypeAsserted) { if aud, err := mapx.GetStringSlice(claims, "aud"); err == nil { result.Audience = aud } else { result.Audience = []string{} } } else { result.Audience = []string{} } if exp, err := mapx.GetTime(claims, "exp"); err == nil { result.ExpiresAt = exp } if iat, err := mapx.GetTime(claims, "iat"); err == nil { result.IssuedAt = iat } if nbf, err := mapx.GetTime(claims, "nbf"); err == nil { result.NotBefore = nbf } return result } ================================================ FILE: oryx/logrusx/config.schema.json ================================================ { "$id": "ory://logging-config", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Log", "description": "Configure logging using the following options. Logs will always be sent to stdout and stderr.", "type": "object", "properties": { "level": { "title": "Level", "description": "The level of log entries to show. Debug enables stack traces on errors.", "type": "string", "default": "info", "enum": ["panic", "fatal", "error", "warn", "info", "debug", "trace"] }, "format": { "title": "Log Format", "description": "The output format of log messages.", "type": "string", "default": "text", "enum": ["json", "json_pretty", "gelf", "text"] }, "leak_sensitive_values": { "type": "boolean", "title": "Leak Sensitive Log Values", "description": "If set will leak sensitive values (e.g. emails) in the logs.", "default": false }, "redaction_text": { "type": "string", "title": "Sensitive log value redaction text", "description": "Text to use, when redacting sensitive log value." }, "additional_redacted_headers": { "type": "array", "title": "Additional redacted headers", "description": "List of HTTP headers which will be redacted.", "items": { "type": "string" } } }, "additionalProperties": false } ================================================ FILE: oryx/logrusx/helper.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package logrusx import ( "context" "errors" "fmt" "log" "net/http" "net/url" "reflect" "strings" "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "github.com/ory/x/errorsx" ) type ( Logger struct { *logrus.Entry leakSensitive bool redactionText string additionalRedactedHeaders map[string]struct{} opts []Option } Provider interface { Logger() *Logger } ) var opts = otelhttptrace.WithPropagators(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) func (l *Logger) Logrus() *logrus.Logger { return l.Entry.Logger } func (l *Logger) NewEntry() *Logger { ll := *l ll.Entry = logrus.NewEntry(l.Logger) return &ll } func (l *Logger) WithContext(ctx context.Context) *Logger { ll := *l ll.Entry = l.Logger.WithContext(ctx) return &ll } func (l *Logger) HTTPHeadersRedacted(h http.Header) map[string]any { headers := map[string]any{} for key, value := range h { switch keyLower := strings.ToLower(key); keyLower { case "authorization", "cookie", "set-cookie", "x-session-token": headers[keyLower] = l.maybeRedact(value) case "location": locationURL, err := url.Parse(h.Get("Location")) if err != nil { headers[keyLower] = l.maybeRedact(value) continue } if l.leakSensitive { headers[keyLower] = locationURL.String() } else { locationURL.RawQuery = "" locationURL.Fragment = "" headers[keyLower] = locationURL.Redacted() } default: if _, ok := l.additionalRedactedHeaders[keyLower]; ok { headers[keyLower] = l.maybeRedact(value) continue } headers[keyLower] = h.Get(key) } } return headers } func (l *Logger) WithRequest(r *http.Request) *Logger { headers := l.HTTPHeadersRedacted(r.Header) if ua := r.UserAgent(); len(ua) > 0 { headers["user-agent"] = ua } scheme := "https" if r.TLS == nil { scheme = "http" } ll := l.WithField("http_request", map[string]any{ "remote": r.RemoteAddr, "method": r.Method, "path": r.URL.EscapedPath(), "query": l.maybeRedact(r.URL.RawQuery), "scheme": scheme, "host": r.Host, "headers": headers, }) spanCtx := trace.SpanContextFromContext(r.Context()) if !spanCtx.IsValid() { _, _, spanCtx = otelhttptrace.Extract(r.Context(), r, opts) } if spanCtx.IsValid() { traces := make(map[string]string, 2) if spanCtx.HasTraceID() { traces["trace_id"] = spanCtx.TraceID().String() } if spanCtx.HasSpanID() { traces["span_id"] = spanCtx.SpanID().String() } ll = ll.WithField("otel", traces) } return ll } func (l *Logger) WithSpanFromContext(ctx context.Context) *Logger { spanCtx := trace.SpanContextFromContext(ctx) if !spanCtx.IsValid() { return l } traces := make(map[string]string, 2) if spanCtx.HasTraceID() { traces["trace_id"] = spanCtx.TraceID().String() } if spanCtx.HasSpanID() { traces["span_id"] = spanCtx.SpanID().String() } return l.WithField("otel", traces) } func (l *Logger) Logf(level logrus.Level, format string, args ...any) { if !l.leakSensitive { for i, arg := range args { switch urlArg := arg.(type) { case url.URL: urlCopy := url.URL{Scheme: urlArg.Scheme, Host: urlArg.Host, Path: urlArg.Path} args[i] = urlCopy case *url.URL: urlCopy := url.URL{Scheme: urlArg.Scheme, Host: urlArg.Host, Path: urlArg.Path} args[i] = &urlCopy default: continue } } } l.Entry.Logf(level, format, args...) } func (l *Logger) Tracef(format string, args ...any) { l.Logf(logrus.TraceLevel, format, args...) } func (l *Logger) Debugf(format string, args ...any) { l.Logf(logrus.DebugLevel, format, args...) } func (l *Logger) Infof(format string, args ...any) { l.Logf(logrus.InfoLevel, format, args...) } func (l *Logger) Warnf(format string, args ...any) { l.Logf(logrus.WarnLevel, format, args...) } func (l *Logger) Errorf(format string, args ...any) { l.Logf(logrus.ErrorLevel, format, args...) } func (l *Logger) Fatalf(format string, args ...any) { l.Logf(logrus.FatalLevel, format, args...) l.Entry.Logger.Exit(1) } func (l *Logger) Panicf(format string, args ...any) { l.Logf(logrus.PanicLevel, format, args...) } func (l *Logger) WithFields(f logrus.Fields) *Logger { ll := *l ll.Entry = l.Entry.WithFields(f) return &ll } func (l *Logger) WithField(key string, value any) *Logger { ll := *l ll.Entry = l.Entry.WithField(key, value) return &ll } func (l *Logger) maybeRedact(value any) any { if fmt.Sprintf("%v", value) == "" || value == nil { return nil } if !l.leakSensitive { return l.redactionText } return value } func (l *Logger) WithSensitiveField(key string, value any) *Logger { return l.WithField(key, l.maybeRedact(value)) } func (l *Logger) WithError(err error) *Logger { if err == nil { return l } ctx := map[string]any{"message": err.Error()} if l.Entry.Logger.IsLevelEnabled(logrus.DebugLevel) { if e, ok := err.(errorsx.StackTracer); ok { ctx["stack_trace"] = fmt.Sprintf("%+v", e.StackTrace()) } else { ctx["stack_trace"] = fmt.Sprintf("stack trace could not be recovered from error type %s", reflect.TypeOf(err)) } } if c := errorsx.ReasonCarrier(nil); errors.As(err, &c) { ctx["reason"] = c.Reason() } if c := errorsx.RequestIDCarrier(nil); errors.As(err, &c) && c.RequestID() != "" { ctx["request_id"] = c.RequestID() } if c := errorsx.DetailsCarrier(nil); errors.As(err, &c) && c.Details() != nil { ctx["details"] = c.Details() } if c := errorsx.StatusCarrier(nil); errors.As(err, &c) && c.Status() != "" { ctx["status"] = c.Status() } if c := errorsx.StatusCodeCarrier(nil); errors.As(err, &c) && c.StatusCode() != 0 { ctx["status_code"] = c.StatusCode() } if c := errorsx.DebugCarrier(nil); errors.As(err, &c) { ctx["debug"] = c.Debug() } return l.WithField("error", ctx) } func (l *Logger) StdLogger(lvl logrus.Level) *log.Logger { return log.New(writer{l.Logger, lvl}, "", 0) } type writer struct { l *logrus.Logger lvl logrus.Level } func (w writer) Write(p []byte) (n int, err error) { w.l.Log(w.lvl, strings.TrimSuffix(string(p), "\n")) return len(p), nil } ================================================ FILE: oryx/logrusx/logrus.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package logrusx import ( "bytes" "cmp" _ "embed" "io" "net/http" "os" "strings" "testing" "time" "github.com/sirupsen/logrus" gelf "github.com/seatgeek/logrus-gelf-formatter" "github.com/ory/x/stringsx" ) type ( options struct { l *logrus.Logger level *logrus.Level formatter logrus.Formatter format string reportCaller bool exitFunc func(int) leakSensitive bool redactionText string additionalRedactedHeaders []string hooks []logrus.Hook c configurator } Option func(*options) nullConfigurator struct{} configurator interface { Bool(key string) bool String(key string) string Strings(path string) []string } ) //go:embed config.schema.json var ConfigSchema string const ConfigSchemaID = "ory://logging-config" // AddConfigSchema adds the logging schema to the compiler. // The interface is specified instead of `jsonschema.Compiler` to allow the use of any jsonschema library fork or version. func AddConfigSchema(c interface { AddResource(url string, r io.Reader) error }, ) error { return c.AddResource(ConfigSchemaID, bytes.NewBufferString(ConfigSchema)) } func newLogger(parent *logrus.Logger, o *options) *logrus.Logger { l := parent if l == nil { l = logrus.New() } if o.exitFunc != nil { l.ExitFunc = o.exitFunc } for _, hook := range o.hooks { l.AddHook(hook) } setLevel(l, o) setFormatter(l, o) l.ReportCaller = o.reportCaller || l.IsLevelEnabled(logrus.TraceLevel) return l } func setLevel(l *logrus.Logger, o *options) { if o.level != nil { l.Level = *o.level } else { var err error l.Level, err = logrus.ParseLevel(cmp.Or( o.c.String("log.level"), os.Getenv("LOG_LEVEL"))) if err != nil { l.Level = logrus.InfoLevel } } } func setFormatter(l *logrus.Logger, o *options) { if o.formatter != nil { l.Formatter = o.formatter } else { var unknownFormat bool // we first have to set the formatter before we can complain about the unknown format format := stringsx.SwitchExact(cmp.Or(o.format, o.c.String("log.format"), os.Getenv("LOG_FORMAT"))) switch { case format.AddCase("json"): l.Formatter = &logrus.JSONFormatter{PrettyPrint: false, TimestampFormat: time.RFC3339Nano, DisableHTMLEscape: true} case format.AddCase("json_pretty"): l.Formatter = &logrus.JSONFormatter{PrettyPrint: true, TimestampFormat: time.RFC3339Nano, DisableHTMLEscape: true} case format.AddCase("gelf"): l.Formatter = new(gelf.GelfFormatter) default: unknownFormat = true fallthrough case format.AddCase("text", ""): l.Formatter = &logrus.TextFormatter{ DisableQuote: true, DisableTimestamp: false, FullTimestamp: true, } } if unknownFormat { l.WithError(format.ToUnknownCaseErr()).Warn("got unknown \"log.format\", falling back to \"text\"") } } } func ForceLevel(level logrus.Level) Option { return func(o *options) { o.level = &level } } func ForceFormatter(formatter logrus.Formatter) Option { return func(o *options) { o.formatter = formatter } } func WithConfigurator(c configurator) Option { return func(o *options) { o.c = c } } func ForceFormat(format string) Option { return func(o *options) { o.format = format } } func WithHook(hook logrus.Hook) Option { return func(o *options) { o.hooks = append(o.hooks, hook) } } func WithExitFunc(exitFunc func(int)) Option { return func(o *options) { o.exitFunc = exitFunc } } func ReportCaller(reportCaller bool) Option { return func(o *options) { o.reportCaller = reportCaller } } func UseLogger(l *logrus.Logger) Option { return func(o *options) { o.l = l } } func LeakSensitive() Option { return func(o *options) { o.leakSensitive = true } } func RedactionText(text string) Option { return func(o *options) { o.redactionText = text } } func WithAdditionalRedactedHeaders(headers []string) Option { return func(o *options) { o.additionalRedactedHeaders = headers } } func toHeaderMap(headers []string) map[string]struct{} { m := make(map[string]struct{}, len(headers)) for _, h := range headers { m[strings.ToLower(h)] = struct{}{} } return m } func (c *nullConfigurator) Bool(_ string) bool { return false } func (c *nullConfigurator) String(_ string) string { return "" } func (c *nullConfigurator) Strings(_ string) []string { return []string{} } func newOptions(opts []Option) *options { o := new(options) o.c = new(nullConfigurator) for _, f := range opts { f(o) } return o } // New creates a new logger with all the important fields set. func New(name string, version string, opts ...Option) *Logger { o := newOptions(opts) return &Logger{ opts: opts, leakSensitive: o.leakSensitive || o.c.Bool("log.leak_sensitive_values"), redactionText: cmp.Or(o.redactionText, `Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true".`), additionalRedactedHeaders: toHeaderMap(func() []string { if len(o.additionalRedactedHeaders) > 0 { return o.additionalRedactedHeaders } return o.c.Strings("log.additional_redacted_headers") }()), Entry: newLogger(o.l, o).WithFields(logrus.Fields{ "audience": "application", "service_name": name, "service_version": version, }), } } func NewT(t testing.TB, opts ...Option) *Logger { opts = append(opts, LeakSensitive(), WithExitFunc(func(code int) { t.Fatalf("Logger exited with code %d", code) })) l := New(t.Name(), "test", opts...) l.Logger.Out = t.Output() return l } func (l *Logger) UseConfig(c configurator) { l.leakSensitive = l.leakSensitive || c.Bool("log.leak_sensitive_values") l.redactionText = cmp.Or(c.String("log.redaction_text"), l.redactionText) newHeaders := toHeaderMap(c.Strings("log.additional_redacted_headers")) for k := range newHeaders { l.additionalRedactedHeaders[k] = struct{}{} } o := newOptions(append(l.opts, WithConfigurator(c))) setLevel(l.Entry.Logger, o) setFormatter(l.Entry.Logger, o) } func (l *Logger) ReportError(r *http.Request, code int, err error, args ...interface{}) { logger := l.WithError(err).WithRequest(r).WithField("http_response", map[string]interface{}{ "status_code": code, }) switch { case code < 500: logger.Info(args...) default: logger.Error(args...) } } ================================================ FILE: oryx/mapx/type_assert.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package mapx import ( "encoding/json" "errors" "math" "time" ) // ErrKeyDoesNotExist is returned when the key does not exist in the map. var ErrKeyDoesNotExist = errors.New("key is not present in map") // ErrKeyCanNotBeTypeAsserted is returned when the key can not be type asserted. var ErrKeyCanNotBeTypeAsserted = errors.New("key could not be type asserted") // GetString returns a string for a given key in values. func GetString[K comparable](values map[K]any, key K) (string, error) { if v, ok := values[key]; !ok { return "", ErrKeyDoesNotExist } else if sv, ok := v.(string); !ok { return "", ErrKeyCanNotBeTypeAsserted } else { return sv, nil } } // GetStringSlice returns a string slice for a given key in values. func GetStringSlice[K comparable](values map[K]any, key K) ([]string, error) { v, ok := values[key] if !ok { return nil, ErrKeyDoesNotExist } switch v := v.(type) { case []string: return v, nil case []any: vs := make([]string, len(v)) for k, v := range v { var ok bool vs[k], ok = v.(string) if !ok { return nil, ErrKeyCanNotBeTypeAsserted } } return vs, nil } return nil, ErrKeyCanNotBeTypeAsserted } // GetTime returns a string slice for a given key in values. func GetTime[K comparable](values map[K]any, key K) (time.Time, error) { v, ok := values[key] if !ok { return time.Time{}, ErrKeyDoesNotExist } switch v := v.(type) { case time.Time: return v, nil case int64: return time.Unix(v, 0), nil case int32: return time.Unix(int64(v), 0), nil case int: return time.Unix(int64(v), 0), nil case float64: if v < math.MinInt64 || v > math.MaxInt64 { return time.Time{}, errors.New("value is out of range") } return time.Unix(int64(v), 0), nil case float32: if v < math.MinInt64 || v > math.MaxInt64 { return time.Time{}, errors.New("value is out of range") } return time.Unix(int64(v), 0), nil } return time.Time{}, ErrKeyCanNotBeTypeAsserted } // GetInt64 returns an int64 for a given key in values. func GetInt64[K comparable](values map[K]any, key K) (int64, error) { v, ok := values[key] if !ok { return 0, ErrKeyDoesNotExist } switch v := v.(type) { case json.Number: return v.Int64() case int64: return v, nil case int: return int64(v), nil case int32: return int64(v), nil case uint: vv := uint64(v) if vv > math.MaxInt64 { return 0, errors.New("value is out of range") } return int64(vv), nil case uint32: return int64(v), nil case uint64: if v > math.MaxInt64 { return 0, errors.New("value is out of range") } return int64(v), nil } return 0, ErrKeyCanNotBeTypeAsserted } // GetInt32 returns an int32 for a given key in values. func GetInt32[K comparable](values map[K]any, key K) (int32, error) { v, err := GetInt64(values, key) if err != nil { return 0, err } if v > math.MaxInt32 || v < math.MinInt32 { return 0, errors.New("value is out of range") } return int32(v), nil } // GetInt returns an int for a given key in values. func GetInt[K comparable](values map[K]any, key K) (int, error) { v, err := GetInt64(values, key) if err != nil { return 0, err } if v > math.MaxInt || v < math.MinInt { return 0, errors.New("value is out of range") } return int(v), nil } // GetFloat64Default returns a float64 or the default value for a given key in values. func GetFloat64Default[K comparable](values map[K]any, key K, defaultValue float64) float64 { f, err := GetFloat64(values, key) if err != nil { return defaultValue } return f } // GetFloat64 returns a float64 for a given key in values. func GetFloat64[K comparable](values map[K]any, key K) (float64, error) { v, ok := values[key] if !ok { return 0, ErrKeyDoesNotExist } switch v := v.(type) { case json.Number: return v.Float64() case float32: return float64(v), nil case float64: return v, nil } return 0, ErrKeyCanNotBeTypeAsserted } // GetStringDefault returns a string or the default value for a given key in values. func GetStringDefault[K comparable](values map[K]any, key K, defaultValue string) string { if s, err := GetString(values, key); err == nil { return s } return defaultValue } // GetStringSliceDefault returns a string slice or the default value for a given key in values. func GetStringSliceDefault[K comparable](values map[K]any, key K, defaultValue []string) []string { if s, err := GetStringSlice(values, key); err == nil { return s } return defaultValue } ================================================ FILE: oryx/metricsx/metrics.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package metricsx import ( "runtime" "sync" ) // MemoryStatistics is a JSON-able version of runtime.MemStats type MemoryStatistics struct { sync.Mutex // Alloc is bytes of allocated heap objects. Alloc uint64 `json:"alloc"` // TotalAlloc is cumulative bytes allocated for heap objects. TotalAlloc uint64 `json:"totalAlloc"` // Sys is the total bytes of memory obtained from the OS. Sys uint64 `json:"sys"` // Lookups is the number of pointer lookups performed by the // runtime. Lookups uint64 `json:"lookups"` // Mallocs is the cumulative count of heap objects allocated. // The number of live objects is Mallocs - Frees. Mallocs uint64 `json:"mallocs"` // Frees is the cumulative count of heap objects freed. Frees uint64 `json:"frees"` // HeapAlloc is bytes of allocated heap objects. HeapAlloc uint64 `json:"heapAlloc"` // HeapSys is bytes of heap memory obtained from the OS. HeapSys uint64 `json:"heapSys"` // HeapIdle is bytes in idle (unused) spans. HeapIdle uint64 `json:"heapIdle"` // HeapInuse is bytes in in-use spans. HeapInuse uint64 `json:"heapInuse"` // HeapReleased is bytes of physical memory returned to the OS. HeapReleased uint64 `json:"heapReleased"` // HeapObjects is the number of allocated heap objects. HeapObjects uint64 `json:"heapObjects"` // NumGC is the number of completed GC cycles. NumGC uint32 `json:"numGC"` } // ToMap converts to a map[string]interface{}. func (ms *MemoryStatistics) ToMap() map[string]interface{} { return map[string]interface{}{ "alloc": ms.Alloc, "totalAlloc": ms.TotalAlloc, "sys": ms.Sys, "lookups": ms.Lookups, "mallocs": ms.Mallocs, "frees": ms.Frees, "heapAlloc": ms.HeapAlloc, "heapSys": ms.HeapSys, "heapIdle": ms.HeapIdle, "heapInuse": ms.HeapInuse, "heapReleased": ms.HeapReleased, "heapObjects": ms.HeapObjects, "numGC": ms.NumGC, "nonInteraction": 1, } } // Update takes the most recent stats from runtime. func (ms *MemoryStatistics) Update() { var m runtime.MemStats runtime.ReadMemStats(&m) ms.Lock() defer ms.Unlock() ms.Alloc = m.Alloc ms.TotalAlloc = m.TotalAlloc ms.Sys = m.Sys ms.Lookups = m.Lookups ms.Mallocs = m.Mallocs ms.Frees = m.Frees ms.HeapAlloc = m.HeapAlloc ms.HeapSys = m.HeapSys ms.HeapIdle = m.HeapIdle ms.HeapInuse = m.HeapInuse ms.HeapReleased = m.HeapReleased ms.HeapObjects = m.HeapObjects ms.NumGC = m.NumGC } ================================================ FILE: oryx/metricsx/middleware.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package metricsx import ( "context" "crypto/sha256" "encoding/hex" "math" "net/http" "net/url" "os" "runtime" "strconv" "strings" "sync" "time" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "github.com/gofrs/uuid" "github.com/spf13/cobra" "github.com/ory/x/cmdx" "github.com/ory/x/configx" "github.com/ory/x/httpx" "github.com/ory/x/logrusx" "github.com/ory/x/resilience" "github.com/ory/x/urlx" "github.com/ory/analytics-go/v5" ) const ( XForwardedHostHeader = "X-Forwarded-Host" AuthorityHeader = ":authority" ) var ( instance *Service lock sync.Mutex knownHeaders = []string{AuthorityHeader, XForwardedHostHeader} ) // Service helps with providing context on metrics. type Service struct { optOut bool instanceId string o *Options c analytics.Client l *logrusx.Logger mem *MemoryStatistics } // Hash returns a hashed string of the value. func Hash(value string) string { sha := sha256.Sum256([]byte(value)) return hex.EncodeToString(sha[:]) } // Options configures the metrics service. type Options struct { // Service represents the service name, for example "ory-hydra". Service string // DeploymentId represents the cluster id, typically a hash of some unique configuration properties. DeploymentId string // DBDialect specifies the database dialect in use (e.g., "postgres", "mysql", "sqlite"). DBDialect string // When this instance was started StartTime time.Time // IsDevelopment should be true if we assume that we're in a development environment. IsDevelopment bool // WriteKey is the segment API key. WriteKey string // WhitelistedPaths represents a list of paths that can be transmitted in clear text to segment. WhitelistedPaths []string // BuildVersion represents the build version. BuildVersion string // BuildHash represents the build git hash. BuildHash string // BuildTime represents the build time. BuildTime string // Hostname is a public URL configured for the service, used to derive hosted name for telemetry. Hostname string // Config overrides the analytics.Config. If nil, sensible defaults will be used. Config *analytics.Config // MemoryInterval sets how often memory statistics should be transmitted. Defaults to every 12 hours. MemoryInterval time.Duration } type void struct{} func (v *void) Logf(format string, args ...interface{}) { } func (v *void) Errorf(format string, args ...interface{}) { } // New returns a new metrics service. If one has been instantiated already, no new instance will be created. func New( cmd *cobra.Command, l *logrusx.Logger, c *configx.Provider, o *Options, ) *Service { lock.Lock() defer lock.Unlock() if instance != nil { return instance } o.StartTime = time.Now() if o.BuildTime == "" { o.BuildTime = "unknown" } if o.BuildVersion == "" { o.BuildVersion = "unknown" } if o.BuildHash == "" { o.BuildHash = "unknown" } if o.Config == nil { o.Config = &analytics.Config{ Interval: time.Hour * 6, } } o.Config.Logger = new(void) if o.MemoryInterval < time.Minute { o.MemoryInterval = time.Hour * 12 } segment, err := analytics.NewWithConfig(o.WriteKey, *o.Config) if err != nil { l.WithError(err).Fatalf("Unable to initialise software quality assurance features.") return nil } optOut, err := cmd.Flags().GetBool("sqa-opt-out") if err != nil { cmdx.Must(err, `Unable to get command line flag "sqa-opt-out": %s`, err) } if !optOut { optOut = c.Bool("sqa.opt_out") } if !optOut { optOut = c.Bool("sqa_opt_out") } if !optOut { optOut, _ = strconv.ParseBool(os.Getenv("SQA_OPT_OUT")) } if !optOut { optOut, _ = strconv.ParseBool(os.Getenv("SQA-OPT-OUT")) } if !optOut { l.Info("Software quality assurance features are enabled. Learn more at: https://www.ory.sh/docs/ecosystem/sqa") } m := &Service{ optOut: optOut, instanceId: uuid.Must(uuid.NewV4()).String(), o: o, c: segment, l: l, mem: new(MemoryStatistics), } instance = m go m.Identify() go m.Track() return m } // Identify enables reporting to segment. func (sw *Service) Identify() { IdentifySend(sw, true) // User has not opt-out then make identify to be sent every 6 hours if !sw.optOut { for range time.Tick(time.Hour * 6) { IdentifySend(sw, false) } } } func IdentifySend(sw *Service, startup bool) { if err := resilience.Retry(sw.l, time.Minute*5, time.Hour*6, func() error { return sw.c.Enqueue(analytics.Identify{ InstanceId: sw.instanceId, DeploymentId: sw.o.DeploymentId, Project: sw.o.Service, DatabaseDialect: sw.o.DBDialect, ProductVersion: sw.o.BuildVersion, ProductBuild: sw.o.BuildHash, UptimeDeployment: 0, UptimeInstance: math.Round(time.Since(sw.o.StartTime).Seconds()), IsDevelopment: sw.o.IsDevelopment, IsOptOut: sw.optOut, Startup: startup, }) }); err != nil { sw.l.WithError(err).Debug("Could not commit anonymized environment information") } } // Track commits memory statistics to segment. func (sw *Service) Track() { if sw.optOut { return } for { sw.mem.Update() if err := sw.c.Enqueue(analytics.Track{ InstanceId: sw.instanceId, DeploymentId: sw.o.DeploymentId, Project: sw.o.Service, CPU: runtime.NumCPU(), OsName: runtime.GOOS, OsArchitecture: runtime.GOARCH, Alloc: sw.mem.Alloc, TotalAlloc: sw.mem.TotalAlloc, Frees: sw.mem.Frees, Mallocs: sw.mem.Mallocs, Lookups: sw.mem.Lookups, Sys: sw.mem.Sys, NumGC: sw.mem.NumGC, HeapAlloc: sw.mem.HeapAlloc, HeapInuse: sw.mem.HeapInuse, HeapIdle: sw.mem.HeapIdle, HeapObjects: sw.mem.HeapObjects, HeapReleased: sw.mem.HeapReleased, HeapSys: sw.mem.HeapSys, }); err != nil { sw.l.WithError(err).Debug("Could not commit anonymized telemetry data") } time.Sleep(sw.o.MemoryInterval) } } // ServeHTTP is a middleware for sending meta information to segment. func (sw *Service) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { var start time.Time if !sw.optOut { start = time.Now() } next(rw, r) if sw.optOut { return } latency := time.Since(start).Milliseconds() path := sw.anonymizePath(r.URL.Path) host := urlx.ExtractPublicAddress(sw.o.Hostname, r.Header.Get(XForwardedHostHeader), r.Host) // Collecting request info stat, _ := httpx.GetResponseMeta(rw) if err := sw.c.Enqueue(analytics.Page{ InstanceId: sw.instanceId, DeploymentId: sw.o.DeploymentId, Project: sw.o.Service, UrlHost: host, UrlPath: path, RequestCode: stat, RequestLatency: int(latency), }); err != nil { sw.l.WithError(err).Debug("Could not commit anonymized telemetry data") // do nothing... } } func (sw *Service) UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { var start time.Time if !sw.optOut { start = time.Now() } resp, err := handler(ctx, req) if sw.optOut { return resp, err } latency := time.Since(start).Milliseconds() hosts := []string{sw.o.Hostname} if md, ok := metadata.FromIncomingContext(ctx); ok { for _, h := range knownHeaders { if v := md.Get(h); len(v) > 0 { hosts = append(hosts, v[0]) } } } host := urlx.ExtractPublicAddress(hosts...) if err := sw.c.Enqueue(analytics.Page{ InstanceId: sw.instanceId, DeploymentId: sw.o.DeploymentId, Project: sw.o.Service, UrlHost: host, UrlPath: info.FullMethod, RequestCode: int(status.Code(err)), RequestLatency: int(latency), }); err != nil { sw.l.WithError(err).Debug("Could not commit anonymized telemetry data") // do nothing... } return resp, err } func (sw *Service) StreamInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { // this needs a bit of thought, but we don't have streaming RPCs currently anyway sw.l.Info("The telemetry stream interceptor is not yet implemented!") return handler(srv, stream) } func (sw *Service) Close() error { return sw.c.Close() } func (sw *Service) anonymizePath(path string) string { paths := sw.o.WhitelistedPaths path = strings.ToLower(path) for _, p := range paths { p = strings.ToLower(p) if path == p { return p } else if len(path) > len(p) && path[:len(p)+1] == p+"/" { return p } } return "/" } func (sw *Service) anonymizeQuery(query url.Values, salt string) string { for _, q := range query { for i, s := range q { if s != "" { s = Hash(s + "|" + salt) q[i] = s } } } return query.Encode() } ================================================ FILE: oryx/migratest/refresh.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build refresh // +build refresh package migratest import ( "encoding/json" "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func WriteFixtureOnError(t *testing.T, err error, actual interface{}, location string) { content, err := json.MarshalIndent(actual, "", " ") require.NoError(t, err) require.NoError(t, os.MkdirAll(filepath.Dir(location), 0777)) require.NoError(t, os.WriteFile(location, content, 0666)) } ================================================ FILE: oryx/migratest/run.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package migratest import ( "encoding/json" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func ContainsExpectedIds(t *testing.T, path string, ids []string) { files, err := os.ReadDir(path) require.NoError(t, err) for _, f := range files { if filepath.Ext(f.Name()) == ".json" { expected := strings.TrimSuffix(filepath.Base(f.Name()), ".json") assert.Contains(t, ids, expected) } } } func CompareWithFixture(t *testing.T, actual interface{}, prefix string, id string) { location := filepath.Join("fixtures", prefix, id+".json") //#nosec G304 -- false positive expected, err := os.ReadFile(location) WriteFixtureOnError(t, err, actual, location) actualJSON, err := json.Marshal(actual) require.NoError(t, err) if !assert.JSONEq(t, string(expected), string(actualJSON)) { WriteFixtureOnError(t, nil, actual, location) } } ================================================ FILE: oryx/migratest/strict.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build !refresh // +build !refresh package migratest import ( "testing" "github.com/stretchr/testify/require" ) func WriteFixtureOnError(t *testing.T, err error, actual interface{}, location string) { require.NoError(t, err) } ================================================ FILE: oryx/networkx/listener.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package networkx import ( "net" "strings" "github.com/ory/x/configx" ) func AddressIsUnixSocket(address string) bool { return strings.HasPrefix(address, "unix:") } func MakeListener(address string, socketPermission *configx.UnixPermission) (net.Listener, error) { if AddressIsUnixSocket(address) { addr := strings.TrimPrefix(address, "unix:") l, err := net.Listen("unix", addr) if err != nil { return nil, err } err = socketPermission.SetPermission(addr) if err != nil { return nil, err } return l, nil } return net.Listen("tcp", address) } ================================================ FILE: oryx/networkx/manager.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package networkx import ( "embed" "github.com/pkg/errors" "github.com/ory/pop/v6" "github.com/ory/x/sqlcon" ) // Migrations of the network manager. Apply by merging with your local migrations using // fsx.Merge() and then passing all to the migration box. // //go:embed migrations/sql/*.sql var Migrations embed.FS func Determine(c *pop.Connection) (*Network, error) { var p Network if err := sqlcon.HandleError(c.Q().Order("created_at ASC").First(&p)); err != nil { if errors.Is(err, sqlcon.ErrNoRows) { np := NewNetwork() if err := c.Create(np); err != nil { return nil, err } return np, nil } return nil, err } return &p, nil } ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.cockroach.down.sql ================================================ DROP TABLE "networks"; ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.cockroach.up.sql ================================================ CREATE TABLE "networks" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.mysql.down.sql ================================================ DROP TABLE `networks`; ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.mysql.up.sql ================================================ CREATE TABLE `networks` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.postgres.down.sql ================================================ DROP TABLE "networks"; ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.postgres.up.sql ================================================ CREATE TABLE "networks" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.sqlite3.down.sql ================================================ DROP TABLE "networks"; ================================================ FILE: oryx/networkx/migrations/sql/20150100000001000000_networks.sqlite3.up.sql ================================================ CREATE TABLE "networks" ( "id" TEXT PRIMARY KEY, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/networkx/migrations/templates/20150100000001_networks.down.fizz ================================================ drop_table("networks") ================================================ FILE: oryx/networkx/migrations/templates/20150100000001_networks.up.fizz ================================================ create_table("networks") { t.Column("id", "uuid", {primary: true}) } ================================================ FILE: oryx/networkx/network.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package networkx import ( "time" "github.com/gofrs/uuid" ) type Network struct { ID uuid.UUID `json:"id" db:"id"` // CreatedAt is a helper struct field for gobuffalo.pop. CreatedAt time.Time `json:"-" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. UpdatedAt time.Time `json:"-" db:"updated_at"` } func (p Network) TableName() string { return "networks" } func NewNetwork() *Network { return &Network{ ID: uuid.Must(uuid.NewV4()), } } ================================================ FILE: oryx/openapix/doc.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package openapi contains definitions commonly used in Ory's APIs // such as pagination, JSON patches, and more. package openapix ================================================ FILE: oryx/openapix/jsonpatch.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openapix // A JSONPatchDocument request // // swagger:model jsonPatchDocument type JSONPatchDocument []JSONPatch // A JSONPatch document as defined by RFC 6902 // // swagger:model jsonPatch type JSONPatch struct { // The operation to be performed. One of "add", "remove", "replace", "move", "copy", or "test". // // required: true // example: replace Op string `json:"op"` // The path to the target path. Uses JSON pointer notation. // // Learn more [about JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901#section-5). // // required: true // example: /name Path string `json:"path"` // The value to be used within the operations. // // Learn more [about JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901#section-5). // // example: foobar Value interface{} `json:"value"` // This field is used together with operation "move" and uses JSON Pointer notation. // // Learn more [about JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901#section-5). // // example: /name From string `json:"from"` } ================================================ FILE: oryx/openapix/pagination.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package openapix // swagger:model tokenPaginationHeaders type TokenPaginationHeaders struct { // The link header contains pagination links. // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // in: header Link string `json:"link"` // The total number of clients. // // in: header XTotalCount string `json:"x-total-count"` } // swagger:model tokenPagination type TokenPaginationParams struct { // Items per page // // This is the number of items per page to return. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query // default: 250 // min: 1 // max: 1000 PageSize int `json:"page_size"` // Next Page Token // // The next page token. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query PageToken string `json:"page_token"` } ================================================ FILE: oryx/osx/env.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package osx import ( "cmp" "os" ) // GetenvDefault returns an environment variable or the default value if it is empty. func GetenvDefault(key string, def string) string { return cmp.Or(os.Getenv(key), def) } ================================================ FILE: oryx/osx/file.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package osx import ( "encoding/base64" "io" "net/url" "os" "strings" "github.com/hashicorp/go-retryablehttp" "github.com/pkg/errors" "github.com/ory/x/httpx" ) type options struct { disableFileLoader bool disableHTTPLoader bool disableBase64Loader bool base64enc *base64.Encoding disableResilientBase64Loader bool hc *retryablehttp.Client } type Option func(o *options) func (o *options) apply(opts []Option) *options { for _, f := range opts { f(o) } return o } func newOptions() *options { return &options{ disableFileLoader: false, disableHTTPLoader: false, disableBase64Loader: false, base64enc: base64.RawURLEncoding, hc: httpx.NewResilientClient(), } } // WithDisabledFileLoader disables the file loader. func WithDisabledFileLoader() Option { return func(o *options) { o.disableFileLoader = true } } // WithEnabledFileLoader enables the file loader. func WithEnabledFileLoader() Option { return func(o *options) { o.disableFileLoader = false } } // WithDisabledHTTPLoader disables the HTTP loader. func WithDisabledHTTPLoader() Option { return func(o *options) { o.disableHTTPLoader = true } } // WithEnabledHTTPLoader enables the HTTP loader. func WithEnabledHTTPLoader() Option { return func(o *options) { o.disableHTTPLoader = false } } // WithDisabledBase64Loader disables the base64 loader. func WithDisabledBase64Loader() Option { return func(o *options) { o.disableBase64Loader = true } } // WithEnabledBase64Loader disables the base64 loader. func WithEnabledBase64Loader() Option { return func(o *options) { o.disableBase64Loader = false } } // WithBase64Encoding sets the base64 encoding. func WithBase64Encoding(enc *base64.Encoding) Option { return func(o *options) { o.base64enc = enc } } // WithoutResilientBase64Encoding sets the base64 encoding. func WithoutResilientBase64Encoding() Option { return func(o *options) { o.disableResilientBase64Loader = true } } // WithHTTPClient sets the HTTP client. func WithHTTPClient(hc *retryablehttp.Client) Option { return func(o *options) { o.hc = hc } } // RestrictedReadFile works similar to ReadFileFromAllSources but has all // sources disabled per default. You need to enable the loaders you wish to use // explicitly. func RestrictedReadFile(source string, opts ...Option) (bytes []byte, err error) { o := newOptions() o.disableFileLoader = true o.disableBase64Loader = true o.disableHTTPLoader = true return readFile(source, o.apply(opts)) } // ReadFileFromAllSources reads a file from base64, http, https, and file sources. // // Using options, you can disable individual loaders. For example, the following will // return an error: // // ReadFileFromAllSources("https://foo.bar/baz.txt", WithDisabledHTTPLoader()) // // Possible formats are: // // - /path/to/file // - file:///path/to/file // - https://host.com/path/to/file // - http://host.com/path/to/file // - base64:// // // For more options, check: // // - WithDisabledFileLoader // - WithDisabledHTTPLoader // - WithDisabledBase64Loader // - WithBase64Encoding // - WithHTTPClient func ReadFileFromAllSources(source string, opts ...Option) (bytes []byte, err error) { return readFile(source, newOptions().apply(opts)) } func readFile(source string, o *options) (bytes []byte, err error) { parsed, err := url.Parse(source) if err != nil { return nil, errors.Wrap(err, "failed to parse URL") } switch parsed.Scheme { case "": if o.disableFileLoader { return nil, errors.New("file loader disabled") } //#nosec G304 -- false positive bytes, err = os.ReadFile(source) if err != nil { return nil, errors.Wrap(err, "unable to read the file") } case "file": if o.disableFileLoader { return nil, errors.New("file loader disabled") } //#nosec G304 -- false positive bytes, err = os.ReadFile(parsed.Host + parsed.Path) if err != nil { return nil, errors.Wrap(err, "unable to read the file") } case "http", "https": if o.disableHTTPLoader { return nil, errors.New("http(s) loader disabled") } resp, err := o.hc.Get(parsed.String()) if err != nil { return nil, errors.Wrap(err, "unable to load remote file") } defer resp.Body.Close() bytes, err = io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "unable to read the HTTP response body") } case "base64": if o.disableBase64Loader { return nil, errors.New("base64 loader disabled") } if o.disableResilientBase64Loader { bytes, err = o.base64enc.DecodeString(strings.TrimPrefix(source, "base64://")) if err != nil { return nil, errors.Wrap(err, "unable to base64 decode the location") } return bytes, nil } for _, enc := range []*base64.Encoding{ base64.StdEncoding, base64.URLEncoding, base64.RawURLEncoding, base64.RawStdEncoding, } { bytes, err = enc.DecodeString(strings.TrimPrefix(source, "base64://")) if err == nil { return bytes, nil } } return nil, errors.Wrap(err, "unable to base64 decode the location") default: return nil, errors.Errorf("unsupported source `%s`", parsed.Scheme) } return bytes, nil } ================================================ FILE: oryx/osx/stub/text.txt ================================================ hello world ================================================ FILE: oryx/otelx/attribute.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "database/sql" "fmt" "go.opentelemetry.io/otel/attribute" ) const nullString = "" func StringAttrs(attrs map[string]string) []attribute.KeyValue { s := make([]attribute.KeyValue, 0, len(attrs)) for k, v := range attrs { s = append(s, attribute.String(k, v)) } return s } func AutoInt[I int | int32 | int64](k string, v I) attribute.KeyValue { // Internally, the OpenTelemetry SDK uses int64 for all integer values anyway. return attribute.Int64(k, int64(v)) } func Nullable[V any, VN *V | sql.Null[V], A func(string, V) attribute.KeyValue](a A, k string, v VN) attribute.KeyValue { switch v := any(v).(type) { case *V: if v == nil { return attribute.String(k, nullString) } return a(k, *v) case sql.Null[V]: if !v.Valid { return attribute.String(k, nullString) } return a(k, v.V) } // This should never happen, as the type switch above is exhaustive to the generic type VN. return attribute.String(k, fmt.Sprintf("", v)) } func NullString[V *string | sql.Null[string]](k string, v V) attribute.KeyValue { return Nullable(attribute.String, k, v) } func NullStringer(k string, v fmt.Stringer) attribute.KeyValue { if v == nil { return attribute.String(k, nullString) } return attribute.String(k, v.String()) } ================================================ FILE: oryx/otelx/config.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "bytes" _ "embed" "io" ) type JaegerConfig struct { LocalAgentAddress string `json:"local_agent_address"` Sampling JaegerSampling `json:"sampling"` } type ZipkinConfig struct { ServerURL string `json:"server_url"` Sampling ZipkinSampling `json:"sampling"` } type OTLPConfig struct { ServerURL string `json:"server_url"` Insecure bool `json:"insecure"` Sampling OTLPSampling `json:"sampling"` AuthorizationHeader string `json:"authorization_header"` } type JaegerSampling struct { ServerURL string `json:"server_url"` TraceIDRatio float64 `json:"trace_id_ratio"` } type ZipkinSampling struct { SamplingRatio float64 `json:"sampling_ratio"` } type OTLPSampling struct { SamplingRatio float64 `json:"sampling_ratio"` } type ProvidersConfig struct { Jaeger JaegerConfig `json:"jaeger"` Zipkin ZipkinConfig `json:"zipkin"` OTLP OTLPConfig `json:"otlp"` } type Config struct { ServiceName string `json:"service_name"` DeploymentEnvironment string `json:"deployment_environment"` Provider string `json:"provider"` Providers ProvidersConfig `json:"providers"` } //go:embed config.schema.json var ConfigSchema []byte const ConfigSchemaID = "ory://tracing-config" // AddConfigSchema adds the tracing schema to the compiler. // The interface is specified instead of `jsonschema.Compiler` to allow the use of any jsonschema library fork or version. func AddConfigSchema(c interface { AddResource(url string, r io.Reader) error }, ) error { return c.AddResource(ConfigSchemaID, bytes.NewReader(ConfigSchema)) } ================================================ FILE: oryx/otelx/config.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "ory://tracing-config", "type": "object", "additionalProperties": false, "description": "Configure distributed tracing using OpenTelemetry", "properties": { "provider": { "type": "string", "description": "Set this to the tracing backend you wish to use. Supports Jaeger, Zipkin, and OTEL.", "enum": ["jaeger", "otel", "zipkin"], "examples": ["jaeger"] }, "service_name": { "type": "string", "description": "Specifies the service name to use on the tracer.", "examples": ["Ory Hydra", "Ory Kratos", "Ory Keto", "Ory Oathkeeper"] }, "deployment_environment": { "type": "string", "description": "Specifies the deployment environment to use on the tracer.", "examples": ["development", "staging", "production"] }, "providers": { "type": "object", "additionalProperties": false, "properties": { "jaeger": { "type": "object", "additionalProperties": false, "description": "Configures the jaeger tracing backend.", "properties": { "local_agent_address": { "type": "string", "description": "The address of the jaeger-agent where spans should be sent to.", "anyOf": [ { "title": "IPv6 Address and Port", "pattern": "^\\[(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\]:([0-9]*)$" }, { "title": "IPv4 Address and Port", "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}:([0-9]*)$" }, { "title": "Hostname and Port", "pattern": "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9]):([0-9]*)$" } ], "examples": ["127.0.0.1:6831"] }, "sampling": { "type": "object", "propertyNames": { "enum": ["server_url", "trace_id_ratio"] }, "additionalProperties": false, "properties": { "server_url": { "type": "string", "description": "The address of jaeger-agent's HTTP sampling server", "format": "uri", "examples": ["http://localhost:5778/sampling"] }, "trace_id_ratio": { "type": "number", "description": "Trace Id ratio sample", "examples": [0.5] } } } } }, "zipkin": { "type": "object", "additionalProperties": false, "description": "Configures the zipkin tracing backend.", "properties": { "server_url": { "type": "string", "description": "The address of the Zipkin server where spans should be sent to.", "format": "uri", "examples": ["http://localhost:9411/api/v2/spans"] }, "sampling": { "type": "object", "propertyNames": { "enum": ["sampling_ratio"] }, "additionalProperties": false, "properties": { "sampling_ratio": { "type": "number", "description": "Sampling ratio for spans.", "examples": [0.4] } } } } }, "otlp": { "type": "object", "additionalProperties": false, "description": "Configures the OTLP tracing backend.", "properties": { "server_url": { "type": "string", "description": "The endpoint of the OTLP exporter (HTTP) where spans should be sent to.", "anyOf": [ { "title": "IPv6 Address and Port", "pattern": "^\\[(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\]:([0-9]*)$" }, { "title": "IPv4 Address and Port", "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}:([0-9]*)$" }, { "title": "Hostname and Port", "pattern": "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9]):([0-9]*)$" } ], "examples": ["localhost:4318"] }, "insecure": { "type": "boolean", "description": "Will use HTTP if set to true; defaults to HTTPS." }, "sampling": { "type": "object", "propertyNames": { "enum": ["sampling_ratio"] }, "additionalProperties": false, "properties": { "sampling_ratio": { "type": "number", "description": "Sampling ratio for spans.", "examples": [0.4] } } }, "authorization_header": { "type": "string", "examples": ["Bearer 2389s8fs9d8fus9f"] } } } } } } } ================================================ FILE: oryx/otelx/jaeger.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "net" "go.opentelemetry.io/contrib/propagators/b3" jaegerPropagator "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/contrib/samplers/jaegerremote" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/jaeger" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" "go.opentelemetry.io/otel/trace" ) // SetupJaeger configures and returns a Jaeger tracer. // // The returned tracer will by default attempt to send spans to a local Jaeger agent. // Optionally, [otelx.JaegerConfig.LocalAgentAddress] can be set to specify a different target. // // By default, unless a parent sampler has taken a sampling decision, every span is sampled. // [otelx.JaegerSampling.TraceIDRatio] may be used to customize the sampling probability, // optionally alongside [otelx.JaegerSampling.ServerURL] to consult a remote server // for the sampling strategy to be used. func SetupJaeger(t *Tracer, tracerName string, c *Config) (trace.Tracer, error) { host, port, err := net.SplitHostPort(c.Providers.Jaeger.LocalAgentAddress) if err != nil { return nil, err } exp, err := jaeger.New( jaeger.WithAgentEndpoint( jaeger.WithAgentHost(host), jaeger.WithAgentPort(port), ), ) if err != nil { return nil, err } tpOpts := []sdktrace.TracerProviderOption{ sdktrace.WithBatcher(exp), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(c.ServiceName), semconv.DeploymentEnvironmentName(c.DeploymentEnvironment))), } samplingServerURL := c.Providers.Jaeger.Sampling.ServerURL traceIdRatio := c.Providers.Jaeger.Sampling.TraceIDRatio sampler := sdktrace.TraceIDRatioBased(traceIdRatio) if samplingServerURL != "" { sampler = jaegerremote.New( "jaegerremote", jaegerremote.WithSamplingServerURL(samplingServerURL), jaegerremote.WithInitialSampler(sampler), ) } // Respect any sampling decision taken by the client. sampler = sdktrace.ParentBased(sampler) tpOpts = append(tpOpts, sdktrace.WithSampler(sampler)) tp := sdktrace.NewTracerProvider(tpOpts...) otel.SetTracerProvider(tp) // At the moment, software across our cloud stack only support Zipkin (B3) // and Jaeger propagation formats. Proposals for standardized formats for // context propagation are in the works (ref: https://www.w3.org/TR/trace-context/ // and https://www.w3.org/TR/baggage/). // // Simply add propagation.TraceContext{} and propagation.Baggage{} // here to enable those as well. prop := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, jaegerPropagator.Jaeger{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader|b3.B3SingleHeader)), propagation.Baggage{}, ) otel.SetTextMapPropagator(prop) return tp.Tracer(tracerName), nil } ================================================ FILE: oryx/otelx/middleware.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "cmp" "context" "net/http" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) var WithDefaultFilters otelhttp.Option = otelhttp.WithFilter(func(r *http.Request) bool { return !(strings.HasPrefix(r.URL.Path, "/health") || strings.HasPrefix(r.URL.Path, "/admin/health") || strings.HasPrefix(r.URL.Path, "/metrics") || strings.HasPrefix(r.URL.Path, "/admin/metrics")) }) type contextKey int const callbackContextKey contextKey = iota func SpanNameRecorderMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { cb, _ := r.Context().Value(callbackContextKey).(func(string)) if cb == nil { return } if r.Pattern != "" { cb(r.Pattern) } }() next.ServeHTTP(w, r) }) } func SpanNameRecorderNegroniFunc(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { defer func() { cb, _ := r.Context().Value(callbackContextKey).(func(string)) if cb == nil { return } if r.Pattern != "" { cb(r.Pattern) } }() next(w, r) } func NewMiddleware(next http.Handler, operation string, opts ...otelhttp.Option) http.Handler { myOpts := []otelhttp.Option{ WithDefaultFilters, otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { return cmp.Or(r.Pattern, operation, r.Method+" "+r.URL.Path) }), } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { callback := func(s string) { r.Pattern = cmp.Or(r.Pattern, s) } ctx := context.WithValue(r.Context(), callbackContextKey, callback) r2 := r.WithContext(ctx) next.ServeHTTP(w, r2) r.Pattern = cmp.Or(r2.Pattern, r.Pattern) // best-effort in case callback never is called }) return otelhttp.NewHandler(handler, operation, append(myOpts, opts...)...) } ================================================ FILE: oryx/otelx/otel.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "context" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/embedded" "go.opentelemetry.io/otel/trace/noop" "github.com/ory/x/logrusx" "github.com/ory/x/stringsx" ) type ( Tracer struct { tracer trace.Tracer } Provider interface { Tracer(ctx context.Context) *Tracer } ) // New creates a new tracer. If name is empty, a default tracer name is used // instead. See: https://godocs.io/go.opentelemetry.io/otel/sdk/trace#TracerProvider.Tracer func New(name string, l *logrusx.Logger, c *Config) (*Tracer, error) { t := &Tracer{} if err := t.setup(name, l, c); err != nil { return nil, err } return t, nil } // NewNoop creates a new no-op tracer. func NewNoop() *Tracer { tp := noop.NewTracerProvider() t := &Tracer{tracer: tp.Tracer("")} return t } // setup constructs the tracer based on the given configuration. func (t *Tracer) setup(name string, l *logrusx.Logger, c *Config) error { switch f := stringsx.SwitchExact(c.Provider); { case f.AddCase("jaeger"): tracer, err := SetupJaeger(t, name, c) if err != nil { return err } t.tracer = tracer l.Infof("Jaeger tracer configured! Sending spans to %s", c.Providers.Jaeger.LocalAgentAddress) case f.AddCase("zipkin"): tracer, err := SetupZipkin(t, name, c) if err != nil { return err } t.tracer = tracer l.Infof("Zipkin tracer configured! Sending spans to %s", c.Providers.Zipkin.ServerURL) case f.AddCase("otel"): tracer, err := SetupOTLP(t, name, c) if err != nil { return err } t.tracer = tracer l.Infof("OTLP tracer configured! Sending spans to %s", c.Providers.OTLP.ServerURL) case f.AddCase(""): l.Infof("No tracer configured - skipping tracing setup") t.tracer = noop.NewTracerProvider().Tracer(name) default: return f.ToUnknownCaseErr() } return nil } // IsLoaded returns true if the tracer has been loaded. func (t *Tracer) IsLoaded() bool { if t == nil || t.tracer == nil { return false } return true } // Tracer returns the underlying OpenTelemetry tracer. func (t *Tracer) Tracer() trace.Tracer { return t.tracer } // WithOTLP returns a new tracer with the underlying OpenTelemetry Tracer // replaced. func (t *Tracer) WithOTLP(other trace.Tracer) *Tracer { return &Tracer{other} } // Provider returns a TracerProvider which in turn yields this tracer unmodified. func (t *Tracer) Provider() trace.TracerProvider { return tracerProvider{t: t.Tracer()} } type tracerProvider struct { embedded.TracerProvider t trace.Tracer } var _ trace.TracerProvider = tracerProvider{} // Tracer implements trace.TracerProvider. func (tp tracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer { return tp.t } ================================================ FILE: oryx/otelx/otlp.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "context" "go.opentelemetry.io/contrib/propagators/b3" jaegerPropagator "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" "go.opentelemetry.io/otel/trace" ) func SetupOTLP(t *Tracer, tracerName string, c *Config) (trace.Tracer, error) { ctx := context.Background() clientOpts := []otlptracehttp.Option{ otlptracehttp.WithEndpoint(c.Providers.OTLP.ServerURL), } if c.Providers.OTLP.Insecure { clientOpts = append(clientOpts, otlptracehttp.WithInsecure()) } if c.Providers.OTLP.AuthorizationHeader != "" { clientOpts = append(clientOpts, otlptracehttp.WithHeaders(map[string]string{"Authorization": c.Providers.OTLP.AuthorizationHeader}), ) } exp, err := otlptrace.New( ctx, otlptracehttp.NewClient(clientOpts...), ) if err != nil { return nil, err } tpOpts := []sdktrace.TracerProviderOption{ sdktrace.WithBatcher(exp), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(c.ServiceName), semconv.DeploymentEnvironmentName(c.DeploymentEnvironment), )), sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased( c.Providers.OTLP.Sampling.SamplingRatio, ))), } tp := sdktrace.NewTracerProvider(tpOpts...) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, jaegerPropagator.Jaeger{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader|b3.B3SingleHeader)), propagation.Baggage{}, )) return tp.Tracer(tracerName), nil } ================================================ FILE: oryx/otelx/semconv/context.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package semconv import ( "context" "net/http" "go.opentelemetry.io/otel/attribute" "github.com/ory/x/httpx" ) type contextKey int const contextKeyAttributes contextKey = iota func ContextWithAttributes(ctx context.Context, attrs ...attribute.KeyValue) context.Context { existing, _ := ctx.Value(contextKeyAttributes).([]attribute.KeyValue) return context.WithValue(ctx, contextKeyAttributes, append(existing, attrs...)) } func AttributesFromContext(ctx context.Context) []attribute.KeyValue { fromCtx, _ := ctx.Value(contextKeyAttributes).([]attribute.KeyValue) uniq := make(map[attribute.Key]struct{}) attrs := make([]attribute.KeyValue, 0) for i := len(fromCtx) - 1; i >= 0; i-- { if _, ok := uniq[fromCtx[i].Key]; !ok { uniq[fromCtx[i].Key] = struct{}{} attrs = append(attrs, fromCtx[i]) } } reverse(attrs) return attrs } func Middleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { ctx := ContextWithAttributes(r.Context(), append( AttrGeoLocation(*httpx.ClientGeoLocation(r)), AttrClientIP(httpx.ClientIP(r)), )..., ) next(rw, r.WithContext(ctx)) } func reverse[S ~[]E, E any](s S) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } } ================================================ FILE: oryx/otelx/semconv/deprecated.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package semconv import ( "context" otelattr "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // NewDeprecatedFeatureUsedEvent creates a new event indicating that a deprecated feature was used. // It returns the event name and a trace.EventOption that can be used to // add the event to a span. // // span.AddEvent(NewDeprecatedFeatureUsedEvent(ctx, "deprecated-feature-id", otelattr.String("key", "value"))) func NewDeprecatedFeatureUsedEvent(ctx context.Context, deprecatedCodeFeatureID string, attrs ...otelattr.KeyValue) (string, trace.EventOption) { return DeprecatedFeatureUsed.String(), trace.WithAttributes( append( append( attrs, AttributesFromContext(ctx)..., ), AttrDeprecatedFeatureID(deprecatedCodeFeatureID), )..., ) } const ( AttributeKeyDeprecatedCodePathIDAttributeKey AttributeKey = "DeprecatedFeatureID" DeprecatedFeatureUsed Event = "DeprecatedFeatureUsed" ) func AttrDeprecatedFeatureID(id string) otelattr.KeyValue { return otelattr.String(AttributeKeyDeprecatedCodePathIDAttributeKey.String(), id) } ================================================ FILE: oryx/otelx/semconv/events.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package semconv contains OpenTelemetry semantic convention constants. package semconv import ( "github.com/gofrs/uuid" otelattr "go.opentelemetry.io/otel/attribute" "github.com/ory/x/httpx" ) type Event string func (e Event) String() string { return string(e) } type AttributeKey string func (a AttributeKey) String() string { return string(a) } const ( AttributeKeyIdentityID AttributeKey = "IdentityID" AttributeKeyNID AttributeKey = "ProjectID" AttributeKeyClientIP AttributeKey = "ClientIP" AttributeKeyGeoLocationCity AttributeKey = "GeoLocationCity" AttributeKeyGeoLocationRegion AttributeKey = "GeoLocationRegion" AttributeKeyGeoLocationCountry AttributeKey = "GeoLocationCountry" AttributeKeyGeoLocationLatitude AttributeKey = "GeoLocationLatitude" AttributeKeyGeoLocationLongitude AttributeKey = "GeoLocationLongitude" AttributeKeyWorkspace AttributeKey = "WorkspaceID" AttributeKeySubscriptionID AttributeKey = "SubscriptionID" AttributeKeyProjectEnvironment AttributeKey = "ProjectEnvironment" AttributeKeyWorkspaceAPIKeyID AttributeKey = "WorkspaceAPIKeyID" AttributeKeyProjectAPIKeyID AttributeKey = "ProjectAPIKeyID" ) func AttrIdentityID[V string | uuid.UUID](val V) otelattr.KeyValue { return otelattr.String(AttributeKeyIdentityID.String(), uuidOrString(val)) } func AttrNID(val uuid.UUID) otelattr.KeyValue { return otelattr.String(AttributeKeyNID.String(), val.String()) } func AttrWorkspace(val uuid.UUID) otelattr.KeyValue { return otelattr.String(AttributeKeyWorkspace.String(), val.String()) } func AttrSubscription(val uuid.UUID) otelattr.KeyValue { return otelattr.String(AttributeKeySubscriptionID.String(), val.String()) } func AttrProjectEnvironment(val string) otelattr.KeyValue { return otelattr.String(AttributeKeyProjectEnvironment.String(), val) } func AttrClientIP(val string) otelattr.KeyValue { return otelattr.String(AttributeKeyClientIP.String(), val) } func AttrGeoLocation(val httpx.GeoLocation) []otelattr.KeyValue { geoLocationAttributes := make([]otelattr.KeyValue, 0, 3) if val.City != "" { geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationCity.String(), val.City)) } if val.Country != "" { geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationCountry.String(), val.Country)) } if val.Region != "" { geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationRegion.String(), val.Region)) } if val.Latitude != nil { geoLocationAttributes = append(geoLocationAttributes, otelattr.Float64(AttributeKeyGeoLocationLatitude.String(), *val.Latitude)) } if val.Longitude != nil { geoLocationAttributes = append(geoLocationAttributes, otelattr.Float64(AttributeKeyGeoLocationLongitude.String(), *val.Longitude)) } return geoLocationAttributes } func AttrWorkspaceAPIKeyID[V string | uuid.UUID](val V) otelattr.KeyValue { return otelattr.String(AttributeKeyWorkspaceAPIKeyID.String(), uuidOrString(val)) } func AttrProjectAPIKeyID[V string | uuid.UUID](val V) otelattr.KeyValue { return otelattr.String(AttributeKeyProjectAPIKeyID.String(), uuidOrString(val)) } func uuidOrString[V string | uuid.UUID](val V) string { switch val := any(val).(type) { case string: return val case uuid.UUID: return val.String() } panic("unreachable") } ================================================ FILE: oryx/otelx/semconv/warning.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package semconv import ( "context" otelattr "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // NewWarning creates a new warning event with the given ID and attributes. // It returns the event name and a trace.EventOption that can be used to // add the event to a span. // // span.AddEvent(NewWarning(ctx, "warning-id", otelattr.String("key", "value"))) func NewWarning(ctx context.Context, id string, attrs ...otelattr.KeyValue) (string, trace.EventOption) { return Warning.String(), trace.WithAttributes( append( append( attrs, AttributesFromContext(ctx)..., ), otelattr.String(AttributeWarningID.String(), id), )..., ) } const ( Warning Event = "Warning" AttributeWarningID AttributeKey = "WarningID" ) func AttrWarningID(id string) otelattr.KeyValue { return otelattr.String(AttributeWarningID.String(), id) } ================================================ FILE: oryx/otelx/withspan.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "context" "errors" "fmt" "reflect" "runtime" "strings" pkgerrors "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" "go.opentelemetry.io/otel/trace" ) // WithSpan wraps execution of f in a span identified by name. // // If f returns an error or panics, the span status will be set to the error // state. The error (or panic) will be propagated unmodified. // // f will be wrapped in a child span by default. To make a new root span // instead, pass the trace.WithNewRoot() option. func WithSpan(ctx context.Context, name string, f func(context.Context) error, opts ...trace.SpanStartOption) (err error) { ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("").Start(ctx, name, opts...) defer func() { defer span.End() if r := recover(); r != nil { setErrorStatusPanic(span, r) panic(r) } else if err != nil { span.SetStatus(codes.Error, err.Error()) setErrorTags(span, err) } }() return f(ctx) } // End finishes span, and automatically sets the error state if *err is not nil // or during panicking. // // Usage: // // func Divide(ctx context.Context, numerator, denominator int) (ratio int, err error) { // ctx, span := tracer.Start(ctx, "Divide") // defer otelx.End(span, &err) // if denominator == 0 { // return 0, errors.New("cannot divide by zero") // } // return numerator / denominator, nil // } // // During a panic, we don't fully conform to OpenTelemetry's semantic // conventions because that would require us to emit a span event to attach the // stacktrace and error type, and we don't want to do that. Instead, we set the // tags on the span directly. // https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/ // // For improved compatibility with Datadog, we also set some additional tags as // documented here: // https://docs.datadoghq.com/standard-attributes/?product=apm&search=error func End(span trace.Span, err *error) { defer span.End() if r := recover(); r != nil { setErrorStatusPanic(span, r) panic(r) } if err == nil || *err == nil { return } span.SetStatus(codes.Error, (*err).Error()) setErrorTags(span, *err) } func setErrorStatusPanic(span trace.Span, recovered any) { span.SetAttributes( // OpenTelemetry says to add these attributes to an event, not the span // itself. We don't want to do that, so we're adding them to the span // directly. semconv.ExceptionEscaped(true), // OpenTelemetry describes "exception.stacktrace" We don't love that, // though, so we're using "error.stack" instead, like DataDog). attribute.String("error.stack", stacktrace()), ) if t := reflect.TypeOf(recovered); t != nil { span.SetAttributes(semconv.ExceptionType(t.String())) } switch e := recovered.(type) { case error: span.SetStatus(codes.Error, "panic: "+e.Error()) setErrorTags(span, e) case string, fmt.Stringer: span.SetStatus(codes.Error, fmt.Sprintf("panic: %v", e)) default: span.SetStatus(codes.Error, "panic") case nil: // nothing } } func setErrorTags(span trace.Span, err error) { span.SetAttributes( attribute.String("error", err.Error()), attribute.String("error.message", err.Error()), // DataDog compat attribute.String("error.type", fmt.Sprintf("%T", errors.Unwrap(err))), // the innermost error type is the most useful here ) if e := interface{ StackTrace() pkgerrors.StackTrace }(nil); errors.As(err, &e) { span.SetAttributes(attribute.String("error.stack", fmt.Sprintf("%+v", e.StackTrace()))) } if e := interface{ Reason() string }(nil); errors.As(err, &e) { span.SetAttributes(attribute.String("error.reason", e.Reason())) } if e := interface{ Debug() string }(nil); errors.As(err, &e) { span.SetAttributes(attribute.String("error.debug", e.Debug())) } if e := interface{ ID() string }(nil); errors.As(err, &e) { span.SetAttributes(attribute.String("error.id", e.ID())) } if e := interface{ Details() map[string]interface{} }(nil); errors.As(err, &e) { for k, v := range e.Details() { span.SetAttributes(attribute.String("error.details."+k, fmt.Sprintf("%v", v))) } } } func stacktrace() string { pc := make([]uintptr, 5) n := runtime.Callers(4, pc) if n == 0 { return "" } pc = pc[:n] frames := runtime.CallersFrames(pc) var builder strings.Builder for { frame, more := frames.Next() fmt.Fprintf(&builder, "%s\n\t%s:%d\n", frame.Function, frame.File, frame.Line) if !more { break } } return builder.String() } ================================================ FILE: oryx/otelx/zipkin.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otelx import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/zipkin" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" "go.opentelemetry.io/otel/trace" ) func SetupZipkin(t *Tracer, tracerName string, c *Config) (trace.Tracer, error) { exp, err := zipkin.New(c.Providers.Zipkin.ServerURL) if err != nil { return nil, err } tpOpts := []sdktrace.TracerProviderOption{ sdktrace.WithBatcher(exp), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(c.ServiceName), semconv.DeploymentEnvironmentName(c.DeploymentEnvironment), )), sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased( c.Providers.Zipkin.Sampling.SamplingRatio, ))), } tp := sdktrace.NewTracerProvider(tpOpts...) otel.SetTracerProvider(tp) return tp.Tracer(tracerName), nil } ================================================ FILE: oryx/package.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package x ================================================ FILE: oryx/package.json ================================================ { "private": true, "prettier": "ory-prettier-styles", "devDependencies": { "license-checker": "^25.0.1", "ory-prettier-styles": "1.3.0", "prettier": "2.8.8" } } ================================================ FILE: oryx/pagination/README.md ================================================ # pagination A simple helper for dealing with pagination. ``` go get github.com/ory/pagination ``` ## Example ```go package main import ( "github.com/ory/pagination" "net/http" "net/url" "fmt" ) func main() { u, _ := url.Parse("http://localhost/foo?offset=0&limit=10") limit, offset := pagination.Parse(&http.Request{URL: u}, 5, 5, 10) items := []string{"a", "b", "c", "d"} start, end := pagination.Index(limit, offset, len(items)) fmt.Printf("Got items: %v", items[start:end]) } ``` ================================================ FILE: oryx/pagination/header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pagination import ( "fmt" "math" "net/http" "net/url" "strconv" "strings" ) func header(u *url.URL, rel string, limit, offset int64) string { q := u.Query() q.Set("limit", fmt.Sprintf("%d", limit)) q.Set("offset", fmt.Sprintf("%d", offset)) u.RawQuery = q.Encode() return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel) } type formatter func(location *url.URL, rel string, itemsPerPage int64, offset int64) string // HeaderWithFormatter adds an HTTP header for pagination which uses a custom formatter for generating the URL links. func HeaderWithFormatter(w http.ResponseWriter, u *url.URL, total int64, page, itemsPerPage int, f formatter) { if itemsPerPage <= 0 { itemsPerPage = 1 } itemsPerPage64 := int64(itemsPerPage) offset := int64(page) * itemsPerPage64 // lastOffset will either equal the offset required to contain the remainder, // or the limit. var lastOffset int64 if total%itemsPerPage64 == 0 { lastOffset = total - itemsPerPage64 } else { lastOffset = (total / itemsPerPage64) * itemsPerPage64 } w.Header().Set("X-Total-Count", strconv.FormatInt(total, 10)) // Check for last page if offset >= lastOffset { if total == 0 { w.Header().Set("Link", strings.Join([]string{ f(u, "first", itemsPerPage64, 0), f(u, "next", itemsPerPage64, ((offset/itemsPerPage64)+1)*itemsPerPage64), f(u, "prev", itemsPerPage64, ((offset/itemsPerPage64)-1)*itemsPerPage64), }, ",")) return } if total <= itemsPerPage64 { w.Header().Set("link", f(u, "first", total, 0)) return } w.Header().Set("Link", strings.Join([]string{ f(u, "first", itemsPerPage64, 0), f(u, "prev", itemsPerPage64, lastOffset-itemsPerPage64), }, ",")) return } if offset < itemsPerPage64 { w.Header().Set("Link", strings.Join([]string{ f(u, "next", itemsPerPage64, itemsPerPage64), f(u, "last", itemsPerPage64, lastOffset), }, ",")) return } w.Header().Set("Link", strings.Join([]string{ f(u, "first", itemsPerPage64, 0), f(u, "next", itemsPerPage64, ((offset/itemsPerPage64)+1)*itemsPerPage64), f(u, "prev", itemsPerPage64, ((offset/itemsPerPage64)-1)*itemsPerPage64), f(u, "last", itemsPerPage64, lastOffset), }, ",")) } // Header adds an http header for pagination using a responsewriter where backwards compatibility is required. // The header will contain links any combination of the first, last, next, or previous (prev) pages in a paginated list (given a limit and an offset, and optionally a total). // If total is not set, then no "last" page will be calculated. // If no limit is provided, then it will default to 1. func Header(w http.ResponseWriter, u *url.URL, total int, limit, offset int) { var page int if limit == 0 { limit = 1 } page = int(math.Floor(float64(offset) / float64(limit))) HeaderWithFormatter(w, u, int64(total), page, limit, header) } ================================================ FILE: oryx/pagination/items.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pagination // MaxItemsPerPage is used to prevent DoS attacks against large lists by limiting the items per page to 500. func MaxItemsPerPage(max, is int) int { if is > max { return max } return is } ================================================ FILE: oryx/pagination/keysetpagination/header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "cmp" "fmt" "net/http" "net/url" "strconv" "strings" "github.com/pkg/errors" ) // Pagination Request Parameters // // The `Link` HTTP header contains multiple links (`first`, `next`) formatted as: // `; rel="first"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model keysetPaginationRequestParameters type RequestParameters struct { // Items per Page // // This is the number of items per page to return. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query // default: 250 // min: 1 // max: 1000 PageSize int `json:"page_size"` // Next Page Token // // The next page token. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query PageToken string `json:"page_token"` } // Pagination Response Header // // The `Link` HTTP header contains multiple links (`first`, `next`) formatted as: // `; rel="first"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model keysetPaginationResponseHeaders type ResponseHeaders struct { // The Link HTTP Header // // The `Link` header contains a comma-delimited list of links to the following pages: // // - first: The first page of results. // - next: The next page of results. // // Pages are omitted if they do not exist. For example, if there is no next page, the `next` link is omitted. Examples: // // ; rel="next" // Link string `json:"link"` } func header(u *url.URL, rel, token string, size int) string { q := u.Query() q.Set("page_token", token) q.Set("page_size", strconv.Itoa(size)) u.RawQuery = q.Encode() return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel) } // Header adds the Link header for the page encoded by the paginator. // It contains links to the first and next page, if one exists. func Header(w http.ResponseWriter, u *url.URL, p *Paginator) { size := p.Size() link := []string{header(u, "first", p.defaultToken.Encode(), size)} if !p.isLast { link = append(link, header(u, "next", p.Token().Encode(), size)) } w.Header().Set("Link", strings.Join(link, ",")) } // Parse returns the pagination options from the URL query. func Parse(q url.Values, p PageTokenConstructor) ([]Option, error) { var opts []Option if pt := cmp.Or(q["page_token"]...); pt != "" { pageToken, err := url.QueryUnescape(pt) if err != nil { return nil, errors.WithStack(err) } parsed, err := p(pageToken) if err != nil { return nil, errors.WithStack(err) } opts = append(opts, WithToken(parsed)) } if ps := cmp.Or(q["page_size"]...); ps != "" { size, err := strconv.Atoi(ps) if err != nil { return nil, errors.WithStack(err) } opts = append(opts, WithSize(size)) } return opts, nil } ================================================ FILE: oryx/pagination/keysetpagination/page_token.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "encoding/base64" "fmt" "strings" ) type PageToken = interface { Parse(string) map[string]string Encode() string } var _ PageToken = new(StringPageToken) var _ PageToken = new(MapPageToken) type StringPageToken string func (s StringPageToken) Parse(idField string) map[string]string { return map[string]string{idField: string(s)} } func (s StringPageToken) Encode() string { return string(s) } func NewStringPageToken(s string) (PageToken, error) { return StringPageToken(s), nil } type MapPageToken map[string]string func (m MapPageToken) Parse(_ string) map[string]string { return map[string]string(m) } const pageTokenColumnDelim = "/" func (m MapPageToken) Encode() string { elems := make([]string, 0, len(m)) for k, v := range m { elems = append(elems, fmt.Sprintf("%s=%s", k, v)) } // For now: use Base64 instead of URL escaping, as the Timestamp format we need to use can contain a `+` sign, // which represents a space in URLs, so it's not properly encoded by the Go library. return base64.RawStdEncoding.EncodeToString([]byte(strings.Join(elems, pageTokenColumnDelim))) } func NewMapPageToken(s string) (PageToken, error) { b, err := base64.RawStdEncoding.DecodeString(s) if err != nil { return nil, err } tokens := strings.Split(string(b), pageTokenColumnDelim) r := map[string]string{} for _, p := range tokens { if columnName, value, found := strings.Cut(p, "="); found { r[columnName] = value } } return MapPageToken(r), nil } var _ PageTokenConstructor = NewMapPageToken var _ PageTokenConstructor = NewStringPageToken type PageTokenConstructor = func(string) (PageToken, error) ================================================ FILE: oryx/pagination/keysetpagination/paginator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "errors" "fmt" "github.com/ory/pop/v6" "github.com/ory/pop/v6/columns" ) type ( Item = interface{ PageToken() PageToken } Order string columnOrdering struct { name string order Order } Paginator struct { token, defaultToken PageToken size, defaultSize, maxSize int isLast bool additionalColumn columnOrdering } Option func(*Paginator) *Paginator ) var ErrUnknownOrder = errors.New("unknown order") const ( OrderDescending Order = "DESC" OrderAscending Order = "ASC" DefaultSize = 100 DefaultMaxSize = 500 ) func (o Order) extract() (string, string, error) { switch o { case OrderAscending: return ">", string(o), nil case OrderDescending: return "<", string(o), nil default: return "", "", ErrUnknownOrder } } func (p *Paginator) Token() PageToken { if p.token == nil { return p.defaultToken } return p.token } func (p *Paginator) Size() int { size := p.size if size <= 0 { size = p.defaultSize if size == 0 { size = 100 } } if size > p.maxSize { size = p.maxSize } return size } func (p *Paginator) IsLast() bool { return p.isLast } func (p *Paginator) ToOptions() []Option { opts := make([]Option, 0, 7) if p.token != nil { opts = append(opts, WithToken(p.token)) } if p.defaultToken != nil { opts = append(opts, WithDefaultToken(p.defaultToken)) } if p.size > 0 { opts = append(opts, WithSize(p.size)) } if p.defaultSize != DefaultSize { opts = append(opts, WithDefaultSize(p.defaultSize)) } if p.maxSize != DefaultMaxSize { opts = append(opts, WithMaxSize(p.maxSize)) } if p.additionalColumn.name != "" { opts = append(opts, WithColumn(p.additionalColumn.name, p.additionalColumn.order)) } if p.isLast { opts = append(opts, withIsLast(p.isLast)) } return opts } func (p *Paginator) multipleOrderFieldsQuery(q *pop.Query, idField string, cols map[string]*columns.Column, quoteAndContextualize func(string) string) { tokenParts := p.Token().Parse(idField) idValue := tokenParts[idField] column, ok := cols[p.additionalColumn.name] if !ok { q.Where(fmt.Sprintf(`%s > ?`, quoteAndContextualize(idField)), idValue) return } quoteName := quoteAndContextualize(column.Name) value, ok := tokenParts[column.Name] if !ok { q.Where(fmt.Sprintf(`%s > ?`, quoteAndContextualize(idField)), idValue) return } sign, keyword, err := p.additionalColumn.order.extract() if err != nil { q.Where(fmt.Sprintf(`%s > ?`, quoteAndContextualize(idField)), idValue) return } q. Where(fmt.Sprintf("(%s %s ? OR (%s = ? AND %s > ?))", quoteName, sign, quoteName, quoteAndContextualize(idField)), value, value, idValue). Order(fmt.Sprintf("%s %s", quoteName, keyword)) } // Paginate returns a function that paginates a pop.Query. // Usage: // // q := c.Where("foo = ?", foo).Scope(keysetpagination.Paginate[MyItemType](paginator)) // // This function works regardless of whether your type implements the Item // interface with pointer or value receivers. To understand the type parameters, // see this document: // https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#pointer-method-example func Paginate[I any, PI interface { Item *I }](p *Paginator) pop.ScopeFunc { model := pop.Model{Value: new(I)} id := model.IDField() tableName := model.Alias() return func(q *pop.Query) *pop.Query { quote := q.Connection.Dialect.Quote eid := quote(tableName) + "." + quote(id) quoteAndContextualize := func(name string) string { return quote(tableName) + "." + quote(name) } p.multipleOrderFieldsQuery(q, id, model.Columns().Cols, quoteAndContextualize) return q. Limit(p.Size() + 1). // we always need to order by the id field last Order(fmt.Sprintf(`%s ASC`, eid)) } } // Result removes the last item (if applicable) and returns the paginator for the next page. // // This function works regardless of whether your type implements the Item // interface with pointer or value receivers. To understand the type parameters, // see this document: // https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#pointer-method-example func Result[I any, PI interface { Item *I }](items []I, p *Paginator) ([]I, *Paginator) { if len(items) > p.Size() { items = items[:p.Size()] return items, &Paginator{ token: PI(&items[len(items)-1]).PageToken(), defaultToken: p.defaultToken, size: p.size, defaultSize: p.defaultSize, maxSize: p.maxSize, } } return items, &Paginator{ defaultToken: p.defaultToken, size: p.size, defaultSize: p.defaultSize, maxSize: p.maxSize, isLast: true, } } func WithDefaultToken(t PageToken) Option { return func(opts *Paginator) *Paginator { opts.defaultToken = t return opts } } func WithDefaultSize(size int) Option { return func(opts *Paginator) *Paginator { opts.defaultSize = size return opts } } func WithMaxSize(size int) Option { return func(opts *Paginator) *Paginator { opts.maxSize = size return opts } } func WithToken(t PageToken) Option { return func(opts *Paginator) *Paginator { opts.token = t return opts } } func WithSize(size int) Option { return func(opts *Paginator) *Paginator { opts.size = size return opts } } func WithColumn(name string, order Order) Option { return func(opts *Paginator) *Paginator { opts.additionalColumn = columnOrdering{ name: name, order: order, } return opts } } func withIsLast(isLast bool) Option { return func(opts *Paginator) *Paginator { opts.isLast = isLast return opts } } func GetPaginator(modifiers ...Option) *Paginator { opts := &Paginator{ // these can still be overridden by the modifiers, but they should never be unset maxSize: DefaultMaxSize, defaultSize: DefaultSize, } for _, f := range modifiers { opts = f(opts) } return opts } ================================================ FILE: oryx/pagination/keysetpagination/parse_header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "net/http" "net/url" "github.com/peterhellberg/link" ) // PaginationResult represents a parsed result of the link HTTP header. type PaginationResult struct { // NextToken is the next page token. If it's empty, there is no next page. NextToken string // FirstToken is the first page token. FirstToken string } // ParseHeader parses the response header's Link. func ParseHeader(r *http.Response) *PaginationResult { links := link.ParseResponse(r) return &PaginationResult{ NextToken: findRel(links, "next"), FirstToken: findRel(links, "first"), } } func findRel(links link.Group, rel string) string { for idx, l := range links { if idx == rel { parsed, err := url.Parse(l.URI) if err != nil { continue } return parsed.Query().Get("page_token") } } return "" } ================================================ FILE: oryx/pagination/keysetpagination_v2/.snapshots/TestPageToken-Marshal_snapshot.json ================================================ { "e": "2023-01-01T01:00:00Z", "c": [ { "n": "id", "v": "token" }, { "n": "string", "o": 1, "v": "str" }, { "n": "string-zero", "o": 1, "v": "" }, { "n": "time", "vt": "2023-01-01T00:00:00Z" }, { "n": "uuid", "vu": "b652bbbd-1a84-4fb7-9437-6b78ba2686db" }, { "n": "nulluuid", "vu": "b652bbbd-1a84-4fb7-9437-6b78ba2686da" }, { "n": "int64", "vi": 64 }, { "n": "int64-zero", "vi": 0 }, { "n": "empty-nullint64", "vi": 0 }, { "n": "nullint64", "vi": 64 }, { "n": "nullstring", "v": "str" }, { "n": "empty-nullstring", "v": "" }, { "n": "null-nulluuid", "vn": true }, { "n": "null-nullstring", "vn": true }, { "n": "null-nullint64", "vn": true } ] } ================================================ FILE: oryx/pagination/keysetpagination_v2/page_token.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "database/sql" "encoding/json" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ssoready/hyrumtoken" "github.com/ory/herodot" ) var fallbackEncryptionKey = &[32]byte{} type ( PageToken struct { testNow func() time.Time cols []Column } jsonPageToken = struct { ExpiresAt time.Time `json:"e"` Cols []jsonColumn `json:"c"` } jsonColumn = struct { Name string `json:"n"` Order Order `json:"o,omitempty"` Nullable bool `json:"nl,omitempty"` HasConstraint bool `json:"hc,omitempty"` ValueAny any `json:"v,omitempty"` ValueTime *time.Time `json:"vt,omitempty"` ValueUUID *uuid.UUID `json:"vu,omitempty"` ValueInt64 *int64 `json:"vi,omitempty"` ValueNull bool `json:"vn,omitempty"` } Column struct { Name string Order Order Value any Nullable bool // HasConstraint marks if the column is already constrained by WHERE condition. HasConstraint bool } ) func (t PageToken) Columns() []Column { return t.cols } // Encrypt encrypts the page token using the first key in the provided keyset. // It uses a fallback key if no keys are provided. func (t PageToken) Encrypt(keys [][32]byte) string { key := fallbackEncryptionKey if len(keys) > 0 { key = &keys[0] } return hyrumtoken.Marshal(key, t) } func (t PageToken) MarshalJSON() ([]byte, error) { now := time.Now if t.testNow != nil { now = t.testNow } toEncode := jsonPageToken{ ExpiresAt: now().Add(time.Hour).UTC(), Cols: make([]jsonColumn, len(t.cols)), } for i, col := range t.cols { toEncode.Cols[i] = jsonColumn{ Name: col.Name, Order: col.Order, Nullable: col.Nullable, HasConstraint: col.HasConstraint, } switch v := col.Value.(type) { case time.Time: toEncode.Cols[i].ValueTime = new(v) case uuid.UUID: toEncode.Cols[i].ValueUUID = new(v) case uuid.NullUUID: if v.Valid { toEncode.Cols[i].ValueUUID = new(v.UUID) } else { toEncode.Cols[i].ValueNull = true } case sql.NullString: if v.Valid { toEncode.Cols[i].ValueAny = new(v.String) } else { toEncode.Cols[i].ValueNull = true } case int64: toEncode.Cols[i].ValueInt64 = new(v) case sql.NullInt64: if v.Valid { toEncode.Cols[i].ValueInt64 = new(v.Int64) } else { toEncode.Cols[i].ValueNull = true } default: toEncode.Cols[i].ValueAny = v } } return json.Marshal(toEncode) } var ErrPageTokenExpired = herodot.ErrBadRequest.WithReason("page token expired, do not persist page tokens") func (t *PageToken) UnmarshalJSON(data []byte) error { rawToken := jsonPageToken{} if err := json.Unmarshal(data, &rawToken); err != nil { return err } t.cols = make([]Column, len(rawToken.Cols)) for i, col := range rawToken.Cols { t.cols[i] = Column{ Name: col.Name, Order: col.Order, Nullable: col.Nullable, HasConstraint: col.HasConstraint, } switch { case col.ValueNull: t.cols[i].Value = nil case col.ValueAny != nil: t.cols[i].Value = col.ValueAny case col.ValueUUID != nil: t.cols[i].Value = *col.ValueUUID case col.ValueTime != nil: t.cols[i].Value = *col.ValueTime case col.ValueInt64 != nil: t.cols[i].Value = *col.ValueInt64 } } now := time.Now if t.testNow != nil { now = t.testNow } if rawToken.ExpiresAt.Before(now().UTC()) { return errors.WithStack(ErrPageTokenExpired) } return nil } func NewPageToken(cols ...Column) PageToken { return PageToken{cols: cols} } ================================================ FILE: oryx/pagination/keysetpagination_v2/paginator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "cmp" "reflect" "github.com/jmoiron/sqlx/reflectx" "github.com/ory/herodot" ) var ErrInvalidPaginationToken = herodot.ErrBadRequest.WithReason("invalid pagination token") type ( Paginator struct { token, defaultToken PageToken size, defaultSize, maxSize int isLast bool } Option func(*Paginator) ) const ( DefaultSize = 100 DefaultMaxSize = 500 ) var dbStructTagMapper = reflectx.NewMapper("db") func (p *Paginator) DefaultToken() PageToken { return p.defaultToken } func (p *Paginator) IsLast() bool { return p.isLast } func (p *Paginator) PageToken() PageToken { if p.token.cols != nil { return p.token } return p.defaultToken } func (p *Paginator) Size() int { defaultSize := cmp.Or(p.defaultSize, DefaultSize) maxSize := cmp.Or(p.maxSize, DefaultMaxSize) size := p.size if size <= 0 { size = defaultSize } if size > maxSize { size = maxSize } return size } func (p *Paginator) ToOptions() []Option { opts := make([]Option, 0, 6) if p.token.cols != nil { opts = append(opts, WithToken(p.token)) } if p.defaultToken.cols != nil { opts = append(opts, WithDefaultToken(p.defaultToken)) } if p.size > 0 { opts = append(opts, WithSize(p.size)) } if p.defaultSize != DefaultSize { opts = append(opts, WithDefaultSize(p.defaultSize)) } if p.maxSize != DefaultMaxSize { opts = append(opts, WithMaxSize(p.maxSize)) } if p.isLast { opts = append(opts, withIsLast(p.isLast)) } return opts } // Result removes the last item (if applicable) and returns the paginator for the next page. func Result[I any](items []I, p *Paginator) ([]I, *Paginator) { return ResultFunc(items, p, func(last I, colName string) any { lastItemVal := reflect.ValueOf(last) return dbStructTagMapper.FieldByName(lastItemVal, colName).Interface() }) } // ResultFunc removes the last item (if applicable) and returns the paginator for the next page. // The extractor function is used to extract the column values from the last item. func ResultFunc[I any](items []I, p *Paginator, extractor func(last I, colName string) any) ([]I, *Paginator) { if len(items) <= p.Size() { return items, &Paginator{ isLast: true, defaultToken: p.defaultToken, size: p.size, defaultSize: p.defaultSize, maxSize: p.maxSize, } } items = items[:p.Size()] lastItem := items[len(items)-1] currentCols := p.PageToken().Columns() newCols := make([]Column, len(currentCols)) for i, col := range currentCols { newCols[i] = Column{ Name: col.Name, Order: col.Order, Nullable: col.Nullable, HasConstraint: col.HasConstraint, Value: extractor(lastItem, col.Name), } } return items, &Paginator{ token: NewPageToken(newCols...), defaultToken: p.defaultToken, size: p.size, defaultSize: p.defaultSize, maxSize: p.maxSize, } } func WithSize(size int) Option { return func(p *Paginator) { p.size = size } } func WithDefaultSize(size int) Option { return func(p *Paginator) { p.defaultSize = size } } func WithMaxSize(size int) Option { return func(p *Paginator) { p.maxSize = size } } func WithToken(t PageToken) Option { return func(p *Paginator) { p.token = t } } func WithDefaultToken(t PageToken) Option { return func(p *Paginator) { p.defaultToken = t } } func withIsLast(isLast bool) Option { return func(p *Paginator) { p.isLast = isLast } } func NewPaginator(modifiers ...Option) (*Paginator, error) { p := &Paginator{ // these can still be overridden by the modifiers, but they should never be unset maxSize: DefaultMaxSize, defaultSize: DefaultSize, } for _, f := range modifiers { f(p) } if err := p.validatePageToken(); err != nil { return nil, err } return p, nil } // validatePageToken ensures the page token columns strictly match DefaultToken's schema in order. func (p *Paginator) validatePageToken() error { tokenCols := p.PageToken().Columns() if len(tokenCols) == 0 { return nil } if columnsMatch(tokenCols, p.defaultToken.Columns()) { return nil } return ErrInvalidPaginationToken } func columnsMatch(tokenCols, templateCols []Column) bool { if len(templateCols) == 0 || len(tokenCols) != len(templateCols) { return false } for i, col := range tokenCols { def := templateCols[i] if def.Name != col.Name || def.Order != col.Order || def.Nullable != col.Nullable { return false } } return true } ================================================ FILE: oryx/pagination/keysetpagination_v2/parse_header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "net/http" "net/url" "github.com/peterhellberg/link" ) // ParseHeader parses the response header's Link and returns the first and next page tokens. func ParseHeader(r *http.Response) (first, next string, isLast bool) { links := link.ParseResponse(r) first, _ = findRel(links, "first") next, hasNext := findRel(links, "next") return first, next, !hasNext } func findRel(links link.Group, rel string) (string, bool) { for idx, l := range links { if idx == rel { parsed, err := url.Parse(l.URI) if err != nil { continue } q := parsed.Query() return q.Get("page_token"), q.Has("page_token") } } return "", false } ================================================ FILE: oryx/pagination/keysetpagination_v2/query_builder.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "database/sql/driver" "fmt" "strings" "github.com/ory/pop/v6" ) type Order int const ( OrderAscending Order = iota OrderDescending ) func (o Order) extract() (string, string) { switch o { case OrderAscending: return ">", "ASC" case OrderDescending: return "<", "DESC" default: panic(fmt.Sprintf("keyset pagination: unknown order %d", o)) } } // Paginate returns a function that paginates a pop.Query. // Usage: // // q := c.Where("foo = ?", foo).Scope(keysetpagination.Paginate[MyItemType](paginator)) func Paginate[I any](p *Paginator) pop.ScopeFunc { model := pop.Model{Value: *new(I)} tableName := model.Alias() return func(q *pop.Query) *pop.Query { quoteAndContextualize := func(name string) string { quote := q.Connection.Dialect.Quote return quote(tableName) + "." + quote(name) } dialect := q.Connection.Dialect.Name() where, args, order := BuildWhereAndOrder(p.PageToken().Columns(), quoteAndContextualize, dialect) // IMPORTANT: Ensures correct query logic by grouping conditions. // Without parentheses, `WHERE otherCond AND pageCond1 OR pageCond2` would be // evaluated as `(otherCond = ? AND pageCond1) OR pageCond2`, potentially returning // rows that do not match `otherCond`. // We fix it by forcing the query to be: `WHERE otherCond AND (paginationCond1 OR paginationCond2)`. where = "(" + where + ")" return q. Where(where, args...). Order(order). Limit(p.Size() + 1) } } func BuildWhereAndOrder(columns []Column, quote func(string) string, dialect string) (string, []any, string) { var whereBuilder, orderByBuilder, prevEqual strings.Builder keysetCols := make([]Column, 0, len(columns)) // ORDER BY includes all columns (even constrained ones) for i, part := range columns { column := quote(part.Name) _, keyword := part.Order.extract() if i > 0 { orderByBuilder.WriteString(", ") } orderByBuilder.WriteString(column + " " + keyword) // Postgres orders NULLs differently depending on sort direction; // (ASC → NULLS LAST, DESC → NULLS FIRST), which does not match the // assumptions of our keyset pagination logic and other supported DBs. // We therefore make NULL ordering explicit on Postgres to keep pagination // stable and consistent with sqlite/mysql/cockroachdb. if dialect == "postgres" && part.Nullable { if part.Order == OrderAscending { orderByBuilder.WriteString(" NULLS FIRST") } else { orderByBuilder.WriteString(" NULLS LAST") } } // Build keyset WHERE only from unconstrained columns if !part.HasConstraint { keysetCols = append(keysetCols, part) } } // If everything is constrained, no keyset predicate is needed. if len(keysetCols) == 0 { return "", nil, orderByBuilder.String() } args := make([]any, 0, len(keysetCols)*(len(keysetCols)+1)/2) prevEqualArgs := make([]any, 0, len(keysetCols)) whereBuilder.WriteRune('(') for i, part := range keysetCols { column := quote(part.Name) sign, _ := part.Order.extract() if i > 0 { whereBuilder.WriteString(") OR (") } whereBuilder.WriteString(prevEqual.String()) if prevEqual.Len() > 0 { whereBuilder.WriteString(" AND ") } isNull := part.Nullable && isSQLNull(part.Value) if !part.Nullable { whereBuilder.WriteString(column + " " + sign + " ?") } else if !isNull { whereBuilder.WriteString(column + " IS NOT NULL AND " + column + " " + sign + " ?") } else { whereBuilder.WriteString(column + " IS NOT NULL") } if i > 0 { prevEqual.WriteString(" AND ") } if !part.Nullable { prevEqual.WriteString(column + " = ?") prevEqualArgs = append(prevEqualArgs, part.Value) } else if !isNull { prevEqual.WriteString(column + " = ?") prevEqualArgs = append(prevEqualArgs, part.Value) } else { prevEqual.WriteString(column + " IS NULL") } args = append(args, prevEqualArgs...) } whereBuilder.WriteRune(')') return whereBuilder.String(), args, orderByBuilder.String() } // isSQLNull reports whether v represents a SQL NULL value. func isSQLNull(v any) bool { if v == nil { return true } if valuer, ok := v.(driver.Valuer); ok { val, err := valuer.Value() return err == nil && val == nil } return false } ================================================ FILE: oryx/pagination/keysetpagination_v2/request_params.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package keysetpagination import ( "cmp" "fmt" "net/http" "net/url" "strconv" "strings" "github.com/pkg/errors" "github.com/ssoready/hyrumtoken" ) // Pagination Request Parameters // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model keysetPaginationRequestParameters type RequestParameters struct { // Items per Page // // This is the number of items per page to return. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query // default: 250 // min: 1 // max: 1000 PageSize int `json:"page_size"` // Next Page Token // // The next page token. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query PageToken string `json:"page_token"` } // Pagination Response Header // // The `Link` HTTP header contains multiple links (`first`, `next`) formatted as: // `; rel="first"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model keysetPaginationResponseHeaders type ResponseHeaders struct { // The Link HTTP Header // // The `Link` header contains a comma-delimited list of links to the following pages: // // - first: The first page of results. // - next: The next page of results. // // Pages are omitted if they do not exist. For example, if there is no next page, the `next` link is omitted. Examples: // // ; rel="next" // Link string `json:"link"` } // SetLinkHeader adds the Link header for the page encoded by the paginator. // It contains links to the first and next page, if one exists. func SetLinkHeader(w http.ResponseWriter, keys [][32]byte, u *url.URL, p *Paginator) { size := p.Size() link := []string{linkPart(u, "first", p.DefaultToken().Encrypt(keys), size)} if !p.isLast { link = append(link, linkPart(u, "next", p.PageToken().Encrypt(keys), size)) } w.Header().Set("Link", strings.Join(link, ",")) } func linkPart(u *url.URL, rel, token string, size int) string { q := u.Query() q.Set("page_token", token) q.Set("page_size", strconv.Itoa(size)) u.RawQuery = q.Encode() return fmt.Sprintf("<%s>; rel=%q", u.String(), rel) } // ParseQueryParams extracts the pagination options from the URL query. func ParseQueryParams(keys [][32]byte, q url.Values) ([]Option, error) { var opts []Option if t := cmp.Or(q["page_token"]...); t != "" { raw, err := url.QueryUnescape(t) if err != nil { return nil, errors.WithStack(err) } token, err := ParsePageToken(keys, raw) if err != nil { return nil, err } opts = append(opts, WithToken(token)) } if s := cmp.Or(q["page_size"]...); s != "" { size, err := strconv.Atoi(s) if err != nil { return nil, errors.WithStack(err) } opts = append(opts, WithSize(size)) } return opts, nil } // ParsePageToken parses a page token from the given raw string using the provided keys. // It panics if no keys are provided. func ParsePageToken(keys [][32]byte, raw string) (t PageToken, err error) { for i := range keys { err = errors.WithStack(hyrumtoken.Unmarshal(&keys[i], raw, &t)) if err == nil { return } } // as a last resort, try the fallback key err = hyrumtoken.Unmarshal(fallbackEncryptionKey, raw, &t) return t, errors.WithStack(err) } ================================================ FILE: oryx/pagination/limit.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package pagination provides helpers for dealing with pagination. package pagination // Index uses limit, offset, and a slice's length to compute start and end indices for said slice. func Index(limit, offset, length int) (start, end int) { if offset > length { return length, length } else if limit+offset > length { return offset, length } return offset, offset + limit } ================================================ FILE: oryx/pagination/migrationpagination/.snapshots/TestPaginationHeader-Create_next_and_last,_but_not_previous_or_first_if_at_the_beginning.json ================================================ [ "\u003chttp://example.com?page=1\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiI1MCIsInYiOjJ9\u0026per_page=50\u003e", "rel=\"next\",\u003chttp://example.com?page=2\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIxMDAiLCJ2IjoyfQ\u0026per_page=50\u003e", "rel=\"last\"" ] ================================================ FILE: oryx/pagination/migrationpagination/.snapshots/TestPaginationHeader-Create_only_first_if_the_limits_provided_exceeds_the_number_of_clients_found.json ================================================ [ "\u003chttp://example.com?page=0\u0026page_size=5\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u0026per_page=5\u003e", "rel=\"first\"" ] ================================================ FILE: oryx/pagination/migrationpagination/.snapshots/TestPaginationHeader-Create_previous,_next,_first,_and_last_if_in_the_middle.json ================================================ [ "\u003chttp://example.com?page=0\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u0026per_page=50\u003e", "rel=\"first\",\u003chttp://example.com?page=4\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIyMDAiLCJ2IjoyfQ\u0026per_page=50\u003e", "rel=\"next\",\u003chttp://example.com?page=2\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIxMDAiLCJ2IjoyfQ\u0026per_page=50\u003e", "rel=\"prev\",\u003chttp://example.com?page=5\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIyNTAiLCJ2IjoyfQ\u0026per_page=50\u003e", "rel=\"last\"" ] ================================================ FILE: oryx/pagination/migrationpagination/.snapshots/TestPaginationHeader-Create_previous,_next,_first,_but_not_last_if_in_the_middle_and_no_total_was_provided.json ================================================ [ "\u003chttp://example.com?page=0\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u0026per_page=50\u003e", "rel=\"first\",\u003chttp://example.com?page=4\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIyMDAiLCJ2IjoyfQ\u0026per_page=50\u003e", "rel=\"next\",\u003chttp://example.com?page=2\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIxMDAiLCJ2IjoyfQ\u0026per_page=50\u003e", "rel=\"prev\"" ] ================================================ FILE: oryx/pagination/migrationpagination/.snapshots/TestPaginationHeader-Create_previous_and_first_but_not_next_or_last_if_at_the_end.json ================================================ [ "\u003chttp://example.com?page=0\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u0026per_page=50\u003e", "rel=\"first\",\u003chttp://example.com?page=1\u0026page_size=50\u0026page_token=eyJvZmZzZXQiOiI1MCIsInYiOjJ9\u0026per_page=50\u003e", "rel=\"prev\"" ] ================================================ FILE: oryx/pagination/migrationpagination/.snapshots/TestPaginationHeader-Header_should_default_limit_to_1_no_limit_was_provided.json ================================================ [ "\u003chttp://example.com?page=0\u0026page_size=1\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u0026per_page=1\u003e", "rel=\"first\",\u003chttp://example.com?page=21\u0026page_size=1\u0026page_token=eyJvZmZzZXQiOiIyMSIsInYiOjJ9\u0026per_page=1\u003e", "rel=\"next\",\u003chttp://example.com?page=19\u0026page_size=1\u0026page_token=eyJvZmZzZXQiOiIxOSIsInYiOjJ9\u0026per_page=1\u003e", "rel=\"prev\",\u003chttp://example.com?page=99\u0026page_size=1\u0026page_token=eyJvZmZzZXQiOiI5OSIsInYiOjJ9\u0026per_page=1\u003e", "rel=\"last\"" ] ================================================ FILE: oryx/pagination/migrationpagination/header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package migrationpagination // swagger:model mixedPaginationRequestParameters type RequestParameters struct { // Deprecated Items per Page // // DEPRECATED: Please use `page_token` instead. This parameter will be removed in the future. // // This is the number of items per page. // // required: false // in: query // default: 250 // min: 1 // max: 1000 PerPage int `json:"per_page"` // Deprecated Pagination Page // // DEPRECATED: Please use `page_token` instead. This parameter will be removed in the future. // // This value is currently an integer, but it is not sequential. The value is not the page number, but a // reference. The next page can be any number and some numbers might return an empty list. // // For example, page 2 might not follow after page 1. And even if page 3 and 5 exist, but page 4 might not exist. // The first page can be retrieved by omitting this parameter. Following page pointers will be returned in the // `Link` header. // // required: false // in: query Page int `json:"page"` // Page Size // // This is the number of items per page to return. For details on pagination please head over to the // [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query // default: 250 // min: 1 // max: 500 PageSize int `json:"page_size"` // Next Page Token // // The next page token. For details on pagination please head over to the // [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query PageToken string `json:"page_token"` } // Pagination Response Header // // The `Link` HTTP header contains multiple links (`first`, `next`, `last`, `previous`) formatted as: // `; rel="{page}"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model mixedPagePaginationResponseHeaders type ResponseHeaderAnnotation struct { // The Link HTTP Header // // The `Link` header contains a comma-delimited list of links to the following pages: // // - first: The first page of results. // - next: The next page of results. // - prev: The previous page of results. // - last: The last page of results. // // Pages are omitted if they do not exist. For example, if there is no next page, the `next` link is omitted. // // The header value may look like follows: // // ; rel="first",; rel="next",; rel="prev",; rel="last" Link string `json:"link"` // The X-Total-Count HTTP Header // // The `X-Total-Count` header contains the total number of items in the collection. // // DEPRECATED: This header will be removed eventually. Please use the `Link` header // instead to check whether you are on the last page. TotalCount int `json:"x-total-count"` } ================================================ FILE: oryx/pagination/migrationpagination/pagination.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package migrationpagination import ( "fmt" "net/http" "net/url" "github.com/ory/x/pagination" "github.com/ory/x/pagination/pagepagination" "github.com/ory/x/pagination/tokenpagination" ) type Paginator struct { p *pagepagination.PagePaginator t *tokenpagination.TokenPaginator } func NewPaginator(p *pagepagination.PagePaginator, t *tokenpagination.TokenPaginator) *Paginator { return &Paginator{p: p, t: t} } func NewDefaultPaginator() *Paginator { return &Paginator{p: new(pagepagination.PagePaginator), t: new(tokenpagination.TokenPaginator)} } func (p *Paginator) ParsePagination(r *http.Request) (page, itemsPerPage int) { if r.URL.Query().Has("page_token") || r.URL.Query().Has("page_size") { return p.t.ParsePagination(r) } return p.p.ParsePagination(r) } func header(u *url.URL, rel string, itemsPerPage, offset int64) string { q := u.Query() q.Set("page_size", fmt.Sprintf("%d", itemsPerPage)) q.Set("page_token", tokenpagination.Encode(offset)) q.Set("per_page", fmt.Sprintf("%d", itemsPerPage)) q.Set("page", fmt.Sprintf("%d", offset/itemsPerPage)) u.RawQuery = q.Encode() return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel) } func PaginationHeader(w http.ResponseWriter, u *url.URL, total int64, page, itemsPerPage int) { pagination.HeaderWithFormatter(w, u, total, page, itemsPerPage, header) } ================================================ FILE: oryx/pagination/pagepagination/header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pagepagination // Pagination Request Parameters // // The `Link` HTTP header contains multiple links (`first`, `next`, `last`, `previous`) formatted as: // `; rel="{page}"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model pagePaginationRequestParameters type RequestParameters struct { // Legacy Items per Page // // A DEPRECATED alias for `page_size`. Please transition to using `page_size` going forward. // // required: false // in: query // default: 250 // min: 1 // max: 1000 PerPage int `json:"per_page"` // Legacy Pagination Page // // A DEPRECATED alias for `page_token`. Please transition to using `page_token` going forward. // // required: false // in: query // default: 1 // min: 1 Page int `json:"page"` // Items per Page // // This is the number of items per page to return. For details on pagination please head over to the // [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query // default: 250 // min: 1 // max: 500 PageSize int `json:"page_size"` // Next Page Token // // The next page token. For details on pagination please head over to the // [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query PageToken string `json:"page_token"` } // swagger:model pagePaginationResponseHeaders type ResponseHeaderAnnotation struct { // The Link HTTP Header // // The `Link` header contains a comma-delimited list of links to the following pages: // // - first: The first page of results. // - next: The next page of results. // - prev: The previous page of results. // - last: The last page of results. // // Pages are omitted if they do not exist. For example, if there is no next page, the `next` link is omitted. // // This header will include the `per_page` and `page` parameters for legacy reasons, but these parameters will eventually be removed. // // Example: Link: ; rel="first",; rel="next",; rel="prev",; rel="last" Link string `json:"link"` // The X-Total-Count HTTP Header // // The `X-Total-Count` header contains the total number of items in the collection. // // Example: 123 TotalCount int `json:"x-total-count"` } ================================================ FILE: oryx/pagination/pagepagination/pagination.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pagepagination import ( "fmt" "net/http" "net/url" "strconv" "github.com/ory/x/pagination" ) type PagePaginator struct { MaxItems int DefaultItems int } func (p *PagePaginator) defaults() { if p.MaxItems == 0 { p.MaxItems = 1000 } if p.DefaultItems == 0 { p.DefaultItems = 250 } } // ParsePagination parses limit and page from *http.Request with given limits and defaults. func (p *PagePaginator) ParsePagination(r *http.Request) (page, itemsPerPage int) { p.defaults() if offsetParam := r.URL.Query().Get("page"); offsetParam == "" { page = 0 } else { if offset, err := strconv.ParseInt(offsetParam, 10, 0); err != nil { page = 0 } else { page = int(offset) } } if limitParam := r.URL.Query().Get("per_page"); limitParam == "" { itemsPerPage = p.DefaultItems } else { if limit, err := strconv.ParseInt(limitParam, 10, 0); err != nil { itemsPerPage = p.DefaultItems } else { itemsPerPage = int(limit) } } if itemsPerPage > p.MaxItems { itemsPerPage = p.MaxItems } if itemsPerPage < 1 { itemsPerPage = 1 } if page < 0 { page = 0 } return } func header(u *url.URL, rel string, limit, offset int64) string { q := u.Query() q.Set("per_page", fmt.Sprintf("%d", limit)) q.Set("page", fmt.Sprintf("%d", offset/limit)) u.RawQuery = q.Encode() return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel) } func PaginationHeader(w http.ResponseWriter, u *url.URL, total int64, page, itemsPerPage int) { pagination.HeaderWithFormatter(w, u, total, page, itemsPerPage, header) } ================================================ FILE: oryx/pagination/paginationplanner/planner.go ================================================ // Copyright © 2026 Ory Corp // SPDX-License-Identifier: Apache-2.0 package paginationplanner import ( "github.com/pkg/errors" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" ) // PaginationPlanner chooses the best [PaginationPlan] for a queriedColumns. // Plan is eligible when its required constraint set is satisfied (and an optional Condition matches). // If no plan matches, the FallbackPlan is used. type PaginationPlanner struct { Plans []PaginationPlan FallbackPlan PaginationPlan } func NewPaginationPlanner(fallbackPlan PaginationPlan, plans []PaginationPlan) (*PaginationPlanner, error) { if len(fallbackPlan.DefaultPageToken.Columns()) == 0 { return nil, errors.New("plan must define at least one PageTokenColumn") } for i := range plans { plan := &plans[i] if len(plan.ApplicableQueries) == 0 { return nil, errors.New("plan must define at least one ApplicableQueries") } if len(plan.DefaultPageToken.Columns()) == 0 { return nil, errors.New("plan must define a DefaultPageToken") } plan.populateInternals() } return &PaginationPlanner{ Plans: plans, FallbackPlan: fallbackPlan, }, nil } // GetPaginator selects the first eligible plan for the given queriedColumns constraints. // Eligibility requires an exact ColumnSet match and (if set) Condition match. // If none match, the FallbackPlan is used for building the Paginator. func (p *PaginationPlanner) GetPaginator(q Query, pageOpts ...keysetpagination.Option) (*keysetpagination.Paginator, error) { if len(q) == 0 { return keysetpagination.NewPaginator(append(pageOpts, keysetpagination.WithDefaultToken(p.FallbackPlan.DefaultPageToken))...) } plan := p.pickPlan(q) origCols := plan.DefaultPageToken.Columns() // Make a fresh copy so we don't mutate the original backing array. cols := make([]keysetpagination.Column, len(origCols)) copy(cols, origCols) for i, col := range cols { if val, ok := q.colByName(col.Name); ok && val.IsConstrained() { cols[i].HasConstraint = true } } defaultToken := keysetpagination.WithDefaultToken(keysetpagination.NewPageToken(cols...)) return keysetpagination.NewPaginator(append(pageOpts, defaultToken)...) } func (p *PaginationPlanner) pickPlan(q Query) PaginationPlan { constrainedCols := q.constrainedCols() for _, plan := range p.Plans { if _, ok := plan.applicableQueries[constrainedCols]; !ok { continue } if plan.Condition != nil && !plan.Condition(q) { continue } return plan } return p.FallbackPlan } type PaginationPlan struct { Name string // DefaultPageToken defines the columns that the pagination will be made of DefaultPageToken keysetpagination.PageToken // ApplicableQueries defines the exact sets of columns that this pagination plan is applicable for. ApplicableQueries [][]Column applicableQueries map[uint]struct{} // Condition further restricts the Plan eligibility for given queriedColumns. // This can be used for plan-specific logic, e.g. verifying partial-index predicates match. Condition func(q Query) bool } func (pp *PaginationPlan) populateInternals() { pp.applicableQueries = make(map[uint]struct{}, len(pp.ApplicableQueries)) for _, cols := range pp.ApplicableQueries { var colSet uint for _, col := range cols { colSet |= col.bit } pp.applicableQueries[colSet] = struct{}{} } } type Table struct { nextBit uint usedNames map[string]struct{} } func NewTable() *Table { return &Table{ nextBit: 1, usedNames: make(map[string]struct{}), } } func (t *Table) NewColumn(name string) Column { defer func() { t.nextBit = t.nextBit << 1 }() if _, exists := t.usedNames[name]; exists { panic("column name already used: " + name) } t.usedNames[name] = struct{}{} return Column{ name: name, bit: t.nextBit, } } type Column struct { bit uint name string } func (c Column) Name() string { return c.name } type ColumnConstraint uint8 const ( colUnconstrained ColumnConstraint = iota colConstraintEq // col = ? colConstraintIsNull // col IS NULL ) func (cs ColumnConstraint) IsConstrained() bool { return cs == colConstraintEq || cs == colConstraintIsNull } func (cs ColumnConstraint) IsNull() bool { return cs == colConstraintIsNull } // Query is used to track which column constraints are set. type Query map[Column]ColumnConstraint func NewQuery() Query { return make(Query) } func (qc Query) SetEq(cols ...Column) Query { return qc.set(colConstraintEq, cols...) } func (qc Query) SetIsNull(cols ...Column) Query { return qc.set(colConstraintIsNull, cols...) } func (qc Query) set(c ColumnConstraint, cols ...Column) Query { for _, col := range cols { qc[col] = c } return qc } func (qc Query) colByName(name string) (ColumnConstraint, bool) { for col, constraint := range qc { if col.name == name { return constraint, true } } return colUnconstrained, false } func (qc Query) constrainedCols() uint { var cols uint for col, state := range qc { if !state.IsConstrained() { continue } cols |= col.bit } return cols } ================================================ FILE: oryx/pagination/parse.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pagination import ( "net/http" "strconv" ) // Parse parses limit and offset from *http.Request with given limits and defaults. func Parse(r *http.Request, defaultLimit, defaultOffset, maxLimit int) (int, int) { var offset, limit int if offsetParam := r.URL.Query().Get("offset"); offsetParam == "" { offset = defaultOffset } else { if offset64, err := strconv.ParseInt(offsetParam, 10, 64); err != nil { offset = defaultOffset } else { offset = int(offset64) } } if limitParam := r.URL.Query().Get("limit"); limitParam == "" { limit = defaultLimit } else { if limit64, err := strconv.ParseInt(limitParam, 10, 64); err != nil { limit = defaultLimit } else { limit = int(limit64) } } if limit > maxLimit { limit = maxLimit } if limit < 0 { limit = 0 } if offset < 0 { offset = 0 } return limit, offset } ================================================ FILE: oryx/pagination/tokenpagination/.snapshots/TestPaginationHeader-Create_next_and_last,_but_not_previous_or_first_if_at_the_beginning.json ================================================ [ "\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiI1MCIsInYiOjJ9\u003e", "rel=\"next\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIxMDAiLCJ2IjoyfQ\u003e", "rel=\"last\"" ] ================================================ FILE: oryx/pagination/tokenpagination/.snapshots/TestPaginationHeader-Create_only_first_if_the_limits_provided_exceeds_the_number_of_clients_found.json ================================================ [ "\u003chttp://example.com?page_size=5\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u003e", "rel=\"first\"" ] ================================================ FILE: oryx/pagination/tokenpagination/.snapshots/TestPaginationHeader-Create_previous,_next,_first,_and_last_if_in_the_middle.json ================================================ [ "\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u003e", "rel=\"first\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIyMDAiLCJ2IjoyfQ\u003e", "rel=\"next\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIxMDAiLCJ2IjoyfQ\u003e", "rel=\"prev\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIyNTAiLCJ2IjoyfQ\u003e", "rel=\"last\"" ] ================================================ FILE: oryx/pagination/tokenpagination/.snapshots/TestPaginationHeader-Create_previous,_next,_first,_but_not_last_if_in_the_middle_and_no_total_was_provided.json ================================================ [ "\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u003e", "rel=\"first\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIyMDAiLCJ2IjoyfQ\u003e", "rel=\"next\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIxMDAiLCJ2IjoyfQ\u003e", "rel=\"prev\"" ] ================================================ FILE: oryx/pagination/tokenpagination/.snapshots/TestPaginationHeader-Create_previous_and_first_but_not_next_or_last_if_at_the_end.json ================================================ [ "\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u003e", "rel=\"first\",\u003chttp://example.com?page_size=50\u0026page_token=eyJvZmZzZXQiOiI1MCIsInYiOjJ9\u003e", "rel=\"prev\"" ] ================================================ FILE: oryx/pagination/tokenpagination/.snapshots/TestPaginationHeader-Header_should_default_limit_to_1_no_limit_was_provided.json ================================================ [ "\u003chttp://example.com?page_size=1\u0026page_token=eyJvZmZzZXQiOiIwIiwidiI6Mn0\u003e", "rel=\"first\",\u003chttp://example.com?page_size=1\u0026page_token=eyJvZmZzZXQiOiIyMSIsInYiOjJ9\u003e", "rel=\"next\",\u003chttp://example.com?page_size=1\u0026page_token=eyJvZmZzZXQiOiIxOSIsInYiOjJ9\u003e", "rel=\"prev\",\u003chttp://example.com?page_size=1\u0026page_token=eyJvZmZzZXQiOiI5OSIsInYiOjJ9\u003e", "rel=\"last\"" ] ================================================ FILE: oryx/pagination/tokenpagination/header.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package tokenpagination // Pagination Request Parameters // // The `Link` HTTP header contains multiple links (`first`, `next`, `last`, `previous`) formatted as: // `; rel="{page}"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model tokenPaginationRequestParameters type RequestParameters struct { // Items per Page // // This is the number of items per page to return. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query // default: 250 // min: 1 // max: 500 PageSize int `json:"page_size"` // Next Page Token // // The next page token. // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // required: false // in: query PageToken string `json:"page_token"` } // Pagination Response Header // // The `Link` HTTP header contains multiple links (`first`, `next`, `last`, `previous`) formatted as: // `; rel="{page}"` // // For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). // // swagger:model tokenPaginationResponseHeaders type ResponseHeaders struct { // The Link HTTP Header // // The `Link` header contains a comma-delimited list of links to the following pages: // // - first: The first page of results. // - next: The next page of results. // - prev: The previous page of results. // - last: The last page of results. // // Pages are omitted if they do not exist. For example, if there is no next page, the `next` link is omitted. Examples: // // ; rel="first",; rel="next",; rel="prev",; rel="last" // Link string `json:"link"` // The X-Total-Count HTTP Header // // The `X-Total-Count` header contains the total number of items in the collection. TotalCount int `json:"x-total-count"` } ================================================ FILE: oryx/pagination/tokenpagination/pagination.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package tokenpagination import ( "encoding/base64" "fmt" "net/http" "net/url" "strconv" "github.com/pkg/errors" "github.com/tidwall/gjson" "github.com/ory/x/pagination" "github.com/ory/herodot" ) func Encode(offset int64) string { return base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf(`{"offset":"%d","v":2}`, offset))) } func decode(s string) (int, error) { b, err := base64.RawURLEncoding.DecodeString(s) if err != nil { return 0, errors.WithStack(herodot.ErrBadRequest.WithWrap(err).WithReasonf("Unable to parse pagination token: %s", err)) } return int(gjson.Get(string(b), "offset").Int()), nil } type TokenPaginator struct { MaxItems int DefaultItems int } func (p *TokenPaginator) defaults() { if p.MaxItems == 0 { p.MaxItems = 1000 } if p.DefaultItems == 0 { p.DefaultItems = 250 } } // ParsePagination parses limit and page from *http.Request with given limits and defaults. func (p *TokenPaginator) ParsePagination(r *http.Request) (page, itemsPerPage int) { p.defaults() var offset int if offsetParam := r.URL.Query().Get("page_token"); len(offsetParam) > 0 { offset, _ = decode(offsetParam) } if gotLimit, err := strconv.ParseInt(r.URL.Query().Get("page_size"), 10, 0); err == nil { itemsPerPage = int(gotLimit) } else { itemsPerPage = p.DefaultItems } if itemsPerPage > p.MaxItems { itemsPerPage = p.MaxItems } if itemsPerPage < 1 { itemsPerPage = 1 } if offset > 0 { page = offset / itemsPerPage } if page < 0 { page = 0 } return } func header(u *url.URL, rel string, itemsPerPage, offset int64) string { q := u.Query() q.Set("page_size", fmt.Sprintf("%d", itemsPerPage)) q.Set("page_token", Encode(offset)) u.RawQuery = q.Encode() return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel) } func PaginationHeader(w http.ResponseWriter, u *url.URL, total int64, page, itemsPerPage int) { pagination.HeaderWithFormatter(w, u, total, page, itemsPerPage, header) } ================================================ FILE: oryx/pointerx/pointerx.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package pointerx // Deref returns the input values de-referenced value, or zero value if nil. func Deref[T any](p *T) T { if p == nil { var zero T return zero } return *p } ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-final_status.txt ================================================ stdout: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Applied 20200831110752000019 identity_verifiable_address_remove_code Applied 20200831110752000020 identity_verifiable_address_remove_code Applied 20200831110752000021 identity_verifiable_address_remove_code Applied 20201201161451000000 credential_types_values Applied 20201201161451000001 credential_types_values Applied stderr: ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_down_but_do_not_confirm.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Rollback 20200831110752000019 identity_verifiable_address_remove_code Rollback 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending The SQL statements to be executed from top to bottom are: ------------ 20200831110752000019 - identity_verifiable_address_remove_code ------------ UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL ------------ 20200831110752000018 - identity_verifiable_address_remove_code ------------ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL Do you wish to execute this migration plan? [y/n]: ------------ WARNING ------------ Migration aborted. stderr: To skip the next question use flag --yes (at your own risk). ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_down_but_no_steps.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Applied 20200831110752000019 identity_verifiable_address_remove_code Applied 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending stderr: There are apparently no migrations to roll back. Please provide the --steps argument with a value larger than 0. ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_down_four_steps.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Applied 20200831110752000019 identity_verifiable_address_remove_code Applied 20200831110752000020 identity_verifiable_address_remove_code Rollback 20200831110752000021 identity_verifiable_address_remove_code Rollback 20201201161451000000 credential_types_values Rollback 20201201161451000001 credential_types_values Rollback The SQL statements to be executed from top to bottom are: ------------ 20201201161451000001 - credential_types_values ------------ ------------ 20201201161451000000 - credential_types_values ------------ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ------------ 20200831110752000021 - identity_verifiable_address_remove_code ------------ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" TEXT ------------ 20200831110752000020 - identity_verifiable_address_remove_code ------------ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" DATETIME ------------ SUCCESS ------------ Successfully applied migrations! stderr: ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_down_two_steps.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Rollback 20200831110752000019 identity_verifiable_address_remove_code Rollback 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending The SQL statements to be executed from top to bottom are: ------------ 20200831110752000019 - identity_verifiable_address_remove_code ------------ UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL ------------ 20200831110752000018 - identity_verifiable_address_remove_code ------------ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL Do you wish to execute this migration plan? [y/n]: ------------ SUCCESS ------------ Successfully applied migrations! stderr: To skip the next question use flag --yes (at your own risk). ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_rollbacks_up_again.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Pending 20200831110752000019 identity_verifiable_address_remove_code Pending 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending The SQL statements to be executed from top to bottom are: ------------ 20200831110752000018 - identity_verifiable_address_remove_code ------------ ------------ 20200831110752000019 - identity_verifiable_address_remove_code ------------ ------------ 20200831110752000020 - identity_verifiable_address_remove_code ------------ ------------ 20200831110752000021 - identity_verifiable_address_remove_code ------------ ------------ 20201201161451000000 - credential_types_values ------------ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password') ------------ 20201201161451000001 - credential_types_values ------------ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); Do you wish to execute this migration plan? [y/n]: ------------ SUCCESS ------------ Successfully applied migrations! stderr: To skip the next question use flag --yes (at your own risk). ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_rollbacks_up_without_confirm.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Applied 20200831110752000019 identity_verifiable_address_remove_code Applied 20200831110752000020 identity_verifiable_address_remove_code Applied 20200831110752000021 identity_verifiable_address_remove_code Applied 20201201161451000000 credential_types_values Applied 20201201161451000001 credential_types_values Applied The SQL statements to be executed from top to bottom are: Do you wish to execute this migration plan? [y/n]: ------------ WARNING ------------ Migration aborted. stderr: To skip the next question use flag --yes (at your own risk). ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-migrate_up.txt ================================================ stdout: The migration plan is as follows: Version Name Status 20191100000001000000 identities Pending 20191100000001000001 identities Pending 20191100000001000002 identities Pending 20191100000001000003 identities Pending 20191100000001000004 identities Pending 20191100000001000005 identities Pending 20191100000002000000 requests Pending 20191100000002000001 requests Pending 20191100000002000002 requests Pending 20191100000002000003 requests Pending 20191100000002000004 requests Pending 20191100000003000000 sessions Pending 20191100000004000000 errors Pending 20191100000006000000 courier Pending 20191100000007000000 errors Pending 20191100000007000001 errors Pending 20191100000007000002 errors Pending 20191100000007000003 errors Pending 20191100000008000000 selfservice_verification Pending 20191100000008000001 selfservice_verification Pending 20191100000008000002 selfservice_verification Pending 20191100000008000003 selfservice_verification Pending 20191100000008000004 selfservice_verification Pending 20191100000008000005 selfservice_verification Pending 20191100000010000000 errors Pending 20191100000010000001 errors Pending 20191100000010000002 errors Pending 20191100000010000003 errors Pending 20191100000010000004 errors Pending 20191100000011000000 courier_body_type Pending 20191100000011000001 courier_body_type Pending 20191100000011000002 courier_body_type Pending 20191100000011000003 courier_body_type Pending 20191100000012000000 login_request_forced Pending 20191100000012000001 login_request_forced Pending 20191100000012000002 login_request_forced Pending 20191100000012000003 login_request_forced Pending 20200317160354000000 create_profile_request_forms Pending 20200317160354000001 create_profile_request_forms Pending 20200317160354000002 create_profile_request_forms Pending 20200317160354000003 create_profile_request_forms Pending 20200317160354000004 create_profile_request_forms Pending 20200317160354000005 create_profile_request_forms Pending 20200317160354000006 create_profile_request_forms Pending 20200401183443000000 continuity_containers Pending 20200402142539000000 rename_profile_flows Pending 20200402142539000001 rename_profile_flows Pending 20200402142539000002 rename_profile_flows Pending 20200519101057000000 create_recovery_addresses Pending 20200519101057000001 create_recovery_addresses Pending 20200519101057000002 create_recovery_addresses Pending 20200519101057000003 create_recovery_addresses Pending 20200519101057000004 create_recovery_addresses Pending 20200519101057000005 create_recovery_addresses Pending 20200519101057000006 create_recovery_addresses Pending 20200519101057000007 create_recovery_addresses Pending 20200601101000000000 create_messages Pending 20200601101000000001 create_messages Pending 20200601101000000002 create_messages Pending 20200601101000000003 create_messages Pending 20200605111551000000 messages Pending 20200605111551000001 messages Pending 20200605111551000002 messages Pending 20200605111551000003 messages Pending 20200605111551000004 messages Pending 20200605111551000005 messages Pending 20200605111551000006 messages Pending 20200605111551000007 messages Pending 20200605111551000008 messages Pending 20200605111551000009 messages Pending 20200605111551000010 messages Pending 20200605111551000011 messages Pending 20200607165100000000 settings Pending 20200607165100000001 settings Pending 20200607165100000002 settings Pending 20200607165100000003 settings Pending 20200607165100000004 settings Pending 20200705105359000000 rename_identities_schema Pending 20200810141652000000 flow_type Pending 20200810141652000001 flow_type Pending 20200810141652000002 flow_type Pending 20200810141652000003 flow_type Pending 20200810141652000004 flow_type Pending 20200810141652000005 flow_type Pending 20200810141652000006 flow_type Pending 20200810141652000007 flow_type Pending 20200810141652000008 flow_type Pending 20200810141652000009 flow_type Pending 20200810141652000010 flow_type Pending 20200810141652000011 flow_type Pending 20200810141652000012 flow_type Pending 20200810141652000013 flow_type Pending 20200810141652000014 flow_type Pending 20200810141652000015 flow_type Pending 20200810141652000016 flow_type Pending 20200810141652000017 flow_type Pending 20200810141652000018 flow_type Pending 20200810141652000019 flow_type Pending 20200810161022000000 flow_rename Pending 20200810161022000001 flow_rename Pending 20200810161022000002 flow_rename Pending 20200810161022000003 flow_rename Pending 20200810161022000004 flow_rename Pending 20200810161022000005 flow_rename Pending 20200810161022000006 flow_rename Pending 20200810161022000007 flow_rename Pending 20200810161022000008 flow_rename Pending 20200810162450000000 flow_fields_rename Pending 20200810162450000001 flow_fields_rename Pending 20200810162450000002 flow_fields_rename Pending 20200810162450000003 flow_fields_rename Pending 20200812124254000000 add_session_token Pending 20200812124254000001 add_session_token Pending 20200812124254000002 add_session_token Pending 20200812124254000003 add_session_token Pending 20200812124254000004 add_session_token Pending 20200812124254000005 add_session_token Pending 20200812124254000006 add_session_token Pending 20200812124254000007 add_session_token Pending 20200812160551000000 add_session_revoke Pending 20200812160551000001 add_session_revoke Pending 20200812160551000002 add_session_revoke Pending 20200812160551000003 add_session_revoke Pending 20200812160551000004 add_session_revoke Pending 20200812160551000005 add_session_revoke Pending 20200812160551000006 add_session_revoke Pending 20200812160551000007 add_session_revoke Pending 20200830121710000000 update_recovery_token Pending 20200830130642000000 add_verification_methods Pending 20200830130642000001 add_verification_methods Pending 20200830130642000002 add_verification_methods Pending 20200830130642000003 add_verification_methods Pending 20200830130642000004 add_verification_methods Pending 20200830130642000005 add_verification_methods Pending 20200830130642000006 add_verification_methods Pending 20200830130642000007 add_verification_methods Pending 20200830130642000008 add_verification_methods Pending 20200830130642000009 add_verification_methods Pending 20200830130642000010 add_verification_methods Pending 20200830130643000000 add_verification_methods Pending 20200830130644000000 add_verification_methods Pending 20200830130644000001 add_verification_methods Pending 20200830130645000000 add_verification_methods Pending 20200830130646000000 add_verification_methods Pending 20200830130646000001 add_verification_methods Pending 20200830130646000002 add_verification_methods Pending 20200830130646000003 add_verification_methods Pending 20200830130646000004 add_verification_methods Pending 20200830130646000005 add_verification_methods Pending 20200830130646000006 add_verification_methods Pending 20200830130646000007 add_verification_methods Pending 20200830130646000008 add_verification_methods Pending 20200830130646000009 add_verification_methods Pending 20200830130646000010 add_verification_methods Pending 20200830130646000011 add_verification_methods Pending 20200830154602000000 add_verification_token Pending 20200830154602000001 add_verification_token Pending 20200830154602000002 add_verification_token Pending 20200830154602000003 add_verification_token Pending 20200830154602000004 add_verification_token Pending 20200830172221000000 recovery_token_expires Pending 20200830172221000001 recovery_token_expires Pending 20200830172221000002 recovery_token_expires Pending 20200830172221000003 recovery_token_expires Pending 20200830172221000004 recovery_token_expires Pending 20200830172221000005 recovery_token_expires Pending 20200830172221000006 recovery_token_expires Pending 20200830172221000007 recovery_token_expires Pending 20200830172221000008 recovery_token_expires Pending 20200830172221000009 recovery_token_expires Pending 20200830172221000010 recovery_token_expires Pending 20200830172221000011 recovery_token_expires Pending 20200830172221000012 recovery_token_expires Pending 20200830172221000013 recovery_token_expires Pending 20200830172221000014 recovery_token_expires Pending 20200830172221000015 recovery_token_expires Pending 20200830172221000016 recovery_token_expires Pending 20200830172221000017 recovery_token_expires Pending 20200830172221000018 recovery_token_expires Pending 20200830172221000019 recovery_token_expires Pending 20200830172221000020 recovery_token_expires Pending 20200830172221000021 recovery_token_expires Pending 20200830172221000022 recovery_token_expires Pending 20200830172221000023 recovery_token_expires Pending 20200830172221000024 recovery_token_expires Pending 20200831110752000000 identity_verifiable_address_remove_code Pending 20200831110752000001 identity_verifiable_address_remove_code Pending 20200831110752000002 identity_verifiable_address_remove_code Pending 20200831110752000003 identity_verifiable_address_remove_code Pending 20200831110752000004 identity_verifiable_address_remove_code Pending 20200831110752000005 identity_verifiable_address_remove_code Pending 20200831110752000006 identity_verifiable_address_remove_code Pending 20200831110752000007 identity_verifiable_address_remove_code Pending 20200831110752000008 identity_verifiable_address_remove_code Pending 20200831110752000009 identity_verifiable_address_remove_code Pending 20200831110752000010 identity_verifiable_address_remove_code Pending 20200831110752000011 identity_verifiable_address_remove_code Pending 20200831110752000012 identity_verifiable_address_remove_code Pending 20200831110752000013 identity_verifiable_address_remove_code Pending 20200831110752000014 identity_verifiable_address_remove_code Pending 20200831110752000015 identity_verifiable_address_remove_code Pending 20200831110752000016 identity_verifiable_address_remove_code Pending 20200831110752000017 identity_verifiable_address_remove_code Pending 20200831110752000018 identity_verifiable_address_remove_code Pending 20200831110752000019 identity_verifiable_address_remove_code Pending 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending The SQL statements to be executed from top to bottom are: ------------ 20191100000001000000 - identities ------------ CREATE TABLE "identities" ( "id" TEXT PRIMARY KEY, "traits_schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ------------ 20191100000001000001 - identities ------------ CREATE TABLE "identity_credential_types" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL ) ------------ 20191100000001000002 - identities ------------ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name) ------------ 20191100000001000003 - identities ------------ CREATE TABLE "identity_credentials" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON DELETE cascade ) ------------ 20191100000001000004 - identities ------------ CREATE TABLE "identity_credential_identifiers" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON DELETE cascade ) ------------ 20191100000001000005 - identities ------------ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ------------ 20191100000002000000 - requests ------------ CREATE TABLE "selfservice_login_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ------------ 20191100000002000001 - requests ------------ CREATE TABLE "selfservice_login_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_login_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_login_request_id) REFERENCES selfservice_login_requests (id) ON DELETE cascade ) ------------ 20191100000002000002 - requests ------------ CREATE TABLE "selfservice_registration_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ------------ 20191100000002000003 - requests ------------ CREATE TABLE "selfservice_registration_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_registration_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_registration_request_id) REFERENCES selfservice_registration_requests (id) ON DELETE cascade ) ------------ 20191100000002000004 - requests ------------ CREATE TABLE "selfservice_profile_management_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ------------ 20191100000003000000 - sessions ------------ CREATE TABLE "sessions" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ------------ 20191100000004000000 - errors ------------ CREATE TABLE "selfservice_errors" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME NOT NULL, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ------------ 20191100000006000000 - courier ------------ CREATE TABLE "courier_messages" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ------------ 20191100000007000000 - errors ------------ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" TEXT NOT NULL DEFAULT ''; ------------ 20191100000007000001 - errors ------------ ------------ 20191100000007000002 - errors ------------ ------------ 20191100000007000003 - errors ------------ ------------ 20191100000008000000 - selfservice_verification ------------ CREATE TABLE "identity_verifiable_addresses" ( "id" TEXT PRIMARY KEY, "code" TEXT NOT NULL, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ) ------------ 20191100000008000001 - selfservice_verification ------------ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ------------ 20191100000008000002 - selfservice_verification ------------ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code) ------------ 20191100000008000003 - selfservice_verification ------------ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value) ------------ 20191100000008000004 - selfservice_verification ------------ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value) ------------ 20191100000008000005 - selfservice_verification ------------ CREATE TABLE "selfservice_verification_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ------------ 20191100000010000000 - errors ------------ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ) ------------ 20191100000010000001 - errors ------------ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors" ------------ 20191100000010000002 - errors ------------ DROP TABLE "selfservice_errors" ------------ 20191100000010000003 - errors ------------ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ------------ 20191100000010000004 - errors ------------ ------------ 20191100000011000000 - courier_body_type ------------ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ------------ 20191100000011000001 - courier_body_type ------------ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at) SELECT id, type, status, body, subject, recipient, created_at, updated_at FROM "courier_messages" ------------ 20191100000011000002 - courier_body_type ------------ DROP TABLE "courier_messages" ------------ 20191100000011000003 - courier_body_type ------------ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ------------ 20191100000012000000 - login_request_forced ------------ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ------------ 20191100000012000001 - login_request_forced ------------ ------------ 20191100000012000002 - login_request_forced ------------ ------------ 20191100000012000003 - login_request_forced ------------ ------------ 20200317160354000000 - create_profile_request_forms ------------ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_profile_management_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ------------ 20200317160354000001 - create_profile_request_forms ------------ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" TEXT ------------ 20200317160354000002 - create_profile_request_forms ------------ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests ------------ 20200317160354000003 - create_profile_request_forms ------------ CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ------------ 20200317160354000004 - create_profile_request_forms ------------ INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) SELECT id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method FROM "selfservice_profile_management_requests" ------------ 20200317160354000005 - create_profile_request_forms ------------ DROP TABLE "selfservice_profile_management_requests" ------------ 20200317160354000006 - create_profile_request_forms ------------ ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ------------ 20200401183443000000 - continuity_containers ------------ CREATE TABLE "continuity_containers" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ------------ 20200402142539000000 - rename_profile_flows ------------ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id" ------------ 20200402142539000001 - rename_profile_flows ------------ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods" ------------ 20200402142539000002 - rename_profile_flows ------------ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ------------ 20200519101057000000 - create_recovery_addresses ------------ CREATE TABLE "identity_recovery_addresses" ( "id" TEXT PRIMARY KEY, "via" TEXT NOT NULL, "value" TEXT NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ) ------------ 20200519101057000001 - create_recovery_addresses ------------ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value) ------------ 20200519101057000002 - create_recovery_addresses ------------ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value) ------------ 20200519101057000003 - create_recovery_addresses ------------ CREATE TABLE "selfservice_recovery_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON DELETE cascade ) ------------ 20200519101057000004 - create_recovery_addresses ------------ CREATE TABLE "selfservice_recovery_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "config" TEXT NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ) ------------ 20200519101057000005 - create_recovery_addresses ------------ CREATE TABLE "identity_recovery_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ) ------------ 20200519101057000006 - create_recovery_addresses ------------ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token) ------------ 20200519101057000007 - create_recovery_addresses ------------ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ------------ 20200601101000000000 - create_messages ------------ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" TEXT; ------------ 20200601101000000001 - create_messages ------------ ------------ 20200601101000000002 - create_messages ------------ ------------ 20200601101000000003 - create_messages ------------ ------------ 20200605111551000000 - messages ------------ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" TEXT ------------ 20200605111551000001 - messages ------------ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" TEXT ------------ 20200605111551000002 - messages ------------ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" TEXT; ------------ 20200605111551000003 - messages ------------ ------------ 20200605111551000004 - messages ------------ ------------ 20200605111551000005 - messages ------------ ------------ 20200605111551000006 - messages ------------ ------------ 20200605111551000007 - messages ------------ ------------ 20200605111551000008 - messages ------------ ------------ 20200605111551000009 - messages ------------ ------------ 20200605111551000010 - messages ------------ ------------ 20200605111551000011 - messages ------------ ------------ 20200607165100000000 - settings ------------ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form' ------------ 20200607165100000001 - settings ------------ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ------------ 20200607165100000002 - settings ------------ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests" ------------ 20200607165100000003 - settings ------------ DROP TABLE "selfservice_settings_requests" ------------ 20200607165100000004 - settings ------------ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ------------ 20200705105359000000 - rename_identities_schema ------------ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ------------ 20200810141652000000 - flow_type ------------ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ------------ 20200810141652000001 - flow_type ------------ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ------------ 20200810141652000002 - flow_type ------------ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ------------ 20200810141652000003 - flow_type ------------ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ------------ 20200810141652000004 - flow_type ------------ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ------------ 20200810141652000005 - flow_type ------------ ------------ 20200810141652000006 - flow_type ------------ ------------ 20200810141652000007 - flow_type ------------ ------------ 20200810141652000008 - flow_type ------------ ------------ 20200810141652000009 - flow_type ------------ ------------ 20200810141652000010 - flow_type ------------ ------------ 20200810141652000011 - flow_type ------------ ------------ 20200810141652000012 - flow_type ------------ ------------ 20200810141652000013 - flow_type ------------ ------------ 20200810141652000014 - flow_type ------------ ------------ 20200810141652000015 - flow_type ------------ ------------ 20200810141652000016 - flow_type ------------ ------------ 20200810141652000017 - flow_type ------------ ------------ 20200810141652000018 - flow_type ------------ ------------ 20200810141652000019 - flow_type ------------ ------------ 20200810161022000000 - flow_rename ------------ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods" ------------ 20200810161022000001 - flow_rename ------------ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows" ------------ 20200810161022000002 - flow_rename ------------ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods" ------------ 20200810161022000003 - flow_rename ------------ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows" ------------ 20200810161022000004 - flow_rename ------------ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods" ------------ 20200810161022000005 - flow_rename ------------ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows" ------------ 20200810161022000006 - flow_rename ------------ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods" ------------ 20200810161022000007 - flow_rename ------------ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows" ------------ 20200810161022000008 - flow_rename ------------ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ------------ 20200810162450000000 - flow_fields_rename ------------ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id" ------------ 20200810162450000001 - flow_fields_rename ------------ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id" ------------ 20200810162450000002 - flow_fields_rename ------------ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id" ------------ 20200810162450000003 - flow_fields_rename ------------ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ------------ 20200812124254000000 - add_session_token ------------ DELETE FROM sessions ------------ 20200812124254000001 - add_session_token ------------ ALTER TABLE "sessions" ADD COLUMN "token" TEXT ------------ 20200812124254000002 - add_session_token ------------ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ------------ 20200812124254000003 - add_session_token ------------ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions" ------------ 20200812124254000004 - add_session_token ------------ DROP TABLE "sessions" ------------ 20200812124254000005 - add_session_token ------------ ALTER TABLE "_sessions_tmp" RENAME TO "sessions" ------------ 20200812124254000006 - add_session_token ------------ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token) ------------ 20200812124254000007 - add_session_token ------------ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ------------ 20200812160551000000 - add_session_revoke ------------ ALTER TABLE "sessions" ADD COLUMN "active" NUMERIC DEFAULT 'false'; ------------ 20200812160551000001 - add_session_revoke ------------ ------------ 20200812160551000002 - add_session_revoke ------------ ------------ 20200812160551000003 - add_session_revoke ------------ ------------ 20200812160551000004 - add_session_revoke ------------ ------------ 20200812160551000005 - add_session_revoke ------------ ------------ 20200812160551000006 - add_session_revoke ------------ ------------ 20200812160551000007 - add_session_revoke ------------ ------------ 20200830121710000000 - update_recovery_token ------------ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ------------ 20200830130642000000 - add_verification_methods ------------ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; ------------ 20200830130642000001 - add_verification_methods ------------ ------------ 20200830130642000002 - add_verification_methods ------------ ------------ 20200830130642000003 - add_verification_methods ------------ ------------ 20200830130642000004 - add_verification_methods ------------ ------------ 20200830130642000005 - add_verification_methods ------------ ------------ 20200830130642000006 - add_verification_methods ------------ ------------ 20200830130642000007 - add_verification_methods ------------ ------------ 20200830130642000008 - add_verification_methods ------------ ------------ 20200830130642000009 - add_verification_methods ------------ ------------ 20200830130642000010 - add_verification_methods ------------ ------------ 20200830130643000000 - add_verification_methods ------------ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ------------ 20200830130644000000 - add_verification_methods ------------ CREATE TABLE "selfservice_verification_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ------------ 20200830130644000001 - add_verification_methods ------------ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" TEXT; ------------ 20200830130645000000 - add_verification_methods ------------ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ------------ 20200830130646000000 - add_verification_methods ------------ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ) ------------ 20200830130646000001 - add_verification_methods ------------ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows" ------------ 20200830130646000002 - add_verification_methods ------------ DROP TABLE "selfservice_verification_flows" ------------ 20200830130646000003 - add_verification_methods ------------ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows" ------------ 20200830130646000004 - add_verification_methods ------------ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ) ------------ 20200830130646000005 - add_verification_methods ------------ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows" ------------ 20200830130646000006 - add_verification_methods ------------ DROP TABLE "selfservice_verification_flows" ------------ 20200830130646000007 - add_verification_methods ------------ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows" ------------ 20200830130646000008 - add_verification_methods ------------ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ) ------------ 20200830130646000009 - add_verification_methods ------------ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows" ------------ 20200830130646000010 - add_verification_methods ------------ DROP TABLE "selfservice_verification_flows" ------------ 20200830130646000011 - add_verification_methods ------------ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ------------ 20200830154602000000 - add_verification_token ------------ CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade ) ------------ 20200830154602000001 - add_verification_token ------------ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token) ------------ 20200830154602000002 - add_verification_token ------------ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token) ------------ 20200830154602000003 - add_verification_token ------------ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id) ------------ 20200830154602000004 - add_verification_token ------------ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ------------ 20200830172221000000 - recovery_token_expires ------------ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00' ------------ 20200830172221000001 - recovery_token_expires ------------ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00' ------------ 20200830172221000002 - recovery_token_expires ------------ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx" ------------ 20200830172221000003 - recovery_token_expires ------------ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx" ------------ 20200830172221000004 - recovery_token_expires ------------ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ------------ 20200830172221000005 - recovery_token_expires ------------ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token) ------------ 20200830172221000006 - recovery_token_expires ------------ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token) ------------ 20200830172221000007 - recovery_token_expires ------------ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens" ------------ 20200830172221000008 - recovery_token_expires ------------ DROP TABLE "identity_recovery_tokens" ------------ 20200830172221000009 - recovery_token_expires ------------ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ------------ 20200830172221000010 - recovery_token_expires ------------ ------------ 20200830172221000011 - recovery_token_expires ------------ ------------ 20200830172221000012 - recovery_token_expires ------------ ------------ 20200830172221000013 - recovery_token_expires ------------ ------------ 20200830172221000014 - recovery_token_expires ------------ ------------ 20200830172221000015 - recovery_token_expires ------------ ------------ 20200830172221000016 - recovery_token_expires ------------ ------------ 20200830172221000017 - recovery_token_expires ------------ ------------ 20200830172221000018 - recovery_token_expires ------------ ------------ 20200830172221000019 - recovery_token_expires ------------ ------------ 20200830172221000020 - recovery_token_expires ------------ ------------ 20200830172221000021 - recovery_token_expires ------------ ------------ 20200830172221000022 - recovery_token_expires ------------ ------------ 20200830172221000023 - recovery_token_expires ------------ ------------ 20200830172221000024 - recovery_token_expires ------------ ------------ 20200831110752000000 - identity_verifiable_address_remove_code ------------ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx" ------------ 20200831110752000001 - identity_verifiable_address_remove_code ------------ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx" ------------ 20200831110752000002 - identity_verifiable_address_remove_code ------------ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx" ------------ 20200831110752000003 - identity_verifiable_address_remove_code ------------ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx" ------------ 20200831110752000004 - identity_verifiable_address_remove_code ------------ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ------------ 20200831110752000005 - identity_verifiable_address_remove_code ------------ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ------------ 20200831110752000006 - identity_verifiable_address_remove_code ------------ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ------------ 20200831110752000007 - identity_verifiable_address_remove_code ------------ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses" ------------ 20200831110752000008 - identity_verifiable_address_remove_code ------------ DROP TABLE "identity_verifiable_addresses" ------------ 20200831110752000009 - identity_verifiable_address_remove_code ------------ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses" ------------ 20200831110752000010 - identity_verifiable_address_remove_code ------------ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx" ------------ 20200831110752000011 - identity_verifiable_address_remove_code ------------ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx" ------------ 20200831110752000012 - identity_verifiable_address_remove_code ------------ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ------------ 20200831110752000013 - identity_verifiable_address_remove_code ------------ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ------------ 20200831110752000014 - identity_verifiable_address_remove_code ------------ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ------------ 20200831110752000015 - identity_verifiable_address_remove_code ------------ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses" ------------ 20200831110752000016 - identity_verifiable_address_remove_code ------------ DROP TABLE "identity_verifiable_addresses" ------------ 20200831110752000017 - identity_verifiable_address_remove_code ------------ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ------------ 20200831110752000018 - identity_verifiable_address_remove_code ------------ ------------ 20200831110752000019 - identity_verifiable_address_remove_code ------------ ------------ 20200831110752000020 - identity_verifiable_address_remove_code ------------ ------------ 20200831110752000021 - identity_verifiable_address_remove_code ------------ ------------ 20201201161451000000 - credential_types_values ------------ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password') ------------ 20201201161451000001 - credential_types_values ------------ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ------------ SUCCESS ------------ Successfully applied migrations! stderr: ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-status_migrated.txt ================================================ stdout: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Applied 20200831110752000019 identity_verifiable_address_remove_code Applied 20200831110752000020 identity_verifiable_address_remove_code Applied 20200831110752000021 identity_verifiable_address_remove_code Applied 20201201161451000000 credential_types_values Applied 20201201161451000001 credential_types_values Applied stderr: ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-status_pre.txt ================================================ stdout: Version Name Status 20191100000001000000 identities Pending 20191100000001000001 identities Pending 20191100000001000002 identities Pending 20191100000001000003 identities Pending 20191100000001000004 identities Pending 20191100000001000005 identities Pending 20191100000002000000 requests Pending 20191100000002000001 requests Pending 20191100000002000002 requests Pending 20191100000002000003 requests Pending 20191100000002000004 requests Pending 20191100000003000000 sessions Pending 20191100000004000000 errors Pending 20191100000006000000 courier Pending 20191100000007000000 errors Pending 20191100000007000001 errors Pending 20191100000007000002 errors Pending 20191100000007000003 errors Pending 20191100000008000000 selfservice_verification Pending 20191100000008000001 selfservice_verification Pending 20191100000008000002 selfservice_verification Pending 20191100000008000003 selfservice_verification Pending 20191100000008000004 selfservice_verification Pending 20191100000008000005 selfservice_verification Pending 20191100000010000000 errors Pending 20191100000010000001 errors Pending 20191100000010000002 errors Pending 20191100000010000003 errors Pending 20191100000010000004 errors Pending 20191100000011000000 courier_body_type Pending 20191100000011000001 courier_body_type Pending 20191100000011000002 courier_body_type Pending 20191100000011000003 courier_body_type Pending 20191100000012000000 login_request_forced Pending 20191100000012000001 login_request_forced Pending 20191100000012000002 login_request_forced Pending 20191100000012000003 login_request_forced Pending 20200317160354000000 create_profile_request_forms Pending 20200317160354000001 create_profile_request_forms Pending 20200317160354000002 create_profile_request_forms Pending 20200317160354000003 create_profile_request_forms Pending 20200317160354000004 create_profile_request_forms Pending 20200317160354000005 create_profile_request_forms Pending 20200317160354000006 create_profile_request_forms Pending 20200401183443000000 continuity_containers Pending 20200402142539000000 rename_profile_flows Pending 20200402142539000001 rename_profile_flows Pending 20200402142539000002 rename_profile_flows Pending 20200519101057000000 create_recovery_addresses Pending 20200519101057000001 create_recovery_addresses Pending 20200519101057000002 create_recovery_addresses Pending 20200519101057000003 create_recovery_addresses Pending 20200519101057000004 create_recovery_addresses Pending 20200519101057000005 create_recovery_addresses Pending 20200519101057000006 create_recovery_addresses Pending 20200519101057000007 create_recovery_addresses Pending 20200601101000000000 create_messages Pending 20200601101000000001 create_messages Pending 20200601101000000002 create_messages Pending 20200601101000000003 create_messages Pending 20200605111551000000 messages Pending 20200605111551000001 messages Pending 20200605111551000002 messages Pending 20200605111551000003 messages Pending 20200605111551000004 messages Pending 20200605111551000005 messages Pending 20200605111551000006 messages Pending 20200605111551000007 messages Pending 20200605111551000008 messages Pending 20200605111551000009 messages Pending 20200605111551000010 messages Pending 20200605111551000011 messages Pending 20200607165100000000 settings Pending 20200607165100000001 settings Pending 20200607165100000002 settings Pending 20200607165100000003 settings Pending 20200607165100000004 settings Pending 20200705105359000000 rename_identities_schema Pending 20200810141652000000 flow_type Pending 20200810141652000001 flow_type Pending 20200810141652000002 flow_type Pending 20200810141652000003 flow_type Pending 20200810141652000004 flow_type Pending 20200810141652000005 flow_type Pending 20200810141652000006 flow_type Pending 20200810141652000007 flow_type Pending 20200810141652000008 flow_type Pending 20200810141652000009 flow_type Pending 20200810141652000010 flow_type Pending 20200810141652000011 flow_type Pending 20200810141652000012 flow_type Pending 20200810141652000013 flow_type Pending 20200810141652000014 flow_type Pending 20200810141652000015 flow_type Pending 20200810141652000016 flow_type Pending 20200810141652000017 flow_type Pending 20200810141652000018 flow_type Pending 20200810141652000019 flow_type Pending 20200810161022000000 flow_rename Pending 20200810161022000001 flow_rename Pending 20200810161022000002 flow_rename Pending 20200810161022000003 flow_rename Pending 20200810161022000004 flow_rename Pending 20200810161022000005 flow_rename Pending 20200810161022000006 flow_rename Pending 20200810161022000007 flow_rename Pending 20200810161022000008 flow_rename Pending 20200810162450000000 flow_fields_rename Pending 20200810162450000001 flow_fields_rename Pending 20200810162450000002 flow_fields_rename Pending 20200810162450000003 flow_fields_rename Pending 20200812124254000000 add_session_token Pending 20200812124254000001 add_session_token Pending 20200812124254000002 add_session_token Pending 20200812124254000003 add_session_token Pending 20200812124254000004 add_session_token Pending 20200812124254000005 add_session_token Pending 20200812124254000006 add_session_token Pending 20200812124254000007 add_session_token Pending 20200812160551000000 add_session_revoke Pending 20200812160551000001 add_session_revoke Pending 20200812160551000002 add_session_revoke Pending 20200812160551000003 add_session_revoke Pending 20200812160551000004 add_session_revoke Pending 20200812160551000005 add_session_revoke Pending 20200812160551000006 add_session_revoke Pending 20200812160551000007 add_session_revoke Pending 20200830121710000000 update_recovery_token Pending 20200830130642000000 add_verification_methods Pending 20200830130642000001 add_verification_methods Pending 20200830130642000002 add_verification_methods Pending 20200830130642000003 add_verification_methods Pending 20200830130642000004 add_verification_methods Pending 20200830130642000005 add_verification_methods Pending 20200830130642000006 add_verification_methods Pending 20200830130642000007 add_verification_methods Pending 20200830130642000008 add_verification_methods Pending 20200830130642000009 add_verification_methods Pending 20200830130642000010 add_verification_methods Pending 20200830130643000000 add_verification_methods Pending 20200830130644000000 add_verification_methods Pending 20200830130644000001 add_verification_methods Pending 20200830130645000000 add_verification_methods Pending 20200830130646000000 add_verification_methods Pending 20200830130646000001 add_verification_methods Pending 20200830130646000002 add_verification_methods Pending 20200830130646000003 add_verification_methods Pending 20200830130646000004 add_verification_methods Pending 20200830130646000005 add_verification_methods Pending 20200830130646000006 add_verification_methods Pending 20200830130646000007 add_verification_methods Pending 20200830130646000008 add_verification_methods Pending 20200830130646000009 add_verification_methods Pending 20200830130646000010 add_verification_methods Pending 20200830130646000011 add_verification_methods Pending 20200830154602000000 add_verification_token Pending 20200830154602000001 add_verification_token Pending 20200830154602000002 add_verification_token Pending 20200830154602000003 add_verification_token Pending 20200830154602000004 add_verification_token Pending 20200830172221000000 recovery_token_expires Pending 20200830172221000001 recovery_token_expires Pending 20200830172221000002 recovery_token_expires Pending 20200830172221000003 recovery_token_expires Pending 20200830172221000004 recovery_token_expires Pending 20200830172221000005 recovery_token_expires Pending 20200830172221000006 recovery_token_expires Pending 20200830172221000007 recovery_token_expires Pending 20200830172221000008 recovery_token_expires Pending 20200830172221000009 recovery_token_expires Pending 20200830172221000010 recovery_token_expires Pending 20200830172221000011 recovery_token_expires Pending 20200830172221000012 recovery_token_expires Pending 20200830172221000013 recovery_token_expires Pending 20200830172221000014 recovery_token_expires Pending 20200830172221000015 recovery_token_expires Pending 20200830172221000016 recovery_token_expires Pending 20200830172221000017 recovery_token_expires Pending 20200830172221000018 recovery_token_expires Pending 20200830172221000019 recovery_token_expires Pending 20200830172221000020 recovery_token_expires Pending 20200830172221000021 recovery_token_expires Pending 20200830172221000022 recovery_token_expires Pending 20200830172221000023 recovery_token_expires Pending 20200830172221000024 recovery_token_expires Pending 20200831110752000000 identity_verifiable_address_remove_code Pending 20200831110752000001 identity_verifiable_address_remove_code Pending 20200831110752000002 identity_verifiable_address_remove_code Pending 20200831110752000003 identity_verifiable_address_remove_code Pending 20200831110752000004 identity_verifiable_address_remove_code Pending 20200831110752000005 identity_verifiable_address_remove_code Pending 20200831110752000006 identity_verifiable_address_remove_code Pending 20200831110752000007 identity_verifiable_address_remove_code Pending 20200831110752000008 identity_verifiable_address_remove_code Pending 20200831110752000009 identity_verifiable_address_remove_code Pending 20200831110752000010 identity_verifiable_address_remove_code Pending 20200831110752000011 identity_verifiable_address_remove_code Pending 20200831110752000012 identity_verifiable_address_remove_code Pending 20200831110752000013 identity_verifiable_address_remove_code Pending 20200831110752000014 identity_verifiable_address_remove_code Pending 20200831110752000015 identity_verifiable_address_remove_code Pending 20200831110752000016 identity_verifiable_address_remove_code Pending 20200831110752000017 identity_verifiable_address_remove_code Pending 20200831110752000018 identity_verifiable_address_remove_code Pending 20200831110752000019 identity_verifiable_address_remove_code Pending 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending stderr: ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-status_two_steps_rolled_back.txt ================================================ stdout: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Applied 20200831110752000019 identity_verifiable_address_remove_code Applied 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending stderr: ================================================ FILE: oryx/popx/.snapshots/TestMigrateSQLUp-status_two_versions_rolled_back.txt ================================================ stdout: Version Name Status 20191100000001000000 identities Applied 20191100000001000001 identities Applied 20191100000001000002 identities Applied 20191100000001000003 identities Applied 20191100000001000004 identities Applied 20191100000001000005 identities Applied 20191100000002000000 requests Applied 20191100000002000001 requests Applied 20191100000002000002 requests Applied 20191100000002000003 requests Applied 20191100000002000004 requests Applied 20191100000003000000 sessions Applied 20191100000004000000 errors Applied 20191100000006000000 courier Applied 20191100000007000000 errors Applied 20191100000007000001 errors Applied 20191100000007000002 errors Applied 20191100000007000003 errors Applied 20191100000008000000 selfservice_verification Applied 20191100000008000001 selfservice_verification Applied 20191100000008000002 selfservice_verification Applied 20191100000008000003 selfservice_verification Applied 20191100000008000004 selfservice_verification Applied 20191100000008000005 selfservice_verification Applied 20191100000010000000 errors Applied 20191100000010000001 errors Applied 20191100000010000002 errors Applied 20191100000010000003 errors Applied 20191100000010000004 errors Applied 20191100000011000000 courier_body_type Applied 20191100000011000001 courier_body_type Applied 20191100000011000002 courier_body_type Applied 20191100000011000003 courier_body_type Applied 20191100000012000000 login_request_forced Applied 20191100000012000001 login_request_forced Applied 20191100000012000002 login_request_forced Applied 20191100000012000003 login_request_forced Applied 20200317160354000000 create_profile_request_forms Applied 20200317160354000001 create_profile_request_forms Applied 20200317160354000002 create_profile_request_forms Applied 20200317160354000003 create_profile_request_forms Applied 20200317160354000004 create_profile_request_forms Applied 20200317160354000005 create_profile_request_forms Applied 20200317160354000006 create_profile_request_forms Applied 20200401183443000000 continuity_containers Applied 20200402142539000000 rename_profile_flows Applied 20200402142539000001 rename_profile_flows Applied 20200402142539000002 rename_profile_flows Applied 20200519101057000000 create_recovery_addresses Applied 20200519101057000001 create_recovery_addresses Applied 20200519101057000002 create_recovery_addresses Applied 20200519101057000003 create_recovery_addresses Applied 20200519101057000004 create_recovery_addresses Applied 20200519101057000005 create_recovery_addresses Applied 20200519101057000006 create_recovery_addresses Applied 20200519101057000007 create_recovery_addresses Applied 20200601101000000000 create_messages Applied 20200601101000000001 create_messages Applied 20200601101000000002 create_messages Applied 20200601101000000003 create_messages Applied 20200605111551000000 messages Applied 20200605111551000001 messages Applied 20200605111551000002 messages Applied 20200605111551000003 messages Applied 20200605111551000004 messages Applied 20200605111551000005 messages Applied 20200605111551000006 messages Applied 20200605111551000007 messages Applied 20200605111551000008 messages Applied 20200605111551000009 messages Applied 20200605111551000010 messages Applied 20200605111551000011 messages Applied 20200607165100000000 settings Applied 20200607165100000001 settings Applied 20200607165100000002 settings Applied 20200607165100000003 settings Applied 20200607165100000004 settings Applied 20200705105359000000 rename_identities_schema Applied 20200810141652000000 flow_type Applied 20200810141652000001 flow_type Applied 20200810141652000002 flow_type Applied 20200810141652000003 flow_type Applied 20200810141652000004 flow_type Applied 20200810141652000005 flow_type Applied 20200810141652000006 flow_type Applied 20200810141652000007 flow_type Applied 20200810141652000008 flow_type Applied 20200810141652000009 flow_type Applied 20200810141652000010 flow_type Applied 20200810141652000011 flow_type Applied 20200810141652000012 flow_type Applied 20200810141652000013 flow_type Applied 20200810141652000014 flow_type Applied 20200810141652000015 flow_type Applied 20200810141652000016 flow_type Applied 20200810141652000017 flow_type Applied 20200810141652000018 flow_type Applied 20200810141652000019 flow_type Applied 20200810161022000000 flow_rename Applied 20200810161022000001 flow_rename Applied 20200810161022000002 flow_rename Applied 20200810161022000003 flow_rename Applied 20200810161022000004 flow_rename Applied 20200810161022000005 flow_rename Applied 20200810161022000006 flow_rename Applied 20200810161022000007 flow_rename Applied 20200810161022000008 flow_rename Applied 20200810162450000000 flow_fields_rename Applied 20200810162450000001 flow_fields_rename Applied 20200810162450000002 flow_fields_rename Applied 20200810162450000003 flow_fields_rename Applied 20200812124254000000 add_session_token Applied 20200812124254000001 add_session_token Applied 20200812124254000002 add_session_token Applied 20200812124254000003 add_session_token Applied 20200812124254000004 add_session_token Applied 20200812124254000005 add_session_token Applied 20200812124254000006 add_session_token Applied 20200812124254000007 add_session_token Applied 20200812160551000000 add_session_revoke Applied 20200812160551000001 add_session_revoke Applied 20200812160551000002 add_session_revoke Applied 20200812160551000003 add_session_revoke Applied 20200812160551000004 add_session_revoke Applied 20200812160551000005 add_session_revoke Applied 20200812160551000006 add_session_revoke Applied 20200812160551000007 add_session_revoke Applied 20200830121710000000 update_recovery_token Applied 20200830130642000000 add_verification_methods Applied 20200830130642000001 add_verification_methods Applied 20200830130642000002 add_verification_methods Applied 20200830130642000003 add_verification_methods Applied 20200830130642000004 add_verification_methods Applied 20200830130642000005 add_verification_methods Applied 20200830130642000006 add_verification_methods Applied 20200830130642000007 add_verification_methods Applied 20200830130642000008 add_verification_methods Applied 20200830130642000009 add_verification_methods Applied 20200830130642000010 add_verification_methods Applied 20200830130643000000 add_verification_methods Applied 20200830130644000000 add_verification_methods Applied 20200830130644000001 add_verification_methods Applied 20200830130645000000 add_verification_methods Applied 20200830130646000000 add_verification_methods Applied 20200830130646000001 add_verification_methods Applied 20200830130646000002 add_verification_methods Applied 20200830130646000003 add_verification_methods Applied 20200830130646000004 add_verification_methods Applied 20200830130646000005 add_verification_methods Applied 20200830130646000006 add_verification_methods Applied 20200830130646000007 add_verification_methods Applied 20200830130646000008 add_verification_methods Applied 20200830130646000009 add_verification_methods Applied 20200830130646000010 add_verification_methods Applied 20200830130646000011 add_verification_methods Applied 20200830154602000000 add_verification_token Applied 20200830154602000001 add_verification_token Applied 20200830154602000002 add_verification_token Applied 20200830154602000003 add_verification_token Applied 20200830154602000004 add_verification_token Applied 20200830172221000000 recovery_token_expires Applied 20200830172221000001 recovery_token_expires Applied 20200830172221000002 recovery_token_expires Applied 20200830172221000003 recovery_token_expires Applied 20200830172221000004 recovery_token_expires Applied 20200830172221000005 recovery_token_expires Applied 20200830172221000006 recovery_token_expires Applied 20200830172221000007 recovery_token_expires Applied 20200830172221000008 recovery_token_expires Applied 20200830172221000009 recovery_token_expires Applied 20200830172221000010 recovery_token_expires Applied 20200830172221000011 recovery_token_expires Applied 20200830172221000012 recovery_token_expires Applied 20200830172221000013 recovery_token_expires Applied 20200830172221000014 recovery_token_expires Applied 20200830172221000015 recovery_token_expires Applied 20200830172221000016 recovery_token_expires Applied 20200830172221000017 recovery_token_expires Applied 20200830172221000018 recovery_token_expires Applied 20200830172221000019 recovery_token_expires Applied 20200830172221000020 recovery_token_expires Applied 20200830172221000021 recovery_token_expires Applied 20200830172221000022 recovery_token_expires Applied 20200830172221000023 recovery_token_expires Applied 20200830172221000024 recovery_token_expires Applied 20200831110752000000 identity_verifiable_address_remove_code Applied 20200831110752000001 identity_verifiable_address_remove_code Applied 20200831110752000002 identity_verifiable_address_remove_code Applied 20200831110752000003 identity_verifiable_address_remove_code Applied 20200831110752000004 identity_verifiable_address_remove_code Applied 20200831110752000005 identity_verifiable_address_remove_code Applied 20200831110752000006 identity_verifiable_address_remove_code Applied 20200831110752000007 identity_verifiable_address_remove_code Applied 20200831110752000008 identity_verifiable_address_remove_code Applied 20200831110752000009 identity_verifiable_address_remove_code Applied 20200831110752000010 identity_verifiable_address_remove_code Applied 20200831110752000011 identity_verifiable_address_remove_code Applied 20200831110752000012 identity_verifiable_address_remove_code Applied 20200831110752000013 identity_verifiable_address_remove_code Applied 20200831110752000014 identity_verifiable_address_remove_code Applied 20200831110752000015 identity_verifiable_address_remove_code Applied 20200831110752000016 identity_verifiable_address_remove_code Applied 20200831110752000017 identity_verifiable_address_remove_code Applied 20200831110752000018 identity_verifiable_address_remove_code Pending 20200831110752000019 identity_verifiable_address_remove_code Pending 20200831110752000020 identity_verifiable_address_remove_code Pending 20200831110752000021 identity_verifiable_address_remove_code Pending 20201201161451000000 credential_types_values Pending 20201201161451000001 credential_types_values Pending stderr: ================================================ FILE: oryx/popx/cmd.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "context" "fmt" "time" "github.com/spf13/cobra" "github.com/ory/x/cmdx" "github.com/ory/x/errorsx" "github.com/ory/x/flagx" ) type MigrationProvider interface { MigrationStatus(context.Context) (MigrationStatuses, error) MigrateUp(context.Context) error MigrateDown(context.Context, int) error } type MigrationPreparer interface { PrepareMigration(context.Context) error } func RegisterMigrateSQLUpFlags(cmd *cobra.Command) *cobra.Command { cmd.Flags().BoolP("yes", "y", false, "If set all confirmation requests are accepted without user interaction.") return cmd } func NewMigrateSQLUpCmd(runE func(cmd *cobra.Command, args []string) error) *cobra.Command { return RegisterMigrateSQLUpFlags(&cobra.Command{ Use: "up [database_url]", Args: cobra.RangeArgs(0, 1), Short: "Apply all pending SQL migrations", Long: `This command applies all pending SQL migrations for Ory {{ title .Root.Name }}. :::warning Before running this command, create a backup of your database. This command can be destructive as it may apply changes that cannot be easily reverted. Run this command close to the SQL instance (same VPC / same machine). ::: It is recommended to review the migrations before running them. You can do this by running the command without the --yes flag: DSN=... {{ .CommandPath }} -e`, Example: `Apply all pending migrations: DSN=... {{ .CommandPath }} -e Apply all pending migrations: DSN=... {{ .CommandPath }} -e --yes`, RunE: runE, }) } func MigrateSQLUp(cmd *cobra.Command, p MigrationProvider) (err error) { // convert migration tables if prep, ok := p.(MigrationPreparer); ok { if err := prep.PrepareMigration(cmd.Context()); err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not convert the migration table:\n%+v\n", err) return cmdx.FailSilently(cmd) } } // print migration status _, _ = fmt.Fprintln(cmd.OutOrStdout(), "The migration plan is as follows:") // print migration status status, err := p.MigrationStatus(cmd.Context()) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get the migration status:\n%+v\n", errorsx.WithStack(err)) return cmdx.FailSilently(cmd) } cmdx.PrintTable(cmd, status) _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe SQL statements to be executed from top to bottom are:\n\n") for i := range status { if status[i].State == Pending { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ %s - %s ------------\n", status[i].Version, status[i].Name) _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n", status[i].ContentUp) } } if !flagx.MustGetBool(cmd, "yes") { _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "To skip the next question use flag --yes (at your own risk).") if !cmdx.AskForConfirmation("Do you wish to execute this migration plan?", cmd.InOrStdin(), cmd.OutOrStdout()) { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ WARNING ------------\n") _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Migration aborted.") return nil } } // apply migrations if err := p.MigrateUp(cmd.Context()); err != nil { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ ERROR ------------\n") _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not apply migrations:\n%+v\n", errorsx.WithStack(err)) return cmdx.FailSilently(cmd) } _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ SUCCESS ------------\n") _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Successfully applied migrations!") return nil } func RegisterMigrateSQLDownFlags(cmd *cobra.Command) *cobra.Command { cmd.Flags().BoolP("yes", "y", false, "If set all confirmation requests are accepted without user interaction.") cmd.Flags().Int("steps", 0, "The number of migrations to roll back.") return cmd } func NewMigrateSQLDownCmd(runE func(cmd *cobra.Command, args []string) error) *cobra.Command { return RegisterMigrateSQLDownFlags(&cobra.Command{ Use: "down [database_url]", Args: cobra.RangeArgs(0, 1), Short: "Rollback the last applied SQL migrations", Long: `This command rolls back the last applied SQL migrations for Ory {{ title .Root.Name }}. :::warning Before running this command, create a backup of your database. This command can be destructive as it may revert changes made by previous migrations. Run this command close to the SQL instance (same VPC / same machine). ::: It is recommended to review the migrations before running them. You can do this by running the command without the --yes flag: DSN=... {{ .CommandPath }} -e`, Example: `See the current migration status: DSN=... {{ .CommandPath }} -e Rollback the last 10 migrations: {{ .CommandPath }} $DSN --steps 10 Rollback the last 10 migrations without confirmation: DSN=... {{ .CommandPath }} -e --yes --steps 10`, RunE: runE, }) } func MigrateSQLDown(cmd *cobra.Command, p MigrationProvider) (err error) { steps := flagx.MustGetInt(cmd, "steps") if steps < 0 { _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Flag --steps must be larger than 0.") return cmdx.FailSilently(cmd) } // convert migration tables if prep, ok := p.(MigrationPreparer); ok { if err := prep.PrepareMigration(cmd.Context()); err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not convert the migration table:\n%+v\n", err) return cmdx.FailSilently(cmd) } } status, err := p.MigrationStatus(cmd.Context()) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get the migration status:\n%+v\n", errorsx.WithStack(err)) return cmdx.FailSilently(cmd) } // Now we need to rollback the last `steps` migrations that have a status of "Applied": var count int var rollingBack int for i := len(status) - 1; i >= 0; i-- { if status[i].State == Applied { count++ if steps > 0 && count <= steps { status[i].State = "Rollback" rollingBack++ } } } // print migration status _, _ = fmt.Fprintln(cmd.OutOrStdout(), "The migration plan is as follows:") cmdx.PrintTable(cmd, status) if rollingBack < 1 { _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "") _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "There are apparently no migrations to roll back.") _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Please provide the --steps argument with a value larger than 0.") _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "") return cmdx.FailSilently(cmd) } _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe SQL statements to be executed from top to bottom are:\n\n") for i := len(status) - 1; i >= 0; i-- { if status[i].State == "Rollback" { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ %s - %s ------------\n", status[i].Version, status[i].Name) _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n", status[i].ContentDown) } } if !flagx.MustGetBool(cmd, "yes") { _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "To skip the next question use flag --yes (at your own risk).") if !cmdx.AskForConfirmation("Do you wish to execute this migration plan?", cmd.InOrStdin(), cmd.OutOrStdout()) { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ WARNING ------------\n") _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Migration aborted.") return nil } } // apply migrations if err := p.MigrateDown(cmd.Context(), rollingBack); err != nil { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ ERROR ------------\n") _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not apply migrations:\n%+v\n", errorsx.WithStack(err)) return cmdx.FailSilently(cmd) } _, _ = fmt.Fprintf(cmd.OutOrStdout(), "------------ SUCCESS ------------\n") _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Successfully applied migrations!") return nil } func RegisterMigrateStatusFlags(cmd *cobra.Command) *cobra.Command { cmdx.RegisterFormatFlags(cmd.PersistentFlags()) cmd.Flags().BoolP("read-from-env", "e", false, "If set, reads the database connection string from the environment variable DSN or config file key dsn.") cmd.Flags().Bool("block", false, "Block until all migrations have been applied") return cmd } func NewMigrateSQLStatusCmd(runE func(cmd *cobra.Command, args []string) error) *cobra.Command { return RegisterMigrateStatusFlags(&cobra.Command{ Use: "status [database_url]", Short: "Display the current migration status", Long: `This command shows the current migration status for Ory {{ title .Root.Name }}. You can use this command to check which migrations have been applied and which are pending. To block until all migrations are applied, use the --block flag: DSN=... {{ .CommandPath }} -e --block`, Example: `See the current migration status: DSN=... {{ .CommandPath }} -e Block until all migrations are applied: DSN=... {{ .CommandPath }} -e --block`, RunE: runE, }) } func MigrateStatus(cmd *cobra.Command, p MigrationProvider) (err error) { block := flagx.MustGetBool(cmd, "block") ctx := cmd.Context() s, err := p.MigrationStatus(ctx) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get migration status: %+v\n", err) return cmdx.FailSilently(cmd) } for block && s.HasPending() { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Waiting for migrations to finish...\n") for _, m := range s { if m.State == Pending { _, _ = fmt.Fprintf(cmd.OutOrStdout(), " - %s\n", m.Name) } } time.Sleep(time.Second) s, err = p.MigrationStatus(ctx) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get migration status: %+v\n", err) return cmdx.FailSilently(cmd) } } cmdx.PrintTable(cmd, s) return nil } ================================================ FILE: oryx/popx/db_columns.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "fmt" "github.com/ory/pop/v6" ) func DBColumns[T any](quoter Quoter) string { return (&pop.Model{Value: new(T)}).Columns().QuotedString(quoter) } // IndexHint returns the table name including the index hint, if the database // supports it. func IndexHint(conn *pop.Connection, table string, index string) string { if conn.Dialect.Name() == "cockroach" { return table + "@" + index } return table } func DBColumnsExcluding[T any](quoter Quoter, exclude ...string) string { cols := (&pop.Model{Value: new(T)}).Columns() for _, e := range exclude { cols.Remove(e) } return cols.QuotedString(quoter) } type ( AliasQuoter struct { Alias string Quoter Quoter } Quoter interface { Quote(key string) string } ) func (pq *AliasQuoter) Quote(key string) string { return fmt.Sprintf("%s.%s", pq.Quoter.Quote(pq.Alias), pq.Quoter.Quote(key)) } ================================================ FILE: oryx/popx/loggers.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "fmt" "testing" "github.com/ory/pop/v6" "github.com/ory/pop/v6/logging" ) func formatter(lvl logging.Level, s string, args ...interface{}) string { if pop.Debug == false { return "" } if lvl == logging.SQL { if len(args) > 0 { xargs := make([]string, len(args)) for i, a := range args { switch a.(type) { case string: xargs[i] = fmt.Sprintf("%q", a) default: xargs[i] = fmt.Sprintf("%v", a) } } s = fmt.Sprintf("%s - %s | %s", lvl, s, xargs) } else { s = fmt.Sprintf("%s - %s", lvl, s) } } else { s = fmt.Sprintf(s, args...) s = fmt.Sprintf("%s - %s", lvl, s) } return s } func TestingLogger(t testing.TB) func(lvl logging.Level, s string, args ...interface{}) { return func(lvl logging.Level, s string, args ...interface{}) { if line := formatter(lvl, s, args...); len(line) > 0 { t.Log(line) } } } func NullLogger() func(lvl logging.Level, s string, args ...interface{}) { return func(lvl logging.Level, s string, args ...interface{}) { // do nothing } } ================================================ FILE: oryx/popx/match.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "regexp" "github.com/pkg/errors" "github.com/ory/pop/v6" ) var MigrationFileRegexp = regexp.MustCompile( `^(\d+)_([^.]+)(\.[a-z0-9]+)?(\.autocommit)?\.(up|down)\.(sql)$`, ) const ( // Human-readable constants for the regex capture groups versionIdx = iota + 1 nameIdx dbTypeIdx autocommitIdx directionIdx typeIdx ) // match holds the information parsed from a migration filename. type match struct { Version string Name string DBType string Direction string Type string Autocommit bool } // parseMigrationFilename parses a migration filename. func parseMigrationFilename(filename string) (*match, error) { matches := MigrationFileRegexp.FindAllStringSubmatch(filename, -1) if len(matches) == 0 { return nil, nil } m := matches[0] var ( autocommit bool dbType string ) if m[dbTypeIdx] == ".autocommit" { // A special case where autocommit group moves forward to the 3rd index. autocommit = true dbType = "all" } else if m[dbTypeIdx] == "" { dbType = "all" } else { dbType = pop.CanonicalDialect(m[dbTypeIdx][1:]) if !pop.DialectSupported(dbType) { return nil, errors.Errorf("unsupported dialect %s", dbType) } } if m[typeIdx] == "fizz" && dbType != "all" { return nil, errors.Errorf("invalid database type %q, expected \"all\" because fizz is database type independent", dbType) } if m[autocommitIdx] == ".autocommit" { autocommit = true } else if m[autocommitIdx] != "" { return nil, errors.Errorf("invalid autocommit flag %q", m[autocommitIdx]) } return &match{ Version: m[versionIdx], Name: m[nameIdx], DBType: dbType, Autocommit: autocommit, Direction: m[directionIdx], Type: m[typeIdx], }, nil } ================================================ FILE: oryx/popx/migration_box.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "database/sql" "fmt" "io/fs" "path" "regexp" "slices" "sort" "strings" "testing" "time" "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/ory/pop/v6" "github.com/ory/x/logrusx" ) type ( // MigrationBox is a embed migration box. MigrationBox struct { c *pop.Connection migrationsUp Migrations migrationsDown Migrations perMigrationTimeout time.Duration l *logrusx.Logger migrationContent MigrationContent } MigrationContent func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error) MigrationBoxOption func(*MigrationBox) ) func WithTemplateValues(v map[string]interface{}) MigrationBoxOption { return func(m *MigrationBox) { m.migrationContent = ParameterizedMigrationContent(v) } } func WithMigrationContentMiddleware(middleware func(content string, err error) (string, error)) MigrationBoxOption { return func(m *MigrationBox) { prev := m.migrationContent m.migrationContent = func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error) { return middleware(prev(mf, c, r, usingTemplate)) } } } // WithGoMigrations adds migrations that have a custom migration runner. // TEST THEM THOROUGHLY! // It will be very hard to fix a buggy migration. func WithGoMigrations(migrations Migrations) MigrationBoxOption { return func(mb *MigrationBox) { for _, m := range migrations { switch m.Direction { case "up": mb.migrationsUp = append(mb.migrationsUp, m) case "down": mb.migrationsDown = append(mb.migrationsDown, m) default: panic(fmt.Sprintf("unknown migration direction %q for %q", m.Direction, m.Version)) } } } } func WithPerMigrationTimeout(timeout time.Duration) MigrationBoxOption { return func(m *MigrationBox) { m.perMigrationTimeout = timeout } } var testdataPattern = regexp.MustCompile(`^(\d+)_testdata(|\.[a-zA-Z0-9]+).sql$`) // WithTestdata adds testdata to the migration box. func WithTestdata(t *testing.T, testdata fs.FS) MigrationBoxOption { return func(m *MigrationBox) { require.NoError(t, fs.WalkDir(testdata, ".", func(path string, info fs.DirEntry, err error) error { if err != nil { return errors.WithStack(err) } if !info.Type().IsRegular() { t.Logf("skipping testdata entry that is not a file: %s", path) return nil } match := testdataPattern.FindStringSubmatch(info.Name()) if len(match) != 2 && len(match) != 3 { t.Logf(`WARNING! Found a test migration which does not match the test data pattern: %s`, info.Name()) return nil } version := match[1] flavor := "all" if len(match) == 3 && len(match[2]) > 0 { flavor = pop.CanonicalDialect(strings.TrimPrefix(match[2], ".")) } // t.Logf("Found test migration \"%s\" (%s, %+v): %s", flavor, match, err, info.Name()) m.migrationsUp = append(m.migrationsUp, Migration{ Version: version + "9", // run testdata after version Path: path, Name: info.Name(), DBType: flavor, Direction: "up", Type: "sql", Runner: func(m Migration, c *pop.Connection) error { b, err := fs.ReadFile(testdata, m.Path) if err != nil { return errors.WithStack(err) } if isMigrationEmpty(string(b)) { return nil } _, err = c.Store.SQLDB().Exec(string(b)) return errors.WithStack(err) }, }) m.migrationsDown = append(m.migrationsDown, Migration{ Version: version + "9", // run testdata after version Path: path, Name: info.Name(), DBType: flavor, Direction: "down", Type: "sql", Runner: func(m Migration, _ *pop.Connection) error { return nil }, }) return nil })) } } var emptySQLReplace = regexp.MustCompile(`(?m)^(\s*--.*|\s*)$`) func isMigrationEmpty(content string) bool { return len(strings.ReplaceAll(emptySQLReplace.ReplaceAllString(content, ""), "\n", "")) == 0 } type queryExecutor interface { Exec(query string, args ...any) (sql.Result, error) } // NewMigrationBox creates a new migration box. func NewMigrationBox(dir fs.FS, c *pop.Connection, l *logrusx.Logger, opts ...MigrationBoxOption) (*MigrationBox, error) { mb := &MigrationBox{ c: c, l: l, migrationContent: ParameterizedMigrationContent(nil), } for _, o := range opts { o(mb) } txRunner := func(b []byte) func(Migration, *pop.Connection) error { return func(mf Migration, c *pop.Connection) error { content, err := mb.migrationContent(mf, c, b, true) if err != nil { return errors.Wrapf(err, "error processing %s", mf.Path) } if isMigrationEmpty(content) { l.WithField("migration", mf.Path).Trace("This is usually ok - ignoring migration because content is empty. This is ok!") return nil } var q queryExecutor = c.Store.SQLDB() if c.TX != nil { q = c.TX } if _, err = q.Exec(content); err != nil { return errors.Wrapf(err, "error executing %s, sql: %s", mf.Path, content) } return nil } } err := mb.findMigrations(dir, txRunner) if err != nil { return mb, err } if err := mb.check(); err != nil { return nil, err } return mb, nil } func (mb *MigrationBox) findMigrations( dir fs.FS, runner func([]byte) func(m Migration, c *pop.Connection) error, ) error { err := fs.WalkDir(dir, ".", func(p string, info fs.DirEntry, err error) error { if err != nil { return errors.WithStack(err) } if !info.Type().IsRegular() { mb.l.Tracef("ignoring non file: %s", info.Name()) return nil } if path.Ext(info.Name()) != ".sql" { mb.l.Tracef("ignoring non SQL file: %s", info.Name()) return nil } details, err := parseMigrationFilename(info.Name()) if err != nil { if strings.HasPrefix(err.Error(), "unsupported dialect") { mb.l.Tracef("This is usually ok - ignoring migration file %s because dialect is not supported: %s", info.Name(), err.Error()) return nil } return errors.WithStack(err) } if details == nil { return errors.Errorf("Found a migration file that does not match the file pattern: filename=%s pattern=%s", info.Name(), MigrationFileRegexp) } content, err := fs.ReadFile(dir, p) if err != nil { return errors.WithStack(err) } mf := Migration{ Path: p, Version: details.Version, Name: details.Name, DBType: details.DBType, Direction: details.Direction, Type: details.Type, Content: string(content), Autocommit: details.Autocommit, } mf.Runner = runner(content) switch details.Direction { case "up": mb.migrationsUp = append(mb.migrationsUp, mf) case "down": mb.migrationsDown = append(mb.migrationsDown, mf) default: return errors.Errorf("unknown migration direction %q for %q", details.Direction, info.Name()) } return nil }) // Sort descending. sort.Sort(mb.migrationsDown) slices.Reverse(mb.migrationsDown) // Sort ascending. sort.Sort(mb.migrationsUp) return errors.WithStack(err) } // hasDownMigrationWithVersion checks if there is a migration with the given // version. func (mb *MigrationBox) hasDownMigrationWithVersion(version string) bool { for _, down := range mb.migrationsDown { if version == down.Version { return true } } return false } // check checks that every "up" migration has a corresponding "down" migration. func (mb *MigrationBox) check() error { for _, up := range mb.migrationsUp { if !mb.hasDownMigrationWithVersion(up.Version) { return errors.Errorf("migration %s has no corresponding down migration", up.Version) } } for _, n := range mb.migrationsUp { if err := n.Valid(); err != nil { return errors.WithStack(err) } } for _, n := range mb.migrationsDown { if err := n.Valid(); err != nil { return errors.WithStack(err) } } return nil } ================================================ FILE: oryx/popx/migration_content.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "bytes" "text/template" "github.com/pkg/errors" "github.com/ory/pop/v6" ) func ParameterizedMigrationContent(params map[string]interface{}) func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error) { return func(mf Migration, c *pop.Connection, b []byte, usingTemplate bool) (string, error) { content := "" if usingTemplate { t := template.New("migration") t.Funcs(SQLTemplateFuncs) t, err := t.Parse(string(b)) if err != nil { return "", errors.Wrapf(err, "could not parse template %s", mf.Path) } var bb bytes.Buffer err = t.Execute(&bb, struct { IsSQLite bool IsCockroach bool IsMySQL bool IsMariaDB bool IsPostgreSQL bool DialectDetails *pop.ConnectionDetails Parameters map[string]interface{} }{ IsSQLite: c.Dialect.Name() == "sqlite3", IsCockroach: c.Dialect.Name() == "cockroach", IsMySQL: c.Dialect.Name() == "mysql", IsMariaDB: c.Dialect.Name() == "mariadb", IsPostgreSQL: c.Dialect.Name() == "postgres", DialectDetails: c.Dialect.Details(), Parameters: params, }) if err != nil { return "", errors.Wrapf(err, "could not execute migration template %s", mf.Path) } content = bb.String() } else { content = string(b) } return content, nil } } ================================================ FILE: oryx/popx/migration_info.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "sort" "strings" "github.com/pkg/errors" "github.com/ory/pop/v6" ) // Migration handles the data for a given database migration type Migration struct { // Path to the migration (./migrations/123_create_widgets.up.sql) Path string // Version of the migration (123) Version string // Name of the migration (create_widgets) Name string // Direction of the migration (up|down) Direction string // Type of migration (sql|go) Type string // DB type (all|postgres|mysql...) DBType string // Runner function to run/execute the migration. Will be wrapped in a // database transaction. Mutually exclusive with RunnerNoTx Runner func(Migration, *pop.Connection) error // Content is the raw content of the migration file Content string // Autocommit indicates whether the migration should be run in autocommit mode Autocommit bool } func (m Migration) Valid() error { if m.Runner == nil { return errors.Errorf("no runner defined for %s", m.Path) } return nil } // Migrations is a collection of Migration type Migrations []Migration func (mfs Migrations) Len() int { return len(mfs) } func (mfs Migrations) Less(i, j int) bool { return compareMigration(mfs[i], mfs[j]) < 0 } func (mfs Migrations) Swap(i, j int) { mfs[i], mfs[j] = mfs[j], mfs[i] } func compareMigration(a, b Migration) int { if a.Version != b.Version { return strings.Compare(a.Version, b.Version) } // Force "all" to be greater. if a.DBType == "all" && b.DBType != "all" { return 1 } else if a.DBType != "all" && b.DBType == "all" { return -1 } return strings.Compare(a.DBType, b.DBType) } // specificity returns an integer representing how specific the migration is. // Higher numbers indicate a more specific migration. func specificity(m *Migration) int { specificity := 0 if m.DBType != "all" { specificity += 1 } return specificity } // isApplicable checks whether the migration is applicable for the given dialect. func isApplicable(m Migration, dialect string) bool { return m.DBType == dialect || m.DBType == "all" } func (mfs Migrations) sortAndFilter(dialect string) Migrations { byVersion := make(map[string]Migration, len(mfs)) for _, migration := range mfs { if !isApplicable(migration, dialect) { continue } if previousMigration, ok := byVersion[migration.Version]; ok { if specificity(&migration) < specificity(&previousMigration) { // Previous migration is more specific, skip this one. continue } } byVersion[migration.Version] = migration } filtered := make(Migrations, 0, len(byVersion)) for k := range byVersion { filtered = append(filtered, byVersion[k]) } sort.Sort(filtered) return filtered } func (mfs Migrations) find(version, dbType string) *Migration { var candidate *Migration for _, m := range mfs { if m.Version == version { switch m.DBType { case "all": // there might still be a more specific migration for the dbType candidate = &m case dbType: return &m } } } return candidate } ================================================ FILE: oryx/popx/migrator.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "context" "fmt" "math" "os" "regexp" "slices" "strings" "time" "github.com/cockroachdb/cockroach-go/v2/crdb" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/ory/pop/v6" "github.com/ory/x/cmdx" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) const ( Pending = "Pending" Applied = "Applied" tracingComponent = "github.com/ory/x/popx" ) func (mb *MigrationBox) shouldNotUseTransaction(m Migration) bool { return m.Autocommit || mb.c.Dialect.Name() == "cockroach" || mb.c.Dialect.Name() == "mysql" } // Up runs pending "up" migrations and applies them to the database. func (mb *MigrationBox) Up(ctx context.Context) error { _, err := mb.UpTo(ctx, 0) return errors.WithStack(err) } // UpTo runs up to step "up" migrations and applies them to the database. // If step <= 0 all pending migrations are run. func (mb *MigrationBox) UpTo(ctx context.Context, step int) (applied int, err error) { ctx, span := startSpan(ctx, MigrationUpOpName, trace.WithAttributes(attribute.Int("step", step))) defer otelx.End(span, &err) c := mb.c.WithContext(ctx) err = mb.exec(ctx, func() error { mtn := sanitizedMigrationTableName(c) mfs := mb.migrationsUp.sortAndFilter(c.Dialect.Name()) for _, mi := range mfs { l := mb.l.WithField("version", mi.Version).WithField("migration_name", mi.Name).WithField("migration_file", mi.Path) appliedMigrations := make([]string, 0, 2) legacyVersion := mi.Version if len(legacyVersion) > 14 { legacyVersion = legacyVersion[:14] } err := c.RawQuery(fmt.Sprintf("SELECT version FROM %s WHERE version IN (?, ?)", mtn), mi.Version, legacyVersion).All(&appliedMigrations) if err != nil { return errors.Wrapf(err, "problem checking for migration version %s", mi.Version) } if slices.Contains(appliedMigrations, mi.Version) { l.Debug("Migration has already been applied, skipping.") continue } if slices.Contains(appliedMigrations, legacyVersion) { l.WithField("legacy_version", legacyVersion).WithField("migration_table", mtn).Debug("Migration has already been applied in a legacy migration run. Updating version in migration table.") if err := mb.isolatedTransaction(ctx, "init-migrate", func(conn *pop.Connection) error { // We do not want to remove the legacy migration version or subsequent migrations might be applied twice. // // Do not activate the following - it is just for reference. // // if _, err := tx.Store.Exec(fmt.Sprintf("DELETE FROM %s WHERE version = ?", mtn), legacyVersion); err != nil { // return errors.Wrapf(err, "problem removing legacy version %s", mi.Version) // } // #nosec G201 - mtn is a system-wide const err := conn.RawQuery(fmt.Sprintf("INSERT INTO %s (version) VALUES (?)", mtn), mi.Version).Exec() return errors.Wrapf(err, "problem inserting migration version %s", mi.Version) }); err != nil { return errors.WithStack(err) } continue } l.Info("Migration has not yet been applied, running migration.") if err := mi.Valid(); err != nil { return errors.WithStack(err) } noTx := mb.shouldNotUseTransaction(mi) if noTx { l.Info("NOT running migrations inside a transaction") if err := mi.Runner(mi, c); err != nil { return errors.WithStack(err) } // #nosec G201 - mtn is a system-wide const if err := c.RawQuery(fmt.Sprintf("INSERT INTO %s (version) VALUES (?)", mtn), mi.Version).Exec(); err != nil { return errors.Wrapf(err, "problem inserting migration version %s. YOUR DATABASE MAY BE IN AN INCONSISTENT STATE! MANUAL INTERVENTION REQUIRED!", mi.Version) } } else { if err := mb.isolatedTransaction(ctx, "up", func(conn *pop.Connection) error { if err := mi.Runner(mi, conn); err != nil { return errors.WithStack(err) } // #nosec G201 - mtn is a system-wide const if err := conn.RawQuery(fmt.Sprintf("INSERT INTO %s (version) VALUES (?)", mtn), mi.Version).Exec(); err != nil { return errors.Wrapf(err, "problem inserting migration version %s", mi.Version) } return nil }); err != nil { return errors.WithStack(err) } } l.WithField("autocommit", noTx).Infof("> %s applied successfully", mi.Name) applied++ if step > 0 && applied >= step { break } } if applied == 0 { mb.l.Infof("Migrations already up to date, nothing to apply") } else { mb.l.Infof("Successfully applied %d migrations.", applied) } return nil }) return applied, errors.WithStack(err) } // Down runs pending "down" migrations and rolls back the // database by the specified number of steps. // If step <= 0, all down migrations are run. func (mb *MigrationBox) Down(ctx context.Context, steps int) (err error) { ctx, span := startSpan(ctx, MigrationDownOpName, trace.WithAttributes(attribute.Int("steps", steps))) defer otelx.End(span, &err) if steps <= 0 { steps = math.MaxInt } c := mb.c.WithContext(ctx) return errors.WithStack(mb.exec(ctx, func() (err error) { mtn := sanitizedMigrationTableName(c) count, err := c.Count(mtn) if err != nil { return errors.Wrap(err, "migration down: unable count existing migration") } steps = min(steps, count) mfs := mb.migrationsDown.sortAndFilter(c.Dialect.Name()) slices.Reverse(mfs) if len(mfs) > count { // skip all migrations that were not yet applied mfs = mfs[len(mfs)-count:] } reverted := 0 defer func() { migrationsToRevertCount := min(steps, len(mfs)) mb.l.Debugf("Successfully reverted %d/%d migrations.", reverted, migrationsToRevertCount) if err != nil { mb.l.WithError(err).Error("Problem reverting migrations.") } }() for i, mi := range mfs { if i >= steps { break } l := mb.l.WithField("version", mi.Version).WithField("migration_name", mi.Name).WithField("migration_file", mi.Path) l.Debugf("handling migration %s", mi.Name) exists, err := c.Where("version = ?", mi.Version).Exists(mtn) if err != nil { return errors.Wrapf(err, "problem checking for migration version %s", mi.Version) } if !exists && len(mi.Version) > 14 { legacyVersion := mi.Version[:14] legacyVersionExists, err := c.Where("version = ?", legacyVersion).Exists(mtn) if err != nil { return errors.Wrapf(err, "problem checking for legacy migration version %s", legacyVersion) } if !legacyVersionExists { return errors.Errorf("neither normal (%s) nor legacy migration (%s) exist", mi.Version, legacyVersion) } } else if !exists { return errors.Errorf("migration version %s does not exist", mi.Version) } if err := mi.Valid(); err != nil { return errors.WithStack(err) } if mb.shouldNotUseTransaction(mi) { err := mi.Runner(mi, c) if err != nil { return errors.WithStack(err) } // #nosec G201 - mtn is a system-wide const if err := c.RawQuery(fmt.Sprintf("DELETE FROM %s WHERE version = ?", mtn), mi.Version).Exec(); err != nil { return errors.Wrapf(err, "problem deleting migration version %s. YOUR DATABASE MAY BE IN AN INCONSISTENT STATE! MANUAL INTERVENTION REQUIRED!", mi.Version) } } else { if err := mb.isolatedTransaction(ctx, "down", func(conn *pop.Connection) error { err := mi.Runner(mi, conn) if err != nil { return errors.WithStack(err) } // #nosec G201 - mtn is a system-wide const if err := conn.RawQuery(fmt.Sprintf("DELETE FROM %s WHERE version = ?", mtn), mi.Version).Exec(); err != nil { return errors.Wrapf(err, "problem deleting migration version %s", mi.Version) } return nil }); err != nil { return errors.WithStack(err) } } l.Infof("%s applied successfully", mi.Name) reverted++ } return nil })) } func (mb *MigrationBox) createTransactionalMigrationTable(ctx context.Context, c *pop.Connection, l *logrusx.Logger) error { mtn := sanitizedMigrationTableName(c) if err := mb.createMigrationStatusTableTransaction(ctx, []string{ fmt.Sprintf(`CREATE TABLE %s (version VARCHAR (48) NOT NULL, version_self INT NOT NULL DEFAULT 0)`, mtn), fmt.Sprintf(`CREATE UNIQUE INDEX %s_version_idx ON %s (version)`, mtn, mtn), fmt.Sprintf(`CREATE INDEX %s_version_self_idx ON %s (version_self)`, mtn, mtn), }); err != nil { return errors.WithStack(err) } l.WithField("migration_table", mtn).Debug("Transactional migration table created successfully.") return nil } func (mb *MigrationBox) migrateToTransactionalMigrationTable(ctx context.Context, c *pop.Connection, l *logrusx.Logger) error { // This means the new pop migrator has also not yet been applied, do that now. mtn := sanitizedMigrationTableName(c) withOn := fmt.Sprintf(" ON %s", mtn) if c.Dialect.Name() != "mysql" { withOn = "" } interimTable := fmt.Sprintf("%s_transactional", mtn) workload := [][]string{ { fmt.Sprintf(`DROP INDEX %s_version_idx%s`, mtn, withOn), fmt.Sprintf(`CREATE TABLE %s (version VARCHAR (48) NOT NULL, version_self INT NOT NULL DEFAULT 0)`, interimTable), fmt.Sprintf(`CREATE UNIQUE INDEX %s_version_idx ON %s (version)`, mtn, interimTable), fmt.Sprintf(`CREATE INDEX %s_version_self_idx ON %s (version_self)`, mtn, interimTable), // #nosec G201 - mtn is a system-wide const fmt.Sprintf(`INSERT INTO %s (version) SELECT version FROM %s`, interimTable, mtn), fmt.Sprintf(`ALTER TABLE %s RENAME TO %s_pop_legacy`, mtn, mtn), }, { fmt.Sprintf(`ALTER TABLE %s RENAME TO %s`, interimTable, mtn), }, } if err := mb.createMigrationStatusTableTransaction(ctx, workload...); err != nil { return errors.WithStack(err) } l.WithField("migration_table", mtn).Debug("Successfully migrated legacy schema_migration to new transactional schema_migration table.") return nil } func (mb *MigrationBox) isolatedTransaction(ctx context.Context, direction string, fn func(c *pop.Connection) error) (err error) { ctx, span := startSpan(ctx, MigrationRunTransactionOpName, trace.WithAttributes(attribute.String("migration_direction", direction))) defer otelx.End(span, &err) if mb.perMigrationTimeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeoutCause(ctx, mb.perMigrationTimeout, errors.Errorf("failed to run all SQL migrations: direction=%s timeout=%s", direction, mb.perMigrationTimeout)) defer cancel() } return Transaction(ctx, mb.c.WithContext(ctx), func(ctx context.Context, connection *pop.Connection) error { return errors.WithStack(fn(connection)) }) } func (mb *MigrationBox) createMigrationStatusTableTransaction(ctx context.Context, transactions ...[]string) error { for _, statements := range transactions { // CockroachDB does not support transactional schema changes, so we have to run // the statements outside of a transaction. if mb.c.Dialect.Name() == "cockroach" || mb.c.Dialect.Name() == "mysql" { for _, statement := range statements { if err := mb.c.WithContext(ctx).RawQuery(statement).Exec(); err != nil { return errors.Wrapf(err, "unable to execute statement: %s", statement) } } } else { if err := mb.isolatedTransaction(ctx, "init", func(conn *pop.Connection) error { for _, statement := range statements { if err := conn.WithContext(ctx).RawQuery(statement).Exec(); err != nil { return errors.Wrapf(err, "unable to execute statement: %s", statement) } } return nil }); err != nil { return errors.WithStack(err) } } } return nil } // CreateSchemaMigrations sets up a table to track migrations. This is an idempotent // operation. func (mb *MigrationBox) CreateSchemaMigrations(ctx context.Context) error { ctx, span := startSpan(ctx, MigrationInitOpName) defer span.End() c := mb.c.WithContext(ctx) mtn := sanitizedMigrationTableName(c) mb.l.WithField("migration_table", mtn).Debug("Checking if legacy migration table exists.") _, err := c.Store.Exec(fmt.Sprintf("select version from %s", mtn)) if err != nil { mb.l.WithError(err).WithField("migration_table", mtn).Debug("An error occurred while checking for the legacy migration table, maybe it does not exist yet? Trying to create.") // This means that the legacy pop migrator has not yet been applied return errors.WithStack(mb.createTransactionalMigrationTable(ctx, c, mb.l)) } mb.l.WithField("migration_table", mtn).Debug("A migration table exists, checking if it is a transactional migration table.") _, err = c.Store.Exec(fmt.Sprintf("select version, version_self from %s", mtn)) if err != nil { mb.l.WithError(err).WithField("migration_table", mtn).Debug("An error occurred while checking for the transactional migration table, maybe it does not exist yet? Trying to create.") return errors.WithStack(mb.migrateToTransactionalMigrationTable(ctx, c, mb.l)) } mb.l.WithField("migration_table", mtn).Debug("Migration tables exist and are up to date.") return nil } type MigrationStatus struct { State string `json:"state"` Version string `json:"version"` Name string `json:"name"` ContentUp string `json:"content"` ContentDown string `json:"content_down"` } type MigrationStatuses []MigrationStatus var _ cmdx.Table = (MigrationStatuses)(nil) func (m MigrationStatuses) Header() []string { return []string{"Version", "Name", "Status"} } func (m MigrationStatuses) Table() [][]string { t := make([][]string, len(m)) for i, s := range m { t[i] = []string{s.Version, s.Name, s.State} } return t } func (m MigrationStatuses) Interface() interface{} { return m } func (m MigrationStatuses) Len() int { return len(m) } func (m MigrationStatuses) IDs() []string { ids := make([]string, len(m)) for i, s := range m { ids[i] = s.Version } return ids } func (m MigrationStatuses) HasPending() bool { for _, mm := range m { if mm.State == Pending { return true } } return false } func sanitizedMigrationTableName(con *pop.Connection) string { return regexp.MustCompile(`\W`).ReplaceAllString(con.MigrationTableName(), "") } func errIsTableNotFound(err error) bool { return strings.Contains(err.Error(), "no such table:") || // sqlite strings.Contains(err.Error(), "Error 1146") || // MySQL strings.Contains(err.Error(), "SQLSTATE 42P01") // PostgreSQL / CockroachDB } // Status prints out the status of applied/pending migrations. func (mb *MigrationBox) Status(ctx context.Context) (MigrationStatuses, error) { ctx, span := startSpan(ctx, MigrationStatusOpName) defer span.End() con := mb.c.WithContext(ctx) migrationsUp := mb.migrationsUp.sortAndFilter(con.Dialect.Name()) if len(migrationsUp) == 0 { return nil, errors.Errorf("unable to find any migrations for dialect: %s", con.Dialect.Name()) } alreadyApplied := make([]string, 0, len(migrationsUp)) err := con.RawQuery(fmt.Sprintf("SELECT version FROM %s", sanitizedMigrationTableName(con))).All(&alreadyApplied) if err != nil { if errIsTableNotFound(err) { // This means that no migrations have been applied and we need to apply all of them first! // // It also means that we can ignore this state and act as if no migrations have been applied yet. } else { // On any other error, we fail. return nil, errors.Wrap(err, "problem with migration") } } statuses := make(MigrationStatuses, len(migrationsUp)) for k, mf := range migrationsUp { downContent := "-- error: no down migration defined for this migration" if mDown := mb.migrationsDown.find(mf.Version, con.Dialect.Name()); mDown != nil { downContent = mDown.Content } statuses[k] = MigrationStatus{ State: Pending, Version: mf.Version, Name: mf.Name, ContentUp: mf.Content, ContentDown: downContent, } if slices.ContainsFunc(alreadyApplied, func(applied string) bool { return applied == mf.Version || (len(mf.Version) > 14 && applied == mf.Version[:14]) }) { statuses[k].State = Applied continue } } return statuses, nil } // DumpMigrationSchema will generate a file of the current database schema func (mb *MigrationBox) DumpMigrationSchema(ctx context.Context) error { c := mb.c.WithContext(ctx) schema := "schema.sql" f, err := os.Create(schema) //#nosec:G304 if err != nil { return errors.WithStack(err) } err = c.Dialect.DumpSchema(f) if err != nil { _ = os.RemoveAll(schema) return errors.WithStack(err) } return nil } func (mb *MigrationBox) exec(ctx context.Context, fn func() error) error { now := time.Now() defer mb.printTimer(now) err := mb.CreateSchemaMigrations(ctx) if err != nil { return errors.Wrap(err, "migrator: problem creating schema migrations") } if mb.c.Dialect.Name() == "sqlite3" { if err := mb.c.RawQuery("PRAGMA foreign_keys=OFF").Exec(); err != nil { return sqlcon.HandleError(err) } } if mb.c.Dialect.Name() == "cockroach" { outer := fn fn = func() error { return errors.WithStack(crdb.Execute(outer)) } } if err := fn(); err != nil { return errors.WithStack(err) } if mb.c.Dialect.Name() == "sqlite3" { if err := mb.c.RawQuery("PRAGMA foreign_keys=ON").Exec(); err != nil { return sqlcon.HandleError(err) } } return nil } func (mb *MigrationBox) printTimer(timerStart time.Time) { diff := time.Since(timerStart).Seconds() if diff > 60 { mb.l.Debugf("%.4f minutes", diff/60) } else { mb.l.Debugf("%.4f seconds", diff) } } ================================================ FILE: oryx/popx/span.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "context" "go.opentelemetry.io/otel/trace" ) const ( MigrationStatusOpName = "migration-status" MigrationInitOpName = "migration-init" MigrationUpOpName = "migration-up" MigrationRunTransactionOpName = "migration-run-transaction" MigrationDownOpName = "migration-down" ) func startSpan(ctx context.Context, opName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { return trace.SpanFromContext(ctx).TracerProvider().Tracer(tracingComponent).Start(ctx, opName, opts...) } ================================================ FILE: oryx/popx/sql_template_funcs.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "regexp" "github.com/pkg/errors" ) var SQLTemplateFuncs = map[string]interface{}{ "identifier": Identifier, } var identifierPattern = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_]*$") func Identifier(i string) (string, error) { if !identifierPattern.MatchString(i) { return "", errors.Errorf("invalid SQL identifier '%s'", i) } return i, nil } ================================================ FILE: oryx/popx/stub/migrations/check/valid/123_a.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/check/valid/123_a.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/check/valid/123_a.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credential_identifiers";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_credentials";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_credential_types";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identities";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.cockroach.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" json NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credentials_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, CONSTRAINT "identity_credentials_identity_credential_types_id_fk" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credential_identifiers_identity_credentials_id_fk" FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.mysql.down.sql ================================================ DROP TABLE `identity_credential_identifiers`; DROP TABLE `identity_credentials`; DROP TABLE `identity_credential_types`; DROP TABLE `identities`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.mysql.up.sql ================================================ CREATE TABLE `identities` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `traits_schema_id` VARCHAR (2048) NOT NULL, `traits` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; CREATE TABLE `identity_credential_types` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `name` VARCHAR (32) NOT NULL ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_credential_types_name_idx` ON `identity_credential_types` (`name`); CREATE TABLE `identity_credentials` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `config` JSON NOT NULL, `identity_credential_type_id` char(36) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade, FOREIGN KEY (`identity_credential_type_id`) REFERENCES `identity_credential_types` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `identity_credential_identifiers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identifier` VARCHAR (255) NOT NULL, `identity_credential_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_credential_id`) REFERENCES `identity_credentials` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_idx` ON `identity_credential_identifiers` (`identifier`); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.postgres.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; DROP TABLE "identity_credentials"; DROP TABLE "identity_credential_types"; DROP TABLE "identities"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.postgres.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL ); CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" jsonb NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade ); CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; DROP TABLE "identity_credentials"; DROP TABLE "identity_credential_types"; DROP TABLE "identities"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000001_identities.sqlite3.up.sql ================================================ CREATE TABLE "identities" ( "id" TEXT PRIMARY KEY, "traits_schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); CREATE TABLE "identity_credential_types" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL ); CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); CREATE TABLE "identity_credentials" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON DELETE cascade ); CREATE TABLE "identity_credential_identifiers" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_login_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_login_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_registration_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_registration_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_profile_management_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_login_request_methods_selfservice_login_requests_id_fk" FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_registration_request_methods_selfservice_registration_requests_id_fk" FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_profile_management_requests_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_login_request_methods`; DROP TABLE `selfservice_login_requests`; DROP TABLE `selfservice_registration_request_methods`; DROP TABLE `selfservice_registration_requests`; DROP TABLE `selfservice_profile_management_requests`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_login_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; CREATE TABLE `selfservice_login_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_login_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_login_request_id`) REFERENCES `selfservice_login_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `selfservice_registration_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; CREATE TABLE `selfservice_registration_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_registration_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_registration_request_id`) REFERENCES `selfservice_registration_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `selfservice_profile_management_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `update_successful` bool NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; DROP TABLE "selfservice_login_requests"; DROP TABLE "selfservice_registration_request_methods"; DROP TABLE "selfservice_registration_requests"; DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade ); CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade ); CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; DROP TABLE "selfservice_login_requests"; DROP TABLE "selfservice_registration_request_methods"; DROP TABLE "selfservice_registration_requests"; DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000002_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); CREATE TABLE "selfservice_login_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_login_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_login_request_id) REFERENCES selfservice_login_requests (id) ON DELETE cascade ); CREATE TABLE "selfservice_registration_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); CREATE TABLE "selfservice_registration_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_registration_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_registration_request_id) REFERENCES selfservice_registration_requests (id) ON DELETE cascade ); CREATE TABLE "selfservice_profile_management_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.cockroach.down.sql ================================================ DROP TABLE "sessions";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.cockroach.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "sessions_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.mysql.down.sql ================================================ DROP TABLE `sessions`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.mysql.up.sql ================================================ CREATE TABLE `sessions` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `authenticated_at` DATETIME NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.postgres.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.postgres.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000003_sessions.sqlite3.up.sql ================================================ CREATE TABLE "sessions" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.cockroach.down.sql ================================================ DROP TABLE "selfservice_errors";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.cockroach.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" json NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.mysql.down.sql ================================================ DROP TABLE `selfservice_errors`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.mysql.up.sql ================================================ CREATE TABLE `selfservice_errors` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `errors` JSON NOT NULL, `seen_at` DATETIME NOT NULL, `was_seen` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.postgres.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.postgres.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" jsonb NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000004_errors.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME NOT NULL, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000005_identities.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000005_identities.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) BINARY; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.cockroach.down.sql ================================================ DROP TABLE "courier_messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.cockroach.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.mysql.down.sql ================================================ DROP TABLE `courier_messages`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.mysql.up.sql ================================================ CREATE TABLE `courier_messages` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `type` INTEGER NOT NULL, `status` INTEGER NOT NULL, `body` VARCHAR (255) NOT NULL, `subject` VARCHAR (255) NOT NULL, `recipient` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.postgres.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.postgres.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000006_courier.sqlite3.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT '';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` DROP COLUMN `csrf_token`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` ADD COLUMN `csrf_token` VARCHAR (255) NOT NULL DEFAULT ""; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at) SELECT id, errors, seen_at, was_seen, created_at, updated_at FROM "selfservice_errors"; DROP TABLE "selfservice_errors"; ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000007_errors.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" TEXT NOT NULL DEFAULT ''; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.cockroach.down.sql ================================================ DROP TABLE "selfservice_verification_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_verifiable_addresses";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.cockroach.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verifiable_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.mysql.down.sql ================================================ DROP TABLE `selfservice_verification_requests`; DROP TABLE `identity_verifiable_addresses`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.mysql.up.sql ================================================ CREATE TABLE `identity_verifiable_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `code` VARCHAR (32) NOT NULL, `status` VARCHAR (16) NOT NULL, `via` VARCHAR (16) NOT NULL, `verified` bool NOT NULL, `value` VARCHAR (400) NOT NULL, `verified_at` DATETIME, `expires_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`); CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); CREATE UNIQUE INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses` (`via`, `value`); CREATE INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses` (`via`, `value`); CREATE TABLE `selfservice_verification_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `via` VARCHAR (16) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `success` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.postgres.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.postgres.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000008_selfservice_verification.sqlite3.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" TEXT PRIMARY KEY, "code" TEXT NOT NULL, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); CREATE TABLE "selfservice_verification_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000009_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255); ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000009_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.cockroach.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.mysql.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.postgres.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.sqlite3.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; DROP TABLE "selfservice_errors"; ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000010_errors.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; DROP TABLE "selfservice_errors"; ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000011_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" RENAME COLUMN "body" TO "_body_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "courier_messages" ADD COLUMN "body" text;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "courier_messages" SET "body" = "_body_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "courier_messages" ALTER COLUMN "body" SET NOT NULL;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "courier_messages" DROP COLUMN "_body_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000011_courier_body_type.mysql.up.sql ================================================ ALTER TABLE `courier_messages` MODIFY `body` text NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000011_courier_body_type.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "body" TYPE text, ALTER COLUMN "body" SET NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000011_courier_body_type.sqlite3.up.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at) SELECT id, type, status, body, subject, recipient, created_at, updated_at FROM "courier_messages"; DROP TABLE "courier_messages"; ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `forced`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `forced` bool NOT NULL DEFAULT false; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_login_requests"; DROP TABLE "selfservice_login_requests"; ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20191100000012_login_request_forced.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.cockroach.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" json NOT NULL DEFAULT '{}';COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_profile_management_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.cockroach.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `form` JSON; UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ALTER TABLE `selfservice_profile_management_requests` MODIFY `form` JSON; DROP TABLE `selfservice_profile_management_request_methods`; ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `active_method`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.mysql.up.sql ================================================ CREATE TABLE `selfservice_profile_management_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_profile_management_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `active_method` VARCHAR (32); INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `form`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" jsonb; UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ALTER TABLE "selfservice_profile_management_requests" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL; DROP TABLE "selfservice_profile_management_request_methods"; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.postgres.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32); INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods"; CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful FROM "selfservice_profile_management_requests"; DROP TABLE "selfservice_profile_management_requests"; ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200317160354_create_profile_request_forms.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_profile_management_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" TEXT; INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) SELECT id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method FROM "selfservice_profile_management_requests"; DROP TABLE "selfservice_profile_management_requests"; ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.cockroach.down.sql ================================================ DROP TABLE "continuity_containers";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.cockroach.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" json, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "continuity_containers_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.mysql.down.sql ================================================ DROP TABLE `continuity_containers`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.mysql.up.sql ================================================ CREATE TABLE `continuity_containers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identity_id` char(36), `name` VARCHAR (255) NOT NULL, `payload` JSON, `expires_at` DATETIME NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.postgres.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.postgres.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" jsonb, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.sqlite3.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200401183443_continuity_containers.sqlite3.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` CHANGE `selfservice_settings_request_id` `selfservice_profile_management_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_profile_management_request_methods`; ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_profile_management_requests`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_request_methods` CHANGE `selfservice_profile_management_request_id` `selfservice_settings_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_profile_management_request_methods` RENAME TO `selfservice_settings_request_methods`; ALTER TABLE `selfservice_profile_management_requests` RENAME TO `selfservice_settings_requests`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200402142539_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "identity_recovery_tokens";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_recovery_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_recovery_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_recovery_addresses";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" json, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_requests_identities_id_fk" FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" json NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_request_methods_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk" FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `identity_recovery_tokens`; DROP TABLE `selfservice_recovery_request_methods`; DROP TABLE `selfservice_recovery_requests`; DROP TABLE `identity_recovery_addresses`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `identity_recovery_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `via` VARCHAR (16) NOT NULL, `value` VARCHAR (400) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses` (`via`, `value`); CREATE INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses` (`via`, `value`); CREATE TABLE `selfservice_recovery_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `messages` JSON, `active_method` VARCHAR (32), `csrf_token` VARCHAR (255) NOT NULL, `state` VARCHAR (32) NOT NULL, `recovered_identity_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`recovered_identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `selfservice_recovery_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `config` JSON NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `identity_recovery_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `identity_recovery_address_id` char(36) NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_recovery_address_id`) REFERENCES `identity_recovery_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_recovery_addresses_code_uq_idx` ON `identity_recovery_tokens` (`token`); CREATE INDEX `identity_recovery_addresses_code_idx` ON `identity_recovery_tokens` (`token`); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; DROP TABLE "selfservice_recovery_request_methods"; DROP TABLE "selfservice_recovery_requests"; DROP TABLE "identity_recovery_addresses"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" jsonb, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" jsonb NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; DROP TABLE "selfservice_recovery_request_methods"; DROP TABLE "selfservice_recovery_requests"; DROP TABLE "identity_recovery_addresses"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101057_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" TEXT PRIMARY KEY, "via" TEXT NOT NULL, "value" TEXT NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); CREATE TABLE "selfservice_recovery_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON DELETE cascade ); CREATE TABLE "selfservice_recovery_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "config" TEXT NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ); CREATE TABLE "identity_recovery_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101058_create_recovery_addresses.mysql.down.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200519101058_create_recovery_addresses.mysql.up.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) BINARY; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `messages`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `messages` JSON; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101000_create_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200601101001_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(32) BINARY; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_requests` DROP COLUMN `messages`; ALTER TABLE `selfservice_login_requests` DROP COLUMN `messages`; ALTER TABLE `selfservice_registration_requests` DROP COLUMN `messages`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` ADD COLUMN `messages` JSON; ALTER TABLE `selfservice_login_requests` ADD COLUMN `messages` JSON; ALTER TABLE `selfservice_registration_requests` ADD COLUMN `messages` JSON; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages"; ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages"; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" jsonb; ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" jsonb; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ); INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, via, success FROM "selfservice_verification_requests"; DROP TABLE "selfservice_verification_requests"; ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false' ); INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced FROM "selfservice_login_requests"; DROP TABLE "selfservice_login_requests"; ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_registration_requests"; DROP TABLE "selfservice_registration_requests"; ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200605111551_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" TEXT; ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" TEXT; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `state`; ALTER TABLE `selfservice_settings_requests` ADD COLUMN `update_successful` bool NOT NULL DEFAULT false; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ALTER TABLE `selfservice_settings_requests` DROP COLUMN `update_successful`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state"; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200607165100_settings.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.cockroach.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.cockroach.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.mysql.down.sql ================================================ ALTER TABLE `identities` CHANGE `schema_id` `traits_schema_id` varchar(2048) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.mysql.up.sql ================================================ ALTER TABLE `identities` CHANGE `traits_schema_id` `schema_id` varchar(2048) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.postgres.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.postgres.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.sqlite3.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200705105359_rename_identities_schema.sqlite3.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_registration_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_settings_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_recovery_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_verification_requests` DROP COLUMN `type`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_registration_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_settings_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_recovery_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_verification_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "messages" TEXT ); INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages FROM "selfservice_login_requests"; DROP TABLE "selfservice_login_requests"; ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT ); INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages FROM "selfservice_registration_requests"; DROP TABLE "selfservice_registration_requests"; ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; CREATE TABLE "_selfservice_recovery_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_recovery_requests_tmp" (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at FROM "selfservice_recovery_requests"; DROP TABLE "selfservice_recovery_requests"; ALTER TABLE "_selfservice_recovery_requests_tmp" RENAME TO "selfservice_recovery_requests"; CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ); INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, via, success FROM "selfservice_verification_requests"; DROP TABLE "selfservice_verification_requests"; ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810141652_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` RENAME TO `selfservice_login_requests`; ALTER TABLE `selfservice_login_flow_methods` RENAME TO `selfservice_login_request_methods`; ALTER TABLE `selfservice_registration_flow_methods` RENAME TO `selfservice_registration_request_methods`; ALTER TABLE `selfservice_registration_flows` RENAME TO `selfservice_registration_requests`; ALTER TABLE `selfservice_settings_flow_methods` RENAME TO `selfservice_settings_request_methods`; ALTER TABLE `selfservice_settings_flows` RENAME TO `selfservice_settings_requests`; ALTER TABLE `selfservice_recovery_flow_methods` RENAME TO `selfservice_recovery_request_methods`; ALTER TABLE `selfservice_recovery_flows` RENAME TO `selfservice_recovery_requests`; ALTER TABLE `selfservice_verification_flows` RENAME TO `selfservice_verification_requests`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_request_methods` RENAME TO `selfservice_login_flow_methods`; ALTER TABLE `selfservice_login_requests` RENAME TO `selfservice_login_flows`; ALTER TABLE `selfservice_registration_request_methods` RENAME TO `selfservice_registration_flow_methods`; ALTER TABLE `selfservice_registration_requests` RENAME TO `selfservice_registration_flows`; ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_settings_flow_methods`; ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_settings_flows`; ALTER TABLE `selfservice_recovery_request_methods` RENAME TO `selfservice_recovery_flow_methods`; ALTER TABLE `selfservice_recovery_requests` RENAME TO `selfservice_recovery_flows`; ALTER TABLE `selfservice_verification_requests` RENAME TO `selfservice_verification_flows`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810161022_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_flow_id` `selfservice_login_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_flow_id` `selfservice_registration_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_flow_id` `selfservice_settings_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_request_id` `selfservice_login_flow_id` char(36) NOT NULL; ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_request_id` `selfservice_registration_flow_id` char(36) NOT NULL; ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_request_id` `selfservice_settings_flow_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200810162450_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.cockroach.up.sql ================================================ DELETE FROM sessions;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" RENAME COLUMN "token" TO "_token_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "sessions" SET "token" = "_token_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" DROP COLUMN "_token_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "sessions_token_idx" ON "sessions" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `token`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.mysql.up.sql ================================================ DELETE FROM sessions; ALTER TABLE `sessions` ADD COLUMN `token` VARCHAR (32); ALTER TABLE `sessions` MODIFY `token` VARCHAR (32); CREATE UNIQUE INDEX `sessions_token_uq_idx` ON `sessions` (`token`); CREATE INDEX `sessions_token_idx` ON `sessions` (`token`); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.postgres.up.sql ================================================ DELETE FROM sessions; ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32); ALTER TABLE "sessions" ALTER COLUMN "token" TYPE VARCHAR (32), ALTER COLUMN "token" DROP NOT NULL; CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; DROP INDEX IF EXISTS "sessions_token_idx"; CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at FROM "sessions"; DROP TABLE "sessions"; ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812124254_add_session_token.sqlite3.up.sql ================================================ DELETE FROM sessions; ALTER TABLE "sessions" ADD COLUMN "token" TEXT; CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions"; DROP TABLE "sessions"; ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `active`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `active` boolean DEFAULT false; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; DROP INDEX IF EXISTS "sessions_token_uq_idx"; CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions"; DROP TABLE "sessions"; ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200812160551_add_session_revoke.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" NUMERIC DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.sqlite3.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830121710_update_recovery_token.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" json NOT NULL DEFAULT '{}';COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_verification_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `form` JSON; UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ALTER TABLE `selfservice_verification_flows` MODIFY `form` JSON; DROP TABLE `selfservice_verification_flow_methods`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `active_method`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `state`; ALTER TABLE `selfservice_verification_flows` ADD COLUMN `via` VARCHAR (16) NOT NULL DEFAULT 'email'; ALTER TABLE `selfservice_verification_flows` ADD COLUMN `success` bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" jsonb; UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ALTER TABLE "selfservice_verification_flows" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL; DROP TABLE "selfservice_verification_flow_methods"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state"; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email'; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form' ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser' ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" TEXT NOT NULL DEFAULT 'email'; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130642_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130643_add_verification_methods.cockroach.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130643_add_verification_methods.mysql.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130643_add_verification_methods.postgres.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130643_add_verification_methods.sqlite3.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130644_add_verification_methods.cockroach.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130644_add_verification_methods.mysql.up.sql ================================================ CREATE TABLE `selfservice_verification_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_verification_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ALTER TABLE `selfservice_verification_flows` ADD COLUMN `active_method` VARCHAR (32); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130644_add_verification_methods.postgres.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130644_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" TEXT; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130645_add_verification_methods.cockroach.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130645_add_verification_methods.mysql.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130645_add_verification_methods.postgres.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130645_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130646_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130646_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `form`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `via`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `success`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130646_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830130646_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.cockroach.down.sql ================================================ DROP TABLE "identity_verification_tokens";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.cockroach.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verification_tokens_identity_verifiable_addresses_id_fk" FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_verification_tokens_selfservice_verification_flows_id_fk" FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.mysql.down.sql ================================================ DROP TABLE `identity_verification_tokens`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.mysql.up.sql ================================================ CREATE TABLE `identity_verification_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `expires_at` DATETIME NOT NULL, `issued_at` DATETIME NOT NULL, `identity_verifiable_address_id` char(36) NOT NULL, `selfservice_verification_flow_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_verifiable_address_id`) REFERENCES `identity_verifiable_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_verification_flow_id`) REFERENCES `selfservice_verification_flows` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_verification_tokens_token_uq_idx` ON `identity_verification_tokens` (`token`); CREATE INDEX `identity_verification_tokens_token_idx` ON `identity_verification_tokens` (`token`); CREATE INDEX `identity_verification_tokens_verifiable_address_id_idx` ON `identity_verification_tokens` (`identity_verifiable_address_id`); CREATE INDEX `identity_verification_tokens_verification_flow_id_idx` ON `identity_verification_tokens` (`selfservice_verification_flow_id`); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.postgres.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.postgres.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.sqlite3.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830154602_add_verification_token.sqlite3.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.cockroach.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.mysql.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36) NOT NULL; ALTER TABLE `identity_recovery_tokens` DROP COLUMN `expires_at`; ALTER TABLE `identity_recovery_tokens` DROP COLUMN `issued_at`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `expires_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE `identity_recovery_tokens` ADD COLUMN `issued_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.postgres.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at"; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" DROP NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.sqlite3.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200830172221_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE identity_verifiable_addresses SET code = substr(md5(uuid_v4()), 0, 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "code" TO "_code_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_verifiable_addresses" SET "code" = "_code_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" SET NOT NULL;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_code_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "expires_at" TO "_expires_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_verifiable_addresses" SET "expires_at" = "_expires_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_expires_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `code` VARCHAR (32); ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `expires_at` DATETIME; UPDATE identity_verifiable_addresses SET code = LEFT(SHA2(RANDOM_BYTES(32), 256), 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ALTER TABLE `identity_verifiable_addresses` MODIFY `code` VARCHAR (32) NOT NULL; ALTER TABLE `identity_verifiable_addresses` MODIFY `expires_at` DATETIME; CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`); CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses`; DROP INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses`; ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `code`; ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `expires_at`; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32); ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp; UPDATE identity_verifiable_addresses SET code = substr(md5(random()::text), 0, 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" TYPE VARCHAR (32), ALTER COLUMN "code" SET NOT NULL; ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "expires_at" TYPE timestamp, ALTER COLUMN "expires_at" DROP NOT NULL; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_code_uq_idx"; DROP INDEX "identity_verifiable_addresses_code_idx"; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code"; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" TEXT; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" DATETIME; UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: oryx/popx/stub/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/legacy/20201201161451_credential_types_values.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/notx/20241031_notx.autocommit.down.sql ================================================ BEGIN;ROLLBACK; ================================================ FILE: oryx/popx/stub/migrations/notx/20241031_notx.autocommit.up.sql ================================================ BEGIN;ROLLBACK; ================================================ FILE: oryx/popx/stub/migrations/source/20191100000001_identities.down.fizz ================================================ drop_table("identity_credential_identifiers") drop_table("identity_credentials") drop_table("identity_credential_types") drop_table("identities") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000001_identities.up.fizz ================================================ create_table("identities") { t.Column("id", "uuid", {primary: true}) t.Column("traits_schema_id", "string", {"size": 2048}) t.Column("traits", "json") } create_table("identity_credential_types") { t.Column("id", "uuid", {primary: true}) t.Column("name", "string", { "size": 32 }) t.DisableTimestamps() } add_index("identity_credential_types", "name", {"unique": true}) create_table("identity_credentials") { t.Column("id", "uuid", {primary: true}) t.Column("config", "json") t.Column("identity_credential_type_id", "uuid") t.Column("identity_id", "uuid") t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) t.ForeignKey("identity_credential_type_id", {"identity_credential_types": ["id"]}, {"on_delete": "cascade"}) } create_table("identity_credential_identifiers") { t.Column("id", "uuid", {primary: true}) t.Column("identifier", "string", {"size": 255}) t.Column("identity_credential_id", "uuid") t.ForeignKey("identity_credential_id", {"identity_credentials": ["id"]}, {"on_delete": "cascade"}) } add_index("identity_credential_identifiers", "identifier", {"unique": true}) ================================================ FILE: oryx/popx/stub/migrations/source/20191100000002_requests.down.fizz ================================================ drop_table("selfservice_login_request_methods") drop_table("selfservice_login_requests") drop_table("selfservice_registration_request_methods") drop_table("selfservice_registration_requests") drop_table("selfservice_profile_management_requests") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000002_requests.up.fizz ================================================ create_table("selfservice_login_requests") { t.Column("id", "uuid", {primary: true}) t.Column("request_url", "string", {"size": 2048}) t.Column("issued_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("expires_at", "timestamp") t.Column("active_method", "string", {"size": 32}) t.Column("csrf_token", "string") } create_table("selfservice_login_request_methods") { t.Column("id", "uuid", {primary: true}) t.Column("method", "string", {"size": 32}) t.Column("selfservice_login_request_id", "uuid") t.Column("config", "json") t.ForeignKey("selfservice_login_request_id", {"selfservice_login_requests": ["id"]}, {"on_delete": "cascade"}) } create_table("selfservice_registration_requests") { t.Column("id", "uuid", {primary: true}) t.Column("request_url", "string", {"size": 2048}) t.Column("issued_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("expires_at", "timestamp") t.Column("active_method", "string", {"size": 32}) t.Column("csrf_token", "string") } create_table("selfservice_registration_request_methods") { t.Column("id", "uuid", {primary: true}) t.Column("method", "string", {"size": 32}) t.Column("selfservice_registration_request_id", "uuid") t.Column("config", "json") t.ForeignKey("selfservice_registration_request_id", {"selfservice_registration_requests": ["id"]}, {"on_delete": "cascade"}) } create_table("selfservice_profile_management_requests") { t.Column("id", "uuid", {primary: true}) t.Column("request_url", "string", {"size": 2048}) t.Column("issued_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("expires_at", "timestamp") t.Column("form", "json") t.Column("update_successful", "bool") t.Column("identity_id", "uuid") t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) } ================================================ FILE: oryx/popx/stub/migrations/source/20191100000003_sessions.down.fizz ================================================ drop_table("sessions") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000003_sessions.up.fizz ================================================ create_table("sessions") { t.Column("id", "uuid", {primary: true}) t.Column("issued_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("expires_at", "timestamp") t.Column("authenticated_at", "timestamp") t.Column("identity_id", "uuid") t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) } ================================================ FILE: oryx/popx/stub/migrations/source/20191100000004_errors.down.fizz ================================================ drop_table("selfservice_errors") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000004_errors.up.fizz ================================================ create_table("selfservice_errors") { t.Column("id", "uuid", {primary: true}) t.Column("errors", "json") t.Column("seen_at", "timestamp") t.Column("was_seen", "bool") } ================================================ FILE: oryx/popx/stub/migrations/source/20191100000005_identities.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255); ================================================ FILE: oryx/popx/stub/migrations/source/20191100000005_identities.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) BINARY; ================================================ FILE: oryx/popx/stub/migrations/source/20191100000006_courier.down.fizz ================================================ drop_table("courier_messages") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000006_courier.up.fizz ================================================ create_table("courier_messages") { t.Column("id", "uuid", {primary: true}) t.Column("type", "int") t.Column("status", "int") t.Column("body", "string") t.Column("subject", "string") t.Column("recipient", "string") } ================================================ FILE: oryx/popx/stub/migrations/source/20191100000007_errors.down.fizz ================================================ drop_column("selfservice_errors", "csrf_token") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000007_errors.up.fizz ================================================ add_column("selfservice_errors", "csrf_token", "string", {"default": ""}) ================================================ FILE: oryx/popx/stub/migrations/source/20191100000008_selfservice_verification.down.fizz ================================================ drop_table("selfservice_verification_requests") drop_table("identity_verifiable_addresses") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000008_selfservice_verification.up.fizz ================================================ create_table("identity_verifiable_addresses") { t.Column("id", "uuid", {primary: true}) t.Column("code", "string", {"size": 32}) t.Column("status", "string", {"size": 16}) t.Column("via", "string", {"size": 16}) t.Column("verified", "bool") t.Column("value", "string", {"size": 400}) t.Column("verified_at", "timestamp", {"null": true}) t.Column("expires_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("identity_id", "uuid") t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) } add_index("identity_verifiable_addresses", ["code"], { "unique": true, "name": "identity_verifiable_addresses_code_uq_idx" }) add_index("identity_verifiable_addresses", ["code"], { "name": "identity_verifiable_addresses_code_idx" }) add_index("identity_verifiable_addresses", ["via", "value"], { "unique": true, "name": "identity_verifiable_addresses_status_via_uq_idx" }) add_index("identity_verifiable_addresses", ["via", "value"], { "name": "identity_verifiable_addresses_status_via_idx" }) create_table("selfservice_verification_requests") { t.Column("id", "uuid", {primary: true}) t.Column("request_url", "string", {"size": 2048}) t.Column("issued_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("expires_at", "timestamp") t.Column("form", "json") t.Column("via", "string", {"size": 16}) t.Column("csrf_token", "string") t.Column("success", "bool") } ================================================ FILE: oryx/popx/stub/migrations/source/20191100000009_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255); ================================================ FILE: oryx/popx/stub/migrations/source/20191100000009_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: oryx/popx/stub/migrations/source/20191100000010_errors.down.fizz ================================================ sql("UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL;") change_column("selfservice_errors", "seen_at", "timestamp", { null: false }) ================================================ FILE: oryx/popx/stub/migrations/source/20191100000010_errors.up.fizz ================================================ change_column("selfservice_errors", "seen_at", "timestamp", { "null": true }) ================================================ FILE: oryx/popx/stub/migrations/source/20191100000011_courier_body_type.down.fizz ================================================ <%# Do nothing because the change will not be able to preserve data and the change is insignificant as it's compatible with both code bases (prior and after this change). WARNING: https://github.com/gobuffalo/fizz/issues/45#issuecomment-586833728 %> ================================================ FILE: oryx/popx/stub/migrations/source/20191100000011_courier_body_type.up.fizz ================================================ change_column("courier_messages", "body", "text", {}) ================================================ FILE: oryx/popx/stub/migrations/source/20191100000012_login_request_forced.down.fizz ================================================ drop_column("selfservice_login_requests", "forced") ================================================ FILE: oryx/popx/stub/migrations/source/20191100000012_login_request_forced.up.fizz ================================================ add_column("selfservice_login_requests", "forced", "bool", {"default": false}) ================================================ FILE: oryx/popx/stub/migrations/source/20200317160354_create_profile_request_forms.down.fizz ================================================ {{ if or .IsPostgreSQL .IsMySQL .IsMariaDB }} add_column("selfservice_profile_management_requests", "form", "json", { "null": true }) sql("UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t);") change_column("selfservice_profile_management_requests", "form", "json", { "null": false }) {{ end }} {{ if .IsCockroach }} add_column("selfservice_profile_management_requests", "form", "json", { "default": "{}" }) {{ end }} drop_table("selfservice_profile_management_request_methods") drop_column("selfservice_profile_management_requests", "active_method") ================================================ FILE: oryx/popx/stub/migrations/source/20200317160354_create_profile_request_forms.up.fizz ================================================ create_table("selfservice_profile_management_request_methods") { t.Column("id", "uuid", {primary: true}) t.Column("method", "string", {"size": 32}) t.Column("selfservice_profile_management_request_id", "uuid") t.Column("config", "json") } add_column("selfservice_profile_management_requests", "active_method", "string", {"size": 32, null: true}) sql("INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests;") drop_column("selfservice_profile_management_requests", "form") ================================================ FILE: oryx/popx/stub/migrations/source/20200401183443_continuity_containers.down.fizz ================================================ drop_table("continuity_containers") ================================================ FILE: oryx/popx/stub/migrations/source/20200401183443_continuity_containers.up.fizz ================================================ create_table("continuity_containers") { t.Column("id", "uuid", {primary: true}) t.Column("identity_id", "uuid", {null: true}) t.Column("name", "string") t.Column("payload", "json", {null: true}) t.Column("expires_at", "timestamp") t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) } ================================================ FILE: oryx/popx/stub/migrations/source/20200402142539_rename_profile_flows.down.fizz ================================================ rename_column("selfservice_settings_request_methods", "selfservice_settings_request_id", "selfservice_profile_management_request_id") rename_table("selfservice_settings_request_methods", "selfservice_profile_management_request_methods") rename_table("selfservice_settings_requests", "selfservice_profile_management_requests") ================================================ FILE: oryx/popx/stub/migrations/source/20200402142539_rename_profile_flows.up.fizz ================================================ rename_column("selfservice_profile_management_request_methods", "selfservice_profile_management_request_id", "selfservice_settings_request_id") rename_table("selfservice_profile_management_request_methods", "selfservice_settings_request_methods") rename_table("selfservice_profile_management_requests", "selfservice_settings_requests") ================================================ FILE: oryx/popx/stub/migrations/source/20200519101057_create_recovery_addresses.down.fizz ================================================ drop_table("identity_recovery_tokens") drop_table("selfservice_recovery_request_methods") drop_table("selfservice_recovery_requests") drop_table("identity_recovery_addresses") ================================================ FILE: oryx/popx/stub/migrations/source/20200519101057_create_recovery_addresses.up.fizz ================================================ create_table("identity_recovery_addresses") { t.Column("id", "uuid", {primary: true}) t.Column("via", "string", {"size": 16}) t.Column("value", "string", {"size": 400}) t.Column("identity_id", "uuid") t.ForeignKey("identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) } add_index("identity_recovery_addresses", ["via", "value"], { "unique": true, "name": "identity_recovery_addresses_status_via_uq_idx" }) add_index("identity_recovery_addresses", ["via", "value"], { "name": "identity_recovery_addresses_status_via_idx" }) create_table("selfservice_recovery_requests") { t.Column("id", "uuid", {primary: true}) t.Column("request_url", "string", {"size": 2048}) t.Column("issued_at", "timestamp", { "default_raw": "CURRENT_TIMESTAMP" }) t.Column("expires_at", "timestamp") t.Column("messages", "json", {"null": true}) t.Column("active_method", "string", {"size": 32, "null": true}) t.Column("csrf_token", "string") t.Column("state", "string", {"size": 32}) t.Column("recovered_identity_id", "uuid", { "null": true }) t.ForeignKey("recovered_identity_id", {"identities": ["id"]}, {"on_delete": "cascade"}) } create_table("selfservice_recovery_request_methods") { t.Column("id", "uuid", {primary: true}) t.Column("method", "string", {"size": 32}) t.Column("config", "json") t.Column("selfservice_recovery_request_id", "uuid") t.ForeignKey("selfservice_recovery_request_id", {"selfservice_recovery_requests": ["id"]}, {"on_delete": "cascade"}) } create_table("identity_recovery_tokens") { t.Column("id", "uuid", {primary: true}) t.Column("token", "string", {"size": 64}) t.Column("used", "bool", {"default": false}) t.Column("used_at", "timestamp", {"null": true}) t.Column("identity_recovery_address_id", "uuid") t.ForeignKey("identity_recovery_address_id", {"identity_recovery_addresses": ["id"]}, {"on_delete": "cascade"}) t.Column("selfservice_recovery_request_id", "uuid") t.ForeignKey("selfservice_recovery_request_id", {"selfservice_recovery_requests": ["id"]}, {"on_delete": "cascade"}) } add_index("identity_recovery_tokens", ["token"], { "unique": true, "name": "identity_recovery_addresses_code_uq_idx" }) add_index("identity_recovery_tokens", ["token"], { "name": "identity_recovery_addresses_code_idx" }) ================================================ FILE: oryx/popx/stub/migrations/source/20200519101058_create_recovery_addresses.mysql.down.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64); ================================================ FILE: oryx/popx/stub/migrations/source/20200519101058_create_recovery_addresses.mysql.up.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) BINARY; ================================================ FILE: oryx/popx/stub/migrations/source/20200601101000_create_messages.down.fizz ================================================ drop_column("selfservice_settings_requests", "messages") ================================================ FILE: oryx/popx/stub/migrations/source/20200601101000_create_messages.up.fizz ================================================ add_column("selfservice_settings_requests", "messages", "json", {"null": true}) ================================================ FILE: oryx/popx/stub/migrations/source/20200601101001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: oryx/popx/stub/migrations/source/20200601101001_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(32) BINARY; ================================================ FILE: oryx/popx/stub/migrations/source/20200605111551_messages.down.fizz ================================================ drop_column("selfservice_verification_requests", "messages") drop_column("selfservice_login_requests", "messages") drop_column("selfservice_registration_requests", "messages") ================================================ FILE: oryx/popx/stub/migrations/source/20200605111551_messages.up.fizz ================================================ add_column("selfservice_verification_requests", "messages", "json", {"null": true}) add_column("selfservice_login_requests", "messages", "json", {"null": true}) add_column("selfservice_registration_requests", "messages", "json", {"null": true}) ================================================ FILE: oryx/popx/stub/migrations/source/20200607165100_settings.down.fizz ================================================ drop_column("selfservice_settings_requests", "state") add_column("selfservice_settings_requests", "update_successful", "bool", {"default": false}) ================================================ FILE: oryx/popx/stub/migrations/source/20200607165100_settings.up.fizz ================================================ add_column("selfservice_settings_requests", "state", "string", {"default": "show_form"}) drop_column("selfservice_settings_requests", "update_successful") ================================================ FILE: oryx/popx/stub/migrations/source/20200705105359_rename_identities_schema.down.fizz ================================================ rename_column("identities", "schema_id", "traits_schema_id") ================================================ FILE: oryx/popx/stub/migrations/source/20200705105359_rename_identities_schema.up.fizz ================================================ rename_column("identities", "traits_schema_id", "schema_id") ================================================ FILE: oryx/popx/stub/migrations/source/20200810141652_flow_type.down.fizz ================================================ drop_column("selfservice_login_requests", "type") drop_column("selfservice_registration_requests", "type") drop_column("selfservice_settings_requests", "type") drop_column("selfservice_recovery_requests", "type") drop_column("selfservice_verification_requests", "type") ================================================ FILE: oryx/popx/stub/migrations/source/20200810141652_flow_type.up.fizz ================================================ add_column("selfservice_login_requests", "type", "string", {"default": "browser", "size": 16}) add_column("selfservice_registration_requests", "type", "string", {"default": "browser", "size": 16}) add_column("selfservice_settings_requests", "type", "string", {"default": "browser", "size": 16}) add_column("selfservice_recovery_requests", "type", "string", {"default": "browser", "size": 16}) add_column("selfservice_verification_requests", "type", "string", {"default": "browser", "size": 16}) ================================================ FILE: oryx/popx/stub/migrations/source/20200810161022_flow_rename.down.fizz ================================================ rename_table("selfservice_login_flows", "selfservice_login_requests") rename_table("selfservice_login_flow_methods", "selfservice_login_request_methods") rename_table("selfservice_registration_flow_methods", "selfservice_registration_request_methods") rename_table("selfservice_registration_flows", "selfservice_registration_requests") rename_table("selfservice_settings_flow_methods", "selfservice_settings_request_methods") rename_table("selfservice_settings_flows", "selfservice_settings_requests") rename_table("selfservice_recovery_flow_methods", "selfservice_recovery_request_methods") rename_table("selfservice_recovery_flows", "selfservice_recovery_requests") rename_table("selfservice_verification_flows", "selfservice_verification_requests") ================================================ FILE: oryx/popx/stub/migrations/source/20200810161022_flow_rename.up.fizz ================================================ rename_table("selfservice_login_request_methods", "selfservice_login_flow_methods") rename_table("selfservice_login_requests", "selfservice_login_flows") rename_table("selfservice_registration_request_methods", "selfservice_registration_flow_methods") rename_table("selfservice_registration_requests", "selfservice_registration_flows") rename_table("selfservice_settings_request_methods", "selfservice_settings_flow_methods") rename_table("selfservice_settings_requests", "selfservice_settings_flows") rename_table("selfservice_recovery_request_methods", "selfservice_recovery_flow_methods") rename_table("selfservice_recovery_requests", "selfservice_recovery_flows") rename_table("selfservice_verification_requests", "selfservice_verification_flows") ================================================ FILE: oryx/popx/stub/migrations/source/20200810162450_flow_fields_rename.down.fizz ================================================ rename_column("selfservice_login_flow_methods", "selfservice_login_flow_id", "selfservice_login_request_id") rename_column("selfservice_registration_flow_methods", "selfservice_registration_flow_id", "selfservice_registration_request_id") rename_column("selfservice_settings_flow_methods", "selfservice_settings_flow_id", "selfservice_settings_request_id") rename_column("selfservice_recovery_flow_methods", "selfservice_recovery_flow_id", "selfservice_recovery_request_id") ================================================ FILE: oryx/popx/stub/migrations/source/20200810162450_flow_fields_rename.up.fizz ================================================ rename_column("selfservice_login_flow_methods", "selfservice_login_request_id", "selfservice_login_flow_id") rename_column("selfservice_registration_flow_methods", "selfservice_registration_request_id", "selfservice_registration_flow_id") rename_column("selfservice_recovery_flow_methods", "selfservice_recovery_request_id", "selfservice_recovery_flow_id") rename_column("selfservice_settings_flow_methods", "selfservice_settings_request_id", "selfservice_settings_flow_id") ================================================ FILE: oryx/popx/stub/migrations/source/20200812124254_add_session_token.down.fizz ================================================ drop_column("sessions", "token") ================================================ FILE: oryx/popx/stub/migrations/source/20200812124254_add_session_token.up.fizz ================================================ sql("DELETE FROM sessions") add_column("sessions", "token", "string", {"size": 32, "null": true}) change_column("sessions", "token", "string", {"size": 32, "null": false}) add_index("sessions", "token", {"unique": true, "name": "sessions_token_uq_idx"}) add_index("sessions", "token", {"name": "sessions_token_idx" }) ================================================ FILE: oryx/popx/stub/migrations/source/20200812160551_add_session_revoke.down.fizz ================================================ drop_column("sessions", "active") ================================================ FILE: oryx/popx/stub/migrations/source/20200812160551_add_session_revoke.up.fizz ================================================ add_column("sessions", "active", "boolean", {"null": false, "default": false}) ================================================ FILE: oryx/popx/stub/migrations/source/20200830121710_update_recovery_token.down.fizz ================================================ rename_column("identity_recovery_tokens", "selfservice_recovery_flow_id", "selfservice_recovery_request_id") ================================================ FILE: oryx/popx/stub/migrations/source/20200830121710_update_recovery_token.up.fizz ================================================ rename_column("identity_recovery_tokens", "selfservice_recovery_request_id", "selfservice_recovery_flow_id") ================================================ FILE: oryx/popx/stub/migrations/source/20200830130642_add_verification_methods.down.fizz ================================================ {{ if or .IsPostgreSQL .IsMySQL .IsMariaDB }} add_column("selfservice_verification_flows", "form", "json", { "null": true }) sql("UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t);") change_column("selfservice_verification_flows", "form", "json", { "null": false }) {{ end }} {{ if .IsCockroach }} add_column("selfservice_verification_flows", "form", "json", { "default": "{}" }) {{ end }} drop_table("selfservice_verification_flow_methods") drop_column("selfservice_verification_flows", "active_method") drop_column("selfservice_verification_flows", "state") add_column("selfservice_verification_flows", "via", "string", {"size": 16, "default": "email"}) add_column("selfservice_verification_flows", "success", "bool", {"default_raw": "FALSE"}) ================================================ FILE: oryx/popx/stub/migrations/source/20200830130642_add_verification_methods.up.fizz ================================================ add_column("selfservice_verification_flows", "state", "string", {"default": "show_form"}) ================================================ FILE: oryx/popx/stub/migrations/source/20200830130643_add_verification_methods.down.fizz ================================================ ================================================ FILE: oryx/popx/stub/migrations/source/20200830130643_add_verification_methods.up.fizz ================================================ sql("UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE") ================================================ FILE: oryx/popx/stub/migrations/source/20200830130644_add_verification_methods.down.fizz ================================================ ================================================ FILE: oryx/popx/stub/migrations/source/20200830130644_add_verification_methods.up.fizz ================================================ create_table("selfservice_verification_flow_methods") { t.Column("id", "uuid", {primary: true}) t.Column("method", "string", {"size": 32}) t.Column("selfservice_verification_flow_id", "uuid") t.Column("config", "json") } add_column("selfservice_verification_flows", "active_method", "string", {"size": 32, null: true}) ================================================ FILE: oryx/popx/stub/migrations/source/20200830130645_add_verification_methods.down.fizz ================================================ ================================================ FILE: oryx/popx/stub/migrations/source/20200830130645_add_verification_methods.up.fizz ================================================ sql("INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows;") ================================================ FILE: oryx/popx/stub/migrations/source/20200830130646_add_verification_methods.down.fizz ================================================ ================================================ FILE: oryx/popx/stub/migrations/source/20200830130646_add_verification_methods.up.fizz ================================================ drop_column("selfservice_verification_flows", "form") drop_column("selfservice_verification_flows", "via") drop_column("selfservice_verification_flows", "success") ================================================ FILE: oryx/popx/stub/migrations/source/20200830154602_add_verification_token.down.fizz ================================================ drop_table("identity_verification_tokens") ================================================ FILE: oryx/popx/stub/migrations/source/20200830154602_add_verification_token.up.fizz ================================================ create_table("identity_verification_tokens") { t.Column("id", "uuid", {primary: true}) t.Column("token", "string", {"size": 64}) t.Column("used", "bool", {"default": false}) t.Column("used_at", "timestamp", {"null": true}) t.Column("expires_at", "timestamp") t.Column("issued_at", "timestamp") t.Column("identity_verifiable_address_id", "uuid") t.ForeignKey("identity_verifiable_address_id", {"identity_verifiable_addresses": ["id"]}, {"on_delete": "cascade"}) t.Column("selfservice_verification_flow_id", "uuid", {"null": true}) t.ForeignKey("selfservice_verification_flow_id", {"selfservice_verification_flows": ["id"]}, {"on_delete": "cascade"}) } add_index("identity_verification_tokens", ["token"], { "unique": true, "name": "identity_verification_tokens_token_uq_idx" }) add_index("identity_verification_tokens", ["token"], { "name": "identity_verification_tokens_token_idx" }) add_index("identity_verification_tokens", ["identity_verifiable_address_id"], { "name": "identity_verification_tokens_verifiable_address_id_idx" }) add_index("identity_verification_tokens", ["selfservice_verification_flow_id"], { "name": "identity_verification_tokens_verification_flow_id_idx" }) ================================================ FILE: oryx/popx/stub/migrations/source/20200830172221_recovery_token_expires.down.fizz ================================================ sql("DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL") change_column("identity_recovery_tokens", "selfservice_recovery_flow_id", "uuid") drop_column("identity_recovery_tokens", "expires_at") drop_column("identity_recovery_tokens", "issued_at") ================================================ FILE: oryx/popx/stub/migrations/source/20200830172221_recovery_token_expires.up.fizz ================================================ add_column("identity_recovery_tokens", "expires_at", "timestamp", { "default": "2000-01-01 00:00:00" }) add_column("identity_recovery_tokens", "issued_at", "timestamp", { "default": "2000-01-01 00:00:00" }) change_column("identity_recovery_tokens", "selfservice_recovery_flow_id", "uuid", {"null": true}) ================================================ FILE: oryx/popx/stub/migrations/source/20200831110752_identity_verifiable_address_remove_code.down.fizz ================================================ add_column("identity_verifiable_addresses", "code", "string", {"size": 32, "null": true}) add_column("identity_verifiable_addresses", "expires_at", "timestamp", { "null": true }) {{ if .IsSQLite }} sql("UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL") sql("UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL") {{ end }} {{ if or .IsMySQL .IsMariaDB }} sql("UPDATE identity_verifiable_addresses SET code = LEFT(SHA2(RANDOM_BYTES(32), 256), 32) WHERE code IS NULL") sql("UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL") {{ end }} {{ if .IsPostgreSQL }} sql("UPDATE identity_verifiable_addresses SET code = substr(md5(random()::text), 0, 32) WHERE code IS NULL") sql("UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL") {{ end }} {{ if .IsCockroach }} sql("UPDATE identity_verifiable_addresses SET code = substr(md5(uuid_v4()), 0, 32) WHERE code IS NULL") sql("UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL") {{ end }} change_column("identity_verifiable_addresses", "code", "string", {"size": 32}) change_column("identity_verifiable_addresses", "expires_at", "timestamp", { "null": false }) add_index("identity_verifiable_addresses", ["code"], { "unique": true, "name": "identity_verifiable_addresses_code_uq_idx" }) add_index("identity_verifiable_addresses", ["code"], { "name": "identity_verifiable_addresses_code_idx" }) ================================================ FILE: oryx/popx/stub/migrations/source/20200831110752_identity_verifiable_address_remove_code.up.fizz ================================================ drop_index("identity_verifiable_addresses", "identity_verifiable_addresses_code_uq_idx") drop_index("identity_verifiable_addresses", "identity_verifiable_addresses_code_idx") drop_column("identity_verifiable_addresses", "code") drop_column("identity_verifiable_addresses", "expires_at") ================================================ FILE: oryx/popx/stub/migrations/source/20201201161451_credential_types_values.down.fizz ================================================ sql("DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'") ================================================ FILE: oryx/popx/stub/migrations/source/20201201161451_credential_types_values.up.fizz ================================================ sql("INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password')") sql("INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc')") ================================================ FILE: oryx/popx/stub/migrations/templating/0_sql_create_tablename_template.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/templating/0_sql_create_tablename_template.up.sql ================================================ CREATE TABLE {{ identifier .Parameters.tableName }} ( "id" UUID NOT NULL, PRIMARY KEY ("id")); ================================================ FILE: oryx/popx/stub/migrations/testdata/20220513_testdata.invalid ================================================ ================================================ FILE: oryx/popx/stub/migrations/testdata/20220513_testdata.sql ================================================ INSERT INTO testdata (Data) VALUES ('testdata'); ================================================ FILE: oryx/popx/stub/migrations/testdata/20220514_testdata.sql ================================================ -- empty migrations should not error ================================================ FILE: oryx/popx/stub/migrations/testdata/invalid ================================================ ================================================ FILE: oryx/popx/stub/migrations/testdata/invalid_testdata.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/testdata_migrations/20220513_create_table.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/testdata_migrations/20220513_create_table.up.sql ================================================ CREATE TABLE "testdata" ( "data" character varying(255) NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.cockroach.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.cockroach.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.mysql.down.sql ================================================ DROP TABLE `identities`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.mysql.up.sql ================================================ CREATE TABLE `identities` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `traits_schema_id` VARCHAR (2048) NOT NULL, `traits` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.postgres.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.postgres.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.sqlite3.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000000_identities.sqlite3.up.sql ================================================ CREATE TABLE "identities" ( "id" TEXT PRIMARY KEY, "traits_schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credential_types" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.cockroach.up.sql ================================================ CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.mysql.down.sql ================================================ DROP TABLE `identity_credential_types` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.mysql.up.sql ================================================ CREATE TABLE `identity_credential_types` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `name` VARCHAR (32) NOT NULL ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.postgres.down.sql ================================================ DROP TABLE "identity_credential_types" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.postgres.up.sql ================================================ CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_types" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000001_identities.sqlite3.up.sql ================================================ CREATE TABLE "identity_credential_types" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credentials" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.mysql.down.sql ================================================ DROP TABLE `identity_credentials` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_credential_types_name_idx` ON `identity_credential_types` (`name`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.postgres.down.sql ================================================ DROP TABLE "identity_credentials" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credentials" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000002_identities.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credential_identifiers" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.cockroach.up.sql ================================================ CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" json NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credentials_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, CONSTRAINT "identity_credentials_identity_credential_types_id_fk" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.mysql.down.sql ================================================ DROP TABLE `identity_credential_identifiers` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.mysql.up.sql ================================================ CREATE TABLE `identity_credentials` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `config` JSON NOT NULL, `identity_credential_type_id` char(36) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade, FOREIGN KEY (`identity_credential_type_id`) REFERENCES `identity_credential_types` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.postgres.down.sql ================================================ DROP TABLE "identity_credential_identifiers" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.postgres.up.sql ================================================ CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" jsonb NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_identifiers" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000003_identities.sqlite3.up.sql ================================================ CREATE TABLE "identity_credentials" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.cockroach.up.sql ================================================ CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credential_identifiers_identity_credentials_id_fk" FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.mysql.up.sql ================================================ CREATE TABLE `identity_credential_identifiers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identifier` VARCHAR (255) NOT NULL, `identity_credential_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_credential_id`) REFERENCES `identity_credentials` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.postgres.up.sql ================================================ CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000004_identities.sqlite3.up.sql ================================================ CREATE TABLE "identity_credential_identifiers" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_idx` ON `identity_credential_identifiers` (`identifier`); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000001000005_identities.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_profile_management_requests`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_login_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000000_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_login_request_methods_selfservice_login_requests_id_fk" FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_registration_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_login_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_login_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_login_request_id`) REFERENCES `selfservice_login_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000001_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_login_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_login_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_login_request_id) REFERENCES selfservice_login_requests (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_registration_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_registration_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_registration_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_registration_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000002_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_registration_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_registration_request_methods_selfservice_registration_requests_id_fk" FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_login_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_registration_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_registration_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_registration_request_id`) REFERENCES `selfservice_registration_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000003_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_registration_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_registration_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_registration_request_id) REFERENCES selfservice_registration_requests (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_login_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_profile_management_requests_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_login_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_profile_management_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `update_successful` bool NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_login_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000002000004_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_profile_management_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.cockroach.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.cockroach.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "sessions_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.mysql.down.sql ================================================ DROP TABLE `sessions`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.mysql.up.sql ================================================ CREATE TABLE `sessions` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `authenticated_at` DATETIME NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.postgres.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.postgres.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000003000000_sessions.sqlite3.up.sql ================================================ CREATE TABLE "sessions" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.cockroach.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.cockroach.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" json NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.mysql.down.sql ================================================ DROP TABLE `selfservice_errors`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.mysql.up.sql ================================================ CREATE TABLE `selfservice_errors` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `errors` JSON NOT NULL, `seen_at` DATETIME NOT NULL, `was_seen` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.postgres.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.postgres.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" jsonb NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000004000000_errors.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME NOT NULL, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000005000000_identities.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000005000000_identities.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000005000001_identities.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000005000001_identities.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.cockroach.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.cockroach.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.mysql.down.sql ================================================ DROP TABLE `courier_messages`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.mysql.up.sql ================================================ CREATE TABLE `courier_messages` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `type` INTEGER NOT NULL, `status` INTEGER NOT NULL, `body` VARCHAR (255) NOT NULL, `subject` VARCHAR (255) NOT NULL, `recipient` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.postgres.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.postgres.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000006000000_courier.sqlite3.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` DROP COLUMN `csrf_token`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` ADD COLUMN `csrf_token` VARCHAR (255) NOT NULL DEFAULT ""; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000000_errors.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" TEXT NOT NULL DEFAULT ''; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000001_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000001_errors.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000002_errors.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at) SELECT id, errors, seen_at, was_seen, created_at, updated_at FROM "selfservice_errors" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000002_errors.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000003_errors.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000007000003_errors.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.cockroach.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.cockroach.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verifiable_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.mysql.down.sql ================================================ DROP TABLE `identity_verifiable_addresses`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.mysql.up.sql ================================================ CREATE TABLE `identity_verifiable_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `code` VARCHAR (32) NOT NULL, `status` VARCHAR (16) NOT NULL, `via` VARCHAR (16) NOT NULL, `verified` bool NOT NULL, `value` VARCHAR (400) NOT NULL, `verified_at` DATETIME, `expires_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.postgres.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.postgres.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000000_selfservice_verification.sqlite3.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" TEXT PRIMARY KEY, "code" TEXT NOT NULL, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.cockroach.down.sql ================================================ DROP TABLE "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.mysql.down.sql ================================================ DROP TABLE `selfservice_verification_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.postgres.down.sql ================================================ DROP TABLE "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000001_selfservice_verification.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.mysql.up.sql ================================================ CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000002_selfservice_verification.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses` (`via`, `value`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000003_selfservice_verification.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.mysql.up.sql ================================================ CREATE INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses` (`via`, `value`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000004_selfservice_verification.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.cockroach.up.sql ================================================ CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.mysql.up.sql ================================================ CREATE TABLE `selfservice_verification_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `via` VARCHAR (16) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `success` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.postgres.up.sql ================================================ CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000008000005_selfservice_verification.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_verification_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000009000000_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000009000000_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000009000001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000009000001_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000000_errors.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.cockroach.down.sql ================================================ UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.mysql.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.postgres.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000001_errors.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000002_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000002_errors.cockroach.up.sql ================================================ UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000002_errors.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000002_errors.sqlite3.up.sql ================================================ DROP TABLE "selfservice_errors" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000003_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000003_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000003_errors.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000003_errors.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000004_errors.cockroach.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000004_errors.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000004_errors.sqlite3.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000010000004_errors.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" RENAME COLUMN "body" TO "_body_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.mysql.up.sql ================================================ ALTER TABLE `courier_messages` MODIFY `body` text NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "body" TYPE text, ALTER COLUMN "body" SET NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000000_courier_body_type.sqlite3.up.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000001_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000001_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "body" text ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000001_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000001_courier_body_type.sqlite3.up.sql ================================================ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at) SELECT id, type, status, body, subject, recipient, created_at, updated_at FROM "courier_messages" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000002_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000002_courier_body_type.cockroach.up.sql ================================================ UPDATE "courier_messages" SET "body" = "_body_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000002_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000002_courier_body_type.sqlite3.up.sql ================================================ DROP TABLE "courier_messages" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000003_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000003_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "body" SET NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000003_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000003_courier_body_type.sqlite3.up.sql ================================================ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000004_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000011000004_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "_body_tmp"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `forced`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `forced` bool NOT NULL DEFAULT false; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000000_login_request_forced.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000001_login_request_forced.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000001_login_request_forced.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000002_login_request_forced.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000002_login_request_forced.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000003_login_request_forced.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20191100000012000003_login_request_forced.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.cockroach.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.cockroach.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `active_method`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.mysql.up.sql ================================================ CREATE TABLE `selfservice_profile_management_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_profile_management_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.postgres.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000000_create_profile_request_forms.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_profile_management_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.cockroach.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.mysql.down.sql ================================================ DROP TABLE `selfservice_profile_management_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `active_method` VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.postgres.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000001_create_profile_request_forms.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" TEXT ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.cockroach.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" json NOT NULL DEFAULT '{}' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.cockroach.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` MODIFY `form` JSON ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.mysql.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.postgres.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful FROM "selfservice_profile_management_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000002_create_profile_request_forms.sqlite3.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.mysql.down.sql ================================================ UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `form`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.postgres.down.sql ================================================ UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000003_create_profile_request_forms.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000004_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `form` JSON ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000004_create_profile_request_forms.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000004_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" jsonb ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000004_create_profile_request_forms.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000004_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000004_create_profile_request_forms.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) SELECT id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method FROM "selfservice_profile_management_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000005_create_profile_request_forms.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000005_create_profile_request_forms.sqlite3.up.sql ================================================ DROP TABLE "selfservice_profile_management_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000006_create_profile_request_forms.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200317160354000006_create_profile_request_forms.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.cockroach.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.cockroach.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" json, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "continuity_containers_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.mysql.down.sql ================================================ DROP TABLE `continuity_containers`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.mysql.up.sql ================================================ CREATE TABLE `continuity_containers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identity_id` char(36), `name` VARCHAR (255) NOT NULL, `payload` JSON, `expires_at` DATETIME NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.postgres.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.postgres.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" jsonb, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.sqlite3.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200401183443000000_continuity_containers.sqlite3.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_profile_management_requests`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_request_methods` CHANGE `selfservice_profile_management_request_id` `selfservice_settings_request_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000000_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_profile_management_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_request_methods` RENAME TO `selfservice_settings_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000001_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` CHANGE `selfservice_settings_request_id` `selfservice_profile_management_request_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` RENAME TO `selfservice_settings_requests`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200402142539000002_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `identity_recovery_addresses`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `identity_recovery_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `via` VARCHAR (16) NOT NULL, `value` VARCHAR (400) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000000_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" TEXT PRIMARY KEY, "via" TEXT NOT NULL, "value" TEXT NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `selfservice_recovery_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses` (`via`, `value`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000001_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "selfservice_recovery_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `selfservice_recovery_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses` (`via`, `value`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "selfservice_recovery_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000002_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" json, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_requests_identities_id_fk" FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `identity_recovery_tokens` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `selfservice_recovery_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `messages` JSON, `active_method` VARCHAR (32), `csrf_token` VARCHAR (255) NOT NULL, `state` VARCHAR (32) NOT NULL, `recovered_identity_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`recovered_identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" jsonb, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000003_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_recovery_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" json NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_request_methods_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `selfservice_recovery_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `config` JSON NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" jsonb NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000004_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_recovery_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "config" TEXT NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk" FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `identity_recovery_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `identity_recovery_address_id` char(36) NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_recovery_address_id`) REFERENCES `identity_recovery_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000005_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "identity_recovery_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_recovery_addresses_code_uq_idx` ON `identity_recovery_tokens` (`token`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000006_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_addresses_code_idx` ON `identity_recovery_tokens` (`token`); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101057000007_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101058000000_create_recovery_addresses.mysql.down.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101058000000_create_recovery_addresses.mysql.up.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101058000001_create_recovery_addresses.mysql.down.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200519101058000001_create_recovery_addresses.mysql.up.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" json; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `messages`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `messages` JSON; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000000_create_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000001_create_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000001_create_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000002_create_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful FROM "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000002_create_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000003_create_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101000000003_create_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101001000000_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101001000000_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(32) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101001000001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20200601101001000001_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(32) BINARY ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" json ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_requests` DROP COLUMN `messages`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` ADD COLUMN `messages` JSON ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" jsonb ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000000_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" TEXT ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" json ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `messages` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `messages` JSON ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" jsonb ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000001_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" TEXT ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" json; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_requests` DROP COLUMN `messages` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_requests` ADD COLUMN `messages` JSON; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000002_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000003_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000003_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000004_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000004_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000005_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000005_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000006_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced FROM "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000006_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000007_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000007_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000008_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000008_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000009_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000009_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000010_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, via, success FROM "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000010_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000011_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200605111551000011_messages.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `update_successful` bool NOT NULL DEFAULT false; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000000_settings.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `state` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `update_successful`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000001_settings.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000002_settings.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000002_settings.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000003_settings.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages FROM "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000003_settings.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000004_settings.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200607165100000004_settings.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.cockroach.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.cockroach.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.mysql.down.sql ================================================ ALTER TABLE `identities` CHANGE `schema_id` `traits_schema_id` varchar(2048) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.mysql.up.sql ================================================ ALTER TABLE `identities` CHANGE `traits_schema_id` `schema_id` varchar(2048) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.postgres.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.postgres.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.sqlite3.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200705105359000000_rename_identities_schema.sqlite3.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_requests` DROP COLUMN `type`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000000_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_requests` DROP COLUMN `type` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000001_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `type` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, via, success FROM "selfservice_verification_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000002_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_requests` DROP COLUMN `type` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000003_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `type` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_recovery_requests_tmp" RENAME TO "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000004_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000005_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000005_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000006_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_recovery_requests_tmp" (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at FROM "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000006_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000007_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_recovery_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000007_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000008_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000008_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000009_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000009_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000010_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000010_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000011_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000011_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000012_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000012_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000013_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000013_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000014_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages FROM "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000014_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000015_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000015_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000016_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000016_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000017_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000017_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000018_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages FROM "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000018_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000019_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "messages" TEXT ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810141652000019_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` RENAME TO `selfservice_verification_requests`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_request_methods` RENAME TO `selfservice_login_flow_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000000_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flows` RENAME TO `selfservice_recovery_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` RENAME TO `selfservice_login_flows` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000001_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flow_methods` RENAME TO `selfservice_recovery_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_request_methods` RENAME TO `selfservice_registration_flow_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000002_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` RENAME TO `selfservice_settings_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_requests` RENAME TO `selfservice_registration_flows` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000003_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flow_methods` RENAME TO `selfservice_settings_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_settings_flow_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000004_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` RENAME TO `selfservice_registration_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_settings_flows` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000005_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flow_methods` RENAME TO `selfservice_registration_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_request_methods` RENAME TO `selfservice_recovery_flow_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000006_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` RENAME TO `selfservice_login_request_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_requests` RENAME TO `selfservice_recovery_flows` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000007_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` RENAME TO `selfservice_login_requests` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` RENAME TO `selfservice_verification_flows`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810161022000008_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_request_id` `selfservice_login_flow_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000000_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_flow_id` `selfservice_settings_request_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_request_id` `selfservice_registration_flow_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000001_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_flow_id` `selfservice_registration_request_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000002_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_flow_id` `selfservice_login_request_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_request_id` `selfservice_settings_flow_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200810162450000003_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.cockroach.up.sql ================================================ DELETE FROM sessions ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `token`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.mysql.up.sql ================================================ DELETE FROM sessions ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.postgres.up.sql ================================================ DELETE FROM sessions ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000000_add_session_token.sqlite3.up.sql ================================================ DELETE FROM sessions ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `token` VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.sqlite3.down.sql ================================================ DROP TABLE "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000001_add_session_token.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" TEXT ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" RENAME COLUMN "token" TO "_token_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.mysql.up.sql ================================================ ALTER TABLE `sessions` MODIFY `token` VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.postgres.up.sql ================================================ ALTER TABLE "sessions" ALTER COLUMN "token" TYPE VARCHAR (32), ALTER COLUMN "token" DROP NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at FROM "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000002_add_session_token.sqlite3.up.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.mysql.up.sql ================================================ CREATE UNIQUE INDEX `sessions_token_uq_idx` ON `sessions` (`token`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.postgres.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000003_add_session_token.sqlite3.up.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.cockroach.up.sql ================================================ UPDATE "sessions" SET "token" = "_token_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.mysql.up.sql ================================================ CREATE INDEX `sessions_token_idx` ON `sessions` (`token`); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.postgres.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000004_add_session_token.sqlite3.up.sql ================================================ DROP TABLE "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000005_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000005_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "_token_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000005_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000005_add_session_token.sqlite3.up.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000006_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000006_add_session_token.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000006_add_session_token.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000006_add_session_token.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000007_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000007_add_session_token.cockroach.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000007_add_session_token.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812124254000007_add_session_token.sqlite3.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `active`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `active` boolean DEFAULT false; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000000_add_session_revoke.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" NUMERIC DEFAULT 'false'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000001_add_session_revoke.sqlite3.down.sql ================================================ DROP TABLE "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000001_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000002_add_session_revoke.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000002_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000003_add_session_revoke.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000003_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000004_add_session_revoke.sqlite3.down.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000004_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000005_add_session_revoke.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000005_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000006_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000006_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000007_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200812160551000007_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.sqlite3.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830121710000000_update_recovery_token.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `success` bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000000_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `via` VARCHAR (16) NOT NULL DEFAULT 'email' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" TEXT NOT NULL DEFAULT 'email' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000001_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `state` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000002_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `active_method` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000003_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.cockroach.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.mysql.down.sql ================================================ DROP TABLE `selfservice_verification_flow_methods` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.postgres.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type FROM "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000004_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" json NOT NULL DEFAULT '{}' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` MODIFY `form` JSON ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000005_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000006_add_verification_methods.mysql.down.sql ================================================ UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000006_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000006_add_verification_methods.postgres.down.sql ================================================ UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000006_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000006_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000006_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000007_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `form` JSON ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000007_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000007_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" jsonb ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000007_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000007_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000007_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000008_add_verification_methods.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state FROM "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000008_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000009_add_verification_methods.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form' ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000009_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000010_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130642000010_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.cockroach.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.mysql.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.postgres.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130643000000_add_verification_methods.sqlite3.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.cockroach.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.mysql.up.sql ================================================ CREATE TABLE `selfservice_verification_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_verification_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.postgres.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000000_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `active_method` VARCHAR (32); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130644000001_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" TEXT; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.cockroach.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.mysql.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.postgres.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130645000000_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `form` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000000_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `via` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000001_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `success`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000002_add_verification_methods.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000003_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000003_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000004_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000004_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000005_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000005_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000006_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000006_add_verification_methods.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000007_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000007_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000008_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000008_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000009_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000009_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000010_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000010_add_verification_methods.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000011_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830130646000011_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.cockroach.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.cockroach.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verification_tokens_identity_verifiable_addresses_id_fk" FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_verification_tokens_selfservice_verification_flows_id_fk" FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.mysql.down.sql ================================================ DROP TABLE `identity_verification_tokens`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.mysql.up.sql ================================================ CREATE TABLE `identity_verification_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `expires_at` DATETIME NOT NULL, `issued_at` DATETIME NOT NULL, `identity_verifiable_address_id` char(36) NOT NULL, `selfservice_verification_flow_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_verifiable_address_id`) REFERENCES `identity_verifiable_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_verification_flow_id`) REFERENCES `selfservice_verification_flows` (`id`) ON DELETE cascade ) ENGINE=InnoDB ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.postgres.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.postgres.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.sqlite3.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000000_add_verification_token.sqlite3.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verification_tokens_token_uq_idx` ON `identity_verification_tokens` (`token`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000001_add_verification_token.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_token_idx` ON `identity_verification_tokens` (`token`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000002_add_verification_token.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_verifiable_address_id_idx` ON `identity_verification_tokens` (`identity_verifiable_address_id`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000003_add_verification_token.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_verification_flow_id_idx` ON `identity_verification_tokens` (`selfservice_verification_flow_id`); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830154602000004_add_verification_token.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` DROP COLUMN `issued_at`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `expires_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000000_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` DROP COLUMN `expires_at` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `issued_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000001_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00' ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" DROP NOT NULL; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at FROM "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000002_recovery_token_expires.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.mysql.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.postgres.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000003_recovery_token_expires.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000004_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000004_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000004_recovery_token_expires.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000004_recovery_token_expires.sqlite3.up.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000005_recovery_token_expires.cockroach.down.sql ================================================ UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000005_recovery_token_expires.cockroach.up.sql ================================================ UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000005_recovery_token_expires.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000005_recovery_token_expires.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000006_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000006_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000006_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000006_recovery_token_expires.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000007_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000007_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000007_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000007_recovery_token_expires.sqlite3.up.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000008_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000008_recovery_token_expires.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000008_recovery_token_expires.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000008_recovery_token_expires.sqlite3.up.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000009_recovery_token_expires.cockroach.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000009_recovery_token_expires.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000009_recovery_token_expires.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000009_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000010_recovery_token_expires.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at FROM "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000010_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000011_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000011_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000012_recovery_token_expires.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000012_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000013_recovery_token_expires.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000013_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000014_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000014_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000015_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000015_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000016_recovery_token_expires.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000016_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000017_recovery_token_expires.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000017_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000018_recovery_token_expires.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000018_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000019_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000019_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000020_recovery_token_expires.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000020_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000021_recovery_token_expires.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000021_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000022_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000022_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000023_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000023_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000024_recovery_token_expires.sqlite3.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200830172221000024_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.mysql.down.sql ================================================ CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.postgres.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000000_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.mysql.down.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.postgres.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000001_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_expires_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` MODIFY `expires_at` DATETIME ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `code` ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "expires_at" TYPE timestamp, ALTER COLUMN "expires_at" DROP NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000002_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE "identity_verifiable_addresses" SET "expires_at" = "_expires_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` MODIFY `code` VARCHAR (32) NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `expires_at`; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" TYPE VARCHAR (32), ALTER COLUMN "code" SET NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000003_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.mysql.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.postgres.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000004_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "expires_at" TO "_expires_at_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.mysql.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = LEFT(SHA2(RANDOM_BYTES(32), 256), 32) WHERE code IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.postgres.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = substr(md5(random()::text), 0, 32) WHERE code IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000005_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_code_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `expires_at` DATETIME ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000006_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" SET NOT NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `code` VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000007_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000008_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE "identity_verifiable_addresses" SET "code" = "_code_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000008_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000008_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000008_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP TABLE "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000009_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000009_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000009_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000009_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000010_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "code" TO "_code_tmp" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000010_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000010_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000010_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000011_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000011_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000011_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000011_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000012_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = substr(md5(uuid_v4()), 0, 32) WHERE code IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000012_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000012_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000012_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000013_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000013_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000013_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000013_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000014_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000014_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000014_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000014_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000015_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ) ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000015_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000016_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000016_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP TABLE "identity_verifiable_addresses" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000017_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx" ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000017_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000018_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000018_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000019_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000019_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000020_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" DATETIME ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000020_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000021_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" TEXT ================================================ FILE: oryx/popx/stub/migrations/transactional/20200831110752000021_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password') ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password') ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password') ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000000_credential_types_values.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password') ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.cockroach.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.mysql.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.postgres.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.sqlite3.down.sql ================================================ ================================================ FILE: oryx/popx/stub/migrations/transactional/20201201161451000001_credential_types_values.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: oryx/popx/transaction.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package popx import ( "context" "runtime" "strings" "github.com/cockroachdb/cockroach-go/v2/crdb" "github.com/jackc/pgconn" pgxconn "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/ory/pop/v6" ) type transactionContextKey int const transactionKey transactionContextKey = 0 func WithTransaction(ctx context.Context, tx *pop.Connection) context.Context { return context.WithValue(ctx, transactionKey, tx) } func InTransaction(ctx context.Context) bool { return ctx.Value(transactionKey) != nil } func Transaction(ctx context.Context, connection *pop.Connection, callback func(context.Context, *pop.Connection) error) error { c := ctx.Value(transactionKey) if c != nil { if conn, ok := c.(*pop.Connection); ok { return errors.WithStack(callback(ctx, conn.WithContext(ctx))) } } if connection.Dialect.Name() == "cockroach" { return connection.WithContext(ctx).Dialect.Lock(func() error { transaction, err := connection.NewTransaction() if err != nil { return errors.WithStack(err) } attempt := 0 return errors.WithStack(crdb.ExecuteInTx(ctx, sqlxTxAdapter{transaction.TX.Tx}, func() error { attempt++ if attempt > 1 { caller := caller() transactionRetries.WithLabelValues(caller).Inc() } return errors.WithStack(callback(WithTransaction(ctx, transaction), transaction)) })) }) } if strings.HasPrefix(connection.Dialect.Name(), "postgres") { var err error for attempt := range 10 { _ = attempt err = connection.WithContext(ctx).Transaction(func(tx *pop.Connection) error { return callback(WithTransaction(ctx, tx), tx) }) if err == nil { return nil } if e := new(pgconn.PgError); errors.As(err, &e) && e.Code == "40001" { continue } if e := new(pgxconn.PgError); errors.As(err, &e) && e.Code == "40001" { continue } return err } return err } return errors.WithStack(connection.WithContext(ctx).Transaction(func(tx *pop.Connection) error { return errors.WithStack(callback(WithTransaction(ctx, tx), tx)) })) } func GetConnection(ctx context.Context, connection *pop.Connection) *pop.Connection { c := ctx.Value(transactionKey) if c != nil { if conn, ok := c.(*pop.Connection); ok { return conn.WithContext(ctx) } } return connection.WithContext(ctx) } type sqlxTxAdapter struct { *sqlx.Tx } var _ crdb.Tx = sqlxTxAdapter{} func (s sqlxTxAdapter) Exec(ctx context.Context, query string, args ...interface{}) error { _, err := s.Tx.ExecContext(ctx, query, args...) return errors.WithStack(err) } func (s sqlxTxAdapter) Commit(ctx context.Context) error { return errors.WithStack(s.Tx.Commit()) } func (s sqlxTxAdapter) Rollback(ctx context.Context) error { return errors.WithStack(s.Tx.Rollback()) } var ( transactionRetries = prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "ory_x_popx_cockroach_transaction_retries_total", Help: "Counts the number of automatic CockroachDB transaction retries", }, []string{"caller"}) TransactionRetries prometheus.Collector = transactionRetries _ = transactionRetries.WithLabelValues(unknownCaller) // make sure the metric is always present unknownCaller = "unknown" ) func caller() string { pc := make([]uintptr, 3) // The number stack frames to skip was determined by putting a breakpoint in // ory/kratos and looking for the topmost frame which isn't from ory/x or // ory/pop. n := runtime.Callers(8, pc) if n == 0 { return unknownCaller } pc = pc[:n] frames := runtime.CallersFrames(pc) for { frame, more := frames.Next() if frame.Function != "" { return frame.Function } if !more { break } } return unknownCaller } ================================================ FILE: oryx/profilex/profiling.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package profilex import ( "os" "github.com/pkg/profile" ) type noop struct{} // Stop is a noop. func (p *noop) Stop() {} // Profile parses the PROFILING environment variable and executes the proper profiling task. func Profile() interface { Stop() } { switch os.Getenv("PROFILING") { case "cpu": return profile.Start(profile.CPUProfile, profile.NoShutdownHook) case "mem": return profile.Start(profile.MemProfile, profile.NoShutdownHook) case "mutex": return profile.Start(profile.MutexProfile, profile.NoShutdownHook) case "block": return profile.Start(profile.BlockProfile, profile.NoShutdownHook) } return new(noop) } // HelpMessage returns a string explaining how profiling works. func HelpMessage() string { return `- PROFILING: Set "PROFILING=cpu" to enable cpu profiling and "PROFILING=mem" to enable memory profiling. It is not possible to do both at the same time. Profiling is disabled per default. Example: PROFILING=cpu` } ================================================ FILE: oryx/prometheusx/handler.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package prometheusx import ( "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" ) const ( MetricsPrometheusPath = "/metrics/prometheus" ) type muxrouter interface { GET(path string, handle http.HandlerFunc) } // SetMuxRoutes registers the prometheus handler. func SetMuxRoutes(mux muxrouter) { mux.GET(MetricsPrometheusPath, promhttp.Handler().ServeHTTP) } ================================================ FILE: oryx/prometheusx/metrics.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package prometheusx import ( "net/http" "regexp" "strings" grpcPrometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/negroni" ) type HTTPMetrics struct { responseTime *prometheus.HistogramVec totalRequests *prometheus.CounterVec duration *prometheus.HistogramVec responseSize *prometheus.HistogramVec requestSize *prometheus.HistogramVec handlerStatuses *prometheus.CounterVec } const HTTPPrefix = "http" func NewHTTPMetrics(app, metricsPrefix, version, hash, date string) *HTTPMetrics { labels := map[string]string{ "app": app, "version": version, "hash": hash, "buildTime": date, } if metricsPrefix != "" { metricsPrefix += "_" } pm := &HTTPMetrics{ responseTime: prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: metricsPrefix + "response_time_seconds", Help: "Description", ConstLabels: labels, }, []string{"endpoint"}, ), totalRequests: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: metricsPrefix + "requests_total", Help: "number of requests", ConstLabels: labels, }, []string{"code", "method", "endpoint"}), duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: metricsPrefix + "requests_duration_seconds", Help: "duration of a requests in seconds", ConstLabels: labels, }, []string{"code", "method", "endpoint"}), responseSize: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: metricsPrefix + "response_size_bytes", Help: "size of the responses in bytes", ConstLabels: labels, }, []string{"code", "method"}), requestSize: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: metricsPrefix + "requests_size_bytes", Help: "size of the requests in bytes", ConstLabels: labels, }, []string{"code", "method"}), handlerStatuses: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: metricsPrefix + "requests_statuses_total", Help: "count number of responses per status", ConstLabels: labels, }, []string{"method", "status_bucket"}), } err := prometheus.Register(pm) if e := new(prometheus.AlreadyRegisteredError); errors.As(err, e) { return pm } else if err != nil { panic(err) } grpcPrometheus.EnableHandlingTimeHistogram() return pm } // Describe implements prometheus Collector interface. func (h *HTTPMetrics) Describe(in chan<- *prometheus.Desc) { h.duration.Describe(in) h.totalRequests.Describe(in) h.requestSize.Describe(in) h.responseSize.Describe(in) h.handlerStatuses.Describe(in) h.responseTime.Describe(in) } // Collect implements prometheus Collector interface. func (h *HTTPMetrics) Collect(in chan<- prometheus.Metric) { h.duration.Collect(in) h.totalRequests.Collect(in) h.requestSize.Collect(in) h.responseSize.Collect(in) h.handlerStatuses.Collect(in) h.responseTime.Collect(in) } func (h *HTTPMetrics) instrumentHandlerStatusBucket(next http.Handler) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { rr := negroni.NewResponseWriter(rw) next.ServeHTTP(rr, r) statusBucket := "unknown" switch status := rr.Status(); { case status >= 200 && status <= 299: statusBucket = "2xx" case status >= 300 && status <= 399: statusBucket = "3xx" case status >= 400 && status <= 499: statusBucket = "4xx" case status >= 500 && status <= 599: statusBucket = "5xx" } h.handlerStatuses.With(prometheus.Labels{"method": r.Method, "status_bucket": statusBucket}). Inc() } } // Instrument will instrument any http.Handler with custom metrics func (h *HTTPMetrics) Instrument(next http.Handler, endpoint string) http.Handler { labels := prometheus.Labels{} labelsWithEndpoint := prometheus.Labels{"endpoint": endpoint} wrapped := promhttp.InstrumentHandlerResponseSize(h.responseSize.MustCurryWith(labels), next) wrapped = promhttp.InstrumentHandlerCounter(h.totalRequests.MustCurryWith(labelsWithEndpoint), wrapped) wrapped = promhttp.InstrumentHandlerDuration(h.duration.MustCurryWith(labelsWithEndpoint), wrapped) wrapped = promhttp.InstrumentHandlerDuration(h.responseTime.MustCurryWith(prometheus.Labels{"endpoint": endpoint}), wrapped) wrapped = promhttp.InstrumentHandlerRequestSize(h.requestSize.MustCurryWith(labels), wrapped) wrapped = h.instrumentHandlerStatusBucket(wrapped) return wrapped } var paramPlaceHolderRE = regexp.MustCompile(`\{[a-zA-Z0-9_-]+}`) func GetLabelForPattern(pattern string) string { return paramPlaceHolderRE.ReplaceAllString(strings.TrimSuffix(pattern, "/{$}"), "{param}") } ================================================ FILE: oryx/proxy/proxy.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package proxy import ( "context" "log" "net/http" "net/http/httputil" "github.com/pkg/errors" "github.com/rs/cors" "go.opentelemetry.io/otel" ) type ( RespMiddleware func(resp *http.Response, config *HostConfig, body []byte) ([]byte, error) ReqMiddleware func(req *httputil.ProxyRequest, config *HostConfig, body []byte) ([]byte, error) HostMapper func(ctx context.Context, r *http.Request) (context.Context, *HostConfig, error) options struct { hostMapper HostMapper onResError func(*http.Response, error) error onReqError func(*http.Request, error) respMiddlewares []RespMiddleware reqMiddlewares []ReqMiddleware transport http.RoundTripper errHandler func(http.ResponseWriter, *http.Request, error) } HostConfig struct { // CorsEnabled is a flag to enable or disable CORS // Default: false CorsEnabled bool // CorsOptions allows to configure CORS // If left empty, no CORS headers will be set even when CorsEnabled is true CorsOptions *cors.Options // CookieDomain is the host under which cookies are set. // If left empty, no cookie domain will be set CookieDomain string // UpstreamHost is the next upstream host the proxy will pass the request to. // e.g. fluffy-bear-afiu23iaysd.oryapis.com UpstreamHost string // UpstreamScheme is the protocol used by the upstream service. UpstreamScheme string // TargetHost is the final target of the request. Should be the same as UpstreamHost // if the request is directly passed to the target service. TargetHost string // TargetScheme is the final target's scheme // (i.e. the scheme the target thinks it is running under) TargetScheme string // PathPrefix is a prefix that is prepended on the original host, // but removed before forwarding. PathPrefix string // TrustForwardedHosts is a flag that indicates whether the proxy should trust the // X-Forwarded-* headers or not. TrustForwardedHeaders bool // originalHost the original hostname the request is coming from. // This value will be maintained internally by the proxy. originalHost string // originalScheme is the original scheme of the request. // This value will be maintained internally by the proxy. originalScheme string // ForceOriginalSchemeHTTP forces the original scheme to be https if enabled. ForceOriginalSchemeHTTPS bool } Options func(*options) contextKey string ) const ( hostConfigKey contextKey = "host config" ) func (c *HostConfig) setScheme(r *httputil.ProxyRequest) { if c.ForceOriginalSchemeHTTPS { c.originalScheme = "https" } else if forwardedProto := r.In.Header.Get("X-Forwarded-Proto"); forwardedProto != "" { c.originalScheme = forwardedProto } else if r.In.TLS == nil { c.originalScheme = "http" } else { c.originalScheme = "https" } } func (c *HostConfig) setHost(r *httputil.ProxyRequest) { if forwardedHost := r.In.Header.Get("X-Forwarded-Host"); forwardedHost != "" { c.originalHost = forwardedHost } else { c.originalHost = r.In.Host } } // rewriter is a custom internal function for altering a http.Request func rewriter(o *options) func(*httputil.ProxyRequest) { return func(r *httputil.ProxyRequest) { ctx := r.Out.Context() ctx, span := otel.GetTracerProvider().Tracer("").Start(ctx, "x.proxy") defer span.End() _, c, err := o.getHostConfig(ctx, r.In) if err != nil { o.onReqError(r.Out, err) return } if c.TrustForwardedHeaders { headers := []string{ "X-Forwarded-Host", "X-Forwarded-Proto", "X-Forwarded-For", } for _, h := range headers { if v := r.In.Header.Get(h); v != "" { r.Out.Header.Set(h, v) } } } c.setScheme(r) c.setHost(r) headerRequestRewrite(r.Out, c) var body []byte var cb *compressableBody if r.Out.ContentLength != 0 { body, cb, err = readBody(r.Out.Header, r.Out.Body) if err != nil { o.onReqError(r.Out, err) return } } for _, m := range o.reqMiddlewares { if body, err = m(r, c, body); err != nil { o.onReqError(r.Out, err) return } } n, err := cb.Write(body) if err != nil { o.onReqError(r.Out, err) return } r.Out.Header.Del("Content-Length") r.Out.ContentLength = int64(n) r.Out.Body = cb } } // modifyResponse is a custom internal function for altering a http.Response func modifyResponse(o *options) func(*http.Response) error { return func(r *http.Response) error { _, c, err := o.getHostConfig(r.Request.Context(), r.Request) if err != nil { return err } if err := headerResponseRewrite(r, c); err != nil { return o.onResError(r, err) } body, cb, err := bodyResponseRewrite(r, c) if err != nil { return o.onResError(r, err) } for _, m := range o.respMiddlewares { if body, err = m(r, c, body); err != nil { return o.onResError(r, err) } } n, err := cb.Write(body) if err != nil { return o.onResError(r, err) } n, t, err := handleWebsocketResponse(n, cb, r.Body) if err != nil { return err } r.Header.Del("Content-Length") r.ContentLength = int64(n) r.Body = t return nil } } func WithOnError(onReqErr func(*http.Request, error), onResErr func(*http.Response, error) error) Options { return func(o *options) { o.onReqError = onReqErr o.onResError = onResErr } } func WithReqMiddleware(middlewares ...ReqMiddleware) Options { return func(o *options) { o.reqMiddlewares = append(o.reqMiddlewares, middlewares...) } } func WithRespMiddleware(middlewares ...RespMiddleware) Options { return func(o *options) { o.respMiddlewares = append(o.respMiddlewares, middlewares...) } } func WithTransport(t http.RoundTripper) Options { return func(o *options) { o.transport = t } } func WithErrorHandler(eh func(w http.ResponseWriter, r *http.Request, err error)) Options { return func(o *options) { o.errHandler = eh } } func (o *options) getHostConfig(ctx context.Context, r *http.Request) (context.Context, *HostConfig, error) { if cached, ok := ctx.Value(hostConfigKey).(*HostConfig); ok && cached != nil { return ctx, cached, nil } ctx, c, err := o.hostMapper(ctx, r) if err != nil { return nil, nil, err } // cache the host config in the request context // this will be passed on to the request and response proxy functions ctx = context.WithValue(ctx, hostConfigKey, c) return ctx, c, nil } func (o *options) beforeProxyMiddleware(h http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { // get the hostmapper configurations before the request is proxied ctx, c, err := o.getHostConfig(request.Context(), request) if err != nil { o.onReqError(request, err) return } // Add our Cors middleware. // This middleware will only trigger if the host config has cors enabled on that request. if c.CorsEnabled && c.CorsOptions != nil { cors.New(*c.CorsOptions).HandlerFunc(writer, request) } h.ServeHTTP(writer, request.WithContext(ctx)) }) } func defaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) { switch { case errors.Is(err, context.Canceled): w.WriteHeader(499) // http://nginx.org/en/docs/dev/development_guide.html case isTimeoutError(err): w.WriteHeader(http.StatusGatewayTimeout) default: log.Printf("http: proxy error: %v", err) w.WriteHeader(http.StatusBadGateway) } } func isTimeoutError(err error) bool { var te interface{ Timeout() bool } = nil return errors.As(err, &te) && te.Timeout() || errors.Is(err, context.DeadlineExceeded) } // New creates a new Proxy // A Proxy sets up a middleware with custom request and response modification handlers func New(hostMapper HostMapper, opts ...Options) http.Handler { o := &options{ hostMapper: hostMapper, onReqError: func(*http.Request, error) {}, onResError: func(_ *http.Response, err error) error { return err }, transport: http.DefaultTransport, errHandler: defaultErrorHandler, } for _, op := range opts { op(o) } rp := &httputil.ReverseProxy{ Rewrite: rewriter(o), ModifyResponse: modifyResponse(o), Transport: o.transport, ErrorHandler: o.errHandler, } return o.beforeProxyMiddleware(rp) } ================================================ FILE: oryx/proxy/rewrites.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package proxy import ( "bytes" "compress/gzip" "io" "net/http" "net/url" "path" "strings" "github.com/pkg/errors" ) type compressableBody struct { buf bytes.Buffer w io.WriteCloser } // we require a read and write for websocket connections var _ io.ReadWriteCloser = new(compressableBody) func (b *compressableBody) Close() error { if b != nil { b.buf.Reset() if b.w != nil { return b.w.Close() } } return nil } func (b *compressableBody) Write(d []byte) (int, error) { if b == nil { // this happens when the body is empty return 0, nil } var w io.Writer = &b.buf if b.w != nil { w = b.w defer b.w.Close() } return w.Write(d) } func (b *compressableBody) Read(p []byte) (n int, err error) { if b == nil { // this happens when the body is empty return 0, io.EOF } return b.buf.Read(p) } func headerRequestRewrite(req *http.Request, c *HostConfig) { req.URL.Scheme = c.UpstreamScheme req.URL.Host = c.UpstreamHost req.URL.Path = strings.TrimPrefix(req.URL.Path, c.PathPrefix) if _, ok := req.Header["User-Agent"]; !ok { // explicitly disable User-Agent so it's not set to default value req.Header.Set("User-Agent", "") } } func headerResponseRewrite(resp *http.Response, c *HostConfig) error { redir, err := resp.Location() if err != nil { if !errors.Is(err, http.ErrNoLocation) { return errors.WithStack(err) } } else if redir.Host == c.TargetHost { redir.Scheme = c.originalScheme redir.Host = c.originalHost redir.Path = path.Join(c.PathPrefix, redir.Path) resp.Header.Set("Location", redir.String()) } ReplaceCookieDomainAndSecure(resp, c.TargetHost, c.CookieDomain, c.originalScheme == "https") return nil } // ReplaceCookieDomainAndSecure replaces the domain of all matching Set-Cookie headers in the response. func ReplaceCookieDomainAndSecure(resp *http.Response, original, replacement string, secure bool) { original, replacement = stripPort(original), stripPort(replacement) // cookies don't distinguish ports cookies := resp.Cookies() resp.Header.Del("Set-Cookie") for _, co := range cookies { co.Domain = replacement co.Secure = secure if !secure { co.SameSite = http.SameSiteLaxMode } resp.Header.Add("Set-Cookie", co.String()) } } func bodyResponseRewrite(resp *http.Response, c *HostConfig) ([]byte, *compressableBody, error) { if resp.ContentLength == 0 { return nil, nil, nil } body, cb, err := readBody(resp.Header, resp.Body) if err != nil { return nil, nil, err } if c.TargetScheme == "" { c.TargetScheme = "https" } return bytes.ReplaceAll(body, []byte(c.TargetScheme+"://"+c.TargetHost), []byte(c.originalScheme+"://"+c.originalHost+c.PathPrefix)), cb, nil } func readBody(h http.Header, body io.ReadCloser) ([]byte, *compressableBody, error) { defer body.Close() cb := &compressableBody{} switch h.Get("Content-Encoding") { case "gzip": var err error body, err = gzip.NewReader(body) if err != nil { return nil, nil, errors.WithStack(err) } cb.w = gzip.NewWriter(&cb.buf) default: // do nothing, we can read directly } b, err := io.ReadAll(body) if err != nil { return nil, nil, errors.WithStack(err) } return b, cb, nil } func handleWebsocketResponse(n int, cb *compressableBody, body io.ReadCloser) (int, io.ReadWriteCloser, error) { var err error readWriteCloser, ok := body.(io.ReadWriteCloser) if ok { if cb != nil { n, err = readWriteCloser.Write(cb.buf.Bytes()) if err != nil { return 0, nil, errors.WithStack(err) } } return n, readWriteCloser, nil } return n, cb, nil } // stripPort removes the optional port from the host. func stripPort(host string) string { return (&url.URL{Host: host}).Hostname() } ================================================ FILE: oryx/proxy/stubs/auth.example.com.json ================================================ { "ui": { "action": "https://auth.example.com" }, "callbacks": [ "https://auth.example.com/path/to/resource", "https://auth.example.com/path?q=https://localhost:8000" ] } ================================================ FILE: oryx/randx/README.md ================================================ `randx.RuneSequence` generates even distributions for the given character set and length. All results are therefore also evenly distributed. ## AlphaNum [Alphabet and Numeric](../docs/alpha_num.png) ## AlphaNum [Alphabet and Numeric](../docs/num.png) ================================================ FILE: oryx/randx/sequence.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package randx import ( "crypto/rand" "math/big" ) var rander = rand.Reader // random function var ( // AlphaNum contains runes [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]. AlphaNum = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") // Alpha contains runes [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]. Alpha = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") // AlphaLowerNum contains runes [abcdefghijklmnopqrstuvwxyz0123456789]. AlphaLowerNum = []rune("abcdefghijklmnopqrstuvwxyz0123456789") // AlphaUpperNum contains runes [ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]. AlphaUpperNum = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") // AlphaLower contains runes [abcdefghijklmnopqrstuvwxyz]. AlphaLower = []rune("abcdefghijklmnopqrstuvwxyz") // AlphaUpperVowels contains runes [AEIOUY]. AlphaUpperVowels = []rune("AEIOUY") // AlphaUpperNoVowels contains runes [BCDFGHJKLMNPQRSTVWXZ]. AlphaUpperNoVowels = []rune("BCDFGHJKLMNPQRSTVWXZ") // AlphaUpper contains runes [ABCDEFGHIJKLMNOPQRSTUVWXYZ]. AlphaUpper = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") // Numeric contains runes [0123456789]. Numeric = []rune("0123456789") // AlphaNumNoAmbiguous is equivalent to AlphaNum but without visually ambiguous characters [0Oo1IlB8S5Z2]. AlphaNumNoAmbiguous = []rune("abcdefghijkmnpqrstuvwxyzACDEFGHJKLMNPQRTUVWXY34679") ) // RuneSequence returns a random sequence using the defined allowed runes. func RuneSequence(l int, allowedRunes []rune) (seq []rune, err error) { c := big.NewInt(int64(len(allowedRunes))) seq = make([]rune, l) for i := 0; i < l; i++ { r, err := rand.Int(rander, c) if err != nil { return seq, err } rn := allowedRunes[r.Uint64()] seq[i] = rn } return seq, nil } // MustString returns a random string sequence using the defined runes. Panics on error. func MustString(l int, allowedRunes []rune) string { seq, err := RuneSequence(l, allowedRunes) if err != nil { panic(err) } return string(seq) } ================================================ FILE: oryx/randx/strength/go.mod ================================================ module github.com/ory/x/randx/strength go 1.26 replace github.com/ory/x => ../.. require ( github.com/ory/x v0.0.729 gonum.org/v1/plot v0.16.0 ) require ( codeberg.org/go-fonts/liberation v0.5.0 // indirect codeberg.org/go-latex/latex v0.1.0 // indirect codeberg.org/go-pdf/fpdf v0.11.1 // indirect git.sr.ht/~sbinet/gg v0.6.0 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect github.com/campoy/embedmd v1.0.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect golang.org/x/image v0.30.0 // indirect golang.org/x/text v0.31.0 // indirect ) ================================================ FILE: oryx/randx/strength/go.sum ================================================ codeberg.org/go-fonts/dejavu v0.4.0 h1:2yn58Vkh4CFK3ipacWUAIE3XVBGNa0y1bc95Bmfx91I= codeberg.org/go-fonts/dejavu v0.4.0/go.mod h1:abni088lmhQJvso2Lsb7azCKzwkfcnttl6tL1UTWKzg= codeberg.org/go-fonts/latin-modern v0.4.0 h1:vkRCc1y3whKA7iL9Ep0fSGVuJfqjix0ica9UflHORO8= codeberg.org/go-fonts/latin-modern v0.4.0/go.mod h1:BF68mZznJ9QHn+hic9ks2DaFl4sR5YhfM6xTYaP9vNw= codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOLfQ5Bo0= codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs= codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/plot v0.16.0 h1:dK28Qx/Ky4VmPUN/2zeW0ELyM6ucDnBAj5yun7M9n1g= gonum.org/v1/plot v0.16.0/go.mod h1:Xz6U1yDMi6Ni6aaXILqmVIb6Vro8E+K7Q/GeeH+Pn0c= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= ================================================ FILE: oryx/randx/strength/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "fmt" "sort" "gonum.org/v1/plot" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/plotutil" "gonum.org/v1/plot/vg" "github.com/ory/x/randx" ) const iterations = 1000 * 100 type generate func(int, []rune) ([]rune, error) func main() { draw(measureDistribution(iterations, randx.AlphaNum, randx.RuneSequence), "AlphaNum Distribution", "docs/alpha_num.png") draw(measureDistribution(iterations, randx.Numeric, randx.RuneSequence), "Num Distribution", "docs/num.png") draw(measureResultDistribution(100, 6, randx.Numeric, randx.RuneSequence), "Num Distribution", "docs/result_num.png") } func measureResultDistribution(iterations int, length int, characters []rune, fn generate) map[string]int { dist := make(map[string]int) for index := 1; index <= iterations; index++ { // status output to cli if index%1000 == 0 { fmt.Printf("\r%d / %d", index, iterations) } raw, err := fn(length, characters) if err != nil { panic(err) } dist[string(raw)] = dist[string(raw)] + 1 } return dist } func measureDistribution(iterations int, characters []rune, fn generate) map[string]int { dist := make(map[string]int) for index := 1; index <= iterations; index++ { // status output to cli if index%1000 == 0 { fmt.Printf("\r%d / %d", index, iterations) } raw, err := fn(100, characters) if err != nil { panic(err) } for _, s := range raw { c := string(s) i := dist[c] dist[c] = i + 1 } } return dist } func draw(distribution map[string]int, title, filename string) { keys, values := orderMap(distribution) group := plotter.Values{} for _, v := range values { group = append(group, float64(v)) } p := plot.New() p.Title.Text = title p.Y.Label.Text = "N" bars, err := plotter.NewBarChart(group, vg.Points(4)) if err != nil { panic(err) } bars.LineStyle.Width = vg.Length(0) bars.Color = plotutil.Color(0) p.Add(bars) p.NominalX(keys...) if err := p.Save(300*vg.Millimeter, 150*vg.Millimeter, filename); err != nil { panic(err) } } func orderMap(m map[string]int) (keys []string, values []int) { keys = []string{} values = []int{} for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, key := range keys { values = append(values, m[key]) } return keys, values } ================================================ FILE: oryx/reqlog/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 Dan Buch and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: oryx/reqlog/external_latency.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package reqlog import ( "context" "sync/atomic" "time" ) func withEnableExternalLatencyMeasurement(ctx context.Context) context.Context { return context.WithValue(ctx, externalLatencyKey, new(int64)) } func AccumulateExternalLatency(ctx context.Context, dur time.Duration) { total, ok := ctx.Value(externalLatencyKey).(*int64) if !ok { return } atomic.AddInt64(total, int64(dur)) } func getExternalLatency(ctx context.Context) time.Duration { total, ok := ctx.Value(externalLatencyKey).(*int64) if !ok { return 0 } return time.Duration(atomic.LoadInt64(total)) } type contextKey int const externalLatencyKey contextKey = 1 ================================================ FILE: oryx/reqlog/middleware.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package reqlog import ( "net/http" "sync" "time" "github.com/sirupsen/logrus" "github.com/urfave/negroni" "github.com/ory/x/logrusx" ) type timer interface { Now() time.Time Since(time.Time) time.Duration } type realClock struct{} func (rc *realClock) Now() time.Time { return time.Now() } func (rc *realClock) Since(t time.Time) time.Duration { return time.Since(t) } // Middleware is a middleware handler that logs the request as it goes in and the response as it goes out. type Middleware struct { // Logger is the log.Logger instance used to log messages with the Logger middleware Logger *logrusx.Logger // Name is the name of the application as recorded in latency metrics Name string Before func(*logrusx.Logger, *http.Request, string) *logrusx.Logger After func(*logrusx.Logger, *http.Request, negroni.ResponseWriter, time.Duration, string) *logrusx.Logger logStarting bool clock timer logLevel logrus.Level // Silence log for specific URL paths silencePaths map[string]bool sync.RWMutex } // NewMiddleware returns a reqlog middleware with default settings func NewMiddleware() *Middleware { return NewCustomMiddleware(logrus.InfoLevel, &logrus.TextFormatter{}, "web") } // NewCustomMiddleware returns a reqlog middleware with the given level and formatter func NewCustomMiddleware(level logrus.Level, formatter logrus.Formatter, name string) *Middleware { log := logrusx.New(name, "", logrusx.ForceFormatter(formatter), logrusx.ForceLevel(level)) return &Middleware{ Logger: log, Name: name, Before: DefaultBefore, After: DefaultAfter, logLevel: logrus.InfoLevel, logStarting: true, clock: &realClock{}, silencePaths: map[string]bool{}, } } // NewMiddlewareFromLogger returns a reqlog middleware which writes to a given logrus logger. func NewMiddlewareFromLogger(logger *logrusx.Logger, name string) *Middleware { return &Middleware{ Logger: logger, Name: name, Before: DefaultBefore, After: DefaultAfter, logLevel: logrus.InfoLevel, logStarting: true, clock: &realClock{}, silencePaths: map[string]bool{}, } } // SetLogStarting accepts a bool to control the logging of "started handling // request" prior to passing to the next middleware func (m *Middleware) SetLogStarting(v bool) { m.logStarting = v } // ExcludePaths adds new URL paths to be ignored during logging. The URL u is parsed, hence the returned error func (m *Middleware) ExcludePaths(paths ...string) *Middleware { for _, path := range paths { m.Lock() m.silencePaths[path] = true m.Unlock() } return m } func (m *Middleware) Wrap(handler http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { m.ServeHTTP(rw, r, handler.ServeHTTP) }) } func (m *Middleware) WrapFunc(handler http.HandlerFunc) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { m.ServeHTTP(rw, r, handler) } } func (m *Middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if m.Before == nil { m.Before = DefaultBefore } if m.After == nil { m.After = DefaultAfter } logLevel := m.logLevel m.RLock() if _, ok := m.silencePaths[r.URL.Path]; ok { logLevel = logrus.TraceLevel } m.RUnlock() start := m.clock.Now() // Try to get the real IP remoteAddr := r.RemoteAddr if realIP := r.Header.Get("X-Real-IP"); realIP != "" { remoteAddr = realIP } entry := m.Logger.NewEntry() entry = m.Before(entry, r, remoteAddr) if m.logStarting { entry.Log(logLevel, "started handling request") } nrw, ok := rw.(negroni.ResponseWriter) if !ok { nrw = negroni.NewResponseWriter(rw) } r = r.WithContext(withEnableExternalLatencyMeasurement(r.Context())) next(nrw, r) latency := m.clock.Since(start) m.After(entry, r, nrw, latency, m.Name).Log(logLevel, "completed handling request") } // BeforeFunc is the func type used to modify or replace the *logrusx.Logger prior // to calling the next func in the middleware chain type BeforeFunc func(*logrusx.Logger, *http.Request, string) *logrusx.Logger // AfterFunc is the func type used to modify or replace the *logrusx.Logger after // calling the next func in the middleware chain type AfterFunc func(*logrusx.Logger, negroni.ResponseWriter, time.Duration, string) *logrusx.Logger // DefaultBefore is the default func assigned to *Middleware.Before func DefaultBefore(entry *logrusx.Logger, req *http.Request, remoteAddr string) *logrusx.Logger { return entry.WithRequest(req) } // DefaultAfter is the default func assigned to *Middleware.After func DefaultAfter(entry *logrusx.Logger, req *http.Request, res negroni.ResponseWriter, latency time.Duration, name string) *logrusx.Logger { e := entry.WithRequest(req).WithField("http_response", map[string]any{ "status": res.Status(), "size": res.Size(), "text_status": http.StatusText(res.Status()), "took": latency, "headers": entry.HTTPHeadersRedacted(res.Header()), }) if el := getExternalLatency(req.Context()); el > 0 { e = e.WithFields(map[string]any{ "took_internal": latency - el, "took_external": el, }) } return e } ================================================ FILE: oryx/resilience/retry.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package resilience provides helpers for dealing with resilience. package resilience import ( "time" "github.com/pkg/errors" "github.com/ory/x/logrusx" ) // Retry executes a f until no error is returned or failAfter is reached. func Retry(logger *logrusx.Logger, maxWait time.Duration, failAfter time.Duration, f func() error) (err error) { var lastStart time.Time err = errors.New("did not connect") loopWait := time.Millisecond * 100 retryStart := time.Now().UTC() for retryStart.Add(failAfter).After(time.Now().UTC()) { lastStart = time.Now().UTC() if err = f(); err == nil { return nil } if lastStart.Add(maxWait * 2).Before(time.Now().UTC()) { retryStart = time.Now().UTC() } logger.WithError(err).Infof("Retrying in %f seconds...", loopWait.Seconds()) time.Sleep(loopWait) loopWait = loopWait * time.Duration(int64(2)) if loopWait > maxWait { loopWait = maxWait } } return err } ================================================ FILE: oryx/safecast/safecast.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package safecast import "math" // Clamp if needed. func Uint64ToInt64(in uint64) int64 { if in > math.MaxInt64 { return math.MaxInt64 } return int64(in) } ================================================ FILE: oryx/serverx/404.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package serverx import ( _ "embed" "net/http" "github.com/ory/herodot/httputil" ) //go:embed 404.html var page404HTML string //go:embed 404.json var page404JSON string // DefaultNotFoundHandler is a default handler for handling 404 errors. var DefaultNotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var contentType, body string switch httputil.NegotiateContentType(r, []string{ "text/html", "text/plain", "application/json", }, "text/html") { case "text/plain": contentType = "text/plain" body = "Error 404 - The requested route does not exist. Make sure you are using the right path, domain, and port." case "application/json": contentType = "application/json" body = page404JSON default: fallthrough case "text/html": contentType = "text/html" body = page404HTML } w.Header().Set("Content-Type", contentType+"; charset=utf-8") w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(body)) }) ================================================ FILE: oryx/serverx/404.html ================================================ 404 - Route not found

Error 404

The requested route does not exist. Make sure you are using the right path, domain, and port.

================================================ FILE: oryx/serverx/404.json ================================================ { "error": { "code": 404, "message": "Not Found", "reason": "The requested route does not exist. Make sure you are using the right path, domain, and port." } } ================================================ FILE: oryx/servicelocatorx/options.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package servicelocatorx import ( "github.com/urfave/negroni" "google.golang.org/grpc" "github.com/ory/x/contextx" "github.com/ory/x/logrusx" ) type ( Options struct { logger *logrusx.Logger contextualizer contextx.Contextualizer httpMiddlewares []negroni.Handler grpcUnaryInterceptors []grpc.UnaryServerInterceptor grpcStreamInterceptors []grpc.StreamServerInterceptor } Option func(o *Options) ) func WithLogger(l *logrusx.Logger) Option { return func(o *Options) { o.logger = l } } func WithContextualizer(ctxer contextx.Contextualizer) Option { return func(o *Options) { o.contextualizer = ctxer } } func WithHTTPMiddlewares(m ...negroni.Handler) Option { return func(o *Options) { o.httpMiddlewares = m } } func WithGRPCUnaryInterceptors(i ...grpc.UnaryServerInterceptor) Option { return func(o *Options) { o.grpcUnaryInterceptors = i } } func WithGRPCStreamInterceptors(i ...grpc.StreamServerInterceptor) Option { return func(o *Options) { o.grpcStreamInterceptors = i } } func (o *Options) Logger() *logrusx.Logger { return o.logger } func (o *Options) Contextualizer() contextx.Contextualizer { return o.contextualizer } func (o *Options) HTTPMiddlewares() []negroni.Handler { return o.httpMiddlewares } func (o *Options) GRPCUnaryInterceptors() []grpc.UnaryServerInterceptor { return o.grpcUnaryInterceptors } func (o *Options) GRPCStreamInterceptors() []grpc.StreamServerInterceptor { return o.grpcStreamInterceptors } func NewOptions(options ...Option) *Options { o := &Options{ contextualizer: &contextx.Default{}, } for _, opt := range options { opt(o) } return o } ================================================ FILE: oryx/snapshotx/.snapshots/TestDeleteMatches-file=1.json-fn.json ================================================ { "foo": { "other": "fdsa" }, "nested": { "nested": { "arr": [ { }, { } ] } }, "arr": [ { }, { "arr": [ { }, { } ] } ] } ================================================ FILE: oryx/snapshotx/.snapshots/TestDeleteMatches-file=2.json-fn.json ================================================ { "created_at": "1234", "updated_at": "1234", "nested": { "created_at": 1234, "nested": { "created_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } }, "arr": [ { "created_at": 1234 }, { "updated_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } ] } ================================================ FILE: oryx/snapshotx/.snapshots/TestDeleteMatches-file=3.json-fn.json ================================================ { "updated_at": "1234", "nested": { "nested": { "arr": [ { }, { "updated_at": 1234 } ] } }, "arr": [ { }, { "updated_at": 1234, "arr": [ { }, { "updated_at": 1234 } ] } ] } ================================================ FILE: oryx/snapshotx/fixtures/1.json ================================================ { "ignore_nested": [ "updated_at", "created_at" ], "ignore_exact": [ "foo.id" ], "content": { "foo": { "id": "asdf", "other": "fdsa" }, "created_at": "1234", "updated_at": "1234", "nested":{ "created_at": 1234, "nested": { "created_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } }, "arr": [ { "created_at": 1234 }, { "updated_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } ] } } ================================================ FILE: oryx/snapshotx/fixtures/2.json ================================================ { "ignore_nested": [ ], "content": { "created_at": "1234", "updated_at": "1234", "nested":{ "created_at": 1234, "nested": { "created_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } }, "arr": [ { "created_at": 1234 }, { "updated_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } ] } } ================================================ FILE: oryx/snapshotx/fixtures/3.json ================================================ { "ignore_nested": [ "created_at" ], "content": { "created_at": "1234", "updated_at": "1234", "nested": { "created_at": 1234, "nested": { "created_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } }, "arr": [ { "created_at": 1234 }, { "updated_at": 1234, "arr": [ { "created_at": 1234 }, { "updated_at": 1234 } ] } ] } } ================================================ FILE: oryx/snapshotx/snapshot.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package snapshotx import ( "bytes" "encoding/json" "fmt" "slices" "strings" "testing" "github.com/bradleyjkemp/cupaloy/v2" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) type ( Opt = func(*options) options struct { modifiers []func(t *testing.T, raw []byte) []byte name string } ) func ExceptPaths(keys ...string) Opt { return func(o *options) { o.modifiers = append(o.modifiers, func(t *testing.T, raw []byte) []byte { for _, key := range keys { var err error raw, err = sjson.DeleteBytes(raw, key) require.NoError(t, err) } return raw }) } } func ExceptNestedKeys(nestedKeys ...string) Opt { return func(o *options) { o.modifiers = append(o.modifiers, func(t *testing.T, raw []byte) []byte { parsed := gjson.ParseBytes(raw) require.True(t, parsed.IsObject() || parsed.IsArray()) return deleteMatches(t, "", parsed, nestedKeys, []string{}, raw) }) } } func WithReplacement(str, replace string) Opt { return func(o *options) { o.modifiers = append(o.modifiers, func(t *testing.T, raw []byte) []byte { return bytes.ReplaceAll(raw, []byte(str), []byte(replace)) }) } } func WithName(name string) Opt { return func(o *options) { o.name = name } } func newOptions(opts ...Opt) *options { o := &options{} for _, opt := range opts { opt(o) } return o } func (o *options) applyModifiers(t *testing.T, compare []byte) []byte { for _, modifier := range o.modifiers { compare = modifier(t, compare) } return compare } var snapshot = cupaloy.New(cupaloy.SnapshotFileExtension(".json")) func SnapshotTJSON[C ~string | ~[]byte](t *testing.T, compare C, opts ...Opt) { SnapshotT(t, json.RawMessage(compare), opts...) } func SnapshotT(t *testing.T, actual any, opts ...Opt) { t.Helper() compare, err := json.MarshalIndent(actual, "", " ") require.NoErrorf(t, err, "%+v", actual) o := newOptions(opts...) compare = o.applyModifiers(t, compare) if o.name == "" { snapshot.SnapshotT(t, compare) } else { name := strings.ReplaceAll(t.Name()+"_"+o.name, "/", "-") require.NoError(t, snapshot.SnapshotWithName(name, compare)) } } // SnapshotTExcept is deprecated in favor of SnapshotT with Opt. // // DEPRECATED: please use SnapshotT instead func SnapshotTExcept(t *testing.T, actual interface{}, except []string) { t.Helper() compare, err := json.MarshalIndent(actual, "", " ") require.NoError(t, err, "%+v", actual) for _, e := range except { compare, err = sjson.DeleteBytes(compare, e) require.NoError(t, err, "%s", e) } snapshot.SnapshotT(t, compare) } func deleteMatches(t *testing.T, key string, result gjson.Result, matches []string, parents []string, content []byte) []byte { path := parents if key != "" { path = append(parents, key) } if result.IsObject() { result.ForEach(func(key, value gjson.Result) bool { content = deleteMatches(t, key.String(), value, matches, path, content) return true }) } else if result.IsArray() { var i int result.ForEach(func(_, value gjson.Result) bool { content = deleteMatches(t, fmt.Sprintf("%d", i), value, matches, path, content) i++ return true }) } if slices.Contains(matches, key) { content, err := sjson.DeleteBytes(content, strings.Join(path, ".")) require.NoError(t, err) return content } return content } ================================================ FILE: oryx/sqlcon/connector.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package sqlcon provides helpers for dealing with SQL connectivity. package sqlcon import ( "runtime" "strings" ) // GetDriverName returns the driver name of a given DSN. func GetDriverName(dsn string) string { return strings.Split(dsn, "://")[0] } func maxParallelism() int { maxProcs := runtime.GOMAXPROCS(0) numCPU := runtime.NumCPU() if maxProcs < numCPU { return maxProcs } return numCPU } ================================================ FILE: oryx/sqlcon/dockertest/cockroach.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package dockertest import ( "testing" "github.com/cockroachdb/cockroach-go/v2/testserver" "github.com/stretchr/testify/require" ) func NewLocalTestCRDBServer(t testing.TB) string { ts, err := testserver.NewTestServer(testserver.CustomVersionOpt("25.3.3")) require.NoError(t, err) t.Cleanup(ts.Stop) require.NoError(t, ts.WaitForInit()) ts.PGURL().Scheme = "cockroach" return ts.PGURL().String() } ================================================ FILE: oryx/sqlcon/dockertest/test_helper.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package dockertest import ( "cmp" "fmt" "io" "os" "regexp" "strings" "testing" "time" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/pop/v6" "github.com/ory/dockertest/v4" "github.com/ory/x/stringsx" ) func ConnectPop(t require.TestingT, url string) (c *pop.Connection) { require.EventuallyWithT(t, func(t *assert.CollectT) { var err error c, err = pop.NewConnection(&pop.ConnectionDetails{ URL: url, }) require.NoError(t, err) require.NoError(t, c.Open()) require.NoError(t, c.RawQuery("select version()").Exec()) }, 5*time.Minute, time.Second*5, "could not connect to database at %s", url) return } // ## PostgreSQL ## func startPostgreSQL(t testing.TB, version string) dockertest.Resource { pool := dockertest.NewPoolT(t, "") return pool.RunT(t, "postgres", dockertest.WithTag(cmp.Or(version, "16")), dockertest.WithoutReuse(), dockertest.WithEnv([]string{"PGUSER=postgres", "POSTGRES_PASSWORD=secret", "POSTGRES_DB=postgres"}), ) } // RunTestPostgreSQL runs a PostgreSQL database and returns the URL to it. // If a docker container is started for the database, the container be removed // at the end of the test. func RunTestPostgreSQL(t testing.TB) string { if dsn := os.Getenv("TEST_DATABASE_POSTGRESQL"); dsn != "" { t.Logf("Skipping Docker setup because environment variable TEST_DATABASE_POSTGRESQL is set to: %s", dsn) return dsn } r := startPostgreSQL(t, "") return fmt.Sprintf("postgres://postgres:secret@127.0.0.1:%s/postgres?sslmode=disable", r.GetPort("5432/tcp")) } // RunTestPostgreSQLWithVersion connects to a PostgreSQL database. func RunTestPostgreSQLWithVersion(t testing.TB, version string) string { if dsn := os.Getenv("TEST_DATABASE_POSTGRESQL"); dsn != "" { return dsn } r := startPostgreSQL(t, version) return fmt.Sprintf("postgres://postgres:secret@127.0.0.1:%s/postgres?sslmode=disable", r.GetPort("5432/tcp")) } // ## MySQL ## func startMySQL(t testing.TB, version string) dockertest.Resource { pool := dockertest.NewPoolT(t, "") return pool.RunT(t, "mysql", dockertest.WithTag(cmp.Or(version, "8.0")), dockertest.WithoutReuse(), dockertest.WithEnv([]string{ "MYSQL_ROOT_PASSWORD=secret", "MYSQL_ROOT_HOST=%", }), ) } // RunTestMySQL runs a MySQL database and returns the URL to it. // If a docker container is started for the database, the container be removed // at the end of the test. func RunTestMySQL(t testing.TB) string { if dsn := os.Getenv("TEST_DATABASE_MYSQL"); dsn != "" { t.Logf("Skipping Docker setup because environment variable TEST_DATABASE_MYSQL is set to: %s", dsn) return dsn } r := startMySQL(t, "") return fmt.Sprintf("mysql://root:secret@tcp(localhost:%s)/mysql?parseTime=true&multiStatements=true", r.GetPort("3306/tcp")) } // RunTestMySQLWithVersion runs a MySQL database in the specified version and returns the URL to it. // If a docker container is started for the database, the container be removed // at the end of the test. func RunTestMySQLWithVersion(t testing.TB, version string) string { if dsn := os.Getenv("TEST_DATABASE_MYSQL"); dsn != "" { t.Logf("Skipping Docker setup because environment variable TEST_DATABASE_MYSQL is set to: %s", dsn) return dsn } r := startMySQL(t, version) return fmt.Sprintf("mysql://root:secret@tcp(localhost:%s)/mysql?parseTime=true&multiStatements=true", r.GetPort("3306/tcp")) } // ## CockroachDB func startCockroachDB(t testing.TB, version string) dockertest.Resource { pool := dockertest.NewPoolT(t, "") return pool.RunT(t, "cockroachdb/cockroach", dockertest.WithTag(cmp.Or(version, "latest-v25.4")), dockertest.WithoutReuse(), dockertest.WithCmd([]string{"start-single-node", "--insecure"}), ) } // RunTestCockroachDB runs a CockroachDB database and returns the URL to it. // If a docker container is started for the database, the container be removed // at the end of the test. func RunTestCockroachDB(t testing.TB) string { return RunTestCockroachDBWithVersion(t, "") } // RunTestCockroachDBWithVersion runs a CockroachDB database and returns the URL to it. // If a docker container is started for the database, the container be removed // at the end of the test. func RunTestCockroachDBWithVersion(t testing.TB, version string) string { if dsn := os.Getenv("TEST_DATABASE_COCKROACHDB"); dsn != "" { t.Logf("Skipping Docker setup because environment variable TEST_DATABASE_COCKROACHDB is set to: %s", dsn) return dsn } r := startCockroachDB(t, version) return fmt.Sprintf("cockroach://root@localhost:%s/defaultdb?sslmode=disable", r.GetPort("26257/tcp")) } func DumpSchema(t testing.TB, c *pop.Connection) string { name, database, port := c.Dialect.Name(), c.Dialect.Details().Database, c.Dialect.Details().Port t.Logf("Dumping schema for dialect %s, database %s on port %s", name, database, port) var cmd []string var appendToDump string switch dialects := stringsx.SwitchExact(name); { case dialects.AddCase("sqlite3"): return dumpSQLiteSchema(t, c) case dialects.AddCase("postgres"): cmd = []string{"pg_dump", "--username", "postgres", "--schema-only", "--dbname", database} // we need to set the search path because the postgres dump always unsets it appendToDump = "SET search_path TO public;\n" case dialects.AddCase("mysql"): cmd = []string{"mysqldump", "--user", "root", "--password=secret", "--no-data", database} case dialects.AddCase("cockroach"): cmd = []string{"cockroach", "sql", "--insecure", "--database", database, "--execute", "SHOW CREATE ALL TABLES; SHOW CREATE ALL TYPES;", "--format", "raw"} default: t.Log(dialects.ToUnknownCaseErr()) t.FailNow() return "" } cli, err := client.NewClientWithOpts(client.FromEnv) require.NoError(t, err) containers, err := cli.ContainerList(t.Context(), container.ListOptions{ Filters: filters.NewArgs(filters.Arg("publish", port)), }) require.NoError(t, err) require.Lenf(t, containers, 1, "expected exactly one %s container with port %s", name, port) process, err := cli.ContainerExecCreate(t.Context(), containers[0].ID, container.ExecOptions{ Tty: true, AttachStdout: true, Cmd: cmd, }) require.NoError(t, err) resp, err := cli.ContainerExecAttach(t.Context(), process.ID, container.ExecAttachOptions{ Tty: true, }) require.NoError(t, err) dump, err := io.ReadAll(resp.Reader) require.NoErrorf(t, err, "%s", dump) d := string(dump) + appendToDump d = regexp.MustCompile(`(--|#|\\|mysqldump|SHOW CREATE)[^\n]*\n`).ReplaceAllLiteralString(d, "") // comments and other non-schema lines d = strings.ReplaceAll(d, "\r\n", "\n") d = regexp.MustCompile(`\n\n+`).ReplaceAllLiteralString(d, "\n\n") return d } func dumpSQLiteSchema(t testing.TB, c *pop.Connection) string { var sqls []string require.NoError(t, c.RawQuery("SELECT sql FROM sqlite_master WHERE type IN ('table', 'index', 'trigger', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name").All(&sqls)) return strings.Join(sqls, ";\n") + ";\n" } ================================================ FILE: oryx/sqlcon/error.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sqlcon import ( "database/sql" "net/http" "github.com/go-sql-driver/mysql" "github.com/jackc/pgconn" pgxconn "github.com/jackc/pgx/v5/pgconn" "github.com/lib/pq" "github.com/pkg/errors" "google.golang.org/grpc/codes" "github.com/ory/herodot" ) var ( // ErrUniqueViolation is returned when^a SQL INSERT / UPDATE command returns a conflict. ErrUniqueViolation = &herodot.DefaultError{ CodeField: http.StatusConflict, GRPCCodeField: codes.AlreadyExists, StatusField: http.StatusText(http.StatusConflict), ErrorField: "Unable to insert or update resource because a resource with that value exists already", } // ErrNoRows is returned when a SQL SELECT statement returns no rows. ErrNoRows = &herodot.DefaultError{ CodeField: http.StatusNotFound, GRPCCodeField: codes.NotFound, StatusField: http.StatusText(http.StatusNotFound), ErrorField: "Unable to locate the resource", } // ErrConcurrentUpdate is returned when the database is unable to serialize access due to a concurrent update. ErrConcurrentUpdate = &herodot.DefaultError{ CodeField: http.StatusBadRequest, GRPCCodeField: codes.Aborted, StatusField: http.StatusText(http.StatusBadRequest), ErrorField: "Unable to serialize access due to a concurrent update in another session", } ErrNoSuchTable = &herodot.DefaultError{ CodeField: http.StatusInternalServerError, GRPCCodeField: codes.Internal, StatusField: http.StatusText(http.StatusInternalServerError), ErrorField: "Unable to locate the table", } ErrNoSuchColumn = &herodot.DefaultError{ CodeField: http.StatusInternalServerError, GRPCCodeField: codes.Internal, StatusField: http.StatusText(http.StatusInternalServerError), ErrorField: "Unable to locate the column", } ) func handlePostgres(err error, sqlState string) error { switch sqlState { case "23505": // "unique_violation" return errors.WithStack(ErrUniqueViolation.WithWrap(err)) case "40001", // "serialization_failure" in CRDB "CR000": // "serialization_failure" return errors.WithStack(ErrConcurrentUpdate.WithWrap(err)) case "42P01": // "no such table" return errors.WithStack(ErrNoSuchTable.WithWrap(err)) case "42703": // "no such column" return errors.WithStack(ErrNoSuchColumn.WithWrap(err)) } return errors.WithStack(err) } type stater interface { SQLState() string } // HandleError returns the right sqlcon.Err* depending on the input error. func HandleError(err error) error { if err == nil { return nil } var st stater if errors.Is(err, sql.ErrNoRows) { return errors.WithStack(ErrNoRows) } else if errors.As(err, &st) { return errors.WithStack(handlePostgres(err, st.SQLState())) } else if e := new(pq.Error); errors.As(err, &e) { return errors.WithStack(handlePostgres(err, string(e.Code))) } else if e := new(pgconn.PgError); errors.As(err, &e) { return errors.WithStack(handlePostgres(err, e.Code)) } else if e := new(pgxconn.PgError); errors.As(err, &e) { return errors.WithStack(handlePostgres(err, e.Code)) } else if e := new(mysql.MySQLError); errors.As(err, &e) { switch e.Number { case 1062: // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_dup_entry return errors.WithStack(ErrUniqueViolation.WithWrap(err)) case 1146: // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_no_such_table return errors.WithStack(ErrNoSuchTable.WithWrap(e)) case 1054: // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_bad_field_error return errors.WithStack(ErrNoSuchColumn.WithWrap(e)) } } if err := handleSqlite(err); err != nil { return errors.WithStack(err) } return errors.WithStack(err) } ================================================ FILE: oryx/sqlcon/error_nosqlite.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build !sqlite // +build !sqlite package sqlcon // handleSqlite handles the error iff (if and only if) it is an sqlite error func handleSqlite(_ error) error { return nil } ================================================ FILE: oryx/sqlcon/error_sqlite.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build sqlite // +build sqlite package sqlcon import ( "strings" "github.com/mattn/go-sqlite3" "github.com/pkg/errors" ) // handleSqlite handles the error iff (if and only if) it is an sqlite error func handleSqlite(err error) error { if e := new(sqlite3.Error); errors.As(err, e) { switch e.ExtendedCode { case sqlite3.ErrConstraintUnique: fallthrough case sqlite3.ErrConstraintPrimaryKey: return errors.WithStack(ErrUniqueViolation.WithWrap(err)) } switch e.Code { case sqlite3.ErrError: if strings.Contains(err.Error(), "no such table") { return errors.WithStack(ErrNoSuchTable.WithWrap(err)) } case sqlite3.ErrLocked: return errors.WithStack(ErrConcurrentUpdate.WithWrap(err)) } if strings.HasPrefix(e.Error(), "no such column:") || strings.Contains(e.Error(), "has no column named") { return errors.WithStack(ErrNoSuchColumn.WithWrap(err)) } return errors.WithStack(err) } return nil } ================================================ FILE: oryx/sqlcon/message.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sqlcon // HelpMessage returns a string explaining how to set up SQL using environment variables. func HelpMessage() string { return `- DATABASE_URL: A DSN to a persistent backend. Various backends are supported: - Changes are lost on process death (ephemeral storage): - Memory: If DATABASE_URL is "memory", data will be written to memory and is lost when you restart this instance. Example: DATABASE_URL=memory - Changes are kept after process death (persistent storage): - SQL Databases: Officially, PostgreSQL, MySQL and CockroachDB are supported. This project works best with PostgreSQL. - PostgreSQL: If DATABASE_URL is a DSN starting with postgres://, PostgreSQL will be used as storage backend. Example: DATABASE_URL=postgres://user:password@host:123/database Additionally, the following query/DSN parameters are supported: * max_conns (number): Sets the maximum number of open connections to the database. Defaults to the number of CPU cores times 2. * max_idle_conns (number): Sets the maximum number of connections in the idle. Defaults to the number of CPU cores. * max_conn_lifetime (duratino): Sets the maximum amount of time ("ms", "s", "m", "h") a connection may be reused. Defaults to 0s (disabled). * sslmode (string): Whether or not to use SSL (default is require) * disable - No SSL * require - Always SSL (skip verification) * verify-ca - Always SSL (verify that the certificate presented by the server was signed by a trusted CA) * verify-full - Always SSL (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate) * fallback_application_name (string): An application_name to fall back to if one isn't provided. * connect_timeout (number): Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. * sslcert (string): Cert file location. The file must contain PEM encoded data. * sslkey (string): Key file location. The file must contain PEM encoded data. * sslrootcert (string): The location of the root certificate file. The file must contain PEM encoded data. Example: DATABASE_URL=postgres://user:password@host:123/database?sslmode=verify-full - MySQL: If DATABASE_URL is a DSN starting with mysql:// MySQL will be used as storage backend. Be aware that the ?parseTime=true parameter is mandatory, or timestamps will not work. Example: DATABASE_URL=mysql://user:password@tcp(host:123)/database?parseTime=true Additionally, the following query/DSN parameters are supported: * collation (string): Sets the collation used for client-server interaction on connection. In contrast to charset, collation does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail. * loc (string): Sets the location for time.Time values. Note that this sets the location for time.Time values but does not change MySQL's time_zone setting. For that set the time_zone DSN parameter. Please keep in mind, that param values must be url.QueryEscape'ed. Alternatively you can manually replace the / with %2F. For example US/Pacific would be loc=US%2FPacific. * maxAllowedPacket (number): Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. maxAllowedPacket=0 can be used to automatically fetch the max_allowed_packet variable from server on every connection. * readTimeout (duration): I/O read timeout. The value must be a decimal number with a unit suffix ("ms", "s", "m", "h"), such as "30s", "0.5m" or "1m30s". * timeout (duration): Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix ("ms", "s", "m", "h"), such as "30s", "0.5m" or "1m30s". * tls (bool / string): tls=true enables TLS / SSL encrypted connection to the server. Use skip-verify if you want to use a self-signed or invalid certificate (server side). * writeTimeout (duration): I/O write timeout. The value must be a decimal number with a unit suffix ("ms", "s", "m", "h"), such as "30s", "0.5m" or "1m30s". Example: DATABASE_URL=mysql://user:password@tcp(host:123)/database?parseTime=true&writeTimeout=123s - CockroachDB: If DATABASE_URL is a DSN starting with cockroach://, CockroachDB will be used as storage backend. Example: DATABASE_URL=cockroach://user:password@host:123/database Additionally, the following query/DSN parameters are supported: * sslmode (string): Whether or not to use SSL (default is require) * disable - No SSL * require - Always SSL (skip verification) * verify-ca - Always SSL (verify that the certificate presented by the server was signed by a trusted CA) * verify-full - Always SSL (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate) * application_name (string): An initial value for the application_name session variable. * sslcert (string): Cert file location. The file must contain PEM encoded data. * sslkey (string): Key file location. The file must contain PEM encoded data. * sslrootcert (string): The location of the root certificate file. The file must contain PEM encoded data. Example: DATABASE_URL=cockroach://user:password@host:123/database?sslmode=verify-full` } ================================================ FILE: oryx/sqlcon/parse_opts.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sqlcon import ( "fmt" "net/url" "strconv" "strings" "time" "github.com/ory/x/logrusx" ) // ParseConnectionOptions parses values for max_conns, max_idle_conns, max_conn_lifetime from DSNs. // It also returns the URI without those query parameters. func ParseConnectionOptions(l *logrusx.Logger, dsn string) (maxConns int, maxIdleConns int, maxConnLifetime, maxIdleConnTime time.Duration, cleanedDSN string) { maxConns = maxParallelism() * 2 maxIdleConns = maxParallelism() cleanedDSN = dsn hostPath, rawQuery, ok := strings.Cut(dsn, "?") if !ok { l. WithField("sql_max_connections", maxConns). WithField("sql_max_idle_connections", maxIdleConns). WithField("sql_max_connection_lifetime", maxConnLifetime). WithField("sql_max_idle_connection_time", maxIdleConnTime). Debugf("No SQL connection options have been defined, falling back to default connection options.") return } query, err := url.ParseQuery(rawQuery) if err != nil { l. WithField("sql_max_connections", maxConns). WithField("sql_max_idle_connections", maxIdleConns). WithField("sql_max_connection_lifetime", maxConnLifetime). WithField("sql_max_idle_connection_time", maxIdleConnTime). WithError(err). Warnf("Unable to parse SQL DSN query, falling back to default connection options.") return } if v := query.Get("max_conns"); v != "" { s, err := strconv.ParseInt(v, 10, 64) if err != nil { l.WithError(err).Warnf(`SQL DSN query parameter "max_conns" value %v could not be parsed to int, falling back to default value %d`, v, maxConns) } else { maxConns = int(s) } query.Del("max_conns") } if v := query.Get("max_idle_conns"); v != "" { s, err := strconv.ParseInt(v, 10, 64) if err != nil { l.WithError(err).Warnf(`SQL DSN query parameter "max_idle_conns" value %v could not be parsed to int, falling back to default value %d`, v, maxIdleConns) } else { maxIdleConns = int(s) } query.Del("max_idle_conns") } if v := query.Get("max_conn_lifetime"); v != "" { s, err := time.ParseDuration(v) if err != nil { l.WithError(err).Warnf(`SQL DSN query parameter "max_conn_lifetime" value %v could not be parsed to duration, falling back to default value %d`, v, maxConnLifetime) } else { maxConnLifetime = s } query.Del("max_conn_lifetime") } if v := query.Get("max_conn_idle_time"); v != "" { s, err := time.ParseDuration(v) if err != nil { l.WithError(err).Warnf(`SQL DSN query parameter "max_conn_idle_time" value %v could not be parsed to duration, falling back to default value %d`, v, maxIdleConnTime) } else { maxIdleConnTime = s } query.Del("max_conn_idle_time") } cleanedDSN = fmt.Sprintf("%s?%s", hostPath, query.Encode()) return } // FinalizeDSN will return a finalized DSN URI. func FinalizeDSN(l *logrusx.Logger, dsn string) string { if !strings.HasPrefix(dsn, "mysql://") { return dsn } var q url.Values hostPath, query, ok := strings.Cut(dsn, "?") if !ok { q = make(url.Values) } else { var err error q, err = url.ParseQuery(query) if err != nil { l.WithError(err).Warnf("Unable to parse SQL DSN query, could not finalize the DSN URI.") return dsn } } q.Set("multiStatements", "true") q.Set("parseTime", "true") // This causes an UPDATE to return the number of matching rows instead of // the number of rows changed. This ensures compatibility with PostgreSQL and SQLite behavior. q.Set("clientFoundRows", "true") return fmt.Sprintf("%s?%s", hostPath, q.Encode()) } ================================================ FILE: oryx/sqlxx/batch/.snapshots/Test_buildInsertQueryArgs-case=cockroach.json ================================================ { "TableName": "\"test_models\"", "ColumnsDecl": "\"created_at\", \"id\", \"int\", \"nid\", \"null_time_ptr\", \"string\", \"updated_at\"", "Columns": [ "created_at", "id", "int", "nid", "null_time_ptr", "string", "updated_at" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: oryx/sqlxx/batch/.snapshots/Test_buildInsertQueryArgs-case=testModel.json ================================================ { "TableName": "\"test_models\"", "ColumnsDecl": "\"created_at\", \"id\", \"int\", \"nid\", \"null_time_ptr\", \"string\", \"updated_at\"", "Columns": [ "created_at", "id", "int", "nid", "null_time_ptr", "string", "updated_at" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: oryx/sqlxx/batch/.snapshots/Test_buildInsertQueryValues-case=testModel-case=cockroach.json ================================================ [ "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "string", 42, null, { "ID": "00000000-0000-0000-0000-000000000000", "NID": "00000000-0000-0000-0000-000000000000", "String": "string", "Int": 42, "NullTimePtr": null, "created_at": "0001-01-01T00:00:00Z", "updated_at": "0001-01-01T00:00:00Z" } ] ================================================ FILE: oryx/sqlxx/batch/create.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package batch import ( "context" "database/sql" "fmt" "reflect" "sort" "strings" "time" "github.com/jmoiron/sqlx/reflectx" "github.com/ory/x/dbal" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlxx" ) type ( insertQueryArgs struct { TableName string ColumnsDecl string Columns []string Placeholders string } quoter interface { Quote(key string) string } TracerConnection struct { Tracer *otelx.Tracer Connection *pop.Connection } ) func buildInsertQueryArgs[T any](ctx context.Context, dialect string, mapper *reflectx.Mapper, quoter quoter, models []*T) insertQueryArgs { var ( v T model = pop.NewModel(v, ctx) columns []string quotedColumns []string placeholders []string placeholderRow []string ) for _, col := range model.Columns().Cols { columns = append(columns, col.Name) placeholderRow = append(placeholderRow, "?") } // We sort for the sole reason that the test snapshots are deterministic. sort.Strings(columns) for _, col := range columns { quotedColumns = append(quotedColumns, quoter.Quote(col)) } // We generate a list (for every row one) of VALUE statements here that // will be substituted by their column values later: // // (?, ?, ?, ?), // (?, ?, ?, ?), // (?, ?, ?, ?) for _, m := range models { m := reflect.ValueOf(m) pl := make([]string, len(placeholderRow)) copy(pl, placeholderRow) // There is a special case - when using CockroachDB we want to generate // UUIDs using "gen_random_uuid()" which ends up in a VALUE statement of: // // (gen_random_uuid(), ?, ?, ?), for k := range placeholderRow { if columns[k] != "id" { continue } field := mapper.FieldByName(m, columns[k]) val, ok := field.Interface().(uuid.UUID) if !ok { continue } if val == uuid.Nil && dialect == dbal.DriverCockroachDB { pl[k] = "gen_random_uuid()" break } } placeholders = append(placeholders, fmt.Sprintf("(%s)", strings.Join(pl, ", "))) } return insertQueryArgs{ TableName: quoter.Quote(model.TableName()), ColumnsDecl: strings.Join(quotedColumns, ", "), Columns: columns, Placeholders: strings.Join(placeholders, ",\n"), } } func buildInsertQueryValues[T any](dialect string, mapper *reflectx.Mapper, columns []string, models []*T, nowFunc func() time.Time) (values []any, err error) { for _, m := range models { m := reflect.ValueOf(m) now := nowFunc() // Append model fields to args for _, c := range columns { field := mapper.FieldByName(m, c) switch c { case "created_at": if pop.IsZeroOfUnderlyingType(field.Interface()) { field.Set(reflect.ValueOf(now)) } case "updated_at": field.Set(reflect.ValueOf(now)) case "id": if value, ok := field.Interface().(uuid.UUID); ok && value != uuid.Nil { break // breaks switch, not for } else if value, ok := field.Interface().(string); ok && len(value) > 0 { break // breaks switch, not for } else if dialect == dbal.DriverCockroachDB { // This is a special case: // 1. We're using cockroach // 2. It's the primary key field ("ID") // 3. A UUID was not yet set. // // If all these conditions meet, the VALUE statement will look as such: // // (gen_random_uuid(), ?, ?, ?, ...) // // For that reason, we do not add the ID value to the list of arguments, // because one of the arguments is using a built-in and thus doesn't need a value. continue // break switch, not for } id, err := uuid.NewV4() if err != nil { return nil, err } field.Set(reflect.ValueOf(id)) } values = append(values, field.Interface()) // Special-handling for *sqlxx.NullTime: mapper.FieldByName sets this to a zero time.Time, // but we want a nil pointer instead. if i, ok := field.Interface().(*sqlxx.NullTime); ok { if time.Time(*i).IsZero() { field.Set(reflect.Zero(field.Type())) } } } } return values, nil } type createOptions struct { onConflict string } type option func(*createOptions) func OnConflictDoNothing() func(*createOptions) { return func(o *createOptions) { o.onConflict = "ON CONFLICT DO NOTHING" } } // CreateFromSlice is a helper around Create that accepts a slice of models // instead of a slice of model pointers. func CreateFromSlice[T any](ctx context.Context, p *TracerConnection, models []T, opts ...option) (err error) { var ptrs []*T for k := range models { ptrs = append(ptrs, &models[k]) } return Create(ctx, p, ptrs, opts...) } // Create batch-inserts the given models into the database using a single INSERT statement. // The models are either all created or none. func Create[T any](ctx context.Context, p *TracerConnection, models []*T, opts ...option) (err error) { ctx, span := p.Tracer.Tracer().Start(ctx, "persistence.sql.batch.Create") defer otelx.End(span, &err) if len(models) == 0 { return nil } options := &createOptions{} for _, opt := range opts { opt(options) } var v T model := pop.NewModel(v, ctx) conn := p.Connection quoter, ok := conn.Dialect.(quoter) if !ok { return errors.Errorf("store is not a quoter: %T", conn.Store) } queryArgs := buildInsertQueryArgs(ctx, conn.Dialect.Name(), conn.TX.Mapper, quoter, models) values, err := buildInsertQueryValues(conn.Dialect.Name(), conn.TX.Mapper, queryArgs.Columns, models, func() time.Time { return time.Now().UTC().Truncate(time.Microsecond) }) if err != nil { return err } var returningClause string if conn.Dialect.Name() != dbal.DriverMySQL { // PostgreSQL, CockroachDB, SQLite support RETURNING. returningClause = fmt.Sprintf("RETURNING %s", model.IDField()) } query := conn.Dialect.TranslateSQL(fmt.Sprintf( "INSERT INTO %s (%s) VALUES\n%s\n%s\n%s", queryArgs.TableName, queryArgs.ColumnsDecl, queryArgs.Placeholders, options.onConflict, returningClause, )) rows, err := conn.TX.QueryContext(ctx, query, values...) if err != nil { return sqlcon.HandleError(err) } defer rows.Close() // Hydrate the models from the RETURNING clause. // // Databases not supporting RETURNING will just return 0 rows. count := 0 for rows.Next() { if err := setModelID(rows, pop.NewModel(models[count], ctx)); err != nil { return err } count++ } if err := rows.Err(); err != nil { return sqlcon.HandleError(err) } return sqlcon.HandleError(err) } // setModelID was copy & pasted from pop. It basically sets // the primary key to the given value read from the SQL row. func setModelID(row *sql.Rows, model *pop.Model) error { el := reflect.ValueOf(model.Value).Elem() fbn := el.FieldByName("ID") if !fbn.IsValid() { return errors.New("model does not have a field named id") } pkt, err := model.PrimaryKeyType() if err != nil { return errors.WithStack(err) } switch pkt { case "UUID": var id uuid.UUID if err := row.Scan(&id); err != nil { return errors.WithStack(err) } fbn.Set(reflect.ValueOf(id)) default: var id interface{} if err := row.Scan(&id); err != nil { return errors.WithStack(err) } v := reflect.ValueOf(id) switch fbn.Kind() { case reflect.Int, reflect.Int64: fbn.SetInt(v.Int()) default: fbn.Set(reflect.ValueOf(id)) } } return nil } ================================================ FILE: oryx/sqlxx/expand.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sqlxx // Expandable controls what fields to expand for projects. type Expandable string // Expandables is a list of Expandable values. type Expandables []Expandable // String returns a string representation of the Expandable. func (e Expandable) String() string { return string(e) } // ToEager returns the fields used by pop's Eager command. func (e Expandables) ToEager() []string { var s []string for _, e := range e { s = append(s, e.String()) } return s } // Has returns true if the Expandable is in the list. func (e Expandables) Has(search Expandable) bool { for _, e := range e { if e == search { return true } } return false } ================================================ FILE: oryx/sqlxx/sqlxx.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sqlxx import ( "fmt" "net/url" "reflect" "slices" "strings" "github.com/pkg/errors" ) func keys(t any, exclude []string) []string { tt := reflect.TypeOf(t) if tt.Kind() == reflect.Pointer { tt = tt.Elem() } ks := make([]string, 0, tt.NumField()) for i := range tt.NumField() { f := tt.Field(i) key, _, _ := strings.Cut(f.Tag.Get("db"), ",") if key != "" && key != "-" && !slices.Contains(exclude, key) { ks = append(ks, key) } } return ks } // NamedInsertArguments returns columns and arguments for SQL INSERT statements based on a struct's tags. Does // not work with nested structs or maps! // // type st struct { // Foo string `db:"foo"` // Bar string `db:"bar,omitempty"` // Baz string `db:"-"` // Zab string // } // columns, arguments := NamedInsertArguments(new(st)) // query := fmt.Sprintf("INSERT INTO foo (%s) VALUES (%s)", columns, arguments) // // INSERT INTO foo (foo, bar) VALUES (:foo, :bar) func NamedInsertArguments(t any, exclude ...string) (columns string, arguments string) { keys := keys(t, exclude) return strings.Join(keys, ", "), ":" + strings.Join(keys, ", :") } // NamedUpdateArguments returns columns and arguments for SQL UPDATE statements based on a struct's tags. Does // not work with nested structs or maps! // // type st struct { // Foo string `db:"foo"` // Bar string `db:"bar,omitempty"` // Baz string `db:"-"` // Zab string // } // query := fmt.Sprintf("UPDATE foo SET %s", NamedUpdateArguments(new(st))) // // UPDATE foo SET foo=:foo, bar=:bar func NamedUpdateArguments(t any, exclude ...string) string { keys := keys(t, exclude) statements := make([]string, len(keys)) for k, key := range keys { statements[k] = fmt.Sprintf("%s=:%s", key, key) } return strings.Join(statements, ", ") } func OnConflictDoNothing(dialect string, columnNoop string) string { if dialect == "mysql" { return fmt.Sprintf(" ON DUPLICATE KEY UPDATE `%s` = `%s` ", columnNoop, columnNoop) } else { return ` ON CONFLICT DO NOTHING ` } } // ExtractSchemeFromDSN returns the scheme (e.g. `mysql`, `postgres`, etc) component in a DSN string, // as well as the remaining part of the DSN after the scheme separator. // It is an error to not have a scheme present. // This makes sense in the context of a DSN to be able to identify which database is in use. func ExtractSchemeFromDSN(dsn string) (string, string, error) { scheme, afterSchemeSeparator, schemeSeparatorFound := strings.Cut(dsn, "://") if !schemeSeparatorFound { return "", "", errors.New("invalid DSN: missing scheme separator") } if scheme == "" { return "", "", errors.New("invalid DSN: empty scheme") } return scheme, afterSchemeSeparator, nil } // ExtractDbNameFromDSN returns the database name component in a DSN string. func ExtractDbNameFromDSN(dsn string) (string, error) { _, afterScheme, err := ExtractSchemeFromDSN(dsn) if err != nil { return "", err } _, afterSlash, slashFound := strings.Cut(afterScheme, "/") if !slashFound { return "", nil } dbName, _, _ := strings.Cut(afterSlash, "?") return dbName, nil } // ReplaceSchemeInDSN replaces the scheme (e.g. `mysql`, `postgres`, etc) in a DSN string with another one. // This is necessary for example when using `cockroach` as a scheme, but using the postgres driver to connect to the database, // and this driver only accepts `postgres` as a scheme. func ReplaceSchemeInDSN(dsn string, newScheme string) (string, error) { _, afterSchemeSeparator, err := ExtractSchemeFromDSN(dsn) if err != nil { return "", errors.WithStack(err) } return newScheme + "://" + afterSchemeSeparator, nil } // DSNRedacted parses a database DSN and returns a redacted form as a string. // It replaces any password with "xxxxx" just like `url.Redacted()`. // Only the password is redacted, not the username. // This function is necessary because MySQL uses a DSN format not compatible with `url.Parse`. // Additionally and as a consequence of the point above, the scheme is expected to be present and non-empty. // This function is less strict that `url.Parse` in the case of MySQL. // It also does not escape any characters in the username, whereas `url.String()`/`url.Redacted` does. func DSNRedacted(dsn string) (string, error) { scheme, afterSchemeSeparator, err := ExtractSchemeFromDSN(dsn) if err != nil { return "", errors.WithStack(err) } // If this is not MySQL, we simply delegate the work to `url.Parse`. if scheme != "mysql" { u, err := url.Parse(dsn) if err != nil { return "", errors.WithStack(err) } return u.Redacted(), nil } // MySQL has a weird DSN syntax not conforming to a standard URL, of the form: // `[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]` // We only need to parse up to `@` in order to redact the password. The rest is left as-is. usernamePassword, afterUsernamePassword, usernamePasswordSeparatorFound := strings.Cut(afterSchemeSeparator, "@") if !usernamePasswordSeparatorFound { afterUsernamePassword = afterSchemeSeparator } username, password, hasPassword := strings.Cut(usernamePassword, ":") // We only insert a redacted password in the final result if a password was provided in the input. // This behavior matches the one of `url.Redacted()`. if hasPassword { password = ":xxxxx" } res := scheme + "://" if usernamePasswordSeparatorFound { res += username + password + "@" } res += afterUsernamePassword return res, nil } ================================================ FILE: oryx/sqlxx/types.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sqlxx import ( "database/sql" "database/sql/driver" "encoding/json" "fmt" "slices" "strings" "time" "github.com/tidwall/gjson" "github.com/pkg/errors" ) // Duration represents a JSON and SQL compatible time.Duration. // swagger:type string type Duration time.Duration // MarshalJSON returns m as the JSON encoding of m. func (ns Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(ns).String()) } // UnmarshalJSON sets *m to a copy of data. func (ns *Duration) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } p, err := time.ParseDuration(s) if err != nil { return err } *ns = Duration(p) return nil } // StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. type StringSliceJSONFormat []string // Scan implements the Scanner interface. func (m *StringSliceJSONFormat) Scan(value interface{}) error { var val string switch v := value.(type) { case nil: *m = StringSliceJSONFormat{} return nil case string: val = v case []byte: val = string(v) default: return errors.Errorf("cannot scan %#v into StringSliceJSONFormat", value) } if len(val) == 0 { val = "[]" } if parsed := gjson.Parse(val); parsed.Type == gjson.Null { val = "[]" } else if !parsed.IsArray() { return errors.Errorf("expected JSON value to be an array but got type: %s", parsed.Type.String()) } return errors.WithStack(json.Unmarshal([]byte(val), &m)) } // Value implements the driver Valuer interface. func (m StringSliceJSONFormat) Value() (driver.Value, error) { if len(m) == 0 { return "[]", nil } encoded, err := json.Marshal(&m) return string(encoded), errors.WithStack(err) } // StringSlicePipeDelimiter de/encodes the string slice to/from a SQL string. type StringSlicePipeDelimiter []string // Scan implements the Scanner interface. func (n *StringSlicePipeDelimiter) Scan(value interface{}) error { var s sql.NullString if err := s.Scan(value); err != nil { return err } *n = scanStringSlice('|', s.String) return nil } // Value implements the driver Valuer interface. func (n StringSlicePipeDelimiter) Value() (driver.Value, error) { return valueStringSlice('|', n), nil } func scanStringSlice(delimiter rune, value interface{}) []string { escaped := false s := fmt.Sprintf("%s", value) splitted := strings.FieldsFunc(s, func(r rune) bool { if r == '\\' { escaped = !escaped } else if escaped && r != delimiter { escaped = false } return !escaped && r == delimiter }) for k, v := range splitted { splitted[k] = strings.ReplaceAll(v, "\\"+string(delimiter), string(delimiter)) } return splitted } func valueStringSlice(delimiter rune, value []string) string { replace := make([]string, len(value)) for k, v := range value { replace[k] = strings.ReplaceAll(v, string(delimiter), "\\"+string(delimiter)) } return strings.Join(replace, string(delimiter)) } // NullBool represents a bool that may be null. // NullBool implements the Scanner interface so // swagger:type bool // swagger:model nullBool type NullBool struct { Bool bool Valid bool // Valid is true if Bool is not NULL } // Scan implements the Scanner interface. func (ns *NullBool) Scan(value interface{}) error { d := sql.NullBool{} if err := d.Scan(value); err != nil { return err } ns.Bool = d.Bool ns.Valid = d.Valid return nil } // Value implements the driver Valuer interface. func (ns NullBool) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } return ns.Bool, nil } // MarshalJSON returns m as the JSON encoding of m. func (ns NullBool) MarshalJSON() ([]byte, error) { if !ns.Valid { return []byte("null"), nil } return json.Marshal(ns.Bool) } // UnmarshalJSON sets *m to a copy of data. func (ns *NullBool) UnmarshalJSON(data []byte) error { if ns == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } if len(data) == 0 || string(data) == "null" { return nil } ns.Valid = true return errors.WithStack(json.Unmarshal(data, &ns.Bool)) } // FalsyNullBool represents a bool that may be null. // It JSON decodes to false if null. // // swagger:type bool // swagger:model falsyNullBool type FalsyNullBool struct { Bool bool Valid bool // Valid is true if Bool is not NULL } // Scan implements the Scanner interface. func (ns *FalsyNullBool) Scan(value interface{}) error { d := sql.NullBool{} if err := d.Scan(value); err != nil { return err } ns.Bool = d.Bool ns.Valid = d.Valid return nil } // Value implements the driver Valuer interface. func (ns FalsyNullBool) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } return ns.Bool, nil } // MarshalJSON returns m as the JSON encoding of m. func (ns FalsyNullBool) MarshalJSON() ([]byte, error) { if !ns.Valid { return []byte("false"), nil } return json.Marshal(ns.Bool) } // UnmarshalJSON sets *m to a copy of data. func (ns *FalsyNullBool) UnmarshalJSON(data []byte) error { if ns == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } if len(data) == 0 || string(data) == "null" { return nil } ns.Valid = true return errors.WithStack(json.Unmarshal(data, &ns.Bool)) } // swagger:type string // swagger:model nullString type NullString string // MarshalJSON returns m as the JSON encoding of m. func (ns NullString) MarshalJSON() ([]byte, error) { return json.Marshal(string(ns)) } // UnmarshalJSON sets *m to a copy of data. func (ns *NullString) UnmarshalJSON(data []byte) error { if ns == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } if len(data) == 0 { return nil } return errors.WithStack(json.Unmarshal(data, (*string)(ns))) } // Scan implements the Scanner interface. func (ns *NullString) Scan(value interface{}) error { var v sql.NullString if err := (&v).Scan(value); err != nil { return err } *ns = NullString(v.String) return nil } // Value implements the driver Valuer interface. func (ns NullString) Value() (driver.Value, error) { if len(ns) == 0 { return sql.NullString{}.Value() } return sql.NullString{Valid: true, String: string(ns)}.Value() } // String implements the Stringer interface. func (ns NullString) String() string { return string(ns) } // NullTime implements sql.NullTime functionality. // // swagger:model nullTime // required: false type NullTime time.Time // Scan implements the Scanner interface. func (ns *NullTime) Scan(value interface{}) error { var v sql.NullTime if err := (&v).Scan(value); err != nil { return err } *ns = NullTime(v.Time) return nil } // MarshalJSON returns m as the JSON encoding of m. func (ns NullTime) MarshalJSON() ([]byte, error) { var t *time.Time if !time.Time(ns).IsZero() { tt := time.Time(ns) t = &tt } return json.Marshal(t) } // UnmarshalJSON sets *m to a copy of data. func (ns *NullTime) UnmarshalJSON(data []byte) error { var t time.Time if err := json.Unmarshal(data, &t); err != nil { return err } *ns = NullTime(t) return nil } // Value implements the driver Valuer interface. func (ns NullTime) Value() (driver.Value, error) { return sql.NullTime{Valid: !time.Time(ns).IsZero(), Time: time.Time(ns)}.Value() } // MapStringInterface represents a map[string]interface that works well with JSON, SQL, and Swagger. type MapStringInterface map[string]interface{} // Scan implements the Scanner interface. func (n *MapStringInterface) Scan(value interface{}) error { v := fmt.Sprintf("%s", value) if len(v) == 0 { return nil } return errors.WithStack(json.Unmarshal([]byte(v), n)) } // Value implements the driver Valuer interface. func (n MapStringInterface) Value() (driver.Value, error) { value, err := json.Marshal(n) if err != nil { return nil, errors.WithStack(err) } return string(value), nil } // JSONArrayRawMessage represents a json.RawMessage which only accepts arrays that works well with JSON, SQL, and Swagger. type JSONArrayRawMessage json.RawMessage // Scan implements the Scanner interface. func (m *JSONArrayRawMessage) Scan(value interface{}) error { val := fmt.Sprintf("%s", value) if len(val) == 0 { val = "[]" } if parsed := gjson.Parse(val); parsed.Type == gjson.Null { val = "[]" } else if !parsed.IsArray() { return errors.Errorf("expected JSON value to be an array but got type: %s", parsed.Type.String()) } *m = []byte(val) return nil } // Value implements the driver Valuer interface. func (m JSONArrayRawMessage) Value() (driver.Value, error) { if len(m) == 0 { return "[]", nil } if parsed := gjson.ParseBytes(m); parsed.Type == gjson.Null { return "[]", nil } else if !parsed.IsArray() { return nil, errors.Errorf("expected JSON value to be an array but got type: %s", parsed.Type.String()) } return string(m), nil } // JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. type JSONRawMessage json.RawMessage // Scan implements the Scanner interface. func (m *JSONRawMessage) Scan(value interface{}) error { switch v := value.(type) { case []byte: *m = slices.Clone(v) case string: *m = JSONRawMessage(v) case nil: *m = JSONRawMessage("null") default: return errors.Errorf("cannot scan %T into JSONRawMessage", value) } return nil } // Value implements the driver Valuer interface. func (m JSONRawMessage) Value() (driver.Value, error) { if len(m) == 0 { return "null", nil } return string(m), nil } // MarshalJSON returns m as the JSON encoding of m. func (m JSONRawMessage) MarshalJSON() ([]byte, error) { if len(m) == 0 { return []byte("null"), nil } return m, nil } // UnmarshalJSON sets *m to a copy of data. func (m *JSONRawMessage) UnmarshalJSON(data []byte) error { if m == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } *m = append((*m)[0:0], data...) return nil } // NullJSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger and is NULLable- // // swagger:model nullJsonRawMessage type NullJSONRawMessage json.RawMessage // Scan implements the Scanner interface. func (m *NullJSONRawMessage) Scan(value any) error { return (*JSONRawMessage)(m).Scan(value) } // Value implements the driver Valuer interface. func (m NullJSONRawMessage) Value() (driver.Value, error) { if len(m) == 0 || string(m) == "null" { return nil, nil } return string(m), nil } // MarshalJSON returns m as the JSON encoding of m. func (m NullJSONRawMessage) MarshalJSON() ([]byte, error) { if len(m) == 0 { return []byte("null"), nil } return m, nil } // UnmarshalJSON sets *m to a copy of data. func (m *NullJSONRawMessage) UnmarshalJSON(data []byte) error { if m == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } *m = append((*m)[0:0], data...) return nil } // JSONScan is a generic helper for retrieving a SQL JSON-encoded value. func JSONScan(dst, value any) error { // Note: raw is a string (not []byte) because the MySQL driver reuses byte slices across scans. // Using strings avoids the need to manually copy the byte slice. var raw string switch v := value.(type) { case nil: raw = "null" case string: raw = v case []byte: raw = string(v) default: return fmt.Errorf("unable to scan type %T as JSON into %T", value, dst) } if err := json.Unmarshal([]byte(raw), dst); err != nil { return fmt.Errorf("unable to decode JSON payload into %T: %w", dst, err) } return nil } // NullInt64 represents an int64 that may be null. // swagger:model nullInt64 type NullInt64 struct { Int int64 Valid bool // Valid is true if Duration is not NULL } // Scan implements the Scanner interface. func (ns *NullInt64) Scan(value interface{}) error { d := sql.NullInt64{} if err := d.Scan(value); err != nil { return err } ns.Int = d.Int64 ns.Valid = d.Valid return nil } // Value implements the driver Valuer interface. func (ns NullInt64) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } return ns.Int, nil } // MarshalJSON returns m as the JSON encoding of m. func (ns NullInt64) MarshalJSON() ([]byte, error) { if !ns.Valid { return []byte("null"), nil } return json.Marshal(ns.Int) } // UnmarshalJSON sets *m to a copy of data. func (ns *NullInt64) UnmarshalJSON(data []byte) error { if ns == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } if len(data) == 0 || string(data) == "null" { return nil } ns.Valid = true return errors.WithStack(json.Unmarshal(data, &ns.Int)) } // NullDuration represents a nullable JSON and SQL compatible time.Duration. // // swagger:type string // swagger:model nullDuration type NullDuration struct { Duration time.Duration Valid bool } // Scan implements the Scanner interface. func (ns *NullDuration) Scan(value interface{}) error { d := sql.NullInt64{} if err := d.Scan(value); err != nil { return err } ns.Duration = time.Duration(d.Int64) ns.Valid = d.Valid return nil } // Value implements the driver Valuer interface. func (ns NullDuration) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } return int64(ns.Duration), nil } // MarshalJSON returns m as the JSON encoding of m. func (ns NullDuration) MarshalJSON() ([]byte, error) { if !ns.Valid { return []byte("null"), nil } return json.Marshal(ns.Duration.String()) } // UnmarshalJSON sets *m to a copy of data. func (ns *NullDuration) UnmarshalJSON(data []byte) error { if ns == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } if len(data) == 0 || string(data) == "null" { return nil } var s string if err := json.Unmarshal(data, &s); err != nil { return err } p, err := time.ParseDuration(s) if err != nil { return err } ns.Duration = p ns.Valid = true return nil } func (ns Duration) IsZero() bool { return ns == 0 } func (m StringSliceJSONFormat) IsZero() bool { return len(m) == 0 } func (n StringSlicePipeDelimiter) IsZero() bool { return len(n) == 0 } func (ns NullBool) IsZero() bool { return !ns.Valid || !ns.Bool } func (ns FalsyNullBool) IsZero() bool { return !ns.Valid || !ns.Bool } func (ns NullString) IsZero() bool { return len(ns) == 0 } func (ns NullTime) IsZero() bool { return time.Time(ns).IsZero() } func (n MapStringInterface) IsZero() bool { return len(n) == 0 } func (m JSONArrayRawMessage) IsZero() bool { return len(m) == 0 || string(m) == "[]" } func (m JSONRawMessage) IsZero() bool { return len(m) == 0 || string(m) == "null" } func (m NullJSONRawMessage) IsZero() bool { return len(m) == 0 || string(m) == "null" } func (ns NullInt64) IsZero() bool { return !ns.Valid || ns.Int == 0 } func (ns NullDuration) IsZero() bool { return !ns.Valid || ns.Duration == 0 } ================================================ FILE: oryx/stringslice/unique.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package stringslice // Unique returns the given string slice with unique values, preserving order. // Consider using slices.Compact with slices.Sort instead when you don't care about order. func Unique(i []string) []string { u := make([]string, 0, len(i)) m := make(map[string]struct{}, len(i)) for _, val := range i { if _, ok := m[val]; !ok { m[val] = struct{}{} u = append(u, val) } } return u } ================================================ FILE: oryx/stringsx/case.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package stringsx import "unicode" // ToLowerInitial converts a string's first character to lower case. func ToLowerInitial(s string) string { if s == "" { return "" } a := []rune(s) a[0] = unicode.ToLower(a[0]) return string(a) } // ToUpperInitial converts a string's first character to upper case. func ToUpperInitial(s string) string { if s == "" { return "" } a := []rune(s) a[0] = unicode.ToUpper(a[0]) return string(a) } ================================================ FILE: oryx/stringsx/split.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package stringsx import "strings" // Splitx is a special case of strings.Split // which returns an empty slice if the string is empty func Splitx(s, sep string) []string { if s == "" { return []string{} } return strings.Split(s, sep) } ================================================ FILE: oryx/stringsx/switch_case.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package stringsx import ( "fmt" "slices" "strings" ) type ( RegisteredCases struct { cases []string actual string } errUnknownCase struct { *RegisteredCases } RegisteredPrefixes struct { prefixes []string actual string } errUnknownPrefix struct { *RegisteredPrefixes } ) var ( ErrUnknownCase = errUnknownCase{} ErrUnknownPrefix = errUnknownPrefix{} ) func SwitchExact(actual string) *RegisteredCases { return &RegisteredCases{ actual: actual, } } func SwitchPrefix(actual string) *RegisteredPrefixes { return &RegisteredPrefixes{ actual: actual, } } func (r *RegisteredCases) AddCase(cases ...string) bool { r.cases = append(r.cases, cases...) return slices.Contains(cases, r.actual) } func (r *RegisteredPrefixes) HasPrefix(prefixes ...string) bool { r.prefixes = append(r.prefixes, prefixes...) return slices.ContainsFunc(prefixes, func(s string) bool { return strings.HasPrefix(r.actual, s) }) } func (r *RegisteredCases) String() string { return "[" + strings.Join(r.cases, ", ") + "]" } func (r *RegisteredPrefixes) String() string { return "[" + strings.Join(r.prefixes, ", ") + "]" } func (r *RegisteredCases) ToUnknownCaseErr() error { return errUnknownCase{r} } func (r *RegisteredPrefixes) ToUnknownPrefixErr() error { return errUnknownPrefix{r} } func (e errUnknownCase) Error() string { return fmt.Sprintf("expected one of %s but got %s", e.String(), e.actual) } func (e errUnknownCase) Is(err error) bool { _, ok := err.(errUnknownCase) return ok } func (e errUnknownPrefix) Error() string { return fmt.Sprintf("expected %s to have one of the prefixes %s", e.actual, e.String()) } func (e errUnknownPrefix) Is(err error) bool { _, ok := err.(errUnknownPrefix) return ok } ================================================ FILE: oryx/stringsx/truncate.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package stringsx import "unicode/utf8" // TruncateByteLen returns string truncated at the end with the length specified func TruncateByteLen(s string, length int) string { if length <= 0 || len(s) <= length { return s } res := s[:length] // in case we cut in the middle of an utf8 rune, we have to remove the last byte as well until it fits for !utf8.ValidString(res) { res = res[:len(res)-1] } return res } ================================================ FILE: oryx/swaggerx/error.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package swaggerx import ( "encoding/json" "errors" "fmt" "net/http" "github.com/go-openapi/runtime" ) func FormatSwaggerError(err error) string { var e *runtime.APIError if errors.As(err, &e) { body, err := json.MarshalIndent(e, "\t", " ") if err != nil { body = []byte(fmt.Sprintf("%+v", e.Response)) } switch e.Code { case http.StatusForbidden: return fmt.Sprintf("The service responded with status code 403 indicating that you lack permission to access the resource. The full error details are:\n\n\t%s\n\n", body) case http.StatusUnauthorized: return fmt.Sprintf("The service responded with status code 401 indicating that you forgot to include credentials (e.g. token, TLS certificate, ...) in the HTTP request. The full error details are:\n\n\t%s\n\n", body) case http.StatusNotFound: return fmt.Sprintf("The service responded with status code 404 indicating that the resource does not exist. Check that the URL is correct (are you using the correct admin/public/... endpoint?) and that the resource exists. The full error details are:\n\n\t%s\n\n", body) default: return fmt.Sprintf("Unable to complete operation %s because the server responded with status code %d:\n\n\t%s\n", e.OperationName, e.Code, body) } } return fmt.Sprintf("%+v", err) } ================================================ FILE: oryx/testingx/helpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 // Package testingx contains helper functions and extensions used when writing tests in Ory. package testingx import ( "os" "path/filepath" "runtime" "github.com/stretchr/testify/require" ) // RepoRootPath returns the absolute path of the closest parent directory that has a go.mod file relative to the caller. func RepoRootPath(t require.TestingT) (repoRoot string) { _, fpath, _, _ := runtime.Caller(1) for dir := filepath.Dir(filepath.FromSlash(fpath)); dir != filepath.Dir(dir); dir = filepath.Dir(dir) { modPath := filepath.Join(dir, "go.mod") if _, err := os.Stat(modPath); err == nil { repoRoot = dir break } } require.NotEmptyf(t, repoRoot, "could not determine repo root using path: %q", fpath) return repoRoot } ================================================ FILE: oryx/tlsx/cert.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package tlsx import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/pem" "fmt" "io" "math/big" "os" "path/filepath" "slices" "sync/atomic" "testing" "time" "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/ory/x/watcherx" ) // ErrNoCertificatesConfigured is returned when no TLS configuration was found. var ErrNoCertificatesConfigured = errors.New("no tls configuration was found") // ErrInvalidCertificateConfiguration is returned when an invalid TLS configuration was found. var ErrInvalidCertificateConfiguration = errors.New("tls configuration is invalid") // HTTPSCertificate returns loads a HTTP over TLS Certificate by looking at environment variables. func HTTPSCertificate() ([]tls.Certificate, error) { prefix := "HTTPS_TLS" return Certificate( os.Getenv(prefix+"_CERT"), os.Getenv(prefix+"_KEY"), os.Getenv(prefix+"_CERT_PATH"), os.Getenv(prefix+"_KEY_PATH"), ) } // HTTPSCertificateHelpMessage returns a help message for configuring HTTP over TLS Certificates. func HTTPSCertificateHelpMessage() string { return CertificateHelpMessage("HTTPS_TLS") } // CertificateHelpMessage returns a help message for configuring TLS Certificates. func CertificateHelpMessage(prefix string) string { return `- ` + prefix + `_CERT_PATH: The path to the TLS certificate (pem encoded). Example: ` + prefix + `_CERT_PATH=~/cert.pem - ` + prefix + `_KEY_PATH: The path to the TLS private key (pem encoded). Example: ` + prefix + `_KEY_PATH=~/key.pem - ` + prefix + `_CERT: Base64 encoded (without padding) string of the TLS certificate (PEM encoded) to be used for HTTP over TLS (HTTPS). Example: ` + prefix + `_CERT="-----BEGIN CERTIFICATE-----\nMIIDZTCCAk2gAwIBAgIEV5xOtDANBgkqhkiG9w0BAQ0FADA0MTIwMAYDVQQDDClP..." - ` + prefix + `_KEY: Base64 encoded (without padding) string of the private key (PEM encoded) to be used for HTTP over TLS (HTTPS). Example: ` + prefix + `_KEY="-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDg..." ` } // CertificateFromBase64 loads a TLS certificate from a base64-encoded string of // the PEM representations of the cert and key. func CertificateFromBase64(certBase64, keyBase64 string) (tls.Certificate, error) { certPEM, err := base64.StdEncoding.DecodeString(certBase64) if err != nil { return tls.Certificate{}, fmt.Errorf("unable to base64 decode the TLS certificate: %v", err) } keyPEM, err := base64.StdEncoding.DecodeString(keyBase64) if err != nil { return tls.Certificate{}, fmt.Errorf("unable to base64 decode the TLS private key: %v", err) } cert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { return tls.Certificate{}, fmt.Errorf("unable to load X509 key pair: %v", err) } return cert, nil } // [deprecated] Certificate returns a TLS Certificate by looking at its // arguments. If both certPEMBase64 and keyPEMBase64 are not empty and contain // base64-encoded PEM representations of a cert and key, respectively, that key // pair is returned. Otherwise, if certPath and keyPath point to PEM files, the // key pair is loaded from those. Returns ErrNoCertificatesConfigured if all // arguments are empty, and ErrInvalidCertificateConfiguration if the arguments // are inconsistent. // // This function is deprecated. Use CertificateFromBase64 or GetCertificate // instead. func Certificate( certPEMBase64, keyPEMBase64 string, certPath, keyPath string, ) ([]tls.Certificate, error) { if certPEMBase64 == "" && keyPEMBase64 == "" && certPath == "" && keyPath == "" { return nil, errors.WithStack(ErrNoCertificatesConfigured) } if certPEMBase64 != "" && keyPEMBase64 != "" { cert, err := CertificateFromBase64(certPEMBase64, keyPEMBase64) if err != nil { return nil, errors.WithStack(err) } return []tls.Certificate{cert}, nil } if certPath != "" && keyPath != "" { cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, fmt.Errorf("unable to load X509 key pair from files: %v", err) } return []tls.Certificate{cert}, nil } return nil, errors.WithStack(ErrInvalidCertificateConfiguration) } type CertFunc = func(*tls.ClientHelloInfo) (*tls.Certificate, error) // GetCertificate returns a function for use with // "net/tls".Config.GetCertificate. // // The certificate and private key are read from the specified filesystem paths. // The certificate file is watched for changes, upon which the cert+key are // reloaded in the background. Errors during reloading are deduplicated and // reported through the errs channel if it is not nil. When the provided context // is canceled, background reloading stops and the errs channel is closed. // // The returned function always yields the latest successfully loaded // certificate; ClientHelloInfo is unused. func GetCertificate( ctx context.Context, certPath, keyPath string, errs chan<- error, ) (CertFunc, error) { if certPath == "" || keyPath == "" { return nil, errors.WithStack(ErrNoCertificatesConfigured) } cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, errors.WithStack(fmt.Errorf("unable to load X509 key pair from files: %v", err)) } var store atomic.Value store.Store(&cert) events := make(chan watcherx.Event) // The cert could change without the key changing, but not the other way around. // Hence, we only watch the cert. _, err = watcherx.WatchFile(ctx, certPath, events) if err != nil { return nil, errors.WithStack(err) } go func() { if errs != nil { defer close(errs) } var lastReportedError string for { select { case <-ctx.Done(): return case event := <-events: var err error switch event := event.(type) { case *watcherx.ChangeEvent: var cert tls.Certificate cert, err = tls.LoadX509KeyPair(certPath, keyPath) if err == nil { store.Store(&cert) lastReportedError = "" continue } err = fmt.Errorf("unable to load X509 key pair from files: %v", err) case *watcherx.ErrorEvent: err = fmt.Errorf("file watch: %v", event) default: continue } if err.Error() == lastReportedError { // same message as before: don't spam the error channel continue } // fresh error select { case errs <- errors.WithStack(err): lastReportedError = err.Error() case <-time.After(500 * time.Millisecond): } } } }() return func(*tls.ClientHelloInfo) (*tls.Certificate, error) { if cert, ok := store.Load().(*tls.Certificate); ok { return cert, nil } return nil, errors.WithStack(ErrNoCertificatesConfigured) }, nil } // PublicKey returns the public key for a given private key, or nil. func PublicKey(key crypto.PrivateKey) interface{ Equal(x crypto.PublicKey) bool } { switch k := key.(type) { case *rsa.PrivateKey: return &k.PublicKey case *ecdsa.PrivateKey: return &k.PublicKey case ed25519.PrivateKey: return k.Public().(ed25519.PublicKey) default: return nil } } // CreateSelfSignedTLSCertificate creates a self-signed TLS certificate. func CreateSelfSignedTLSCertificate(key interface{}, opts ...CertificateOpts) (*tls.Certificate, error) { c, err := CreateSelfSignedCertificate(key, opts...) if err != nil { return nil, err } block, err := PEMBlockForKey(key) if err != nil { return nil, err } pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw}) pemKey := pem.EncodeToMemory(block) cert, err := tls.X509KeyPair(pemCert, pemKey) if err != nil { return nil, err } return &cert, nil } // CreateSelfSignedCertificate creates a self-signed x509 certificate. func CreateSelfSignedCertificate(key interface{}, opts ...CertificateOpts) (cert *x509.Certificate, err error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return cert, errors.Errorf("failed to generate serial number: %s", err) } certificate := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"ORY GmbH"}, CommonName: "ORY", }, Issuer: pkix.Name{ Organization: []string{"ORY GmbH"}, CommonName: "ORY", }, NotBefore: time.Now().UTC(), NotAfter: time.Now().UTC().Add(time.Hour * 24 * 31), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, IsCA: true, DNSNames: []string{"localhost"}, } for _, opt := range opts { opt(certificate) } der, err := x509.CreateCertificate(rand.Reader, certificate, certificate, PublicKey(key), key) if err != nil { return cert, errors.Errorf("failed to create certificate: %s", err) } cert, err = x509.ParseCertificate(der) if err != nil { return cert, errors.Errorf("failed to encode private key: %s", err) } return cert, nil } // PEMBlockForKey returns a PEM-encoded block for key. func PEMBlockForKey(key interface{}) (*pem.Block, error) { b, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return nil, errors.WithStack(err) } return &pem.Block{Type: "PRIVATE KEY", Bytes: b}, nil } // NewClientCert creates a new client TLS certificate signed by the given CA. func NewClientCert(CAcert *x509.Certificate, CAkey crypto.PrivateKey, opts ...CertificateOpts) (*tls.Certificate, error) { if !slices.Contains(CAcert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { return nil, errors.Errorf("the CA certificate does not have the client authentication extended key usage (OID 1.3.6.1.5.5.7.3.2) set") } serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return nil, errors.Errorf("failed to generate serial number: %s", err) } key, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil { return nil, errors.Errorf("failed to generate private key: %s", err) } template := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Ory GmbH"}, CommonName: "ORY", }, Issuer: CAcert.Subject, NotBefore: time.Now().UTC(), NotAfter: CAcert.NotAfter, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, IsCA: false, } for _, opt := range opts { opt(template) } der, err := x509.CreateCertificate(rand.Reader, template, CAcert, PublicKey(key), CAkey) if err != nil { return nil, errors.Errorf("failed to create certificate: %s", err) } pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) pemBlock, err := PEMBlockForKey(key) if err != nil { return nil, err } pemKey := pem.EncodeToMemory(pemBlock) cert, err := tls.X509KeyPair(pemCert, pemKey) if err != nil { return nil, errors.WithStack(err) } return &cert, nil } type CertificateOpts func(*x509.Certificate) // CreateSelfSignedCertificateForTest writes a new, self-signed TLS // certificate+key (in PEM format) to a temporary location on disk and returns // the paths to both, and the respective contents in base64 encoding. The // files are automatically cleaned up when the given *testing.T concludes its // tests. func CreateSelfSignedCertificateForTest(t testing.TB) (certPath, keyPath, certBase64, keyBase64 string) { tmpDir := t.TempDir() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) cert, err := CreateSelfSignedCertificate(privateKey) require.NoError(t, err) // write cert certFile, err := os.Create(filepath.Join(tmpDir, "cert.pem")) require.NoError(t, err) certPath = certFile.Name() var buf bytes.Buffer enc := base64.NewEncoder(base64.StdEncoding, &buf) require.NoErrorf(t, pem.Encode( io.MultiWriter(enc, certFile), &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}, ), "Failed to write data to %q", certPath) require.NoError(t, enc.Close()) require.NoErrorf(t, certFile.Close(), "Error closing %q", certPath) certBase64 = buf.String() // write key keyFile, err := os.Create(filepath.Join(tmpDir, "key.pem")) require.NoError(t, err) keyPath = keyFile.Name() buf.Reset() enc = base64.NewEncoder(base64.StdEncoding, &buf) privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) require.NoError(t, err) require.NoErrorf(t, pem.Encode( io.MultiWriter(enc, keyFile), &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}, ), "Failed to write data to %q", keyPath) require.NoError(t, enc.Close()) require.NoErrorf(t, keyFile.Close(), "Error closing %q", keyPath) keyBase64 = buf.String() return } ================================================ FILE: oryx/tlsx/termination.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package tlsx import ( "net" "net/http" "strings" "github.com/pkg/errors" "github.com/urfave/negroni" "github.com/ory/x/healthx" "github.com/ory/x/httpx" "github.com/ory/x/logrusx" "github.com/ory/x/prometheusx" ) type dependencies interface { logrusx.Provider httpx.WriterProvider } // EnforceTLSRequests creates a middleware that enforces TLS for incoming HTTP requests. // It allows termination (non-HTTPS traffic) from specific CIDR ranges provided in the `allowTerminationFrom` slice. // If the request is not secure and does not match the allowed CIDR ranges, an error response is returned. // The middleware also validates the `X-Forwarded-Proto` header to ensure it is set to "https". func EnforceTLSRequests(d dependencies, allowTerminationFrom []string) (negroni.Handler, error) { networks := make([]*net.IPNet, 0, len(allowTerminationFrom)) for _, rn := range allowTerminationFrom { _, network, err := net.ParseCIDR(rn) if err != nil { return nil, errors.WithStack(err) } networks = append(networks, network) } return negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if r.TLS != nil || r.URL.Path == healthx.AliveCheckPath || r.URL.Path == healthx.ReadyCheckPath || r.URL.Path == prometheusx.MetricsPrometheusPath { next(rw, r) return } if len(networks) == 0 { d.Logger().WithRequest(r).WithError(errors.New("TLS termination is not enabled")).Error("Could not serve http connection") d.Writer().WriteErrorCode(rw, r, http.StatusBadGateway, errors.New("can not serve request over insecure http")) return } if err := matchesRange(r, networks); err != nil { d.Logger().WithRequest(r).WithError(err).Warnln("Could not serve http connection") d.Writer().WriteErrorCode(rw, r, http.StatusBadGateway, errors.New("can not serve request over insecure http")) return } proto := r.Header.Get("X-Forwarded-Proto") if proto == "" { d.Logger().WithRequest(r).WithError(errors.New("X-Forwarded-Proto header is missing")).Error("Could not serve http connection") d.Writer().WriteErrorCode(rw, r, http.StatusBadGateway, errors.New("can not serve request over insecure http")) return } else if proto != "https" { d.Logger().WithRequest(r).WithError(errors.New("X-Forwarded-Proto header is missing")).Error("Could not serve http connection") d.Writer().WriteErrorCode(rw, r, http.StatusBadGateway, errors.Errorf("expected X-Forwarded-Proto header to be https but got: %s", proto)) return } next(rw, r) }), nil } func matchesRange(r *http.Request, networks []*net.IPNet) error { remoteIP, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return errors.WithStack(err) } check := []string{remoteIP} for fwd := range strings.SplitSeq(r.Header.Get("X-Forwarded-For"), ",") { check = append(check, strings.TrimSpace(fwd)) } for _, ipNet := range networks { for _, ip := range check { addr := net.ParseIP(ip) if ipNet.Contains(addr) { return nil } } } return errors.Errorf("neither remote address nor any x-forwarded-for values match CIDR ranges %+v: %v, ranges, check)", networks, check) } ================================================ FILE: oryx/urlx/copy.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package urlx import "net/url" // Copy returns a copy of the input url. func Copy(src *url.URL) *url.URL { var out = new(url.URL) *out = *src return out } // CopyWithQuery returns a copy of the input url with the given query parameters func CopyWithQuery(src *url.URL, query url.Values) *url.URL { out := Copy(src) q := out.Query() for k := range query { q.Set(k, query.Get(k)) } out.RawQuery = q.Encode() return out } ================================================ FILE: oryx/urlx/extract.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package urlx import ( "context" "net" "net/url" "strings" "sync" "time" "github.com/pkg/errors" ) // hostCache caches DNS lookup results for hostnames to avoid repeated lookups. // The cache is thread-safe and stores true/false whether a hostname resolves to public IPs. type hostCache struct { mu sync.RWMutex cache map[string]bool } // get retrieves a cached value for a hostname. Returns value and whether it was found. func (hc *hostCache) get(hostname string) (bool, bool) { hc.mu.RLock() defer hc.mu.RUnlock() isPublic, found := hc.cache[hostname] return isPublic, found } // set stores the lookup result for a hostname. func (hc *hostCache) set(hostname string, isPublic bool) { hc.mu.Lock() defer hc.mu.Unlock() hc.cache[hostname] = isPublic } // localCache lives for the lifetime of the main process. The cache // size is not expected to grow more than a few hundred bytes. var localCache = &hostCache{ cache: make(map[string]bool), } // ExtractPublicAddress iterates over parameters and extracts the first public // address found. Parameter values are assumed to be in priority order. Returns // an empty string if only private addresses are available. func ExtractPublicAddress(values ...string) string { for _, value := range values { if value == "" || value == "*" { continue } host := value // parse URL addresses if u, err := url.Parse(value); err == nil && len(u.Host) > 1 { host = removeWildcardsFromHostname(u.Host) } // strip port on both URL and non-URL addresses hostname, _, err := net.SplitHostPort(host) if err != nil { hostname = host } // for IP addresses if ip := net.ParseIP(hostname); ip != nil { if !isPrivateIP(ip) { return host } continue } // for hostnames, first check cache if isPublic, found := localCache.get(hostname); found { if isPublic { return host } continue } // otherwise, perform DNS lookup & cache result isPublic := isPublicHostname(hostname) localCache.set(hostname, isPublic) if isPublic { return host } } return "" } // isPrivateIP checks if an IP address is private (RFC 1918/4193). func isPrivateIP(ip net.IP) bool { return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsUnspecified() // 0.0.0.0 or :: } // isPublicHostname performs DNS lookup to determine if hostname resolves to public IPs. // Returns true if at least one resolved IP is public, false if all are private or lookup fails. func isPublicHostname(hostname string) bool { // avoid DNS lookup if localhost lower := strings.ToLower(hostname) if lower == "localhost" { return false } ctx, cancel := context.WithTimeoutCause(context.Background(), 2*time.Second, errors.Errorf("failed to resolve DNS for %s within 2s", hostname)) defer cancel() ips, err := net.DefaultResolver.LookupIPAddr(ctx, hostname) if err != nil { return false } for _, ip := range ips { if !isPrivateIP(ip.IP) { return true } } return false } // removeWildcardsFromHostname removes wildcard segments from a hostname string // by splitting on dots and filtering out asterisk-only segments. func removeWildcardsFromHostname(hostname string) string { sep := strings.Split(hostname, ".") clean := make([]string, 0, len(sep)) for _, s := range sep { if s != "*" && s != "" { clean = append(clean, s) } } return strings.Join(clean, ".") } ================================================ FILE: oryx/urlx/join.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package urlx import ( "net/url" "path" "github.com/ory/x/cmdx" ) // MustJoin joins the paths of two URLs. Fatals if first is not a DSN. func MustJoin(first string, parts ...string) string { u, err := url.Parse(first) if err != nil { cmdx.Fatalf("Unable to parse %s: %s", first, err) } return AppendPaths(u, parts...).String() } // AppendPaths appends the provided paths to the url. // Paths are intentionally *not* URL encoded. // The caller is responsible for url encoding, possibly selectively, the required path components with `url.PathEscape`. func AppendPaths(u *url.URL, paths ...string) (ep *url.URL) { ep = Copy(u) if len(paths) == 0 { return ep } ep.Path = path.Join(append([]string{ep.Path}, paths...)...) last := paths[len(paths)-1] if last != "" && last[len(last)-1] == '/' { ep.Path = ep.Path + "/" } return ep } // SetQuery appends the provided url values to the DSN's query string. func SetQuery(u *url.URL, query url.Values) (ep *url.URL) { ep = Copy(u) q := ep.Query() for k := range query { q.Set(k, query.Get(k)) } ep.RawQuery = q.Encode() return ep } ================================================ FILE: oryx/urlx/parse.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package urlx import ( "net/url" "regexp" "strings" "github.com/ory/x/logrusx" ) // winPathRegex is a regex for [DRIVE-LETTER]: var winPathRegex = regexp.MustCompile("^[A-Za-z]:.*") // Parse parses rawURL into a URL structure with special handling for file:// URLs // // File URLs with relative paths (file://../file, ../file) will be returned as a // url.URL object without the Scheme set to "file". This is because the file // scheme does not support relative paths. Make sure to check for // both "file" or "" (an empty string) in URL.Scheme if you are looking for // a file path. // // Use the companion function GetURLFilePath() to get a file path suitable // for the current operating system. func Parse(rawURL string) (*url.URL, error) { lcRawURL := strings.ToLower(rawURL) if strings.HasPrefix(lcRawURL, "file:///") { return url.Parse(rawURL) } // Normally the first part after file:// is a hostname, but since // this is often misused we interpret the URL like a normal path // by removing the "file://" from the beginning (if it exists) rawURL = trimPrefixIC(rawURL, "file://") if winPathRegex.MatchString(rawURL) { // Windows path return url.Parse("file:///" + rawURL) } if strings.HasPrefix(lcRawURL, "\\\\") { // Windows UNC path // We extract the hostname and create an appropriate file:// URL // based on the hostname and the path host, path := extractUNCPathParts(rawURL) // It is safe to replace the \ with / here because this is POSIX style path return url.Parse("file://" + host + strings.ReplaceAll(path, "\\", "/")) } parsed, err := url.Parse(rawURL) if err != nil { return nil, err } // Since go1.19: // // > The URL type now distinguishes between URLs with no authority and URLs with an empty authority. // > For example, http:///path has an empty authority (host), while http:/path has none. // // See https://golang.org/doc/go1.19#net/url for more details. parsed.OmitHost = false return parsed, nil } // ParseOrPanic parses a url or panics. func ParseOrPanic(in string) *url.URL { out, err := url.Parse(in) if err != nil { panic(err.Error()) } return out } // ParseOrFatal parses a url or fatals. func ParseOrFatal(l *logrusx.Logger, in string) *url.URL { out, err := url.Parse(in) if err != nil { l.WithError(err).Fatalf("Unable to parse url: %s", in) } return out } // ParseRequestURIOrPanic parses a request uri or panics. func ParseRequestURIOrPanic(in string) *url.URL { out, err := url.ParseRequestURI(in) if err != nil { panic(err.Error()) } return out } // ParseRequestURIOrFatal parses a request uri or fatals. func ParseRequestURIOrFatal(l *logrusx.Logger, in string) *url.URL { out, err := url.ParseRequestURI(in) if err != nil { l.WithError(err).Fatalf("Unable to parse url: %s", in) } return out } func extractUNCPathParts(uncPath string) (host, path string) { parts := strings.Split(strings.TrimPrefix(uncPath, "\\\\"), "\\") host = parts[0] if len(parts) > 0 { path = "\\" + strings.Join(parts[1:], "\\") } return host, path } // trimPrefixIC returns s without the provided leading prefix string using // case insensitive matching. // If s doesn't start with prefix, s is returned unchanged. func trimPrefixIC(s, prefix string) string { if strings.HasPrefix(strings.ToLower(s), prefix) { return s[len(prefix):] } return s } ================================================ FILE: oryx/urlx/path.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build !windows // +build !windows package urlx import ( "net/url" ) // GetURLFilePath returns the path of a URL that is compatible with the runtime os filesystem func GetURLFilePath(u *url.URL) string { if u == nil { return "" } return u.Path } ================================================ FILE: oryx/urlx/path_windows.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 //go:build windows // +build windows package urlx import ( "net/url" "path/filepath" "strings" ) // GetURLFilePath returns the path of a URL that is compatible with the runtime os filesystem func GetURLFilePath(u *url.URL) string { if u == nil { return "" } if !(u.Scheme == "file" || u.Scheme == "") { return u.Path } fPath := u.Path if u.Host != "" { // Make UNC Path fPath = "\\\\" + u.Host + filepath.FromSlash(fPath) return fPath } fPathTrimmed := strings.TrimLeft(fPath, "/") if winPathRegex.MatchString(fPathTrimmed) { // On Windows we should remove the initial path separator in case this // is a normal path (for example: "\c:\" -> "c:\"") fPath = fPathTrimmed } return filepath.FromSlash(fPath) } ================================================ FILE: oryx/uuidx/uuid.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package uuidx import "github.com/gofrs/uuid" // NewV4 returns a new randomly generated UUID or panics. func NewV4() uuid.UUID { return uuid.Must(uuid.NewV4()) } ================================================ FILE: oryx/watcherx/definitions.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package watcherx import ( "context" "fmt" "net/url" ) type ( errSchemeUnknown struct { scheme string } EventChannel chan Event Watcher interface { // DispatchNow fires the watcher and causes an event. // // WARNING: The returned channel must be read or no further events will // be propagated due to a deadlock. DispatchNow() (<-chan int, error) } dispatcher struct { trigger chan struct{} done chan int } ) var ( // ErrSchemeUnknown is just for checking with errors.Is() ErrSchemeUnknown = &errSchemeUnknown{} ErrWatcherNotRunning = fmt.Errorf("watcher is not running") ) func (e *errSchemeUnknown) Is(other error) bool { _, ok := other.(*errSchemeUnknown) return ok } func (e *errSchemeUnknown) Error() string { return fmt.Sprintf("unknown scheme '%s' to watch", e.scheme) } func newDispatcher() *dispatcher { return &dispatcher{ trigger: make(chan struct{}), done: make(chan int), } } func (d *dispatcher) DispatchNow() (<-chan int, error) { if d.trigger == nil { return nil, ErrWatcherNotRunning } d.trigger <- struct{}{} return d.done, nil } func Watch(ctx context.Context, u *url.URL, c EventChannel) (Watcher, error) { switch u.Scheme { // see urlx.Parse for why the empty string is also file case "file", "": return WatchFile(ctx, u.Path, c) } return nil, &errSchemeUnknown{u.Scheme} } ================================================ FILE: oryx/watcherx/directory.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package watcherx import ( "context" "os" "path/filepath" "github.com/fsnotify/fsnotify" "github.com/pkg/errors" ) func WatchDirectory(ctx context.Context, dir string, c EventChannel) (Watcher, error) { w, err := fsnotify.NewWatcher() if err != nil { return nil, errors.WithStack(err) } subDirs := make(map[string]struct{}) if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return errors.WithStack(err) } if info.IsDir() { if err := w.Add(path); err != nil { return errors.WithStack(err) } subDirs[path] = struct{}{} } return nil }); err != nil { return nil, err } dw := &directoryWatcher{ dispatcher: newDispatcher(), c: c, dir: dir, subDirs: subDirs, w: w, } go dw.streamDirectoryEvents(ctx) return dw, nil } type directoryWatcher struct { *dispatcher c EventChannel dir string subDirs map[string]struct{} w *fsnotify.Watcher } func (w *directoryWatcher) handleEvent(ctx context.Context, e fsnotify.Event) { if e.Has(fsnotify.Remove) { if _, ok := w.subDirs[e.Name]; ok { // we do not want any event on deletion of a directory delete(w.subDirs, e.Name) return } w.maybeSend(ctx, &RemoveEvent{ source: source(e.Name), }) return } else if e.Has(fsnotify.Write | fsnotify.Create) { if stats, err := os.Stat(e.Name); err != nil { w.maybeSend(ctx, &ErrorEvent{ error: errors.WithStack(err), source: source(e.Name), }) return } else if stats.IsDir() { if err := w.w.Add(e.Name); err != nil { w.maybeSend(ctx, &ErrorEvent{ error: errors.WithStack(err), source: source(e.Name), }) } w.subDirs[e.Name] = struct{}{} return } //#nosec G304 -- false positive data, err := os.ReadFile(e.Name) if err != nil { w.maybeSend(ctx, &ErrorEvent{ error: err, source: source(e.Name), }) } else { w.maybeSend(ctx, &ChangeEvent{ data: data, source: source(e.Name), }) } } } func (w *directoryWatcher) maybeSend(ctx context.Context, e Event) bool { select { case <-ctx.Done(): return false case w.c <- e: return true } } func (w *directoryWatcher) streamDirectoryEvents(ctx context.Context) { defer func() { close(w.done) close(w.c) _ = w.w.Close() }() for { select { case <-ctx.Done(): return case e := <-w.w.Events: w.handleEvent(ctx, e) case <-w.trigger: var eventsSent int if err := filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { //#nosec G304 -- false positive data, err := os.ReadFile(path) if err != nil { if !w.maybeSend(ctx, &ErrorEvent{ error: err, source: source(path), }) { return errors.WithStack(context.Canceled) } } else { if !w.maybeSend(ctx, &ChangeEvent{ data: data, source: source(path), }) { return errors.WithStack(context.Canceled) } } eventsSent++ } return nil }); err != nil { if !w.maybeSend(ctx, &ErrorEvent{ error: err, source: source(w.dir), }) { return } eventsSent++ } w.done <- eventsSent } } } ================================================ FILE: oryx/watcherx/event.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package watcherx import ( "bytes" "encoding/json" "fmt" "io" ) type ( Event interface { // MarshalJSON is required to work multiple times json.Marshaler Reader() io.Reader Source() string String() string setSource(string) } source string ErrorEvent struct { error source } ChangeEvent struct { data []byte source } RemoveEvent struct { source } serialEventType string serialEvent struct { Type serialEventType `json:"type"` Data []byte `json:"data"` Source source `json:"source"` } ) func NewErrorEvent(err error, source_ string) *ErrorEvent { return &ErrorEvent{ error: err, source: source(source_), } } const ( serialTypeChange serialEventType = "change" serialTypeRemove serialEventType = "remove" serialTypeError serialEventType = "error" ) func (e *ErrorEvent) Reader() io.Reader { return bytes.NewBufferString(e.Error()) } func (e *ErrorEvent) MarshalJSON() ([]byte, error) { return json.Marshal(serialEvent{ Type: serialTypeError, Data: []byte(e.Error()), Source: e.source, }) } func (e *ErrorEvent) String() string { return fmt.Sprintf("error: %+v; source: %s", e.error, e.source) } func (e source) Source() string { return string(e) } func (e *source) setSource(nsrc string) { *e = source(nsrc) } func (e *ChangeEvent) Reader() io.Reader { return bytes.NewBuffer(e.data) } func (e *ChangeEvent) MarshalJSON() ([]byte, error) { return json.Marshal(serialEvent{ Type: serialTypeChange, Data: e.data, Source: e.source, }) } func (e *ChangeEvent) String() string { return fmt.Sprintf("data: %s; source: %s", e.data, e.source) } func (e *RemoveEvent) Reader() io.Reader { return nil } func (e *RemoveEvent) MarshalJSON() ([]byte, error) { return json.Marshal(serialEvent{ Type: serialTypeRemove, Source: e.source, }) } func (e *RemoveEvent) String() string { return fmt.Sprintf("removed source: %s", e.source) } ================================================ FILE: oryx/watcherx/file.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package watcherx import ( "context" "os" "path/filepath" "github.com/fsnotify/fsnotify" "github.com/pkg/errors" ) // WatchFile spawns a background goroutine to watch file, reporting any changes // to c. Watching stops when ctx is canceled. func WatchFile(ctx context.Context, file string, c EventChannel) (Watcher, error) { watcher, err := fsnotify.NewWatcher() if err != nil { return nil, errors.WithStack(err) } dir := filepath.Dir(file) if err := watcher.Add(dir); err != nil { return nil, errors.WithStack(err) } resolvedFile, err := filepath.EvalSymlinks(file) if err != nil { if pathError := new(os.PathError); !errors.As(err, &pathError) { return nil, errors.WithStack(err) } // The file does not exist. The watcher should still watch the directory // to get notified about file creation. resolvedFile = "" } else if resolvedFile != file { // If `resolvedFile` != `file` then `file` is a symlink and we have to explicitly watch the referenced file. // This is because fsnotify follows symlinks and watches the destination file, not the symlink // itself. That is at least the case for unix systems. See: https://github.com/fsnotify/fsnotify/issues/199 if err := watcher.Add(file); err != nil { return nil, errors.WithStack(err) } } d := newDispatcher() go streamFileEvents(ctx, watcher, c, d.trigger, d.done, file, resolvedFile) return d, nil } // streamFileEvents watches for file changes and supports symlinks which requires several workarounds due to limitations of fsnotify. // Argument `resolvedFile` is the resolved symlink path of the file, or it is the watchedFile name itself. If `resolvedFile` is empty, then the watchedFile does not exist. func streamFileEvents(ctx context.Context, watcher *fsnotify.Watcher, c EventChannel, sendNow <-chan struct{}, sendNowDone chan<- int, watchedFile, resolvedFile string) { eventSource := source(watchedFile) removeDirectFileWatcher := func() { _ = watcher.Remove(watchedFile) } addDirectFileWatcher := func() { // check if the watchedFile (symlink) exists // if it does not the dir watcher will notify us when it gets created if _, err := os.Lstat(watchedFile); err == nil { if err := watcher.Add(watchedFile); err != nil { c <- &ErrorEvent{ error: errors.WithStack(err), source: eventSource, } } } } defer watcher.Close() for { select { case <-ctx.Done(): return case <-sendNow: if resolvedFile == "" { // The file does not exist. Announce this by sending a RemoveEvent. c <- &RemoveEvent{eventSource} } else { // The file does exist. Announce the current content by sending a ChangeEvent. // #nosec G304 -- false positive data, err := os.ReadFile(watchedFile) if err != nil { select { case c <- &ErrorEvent{ error: errors.WithStack(err), source: eventSource, }: case <-ctx.Done(): return } continue } select { case c <- &ChangeEvent{ data: data, source: eventSource, }: case <-ctx.Done(): return } } // in any of the above cases we send exactly one event select { case sendNowDone <- 1: case <-ctx.Done(): return } case e, ok := <-watcher.Events: if !ok { return } // filter events to only watch watchedFile // e.Name contains the name of the watchedFile (regardless whether it is a symlink), not the resolved file name if filepath.Clean(e.Name) == watchedFile { recentlyResolvedFile, err := filepath.EvalSymlinks(watchedFile) // when there is no error the file exists and any symlinks can be resolved if err != nil { // check if the watchedFile (or the file behind the symlink) was removed if _, ok := err.(*os.PathError); ok { select { case c <- &RemoveEvent{eventSource}: case <-ctx.Done(): return } removeDirectFileWatcher() continue } select { case c <- &ErrorEvent{ error: errors.WithStack(err), source: eventSource, }: case <-ctx.Done(): return } continue } // This catches following three cases: // 1. the watchedFile was written or created // 2. the watchedFile is a symlink and has changed (k8s config map updates) // 3. the watchedFile behind the symlink was written or created switch { case recentlyResolvedFile != resolvedFile: resolvedFile = recentlyResolvedFile // watch the symlink again to update the actually watched file removeDirectFileWatcher() addDirectFileWatcher() // we fallthrough because we also want to read the file in this case fallthrough case e.Has(fsnotify.Write | fsnotify.Create): // #nosec G304 -- false positive data, err := os.ReadFile(watchedFile) if err != nil { select { case c <- &ErrorEvent{ error: errors.WithStack(err), source: eventSource, }: case <-ctx.Done(): return } continue } select { case c <- &ChangeEvent{ data: data, source: eventSource, }: case <-ctx.Done(): return } } } } } } ================================================ FILE: oryx/watcherx/integrationtest/.dockerignore ================================================ event_logger.yml configmap.yml Makefile README.md eventlog_snapshot ================================================ FILE: oryx/watcherx/integrationtest/.gitignore ================================================ tmp_snapshot ================================================ FILE: oryx/watcherx/integrationtest/Dockerfile ================================================ FROM golang:1.26-alpine3.23 AS builder RUN apk -U --no-cache add build-base WORKDIR /go/src/github.com/ory/x ADD go.mod go.mod ADD go.sum go.sum RUN go mod download ADD . . RUN go build -o /usr/bin/eventlogger ./watcherx/integrationtest FROM alpine:3.22 COPY --from=builder /usr/bin/eventlogger /usr/bin/eventlogger ENTRYPOINT ["eventlogger"] CMD ["/etc/config/mock-config"] ================================================ FILE: oryx/watcherx/integrationtest/Makefile ================================================ SHELL=/bin/bash -euo pipefail CLUSTER_NAME=watcherx-integration-test SNAPSHOT_FILE=eventlog_snapshot define generate_snapshot sleep 5 make update sleep 1 kubectl logs eventlogger --context kind-${CLUSTER_NAME} >> $(1) make apply sleep 1 kubectl logs eventlogger --context kind-${CLUSTER_NAME} >> $(1) make update sleep 1 kubectl logs eventlogger --context kind-${CLUSTER_NAME} >> $(1) endef .PHONY: build build: docker build -f Dockerfile -t eventlogger:latest ../.. .PHONY: create create: kind create cluster --name ${CLUSTER_NAME} --wait 1m || true .PHONY: load load: kind load docker-image eventlogger:latest --name ${CLUSTER_NAME} .PHONY: apply apply: kubectl apply -f configmap.yml -f event_logger.yml --context kind-${CLUSTER_NAME} .PHONY: delete delete: kind delete cluster --name ${CLUSTER_NAME} .PHONY: setup setup: build create load apply .PHONY: snapshot snapshot: setup container-restart rm ${SNAPSHOT_FILE} ${call generate_snapshot,$(SNAPSHOT_FILE)} .PHONY: check check: setup container-restart rm tmp_snapshot || true ${call generate_snapshot,tmp_snapshot} diff tmp_snapshot ${SNAPSHOT_FILE} .PHONY: logs logs: kubectl logs eventlogger --context kind-${CLUSTER_NAME} .PHONY: container-restart container-restart: kubectl delete -f event_logger.yml --context kind-${CLUSTER_NAME} kubectl apply -f event_logger.yml --context kind-${CLUSTER_NAME} .PHONY: update update: cat configmap.yml | sed 's/somevalue/othervalue/' | kubectl apply -f - --context kind-${CLUSTER_NAME} cat event_logger.yml | sed 's/somevalue/othervalue/' | kubectl apply -f - --context kind-${CLUSTER_NAME} ================================================ FILE: oryx/watcherx/integrationtest/README.md ================================================ # Integration Test for watcherx/FileWatcher As kubernetes has a special way to change mounted config map values we want to make sure our file watcher is compatible with that. ## Perquisites The versions are the ones that definitely work. - kind (v0.8.1) - kubectl (v1.18.5) - docker (v19.03.12-ce) - make (v4.3) ## Structure The `main.go` just logs all events it gets. It is deployed to a kind kubernetes cluster together with a configmap that gets updated during the test. For details on the test steps have a look at the `Makefile`. ## Running To generate the log snapshot run `make snapshot`. That snapshot should be committed. To check if the FileWatcher works run `make check`. For debugging purposes single steps of the setup have descriptive make target names and can be run separately. It is safe to delete the cluster at any point or rerun snapshot generation. ================================================ FILE: oryx/watcherx/integrationtest/configmap.yml ================================================ kind: ConfigMap apiVersion: v1 metadata: name: changing-config data: mock-config: somevalue ================================================ FILE: oryx/watcherx/integrationtest/event_logger.yml ================================================ kind: Pod apiVersion: v1 metadata: name: eventlogger annotations: variant: somevalue spec: containers: - name: eventlogger image: eventlogger:latest imagePullPolicy: Never volumeMounts: - name: changing-config mountPath: /etc/config restartPolicy: Never volumes: - name: changing-config configMap: name: changing-config ================================================ FILE: oryx/watcherx/integrationtest/eventlog_snapshot ================================================ watching file /etc/config/mock-config got change event: Data: othervalue, Src: /etc/config/mock-config watching file /etc/config/mock-config got change event: Data: othervalue, Src: /etc/config/mock-config got change event: Data: somevalue, Src: /etc/config/mock-config watching file /etc/config/mock-config got change event: Data: othervalue, Src: /etc/config/mock-config got change event: Data: somevalue, Src: /etc/config/mock-config got change event: Data: othervalue, Src: /etc/config/mock-config ================================================ FILE: oryx/watcherx/integrationtest/main.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "io" "os" "github.com/ory/x/watcherx" ) func main() { if len(os.Args) != 2 { _, _ = fmt.Fprintf(os.Stderr, "expected 1 comand line argument but got %d\n", len(os.Args)-1) os.Exit(1) } c := make(chan watcherx.Event) _, err := watcherx.WatchFile(context.Background(), os.Args[1], c) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "could not initialize file watcher: %+v\n", err) os.Exit(1) } fmt.Printf("watching file %s\n", os.Args[1]) for { switch e := (<-c).(type) { case *watcherx.ChangeEvent: var data []byte data, err = io.ReadAll(e.Reader()) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "could not read data: %+v\n", err) os.Exit(1) } fmt.Printf("got change event:\nData: %s,\nSrc: %s\n", data, e.Source()) case *watcherx.RemoveEvent: fmt.Printf("got remove event:\nSrc: %s\n", e.Source()) case *watcherx.ErrorEvent: fmt.Printf("got error event:\nError: %s\n", e.Error()) default: fmt.Println("got unknown event") } } } ================================================ FILE: oryx/watcherx/test_helpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package watcherx import ( "os" "path" "path/filepath" "runtime" "testing" "time" "github.com/stretchr/testify/require" ) func KubernetesAtomicWrite(t *testing.T, dir, fileName, content string) { // atomic write according to https://github.com/kubernetes/kubernetes/blob/master/pkg/volume/util/atomic_writer.go const ( dataDirName = "..data" newDataDirName = "..data_tmp" ) // (2) dataDirPath := filepath.Join(dir, dataDirName) oldTsDir, err := os.Readlink(dataDirPath) if err != nil { require.True(t, os.IsNotExist(err), "%+v", err) // although Readlink() returns "" on err, don't be fragile by relying on it (since it's not specified in docs) // empty oldTsDir indicates that it didn't exist oldTsDir = "" } oldTsPath := filepath.Join(dir, oldTsDir) // (3) we are not interested in the case where a file gets deleted as we just operate on one file // (4) we assume the file needs an update // (5) tsDir, err := os.MkdirTemp(dir, time.Now().UTC().Format("..2006_01_02_15_04_05.")) require.NoError(t, err) tsDirName := filepath.Base(tsDir) // (6) require.NoError( t, os.WriteFile(path.Join(tsDir, fileName), []byte(content), 0600), ) // (7) _, err = os.Readlink(filepath.Join(dir, fileName)) if err != nil && os.IsNotExist(err) { // The link into the data directory for this path doesn't exist; create it require.NoError( t, os.Symlink(filepath.Join(dataDirName, fileName), filepath.Join(dir, fileName)), ) } // (8) newDataDirPath := filepath.Join(dir, newDataDirName) require.NoError( t, os.Symlink(tsDirName, newDataDirPath), ) // (9) if runtime.GOOS == "windows" { require.NoError(t, os.Remove(dataDirPath)) require.NoError(t, os.Symlink(tsDirName, dataDirPath)) require.NoError(t, os.Remove(newDataDirPath)) } else { require.NoError(t, os.Rename(newDataDirPath, dataDirPath)) } // (10) in our case there is nothing to remove // (11) if len(oldTsDir) > 0 { require.NoError(t, os.RemoveAll(oldTsPath)) } } ================================================ FILE: otp/otp.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package otp import ( "github.com/pkg/errors" "github.com/ory/x/randx" ) // Entropy sets the number of characters used for generating verification codes. This must not be // changed to another value as we only have 32 characters available in the SQL schema. const Entropy = 32 func New() (string, error) { code, err := randx.RuneSequence(Entropy, randx.AlphaNum) if err != nil { return "", errors.WithStack(err) } return string(code), nil } ================================================ FILE: package.json ================================================ { "private": true, "scripts": { "openapi-generator-cli": "openapi-generator-cli", "wait-on": "wait-on" }, "prettier": "ory-prettier-styles", "dependencies": { "@openapitools/openapi-generator-cli": "2.30.2", "yamljs": "0.3.0" }, "devDependencies": { "license-checker": "25.0.1", "ory-prettier-styles": "1.3.0", "prettier": "3.8.1", "prettier-plugin-packagejson": "3.0.2", "process": "0.11.10", "wait-on": "9.0.1" }, "overrides": { "axios": ">=1.13.5", "glob": ">=11.1.0" } } ================================================ FILE: persistence/reference.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package persistence import ( "context" "time" "github.com/ory/kratos/x" "github.com/ory/kratos/selfservice/sessiontokenexchange" "github.com/ory/x/networkx" "github.com/gofrs/uuid" "github.com/ory/pop/v6" "github.com/ory/x/popx" "github.com/ory/kratos/continuity" "github.com/ory/kratos/courier" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/errorx" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/kratos/session" ) type Provider interface { Persister() Persister SetPersister(Persister) } type Persister interface { continuity.Persister identity.PrivilegedPool registration.FlowPersister login.FlowPersister settings.FlowPersister courier.Persister session.Persister sessiontokenexchange.Persister errorx.Persister verification.FlowPersister recovery.FlowPersister link.RecoveryTokenPersister link.VerificationTokenPersister code.RecoveryCodePersister code.VerificationCodePersister code.RegistrationCodePersister code.LoginCodePersister CleanupDatabase(context.Context, time.Duration, time.Duration, int) error Close(context.Context) error Ping(context.Context) error MigrationStatus(context.Context) (popx.MigrationStatuses, error) MigrateDown(ctx context.Context, steps int) error MigrateUp(context.Context) error MigrationBox() *popx.MigrationBox GetConnection(context.Context) *pop.Connection Connection(ctx context.Context) *pop.Connection x.TransactionalPersister Networker } type Networker interface { WithNetworkID(nid uuid.UUID) Persister NetworkID(ctx context.Context) uuid.UUID DetermineNetwork(ctx context.Context) (*networkx.Network, error) } ================================================ FILE: persistence/sql/.soda.yml ================================================ development: url: sqlite://a/b sqlite: url: sqlite://a/b postgres: url: postgres://a/b mysql: url: mysql://tcp(a)/b?parseTime=true&multiStatements=true cockroach: url: crdb://a/b ================================================ FILE: persistence/sql/README.md ================================================ # SQL Migrations Migrations consist of one `up` and one `down` file. To create these SQL migrations, copy the last migration in `./persistence/sql/migrations/sql` and change the timestamp to the current timestamp and the name to the desired name. If some logic is different for one of the database systems, add the id after the name to the file name. The content of that file will override the content of the "general" file for that particular DB system. Example: `20220802103909000000_courier_send_count.up.sql` and `20220802103909000000_courier_send_count.down.sql` With for example cockroach specific behavior: `20220802103909000000_courier_send_count.cockroach.up.sql` and `20220802103909000000_courier_send_count.cockroach.down.sql` Replace `cockroach` with `mysql`, `postgres` or `sqlite` if applicable. ## Old Way To create SQL migrations, target each database individually and run ``` $ dialect=mysql # or postgres|cockroach|sqlite $ name= $ ory dev pop migration create -d=$dialect ./persistence/sql/migrations/templates $name $ soda generate sql -e mysql -c ./persistence/sql/.soda.yml -p ./persistence/sql/migrations/templates [name] $ soda generate sql -e sqlite -c ./persistence/sql/.soda.yml -p ./persistence/sql/migrations/templates [name] $ soda generate sql -e postgres -c ./persistence/sql/.soda.yml -p ./persistence/sql/migrations/templates [name] $ soda generate sql -e cockroach -c ./persistence/sql/.soda.yml -p ./persistence/sql/migrations/templates [name] ``` and remove the `sqlite` part from the newly generated file to create a SQL migrations that works with all aforementioned databases. ## Rendering Migrations Because migrations needs to be backwards compatible, and because fizz migrations might change, we render fizz migrations to raw SQL statements using `make migrations-render`. The concrete migrations being applied can be found in [`./migrations/sql`](./migrations/sql). ================================================ FILE: persistence/sql/batch/.snapshots/Test_buildInsertQueryArgs-case=Identities.json ================================================ { "TableName": "\"identities\"", "ColumnsDecl": "\"available_aal\", \"created_at\", \"external_id\", \"id\", \"metadata_admin\", \"metadata_public\", \"nid\", \"organization_id\", \"schema_id\", \"state\", \"state_changed_at\", \"traits\", \"updated_at\"", "Columns": [ "available_aal", "created_at", "external_id", "id", "metadata_admin", "metadata_public", "nid", "organization_id", "schema_id", "state", "state_changed_at", "traits", "updated_at" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: persistence/sql/batch/.snapshots/Test_buildInsertQueryArgs-case=RecoveryAddress#01.json ================================================ { "TableName": "\"identity_recovery_addresses\"", "ColumnsDecl": "\"created_at\", \"id\", \"identity_id\", \"nid\", \"updated_at\", \"value\", \"via\"", "Columns": [ "created_at", "id", "identity_id", "nid", "updated_at", "value", "via" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: persistence/sql/batch/.snapshots/Test_buildInsertQueryArgs-case=RecoveryAddress.json ================================================ { "TableName": "\"identity_recovery_addresses\"", "ColumnsDecl": "\"created_at\", \"id\", \"identity_id\", \"nid\", \"updated_at\", \"value\", \"via\"", "Columns": [ "created_at", "id", "identity_id", "nid", "updated_at", "value", "via" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: persistence/sql/batch/.snapshots/Test_buildInsertQueryArgs-case=cockroach.json ================================================ { "TableName": "\"test_models\"", "ColumnsDecl": "\"created_at\", \"id\", \"int\", \"nid\", \"null_time_ptr\", \"string\", \"traits\", \"updated_at\"", "Columns": [ "created_at", "id", "int", "nid", "null_time_ptr", "string", "traits", "updated_at" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?, ?),\n(?, gen_random_uuid(), ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: persistence/sql/batch/.snapshots/Test_buildInsertQueryArgs-case=testModel.json ================================================ { "TableName": "\"test_models\"", "ColumnsDecl": "\"created_at\", \"id\", \"int\", \"nid\", \"null_time_ptr\", \"string\", \"traits\", \"updated_at\"", "Columns": [ "created_at", "id", "int", "nid", "null_time_ptr", "string", "traits", "updated_at" ], "Placeholders": "(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?),\n(?, ?, ?, ?, ?, ?, ?, ?)" } ================================================ FILE: persistence/sql/batch/.snapshots/Test_buildInsertQueryValues-case=testModel-case=cockroach.json ================================================ [ "2023-01-01T00:00:00Z", "2023-01-01T00:00:00Z", "string", 42, null, { "foo": "bar" } ] ================================================ FILE: persistence/sql/batch/create.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package batch import ( "context" "database/sql" "fmt" "reflect" "slices" "sort" "strings" "time" "github.com/gofrs/uuid" "github.com/jmoiron/sqlx/reflectx" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/ory/pop/v6" "github.com/ory/x/dbal" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlxx" ) type ( insertQueryArgs struct { TableName string ColumnsDecl string Columns []string Placeholders string } quoter interface { Quote(key string) string } TracerConnection struct { Tracer *otelx.Tracer Connection *pop.Connection } // PartialConflictError represents a partial conflict during [Create]. It always // wraps a [sqlcon.ErrUniqueViolation], so that the caller can either abort the // whole transaction, or handle the partial success. PartialConflictError[T any] struct { Failed []*T } ) func (p *PartialConflictError[T]) Error() string { return fmt.Sprintf("partial conflict error: %d models failed to insert", len(p.Failed)) } func (p *PartialConflictError[T]) ErrOrNil() error { if len(p.Failed) == 0 { return nil } return p } func (p *PartialConflictError[T]) Unwrap() error { if len(p.Failed) == 0 { return nil } return sqlcon.ErrUniqueViolation } func buildInsertQueryArgs[T any](ctx context.Context, models []*T, opts *createOpts) insertQueryArgs { var ( v T model = pop.NewModel(v, ctx) columns []string quotedColumns []string placeholders []string placeholderRow []string ) for _, col := range model.Columns().Cols { columns = append(columns, col.Name) placeholderRow = append(placeholderRow, "?") } // We sort for the sole reason that the test snapshots are deterministic. sort.Strings(columns) for _, col := range columns { quotedColumns = append(quotedColumns, opts.quoter.Quote(col)) } // We generate a list (for every row one) of VALUE statements here that // will be substituted by their column values later: // // (?, ?, ?, ?), // (?, ?, ?, ?), // (?, ?, ?, ?) for _, m := range models { m := reflect.ValueOf(m) pl := make([]string, len(placeholderRow)) copy(pl, placeholderRow) // There is a special case - when using CockroachDB we want to generate // UUIDs using "gen_random_uuid()" which ends up in a VALUE statement of: // // (gen_random_uuid(), ?, ?, ?), for k := range placeholderRow { if columns[k] != "id" { continue } field := opts.mapper.FieldByName(m, columns[k]) val, ok := field.Interface().(uuid.UUID) if !ok { continue } if val == uuid.Nil && opts.dialect == dbal.DriverCockroachDB && !opts.partialInserts { pl[k] = "gen_random_uuid()" break } } placeholders = append(placeholders, fmt.Sprintf("(%s)", strings.Join(pl, ", "))) } return insertQueryArgs{ TableName: opts.quoter.Quote(model.TableName()), ColumnsDecl: strings.Join(quotedColumns, ", "), Columns: columns, Placeholders: strings.Join(placeholders, ",\n"), } } func buildInsertQueryValues[T any](columns []string, models []*T, opts *createOpts) (values []any, err error) { for _, m := range models { m := reflect.ValueOf(m) now := opts.now() // Append model fields to args for _, c := range columns { field := opts.mapper.FieldByName(m, c) switch c { case "created_at": if pop.IsZeroOfUnderlyingType(field.Interface()) { field.Set(reflect.ValueOf(now)) } case "updated_at": field.Set(reflect.ValueOf(now)) case "id": if field.Interface().(uuid.UUID) != uuid.Nil { break // breaks switch, not for } else if opts.dialect == dbal.DriverCockroachDB && !opts.partialInserts { // This is a special case: // 1. We're using cockroach // 2. It's the primary key field ("ID") // 3. A UUID was not yet set. // // If all these conditions meet, the VALUE statement will look as such: // // (gen_random_uuid(), ?, ?, ?, ...) // // For that reason, we do not add the ID value to the list of arguments, // because one of the arguments is using a built-in and thus doesn't need a value. continue // break switch, not for } id, err := uuid.NewV4() if err != nil { return nil, err } field.Set(reflect.ValueOf(id)) } values = append(values, field.Interface()) // Special-handling for *sqlxx.NullTime: mapper.FieldByName sets this to a zero time.Time, // but we want a nil pointer instead. if i, ok := field.Interface().(*sqlxx.NullTime); ok { if time.Time(*i).IsZero() { field.Set(reflect.Zero(field.Type())) } } } } return values, nil } type createOpts struct { partialInserts bool dialect string mapper *reflectx.Mapper quoter quoter now func() time.Time } type CreateOpts func(*createOpts) // WithPartialInserts allows to insert only the models that do not conflict with // an existing record. WithPartialInserts will also generate the IDs for the // models before inserting them, so that the successful inserts can be correlated // with the input models. // // In particular, WithPartialInserts does not work with MySQL, because it does // not support the "RETURNING" clause. // // WithPartialInserts does not work with CockroachDB and gen_random_uuid(), // because then the successful inserts cannot be correlated with the input // models. Note: gen_random_uuid() will skip the UNIQUE constraint check, which // needs to hit all regions in a distributed setup. Therefore, WithPartialInserts // should not be used to insert models for only a single identity. var WithPartialInserts CreateOpts = func(o *createOpts) { o.partialInserts = true } func newCreateOpts(conn *pop.Connection, opts ...CreateOpts) *createOpts { o := new(createOpts) o.dialect = conn.Dialect.Name() o.mapper = conn.TX.Mapper o.quoter = conn.Dialect.(quoter) o.now = func() time.Time { return time.Now().UTC().Truncate(time.Microsecond) } for _, f := range opts { f(o) } return o } // Create batch-inserts the given models into the database using a single INSERT // statement. By default, the models are either all created or none. If // [WithPartialInserts] is passed as an option, partial inserts are supported, // and the models that could not be inserted are returned in an // [PartialConflictError]. func Create[T any](ctx context.Context, p *TracerConnection, models []*T, opts ...CreateOpts) (err error) { ctx, span := p.Tracer.Tracer().Start(ctx, "persistence.sql.batch.Create", trace.WithAttributes(attribute.Int("count", len(models)))) defer otelx.End(span, &err) if len(models) == 0 { return nil } var v T model := pop.NewModel(v, ctx) conn := p.Connection options := newCreateOpts(conn, opts...) queryArgs := buildInsertQueryArgs(ctx, models, options) values, err := buildInsertQueryValues(queryArgs.Columns, models, options) if err != nil { return err } var returningClause string if conn.Dialect.Name() != dbal.DriverMySQL { // PostgreSQL, CockroachDB, SQLite support RETURNING. if options.partialInserts { returningClause = fmt.Sprintf("ON CONFLICT DO NOTHING RETURNING %s", model.IDField()) } else { returningClause = fmt.Sprintf("RETURNING %s", model.IDField()) } } query := conn.Dialect.TranslateSQL(fmt.Sprintf( "INSERT INTO %s (%s) VALUES\n%s\n%s", queryArgs.TableName, queryArgs.ColumnsDecl, queryArgs.Placeholders, returningClause, )) rows, err := conn.TX.QueryContext(ctx, query, values...) if err != nil { return sqlcon.HandleError(err) } defer func() { _ = rows.Close() }() // MySQL, which does not support RETURNING, also does not have ON CONFLICT DO // NOTHING, meaning that MySQL will always fail the whole transaction on a single // record conflict. if conn.Dialect.Name() == dbal.DriverMySQL { return nil } if options.partialInserts { return handlePartialInserts(queryArgs, values, models, rows) } else { return handleFullInserts(models, rows) } } func handleFullInserts[T any](models []*T, rows *sql.Rows) error { // Hydrate the models from the RETURNING clause. for i := 0; rows.Next(); i++ { var id uuid.UUID if err := rows.Scan(&id); err != nil { return errors.WithStack(err) } if err := setModelID(id, models[i]); err != nil { return err } } if err := rows.Err(); err != nil { return sqlcon.HandleError(err) } return nil } func handlePartialInserts[T any](queryArgs insertQueryArgs, values []any, models []*T, rows *sql.Rows) error { // Hydrate the models from the RETURNING clause. idsInDB := make(map[uuid.UUID]struct{}) for rows.Next() { var id uuid.UUID if err := rows.Scan(&id); err != nil { return errors.WithStack(err) } idsInDB[id] = struct{}{} } if err := rows.Err(); err != nil { return sqlcon.HandleError(err) } idIdx := slices.Index(queryArgs.Columns, "id") if idIdx == -1 { return errors.New("id column not found") } var idValues []uuid.UUID for i := idIdx; i < len(values); i += len(queryArgs.Columns) { idValues = append(idValues, values[i].(uuid.UUID)) } var partialConflictError PartialConflictError[T] for i, id := range idValues { if _, ok := idsInDB[id]; !ok { partialConflictError.Failed = append(partialConflictError.Failed, models[i]) } else { if err := setModelID(id, models[i]); err != nil { return err } } } return partialConflictError.ErrOrNil() } // setModelID sets the id field of the model to the id. func setModelID(id uuid.UUID, model any) error { el := reflect.ValueOf(model).Elem() idField := el.FieldByName("ID") if !idField.IsValid() { return errors.New("model does not have a field named id") } idField.Set(reflect.ValueOf(id)) return nil } ================================================ FILE: persistence/sql/batch/create_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package batch import ( "context" "fmt" "testing" "time" "github.com/gofrs/uuid" "github.com/jmoiron/sqlx/reflectx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/identity" "github.com/ory/x/dbal" "github.com/ory/x/snapshotx" "github.com/ory/x/sqlxx" ) type ( testModel struct { ID uuid.UUID `db:"id"` NID uuid.UUID `db:"nid"` String string `db:"string"` Int int `db:"int"` Traits identity.Traits `db:"traits"` NullTimePtr *sqlxx.NullTime `db:"null_time_ptr"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } testQuoter struct{} ) func (i testModel) TableName(ctx context.Context) string { return "test_models" } func (tq testQuoter) Quote(s string) string { return fmt.Sprintf("%q", s) } func makeModels[T any]() []*T { models := make([]*T, 10) for k := range models { models[k] = new(T) } return models } func Test_buildInsertQueryArgs(t *testing.T) { ctx := context.Background() t.Run("case=testModel", func(t *testing.T) { models := makeModels[testModel]() opts := &createOpts{ dialect: "other", quoter: testQuoter{}, mapper: reflectx.NewMapper("db")} args := buildInsertQueryArgs(ctx, models, opts) snapshotx.SnapshotT(t, args) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES\n%s", args.TableName, args.ColumnsDecl, args.Placeholders) assert.Equal(t, `INSERT INTO "test_models" ("created_at", "id", "int", "nid", "null_time_ptr", "string", "traits", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?)`, query) }) t.Run("case=Identities", func(t *testing.T) { models := makeModels[identity.Identity]() opts := &createOpts{ dialect: "other", quoter: testQuoter{}, mapper: reflectx.NewMapper("db")} args := buildInsertQueryArgs(ctx, models, opts) snapshotx.SnapshotT(t, args) }) t.Run("case=RecoveryAddress", func(t *testing.T) { models := makeModels[identity.RecoveryAddress]() opts := &createOpts{ dialect: "other", quoter: testQuoter{}, mapper: reflectx.NewMapper("db")} args := buildInsertQueryArgs(ctx, models, opts) snapshotx.SnapshotT(t, args) }) t.Run("case=RecoveryAddress", func(t *testing.T) { models := makeModels[identity.RecoveryAddress]() opts := &createOpts{ dialect: "other", quoter: testQuoter{}, mapper: reflectx.NewMapper("db")} args := buildInsertQueryArgs(ctx, models, opts) snapshotx.SnapshotT(t, args) }) t.Run("case=cockroach", func(t *testing.T) { models := makeModels[testModel]() for k := range models { if k%3 == 0 { models[k].ID = uuid.FromStringOrNil(fmt.Sprintf("ae0125a9-2786-4ada-82d2-d169cf75047%d", k)) } } opts := &createOpts{ dialect: dbal.DriverCockroachDB, quoter: testQuoter{}, mapper: reflectx.NewMapper("db")} args := buildInsertQueryArgs(ctx, models, opts) snapshotx.SnapshotT(t, args) }) } func Test_buildInsertQueryValues(t *testing.T) { t.Run("case=testModel", func(t *testing.T) { model := &testModel{ String: "string", Int: 42, Traits: []byte(`{"foo": "bar"}`), } frozenTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) opts := &createOpts{ mapper: reflectx.NewMapper("db"), quoter: testQuoter{}, now: func() time.Time { return frozenTime }, } t.Run("case=cockroach", func(t *testing.T) { opts.dialect = dbal.DriverCockroachDB values, err := buildInsertQueryValues( []string{"created_at", "updated_at", "id", "string", "int", "null_time_ptr", "traits"}, []*testModel{model}, opts, ) require.NoError(t, err) snapshotx.SnapshotT(t, values) }) t.Run("case=others", func(t *testing.T) { opts.dialect = "other" values, err := buildInsertQueryValues( []string{"created_at", "updated_at", "id", "string", "int", "null_time_ptr", "traits"}, []*testModel{model}, opts, ) require.NoError(t, err) assert.Equal(t, frozenTime, model.CreatedAt) assert.Equal(t, model.CreatedAt, values[0]) assert.Equal(t, frozenTime, model.UpdatedAt) assert.Equal(t, model.UpdatedAt, values[1]) assert.NotZero(t, model.ID) assert.Equal(t, model.ID, values[2]) assert.Equal(t, model.String, values[3]) assert.Equal(t, model.Int, values[4]) assert.Nil(t, model.NullTimePtr) }) }) } ================================================ FILE: persistence/sql/batch/test_persister.go ================================================ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 package batch import ( "context" "errors" "testing" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence" "github.com/ory/pop/v6" "github.com/ory/x/dbal" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) func TestPersister(ctx context.Context, tracer *otelx.Tracer, p persistence.Persister) func(t *testing.T) { return func(t *testing.T) { t.Run("method=batch.Create", func(t *testing.T) { ident1 := identity.NewIdentity("") ident1.NID = p.NetworkID(ctx) ident2 := identity.NewIdentity("") ident2.NID = p.NetworkID(ctx) // Create two identities _ = p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { conn := &TracerConnection{ Tracer: tracer, Connection: tx, } err := Create(ctx, conn, []*identity.Identity{ident1, ident2}) require.NoError(t, err) return nil }) require.NotEqual(t, uuid.Nil, ident1.ID) require.NotEqual(t, uuid.Nil, ident2.ID) // Create conflicting verifiable addresses addresses := []*identity.VerifiableAddress{{ Value: "foo.1@bar.de", IdentityID: ident1.ID, NID: ident1.NID, }, { Value: "foo.2@bar.de", IdentityID: ident1.ID, NID: ident1.NID, }, { Value: "conflict@bar.de", IdentityID: ident1.ID, NID: ident1.NID, }, { Value: "foo.3@bar.de", IdentityID: ident1.ID, NID: ident1.NID, }, { Value: "conflict@bar.de", IdentityID: ident1.ID, NID: ident1.NID, }, { Value: "foo.4@bar.de", IdentityID: ident1.ID, NID: ident1.NID, }} t.Run("case=fails all without partial inserts", func(t *testing.T) { _ = p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { conn := &TracerConnection{ Tracer: tracer, Connection: tx, } err := Create(ctx, conn, addresses) assert.ErrorIs(t, err, sqlcon.ErrUniqueViolation) if partial := new(PartialConflictError[identity.VerifiableAddress]); errors.As(err, &partial) { require.NoError(t, partial, "expected no partial error") } return err }) }) t.Run("case=return partial error with partial inserts", func(t *testing.T) { _ = p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { conn := &TracerConnection{ Tracer: tracer, Connection: tx, } err := Create(ctx, conn, addresses, WithPartialInserts) assert.ErrorIs(t, err, sqlcon.ErrUniqueViolation) if conn.Connection.Dialect.Name() != dbal.DriverMySQL { // MySQL does not support partial errors. partialErr := new(PartialConflictError[identity.VerifiableAddress]) require.ErrorAs(t, err, &partialErr) assert.Len(t, partialErr.Failed, 1) } return nil }) }) }) } } ================================================ FILE: persistence/sql/devices/persister_devices.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package devices import ( "context" "github.com/gofrs/uuid" "github.com/ory/kratos/session" "github.com/ory/pop/v6" "github.com/ory/x/contextx" "github.com/ory/x/popx" "github.com/ory/x/sqlcon" ) var _ session.DevicePersister = (*DevicePersister)(nil) type DevicePersister struct { ctxer contextx.Provider c *pop.Connection nid uuid.UUID } func NewPersister(r contextx.Provider, c *pop.Connection) *DevicePersister { return &DevicePersister{ ctxer: r, c: c, } } func (p *DevicePersister) NetworkID(ctx context.Context) uuid.UUID { return p.ctxer.Contextualizer().Network(ctx, p.nid) } func (p DevicePersister) WithNetworkID(nid uuid.UUID) session.DevicePersister { p.nid = nid return &p } func (p *DevicePersister) CreateDevice(ctx context.Context, d *session.Device) error { d.NID = p.NetworkID(ctx) return sqlcon.HandleError(popx.GetConnection(ctx, p.c.WithContext(ctx)).Create(d)) } ================================================ FILE: persistence/sql/identity/persister_identity.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package identity import ( "cmp" "context" "database/sql" "encoding/base64" "fmt" "maps" "sort" "strings" "sync" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" "github.com/ory/herodot" "github.com/ory/jsonschema/v3" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/otp" "github.com/ory/kratos/persistence/sql/batch" "github.com/ory/kratos/persistence/sql/update" "github.com/ory/kratos/schema" "github.com/ory/kratos/x" "github.com/ory/kratos/x/events" "github.com/ory/pop/v6" "github.com/ory/x/contextx" "github.com/ory/x/crdbx" "github.com/ory/x/errorsx" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/popx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlxx" ) var ( _ identity.Pool = new(IdentityPersister) _ identity.PrivilegedPool = new(IdentityPersister) ) type dependencies interface { schema.IdentitySchemaProvider identity.ValidationProvider logrusx.Provider config.Provider contextx.Provider otelx.Provider } type IdentityPersister struct { r dependencies c *pop.Connection nid uuid.UUID } func NewPersister(r dependencies, c *pop.Connection) *IdentityPersister { return &IdentityPersister{ c: c, r: r, } } func (p *IdentityPersister) NetworkID(ctx context.Context) uuid.UUID { return p.r.Contextualizer().Network(ctx, p.nid) } func (p IdentityPersister) WithNetworkID(nid uuid.UUID) identity.PrivilegedPool { p.nid = nid return &p } func WithTransaction(ctx context.Context, tx *pop.Connection) context.Context { return popx.WithTransaction(ctx, tx) } func (p *IdentityPersister) Transaction(ctx context.Context, callback func(ctx context.Context, connection *pop.Connection) error) error { return popx.Transaction(ctx, p.c.WithContext(ctx), callback) } func (p *IdentityPersister) GetConnection(ctx context.Context) *pop.Connection { return popx.GetConnection(ctx, p.c.WithContext(ctx)) } func (p *IdentityPersister) ListVerifiableAddresses(ctx context.Context, page, itemsPerPage int) (a []identity.VerifiableAddress, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListVerifiableAddresses", trace.WithAttributes( attribute.Int("per_page", itemsPerPage), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) if err := p.GetConnection(ctx).Where("nid = ?", p.NetworkID(ctx)).Order("id DESC").Paginate(page, x.MaxItemsPerPage(itemsPerPage)).All(&a); err != nil { return nil, sqlcon.HandleError(err) } return a, nil } func (p *IdentityPersister) ListRecoveryAddresses(ctx context.Context, page, itemsPerPage int) (a []identity.RecoveryAddress, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListRecoveryAddresses", trace.WithAttributes( attribute.Int("per_page", itemsPerPage), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) if err := p.GetConnection(ctx).Where("nid = ?", p.NetworkID(ctx)).Order("id DESC").Paginate(page, x.MaxItemsPerPage(itemsPerPage)).All(&a); err != nil { return nil, sqlcon.HandleError(err) } return a, nil } func stringToLowerTrim(match string) string { return strings.ToLower(strings.TrimSpace(match)) } func NormalizeIdentifier(ct identity.CredentialsType, match string) string { switch ct { case identity.CredentialsTypeLookup: // lookup credentials are case-sensitive return match case identity.CredentialsTypeTOTP: // totp credentials are case-sensitive return match case identity.CredentialsTypeOIDC, identity.CredentialsTypeSAML: // OIDC credentials are case-sensitive return match case identity.CredentialsTypePassword, identity.CredentialsTypeCodeAuth, identity.CredentialsTypeWebAuthn: return stringToLowerTrim(match) default: return match } } func (p *IdentityPersister) FindIdentityByCredentialIdentifier(ctx context.Context, identifier string, caseSensitive bool, expand identity.Expandables) (_ *identity.Identity, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindIdentityByCredentialIdentifier", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) var find struct { IdentityID uuid.UUID `db:"identity_id"` } if !caseSensitive { identifier = NormalizeIdentifier(identity.CredentialsTypePassword, identifier) } nid := p.NetworkID(ctx) if err := p.GetConnection(ctx).RawQuery(` SELECT ic.identity_id FROM identity_credentials ic INNER JOIN identity_credential_identifiers ici ON ic.id = ici.identity_credential_id WHERE ici.identifier = ? AND ic.nid = ? AND ici.nid = ? LIMIT 1`, identifier, nid, nid, ).First(&find); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, sqlcon.HandleError(err) } return nil, sqlcon.HandleError(err) } return p.GetIdentity(ctx, find.IdentityID, expand) } func (p *IdentityPersister) FindByCredentialsIdentifier(ctx context.Context, ct identity.CredentialsType, match string) (_ *identity.Identity, _ *identity.Credentials, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindByCredentialsIdentifier", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) nid := p.NetworkID(ctx) var find struct { IdentityID uuid.UUID `db:"identity_id"` } // Force case-insensitivity and trimming for identifiers match = NormalizeIdentifier(ct, match) // Fast path: if the credential type ID is a known constant, skip the join with // identity_credential_types. In the rare case the constant is stale (old // self-hosted deployments where IDs were dynamic), the query returns no rows and // we fall through to the join-based query below. var found bool if typeID, ok := identity.ConstantCredentialsTypeToId[ct]; ok { if err := p.GetConnection(ctx).RawQuery(` SELECT ic.identity_id FROM identity_credentials ic INNER JOIN identity_credential_identifiers ici ON ic.id = ici.identity_credential_id AND ici.identity_credential_type_id = ? WHERE ici.identifier = ? AND ic.nid = ? AND ici.nid = ? LIMIT 1`, // pop doesn't understand how to add a limit clause to this query typeID, match, nid, nid, ).First(&find); err == nil { found = true } } if !found { if err := p.GetConnection(ctx).RawQuery(` SELECT ic.identity_id FROM identity_credentials ic INNER JOIN identity_credential_types ict ON ic.identity_credential_type_id = ict.id INNER JOIN identity_credential_identifiers ici ON ic.id = ici.identity_credential_id AND ici.identity_credential_type_id = ict.id WHERE ici.identifier = ? AND ic.nid = ? AND ici.nid = ? AND ict.name = ? LIMIT 1`, // pop doesn't understand how to add a limit clause to this query match, nid, nid, ct, ).First(&find); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil, sqlcon.HandleError(err) // herodot.ErrNotFound.WithTrace(err).WithReasonf(`No identity matching credentials identifier "%s" could be found.`, match) } return nil, nil, sqlcon.HandleError(err) } } span.SetAttributes(attribute.String("identity.id", find.IdentityID.String())) i, err := p.GetIdentityConfidential(ctx, find.IdentityID) if err != nil { return nil, nil, err } creds, ok := i.GetCredentials(ct) if !ok { return nil, nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The SQL adapter failed to return the appropriate credentials_type \"%s\". This is a bug in the code.", ct)) } return i, creds, nil } func (p *IdentityPersister) FindIdentityByWebauthnUserHandle(ctx context.Context, userHandle []byte) (_ *identity.Identity, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindIdentityByWebauthnUserHandle") defer otelx.End(span, &err) var id identity.Identity var jsonPath string con := p.GetConnection(ctx) switch con.Dialect.Name() { case "sqlite", "mysql": jsonPath = "$.user_handle" default: jsonPath = "user_handle" } columns := popx.DBColumns[identity.Identity](&popx.AliasQuoter{Alias: "identities", Quoter: con.Dialect}) if err := con.RawQuery(fmt.Sprintf(` SELECT %s FROM identities INNER JOIN identity_credentials ON identities.id = identity_credentials.identity_id AND identities.nid = identity_credentials.nid AND identity_credentials.identity_credential_type_id = ( SELECT id FROM identity_credential_types WHERE name = ? ) WHERE identity_credentials.config ->> '%s' = ? AND identity_credentials.config ->> '%s' IS NOT NULL AND identities.nid = ? LIMIT 1`, columns, jsonPath, jsonPath), identity.CredentialsTypeWebAuthn, base64.StdEncoding.EncodeToString(userHandle), p.NetworkID(ctx), ).First(&id); err != nil { return nil, sqlcon.HandleError(err) } return &id, nil } func (p *IdentityPersister) createIdentityCredentials(ctx context.Context, identities ...*identity.Identity) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.createIdentityCredentials", trace.WithAttributes( attribute.Int("num_identities", len(identities)), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) var ( nid = p.NetworkID(ctx) traceConn = &batch.TracerConnection{Tracer: p.r.Tracer(ctx), Connection: p.GetConnection(ctx)} credentials []*identity.Credentials identifiers []*identity.CredentialIdentifier ) var opts []batch.CreateOpts if len(identities) > 1 { opts = append(opts, batch.WithPartialInserts) } for _, ident := range identities { for k := range ident.Credentials { cred := ident.Credentials[k] if len(cred.Config) == 0 { cred.Config = sqlxx.JSONRawMessage("{}") } ct, err := FindIdentityCredentialsTypeByName(p.GetConnection(ctx), cred.Type) if err != nil { return err } cred.ID, err = uuid.NewV4() if err != nil { return err } cred.IdentityID = ident.ID cred.NID = nid cred.IdentityCredentialTypeID = ct credentials = append(credentials, &cred) ident.Credentials[k] = cred } } if err = batch.Create(ctx, traceConn, credentials, opts...); err != nil { return err } for _, cred := range credentials { for _, identifier := range cred.Identifiers { // Force case-insensitivity and trimming for identifiers identifier = NormalizeIdentifier(cred.Type, identifier) if identifier == "" { return errors.WithStack(herodot.ErrMisconfiguration.WithReasonf( "Unable to create identity credentials with missing or empty identifier.")) } ct, err := FindIdentityCredentialsTypeByName(p.GetConnection(ctx), cred.Type) if err != nil { return err } identifiers = append(identifiers, &identity.CredentialIdentifier{ Identifier: identifier, IdentityID: new(cred.IdentityID), IdentityCredentialsID: cred.ID, IdentityCredentialsTypeID: ct, NID: p.NetworkID(ctx), }) } } if err = batch.Create(ctx, traceConn, identifiers, opts...); err != nil { return err } return nil } func (p *IdentityPersister) createVerifiableAddresses(ctx context.Context, conn *pop.Connection, identities ...*identity.Identity) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.createVerifiableAddresses", trace.WithAttributes( attribute.Int("num_identities", len(identities)), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) work := make([]*identity.VerifiableAddress, 0, len(identities)) for _, id := range identities { for i := range id.VerifiableAddresses { work = append(work, &id.VerifiableAddresses[i]) } } var opts []batch.CreateOpts if len(identities) > 1 { opts = append(opts, batch.WithPartialInserts) } return batch.Create(ctx, &batch.TracerConnection{Tracer: p.r.Tracer(ctx), Connection: conn}, work, opts...) } type differ interface { Signature() string GetID() uuid.UUID } func updateAssociationWith[T differ](ctx context.Context, p *IdentityPersister, fromDatabase, updateTo []T, ) (result []T, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.updateAssociationWith", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) toKeep, toCreate, toRemoveIDs := diffAssociations(fromDatabase, updateTo) // Subtle: we delete the old associations from the DB first, because else // they could cause UNIQUE constraints to fail on insert. // Foreign key cascade will take care of deleting dependent records. if len(toRemoveIDs) > 0 { if err := p.GetConnection(ctx).Where("id IN (?)", toRemoveIDs).Where("nid = ?", p.NetworkID(ctx)).Delete(new(T)); err != nil { return nil, sqlcon.HandleError(err) } } if len(toCreate) > 0 { if err := batch.Create(ctx, &batch.TracerConnection{ Tracer: p.r.Tracer(ctx), Connection: p.GetConnection(ctx), }, toCreate, ); err != nil { return nil, err } } result = make([]T, 0, len(toKeep)+len(toCreate)) for _, v := range toKeep { result = append(result, *v) } for _, v := range toCreate { result = append(result, *v) } return result, nil } func updateAssociation[T differ](ctx context.Context, p *IdentityPersister, i *identity.Identity, inID []T, ) (result []T, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.updateAssociation", trace.WithAttributes( attribute.Stringer("identity.id", i.ID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) var inDB []T if err := p.GetConnection(ctx). Where("identity_id = ? AND nid = ?", i.ID, p.NetworkID(ctx)). All(&inDB); err != nil { return nil, sqlcon.HandleError(err) } return updateAssociationWith(ctx, p, inDB, inID) } func (p *IdentityPersister) updateCredentialsAssociation(ctx context.Context, identityID uuid.UUID, fromDatabase []identity.Credentials, updateTo []identity.Credentials) (result map[identity.CredentialsType]identity.Credentials, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.updateCredentialsAssociation", trace.WithAttributes( attribute.Stringer("identity.id", identityID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) nid := p.NetworkID(ctx) // Normalize new credentials by ensuring IdentityID, NID, and identifiers are set before hashing for i := range updateTo { updateTo[i].IdentityID = identityID updateTo[i].NID = nid // Normalize identifiers to match what's stored in the database (make a copy to avoid modifying original) normalizedIdentifiers := make([]string, len(updateTo[i].Identifiers)) for j, identifier := range updateTo[i].Identifiers { normalizedIdentifiers[j] = NormalizeIdentifier(updateTo[i].Type, identifier) } updateTo[i].Identifiers = normalizedIdentifiers } credsToKeep, newCreds, credsToDeleteIDs := diffAssociations(fromDatabase, updateTo) if len(credsToDeleteIDs) > 0 { // Delete the credential and its identifiers. conn := p.GetConnection(ctx) q := "DELETE FROM identity_credentials WHERE nid = ? AND id IN (?)" if conn.Dialect.Name() == "cockroach" { q = "DELETE FROM identity_credentials@primary WHERE nid = ? AND id IN (?)" } if err := conn.RawQuery(q, nid, credsToDeleteIDs).Exec(); err != nil { return nil, sqlcon.HandleError(err) } } // Create new credentials that aren't already in the database credsToCreate := make(map[identity.CredentialsType]identity.Credentials, len(newCreds)) for _, c := range newCreds { credsToCreate[c.Type] = *c } if len(credsToCreate) > 0 { if err := p.createIdentityCredentials(ctx, &identity.Identity{ ID: identityID, Credentials: credsToCreate, }); err != nil { return nil, err } } result = make(map[identity.CredentialsType]identity.Credentials, len(credsToKeep)+len(credsToCreate)) for _, c := range credsToKeep { result[c.Type] = *c } maps.Copy(result, credsToCreate) return result, nil } func diffAssociations[T differ](fromDatabase, updateTo []T) (unchanged, toCreate []*T, toRemoveIDs []uuid.UUID) { newAssocs := make(map[string]*T, len(updateTo)) oldAssocs := make(map[string]*T, len(fromDatabase)) for i, a := range updateTo { newAssocs[a.Signature()] = &updateTo[i] } for i, a := range fromDatabase { oldAssocs[a.Signature()] = &fromDatabase[i] } toRemoveIDs = make([]uuid.UUID, 0, len(fromDatabase)) toCreate = make([]*T, 0, len(updateTo)) unchanged = make([]*T, 0, len(updateTo)) for h, a := range oldAssocs { if _, found := newAssocs[h]; found { delete(newAssocs, h) unchanged = append(unchanged, a) } else { toRemoveIDs = append(toRemoveIDs, (*a).GetID()) } } for _, a := range newAssocs { toCreate = append(toCreate, a) } return } func (p *IdentityPersister) normalizeAllAddressess(ctx context.Context, identities ...*identity.Identity) { for _, id := range identities { p.normalizeRecoveryAddresses(ctx, id) p.normalizeVerifiableAddresses(ctx, id) } } func (p *IdentityPersister) normalizeVerifiableAddresses(ctx context.Context, id *identity.Identity) { for k := range id.VerifiableAddresses { v := id.VerifiableAddresses[k] v.IdentityID = id.ID v.NID = p.NetworkID(ctx) v.Value = stringToLowerTrim(v.Value) v.Via = cmp.Or(v.Via, identity.AddressTypeEmail) if len(v.Status) == 0 { if v.Verified { v.Status = identity.VerifiableAddressStatusCompleted } else { v.Status = identity.VerifiableAddressStatusPending } } // If verified is true but no timestamp is set, we default to time.Now if v.Verified && (v.VerifiedAt == nil || time.Time(*v.VerifiedAt).IsZero()) { v.VerifiedAt = new(sqlxx.NullTime(time.Now())) } if !v.Verified { v.VerifiedAt = nil } id.VerifiableAddresses[k] = v } } func (p *IdentityPersister) normalizeRecoveryAddresses(ctx context.Context, id *identity.Identity) { for k := range id.RecoveryAddresses { id.RecoveryAddresses[k].IdentityID = id.ID id.RecoveryAddresses[k].NID = p.NetworkID(ctx) id.RecoveryAddresses[k].Value = stringToLowerTrim(id.RecoveryAddresses[k].Value) id.RecoveryAddresses[k].Via = cmp.Or(id.RecoveryAddresses[k].Via, identity.AddressTypeEmail) } } func (p *IdentityPersister) createRecoveryAddresses(ctx context.Context, conn *pop.Connection, identities ...*identity.Identity) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.createRecoveryAddresses", trace.WithAttributes( attribute.Int("num_identities", len(identities)), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) // https://go.dev/play/p/b1kU5Bme2Fr work := make([]*identity.RecoveryAddress, 0, len(identities)) for _, id := range identities { for i := range id.RecoveryAddresses { work = append(work, &id.RecoveryAddresses[i]) } } var opts []batch.CreateOpts if len(identities) > 1 { opts = append(opts, batch.WithPartialInserts) } return batch.Create(ctx, &batch.TracerConnection{Tracer: p.r.Tracer(ctx), Connection: conn}, work, opts...) } func (p *IdentityPersister) CountIdentities(ctx context.Context) (n int64, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CountIdentities", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) count, err := p.c.WithContext(ctx).Where("nid = ?", p.NetworkID(ctx)).Count(new(identity.Identity)) if err != nil { return 0, sqlcon.HandleError(err) } span.SetAttributes(attribute.Int("num_identities", count)) return int64(count), nil } func (p *IdentityPersister) CreateIdentity(ctx context.Context, ident *identity.Identity) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateIdentity", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) return p.CreateIdentities(ctx, ident) } func (p *IdentityPersister) CreateIdentities(ctx context.Context, identities ...*identity.Identity) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateIdentities", trace.WithAttributes( attribute.Int("identities.count", len(identities)), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) for _, ident := range identities { ident.NID = p.NetworkID(ctx) if ident.SchemaID == "" { ident.SchemaID = p.r.Config().DefaultIdentityTraitsSchemaID(ctx) } stateChangedAt := sqlxx.NullTime(time.Now()) ident.StateChangedAt = &stateChangedAt if ident.State == "" { ident.State = identity.StateActive } if len(ident.Traits) == 0 { ident.Traits = identity.Traits("{}") } if err = p.InjectTraitsSchemaURL(ctx, ident); err != nil { return err } if err = p.validateIdentity(ctx, ident); err != nil { return err } } var succeededIDs []uuid.UUID var partialErr *identity.CreateIdentitiesError if err := p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { conn := &batch.TracerConnection{ Tracer: p.r.Tracer(ctx), Connection: tx, } succeededIDs = make([]uuid.UUID, 0, len(identities)) failedIdentityIDs := make(map[uuid.UUID]struct{ created bool }) partialErr = nil createdIdentities := make([]*identity.Identity, 0, len(identities)) var opts []batch.CreateOpts if len(identities) > 1 { opts = append(opts, batch.WithPartialInserts) } if err := batch.Create(ctx, conn, identities, opts...); err != nil { if partialErr := new(batch.PartialConflictError[identity.Identity]); errors.As(err, &partialErr) { for _, k := range partialErr.Failed { failedIdentityIDs[k.ID] = struct{ created bool }{false} } // Mark all created identities that were not in the failed list as created. for _, ident := range identities { if _, ok := failedIdentityIDs[ident.ID]; !ok { createdIdentities = append(createdIdentities, ident) } } } else { return sqlcon.HandleError(err) } } else { // If no errors occurred, we can safely assume all identities were created. createdIdentities = identities } p.normalizeAllAddressess(ctx, createdIdentities...) if err = p.createVerifiableAddresses(ctx, tx, createdIdentities...); err != nil { if partialErr := new(batch.PartialConflictError[identity.VerifiableAddress]); errors.As(err, &partialErr) { for _, k := range partialErr.Failed { failedIdentityIDs[k.IdentityID] = struct{ created bool }{true} } } else { return sqlcon.HandleError(err) } } if err = p.createRecoveryAddresses(ctx, tx, createdIdentities...); err != nil { if partialErr := new(batch.PartialConflictError[identity.RecoveryAddress]); errors.As(err, &partialErr) { for _, k := range partialErr.Failed { failedIdentityIDs[k.IdentityID] = struct{ created bool }{true} } } else { return sqlcon.HandleError(err) } } if err = p.createIdentityCredentials(ctx, createdIdentities...); err != nil { if partialErr := new(batch.PartialConflictError[identity.Credentials]); errors.As(err, &partialErr) { for _, k := range partialErr.Failed { failedIdentityIDs[k.IdentityID] = struct{ created bool }{true} } } else if partialErr := new(batch.PartialConflictError[identity.CredentialIdentifier]); errors.As(err, &partialErr) { for _, k := range partialErr.Failed { credID := k.IdentityCredentialsID for _, ident := range identities { for _, cred := range ident.Credentials { if cred.ID == credID { failedIdentityIDs[ident.ID] = struct{ created bool }{true} } } } } } else { return sqlcon.HandleError(err) } } // If any of the batch inserts failed on conflict, let's delete the corresponding // identity and return a list of failed identities in the error. if len(failedIdentityIDs) > 0 { partialErr = identity.NewCreateIdentitiesError(len(failedIdentityIDs)) idsToBeRemoved := make([]uuid.UUID, 0, len(failedIdentityIDs)) for _, ident := range identities { if info, ok := failedIdentityIDs[ident.ID]; ok { partialErr.AddFailedIdentity(ident, sqlcon.ErrUniqueViolation) if info.created { idsToBeRemoved = append(idsToBeRemoved, ident.ID) } } else { succeededIDs = append(succeededIDs, ident.ID) } } // Manually roll back by deleting the identities that were inserted before the // error occurred. if err := p.DeleteIdentities(ctx, idsToBeRemoved); err != nil { return sqlcon.HandleError(err) } return nil } else { // No failures: report all identities as created. for _, ident := range identities { succeededIDs = append(succeededIDs, ident.ID) } } return nil }); err != nil { return err } // Report succeeded identities as created. for _, identID := range succeededIDs { span.AddEvent(events.NewIdentityCreated(ctx, identID)) } return partialErr.ErrOrNil() } func (p *IdentityPersister) HydrateIdentityAssociations(ctx context.Context, i *identity.Identity, expand identity.Expandables) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.HydrateIdentityAssociations", trace.WithAttributes( attribute.Stringer("identity.id", i.ID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) nid := p.NetworkID(ctx) eg, ctx := errgroup.WithContext(ctx) if expand.Has(identity.ExpandFieldRecoveryAddresses) { eg.Go(func() error { // We use WithContext to get a copy of the connection struct, which solves the race detector // from complaining incorrectly. // // https://github.com/ory/pop/issues/723 if err := p.GetConnection(ctx).WithContext(ctx). Where("identity_id = ? AND nid = ?", i.ID, nid). Order("id ASC"). All(&i.RecoveryAddresses); err != nil { return sqlcon.HandleError(err) } return nil }) } if expand.Has(identity.ExpandFieldVerifiableAddresses) { eg.Go(func() error { // We use WithContext to get a copy of the connection struct, which solves the race detector // from complaining incorrectly. // // https://github.com/ory/pop/issues/723 if err := p.GetConnection(ctx).WithContext(ctx). Order("id ASC"). Where("identity_id = ? AND nid = ?", i.ID, nid). All(&i.VerifiableAddresses); err != nil { return sqlcon.HandleError(err) } return nil }) } if expand.Has(identity.ExpandFieldCredentials) { eg.Go(func() (err error) { // We use WithContext to get a copy of the connection struct, which solves the race detector // from complaining incorrectly. // // https://github.com/ory/pop/issues/723 creds, err := QueryForCredentials(p.GetConnection(ctx).WithContext(ctx), Where{"identity_credentials.identity_id = ?", []interface{}{i.ID}}, Where{"identity_credentials.nid = ?", []interface{}{nid}}) if err != nil { return err } i.Credentials = creds[i.ID] return }) } if err := eg.Wait(); err != nil { return err } if err := i.Validate(); err != nil { return err } if err := identity.UpgradeCredentials(i); err != nil { return err } return p.InjectTraitsSchemaURL(ctx, i) } type queryCredentials struct { Identifier string `db:"cred_identifier"` identity.Credentials } func (queryCredentials) TableName() string { return "identity_credentials" } type Where struct { Condition string Args []interface{} } // QueryForCredentials queries for identity credentials with custom WHERE // clauses, returning the results resolved by the owning identity's UUID. func QueryForCredentials(con *pop.Connection, where ...Where) (credentialsPerIdentity map[uuid.UUID](map[identity.CredentialsType]identity.Credentials), err error) { // This query has been meticulously crafted to be as fast as possible. // If you touch it, you will likely introduce a performance regression. q := con.Select( "COALESCE(identity_credential_identifiers.identifier, '') cred_identifier", "identity_credentials.id", "identity_credentials.identity_credential_type_id", "identity_credentials.identity_id", "identity_credentials.nid", "identity_credentials.config", "identity_credentials.version", "identity_credentials.created_at", "identity_credentials.updated_at", ).LeftJoin(identifiersTableNameWithIndexHint(con), "identity_credential_identifiers.identity_credential_id = identity_credentials.id AND identity_credential_identifiers.nid = identity_credentials.nid", ) for _, w := range where { q = q.Where("("+w.Condition+")", w.Args...) } var results []queryCredentials if err := q.All(&results); err != nil { return nil, sqlcon.HandleError(err) } // assemble credentialsPerIdentity = map[uuid.UUID](map[identity.CredentialsType]identity.Credentials){} for _, res := range results { res.Type, err = FindIdentityCredentialsTypeByID(con, res.IdentityCredentialTypeID) if err != nil { return nil, err } credentials, ok := credentialsPerIdentity[res.IdentityID] if !ok { credentialsPerIdentity[res.IdentityID] = make(map[identity.CredentialsType]identity.Credentials) credentials = credentialsPerIdentity[res.IdentityID] } identifiers := credentials[res.Type].Identifiers if res.Identifier != "" { identifiers = append(identifiers, res.Identifier) } if identifiers == nil { identifiers = make([]string, 0) } res.Identifiers = identifiers credentials[res.Type] = res.Credentials } // We need deterministic ordering for testing, but sorting in the // database can be expensive under certain circumstances. for _, creds := range credentialsPerIdentity { for k := range creds { sort.Strings(creds[k].Identifiers) } } return credentialsPerIdentity, nil } func identifiersTableNameWithIndexHint(con *pop.Connection) string { ici := "identity_credential_identifiers" switch con.Dialect.Name() { case "cockroach": ici += "@identity_credential_identifiers_ici_nid_i_idx" case "sqlite3": ici += " INDEXED BY identity_credential_identifiers_ici_nid_i_idx" case "mysql": ici += " USE INDEX(identity_credential_identifiers_ici_nid_i_idx)" default: // good luck 🤷‍♂️ } return ici } func paginationAttributes(params *identity.ListIdentityParameters, paginator *keysetpagination.Paginator) []attribute.KeyValue { attrs := []attribute.KeyValue{ attribute.StringSlice("expand", params.Expand.ToEager()), attribute.Bool("use:credential_identifier_filter", params.CredentialsIdentifier != ""), attribute.Bool("use:credential_identifier_similar_filter", params.CredentialsIdentifierSimilar != ""), } if params.PagePagination != nil { attrs = append(attrs, attribute.Int("page", params.PagePagination.Page), attribute.Int("per_page", params.PagePagination.ItemsPerPage)) } else { attrs = append(attrs, attribute.String("page_token", paginator.Token().Encode()), attribute.Int("page_size", paginator.Size())) } return attrs } // getCredentialTypeIDs returns a map of credential types to their respective IDs. // // If a credential type is not found, an error is returned. func (p *IdentityPersister) getCredentialTypeIDs(ctx context.Context, credentialTypes []identity.CredentialsType) (map[identity.CredentialsType]uuid.UUID, error) { result := map[identity.CredentialsType]uuid.UUID{} for _, ct := range credentialTypes { typeID, err := FindIdentityCredentialsTypeByName(p.GetConnection(ctx), ct) if err != nil { return nil, err } result[ct] = typeID } return result, nil } func (p *IdentityPersister) ListIdentities(ctx context.Context, params identity.ListIdentityParameters) (_ []identity.Identity, nextPage *keysetpagination.Paginator, err error) { paginator := keysetpagination.GetPaginator(append( params.KeySetPagination, keysetpagination.WithDefaultToken(identity.DefaultPageToken()), keysetpagination.WithDefaultSize(250), keysetpagination.WithColumn("id", "ASC"))...) ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListIdentities", trace.WithAttributes(append( paginationAttributes(¶ms, paginator), attribute.Stringer("network.id", p.NetworkID(ctx)))...)) defer otelx.End(span, &err) if _, err := uuid.FromString(paginator.Token().Parse("id")["id"]); err != nil { return nil, nil, errors.WithStack(x.PageTokenInvalid) } nid := p.NetworkID(ctx) var is []identity.Identity if err = p.Transaction(ctx, func(ctx context.Context, con *pop.Connection) error { is = make([]identity.Identity, 0) // Make sure we reset this to 0 in case of retries. nextPage = nil if err := crdbx.SetTransactionReadOnly(con); err != nil { return err } if err := crdbx.SetTransactionConsistency(con, params.ConsistencyLevel, p.r.Config().DefaultConsistencyLevel(ctx)); err != nil { return err } joins := "" wheres := "identities.nid = ? AND identities.id > ?" args := []any{nid, paginator.Token().Encode()} limit := fmt.Sprintf("LIMIT %d", paginator.Size()+1) if params.PagePagination != nil { wheres = "identities.nid = ?" args = []any{nid} paginator := pop.NewPaginator(params.PagePagination.Page+1, params.PagePagination.ItemsPerPage) limit = fmt.Sprintf("LIMIT %d OFFSET %d", paginator.PerPage, paginator.Offset) } identifier := params.CredentialsIdentifier identifierOperator := "=" if identifier == "" && params.CredentialsIdentifierSimilar != "" { identifier = x.EscapeLikePattern(params.CredentialsIdentifierSimilar) + "%" identifierOperator = "LIKE" } if len(identifier) > 0 { types, err := p.getCredentialTypeIDs(ctx, []identity.CredentialsType{ identity.CredentialsTypeWebAuthn, identity.CredentialsTypePassword, identity.CredentialsTypeCodeAuth, identity.CredentialsTypeOIDC, identity.CredentialsTypeSAML, }) if err != nil { return err } // When filtering by credentials identifier, we most likely are looking for a username or email. It is therefore // important to normalize the identifier before querying the database. joins = params.TransformStatement(` INNER JOIN identity_credentials ic ON ic.identity_id = identities.id AND ic.nid = identities.nid INNER JOIN identity_credential_identifiers ici ON ici.identity_credential_id = ic.id AND ici.nid = ic.nid `) wheres += fmt.Sprintf(` AND ic.nid = ? AND ici.nid = ? AND ((ici.identity_credential_type_id IN (?, ?, ?) AND ici.identifier %s ?) OR (ici.identity_credential_type_id IN (?, ?) AND ici.identifier %s ?)) `, identifierOperator, identifierOperator) args = append(args, nid, nid, types[identity.CredentialsTypeWebAuthn], types[identity.CredentialsTypePassword], types[identity.CredentialsTypeCodeAuth], NormalizeIdentifier(identity.CredentialsTypePassword, identifier), types[identity.CredentialsTypeOIDC], types[identity.CredentialsTypeSAML], identifier, ) } if len(params.IdsFilter) > 0 { wheres += ` AND identities.id in (?) ` args = append(args, params.IdsFilter) } else if !params.OrganizationID.IsNil() { wheres += ` AND identities.organization_id = ? ` args = append(args, params.OrganizationID.String()) } columns := popx.DBColumns[identity.Identity](&popx.AliasQuoter{Alias: "identities", Quoter: con.Dialect}) query := fmt.Sprintf(` SELECT DISTINCT %s FROM identities AS identities %s WHERE %s ORDER BY identities.id ASC %s`, columns, joins, wheres, limit) if err := con.RawQuery(query, args...).All(&is); err != nil { return sqlcon.HandleError(err) } if params.PagePagination == nil { is, nextPage = keysetpagination.Result(is, paginator) } if len(is) == 0 { return nil } identitiesByID := make(map[uuid.UUID]*identity.Identity, len(is)) identityIDs := make([]any, len(is)) for k := range is { identitiesByID[is[k].ID] = &is[k] identityIDs[k] = is[k].ID } for _, e := range params.Expand { switch e { case identity.ExpandFieldCredentials: creds, err := QueryForCredentials(con, Where{"identity_credentials.nid = ?", []interface{}{nid}}, Where{"identity_credentials.identity_id IN (?)", identityIDs}) if err != nil { return err } for k := range is { is[k].Credentials = creds[is[k].ID] } case identity.ExpandFieldVerifiableAddresses: addrs := make([]identity.VerifiableAddress, 0) if err := con.Where("identity_id IN (?)", identityIDs).Where("nid = ?", nid).Order("id").All(&addrs); err != nil { return sqlcon.HandleError(err) } for _, addr := range addrs { identitiesByID[addr.IdentityID].VerifiableAddresses = append(identitiesByID[addr.IdentityID].VerifiableAddresses, addr) } case identity.ExpandFieldRecoveryAddresses: addrs := make([]identity.RecoveryAddress, 0) if err := con.Where("identity_id IN (?)", identityIDs).Where("nid = ?", nid).Order("id").All(&addrs); err != nil { return sqlcon.HandleError(err) } for _, addr := range addrs { identitiesByID[addr.IdentityID].RecoveryAddresses = append(identitiesByID[addr.IdentityID].RecoveryAddresses, addr) } } } return nil }); err != nil { return nil, nil, err } schemaCache := map[string]string{} for k := range is { i := &is[k] if u, ok := schemaCache[i.SchemaID]; ok { i.SchemaURL = u } else { if err := p.InjectTraitsSchemaURL(ctx, i); err != nil { return nil, nil, err } schemaCache[i.SchemaID] = i.SchemaURL } if err := i.Validate(); err != nil { return nil, nil, err } if err := identity.UpgradeCredentials(i); err != nil { return nil, nil, err } is[k] = *i } return is, nextPage, nil } func (p *IdentityPersister) UpdateIdentityColumns(ctx context.Context, i *identity.Identity, columns ...string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateIdentityColumns", trace.WithAttributes( attribute.Stringer("identity.id", i.ID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) if err := p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { _, err := tx.Where("id = ? AND nid = ?", i.ID, p.NetworkID(ctx)).UpdateQuery(i, columns...) return sqlcon.HandleError(err) }); err != nil { return err } span.AddEvent(events.NewIdentityUpdated(ctx, i.ID)) return nil } func (p *IdentityPersister) UpdateIdentity(ctx context.Context, i *identity.Identity, mods ...identity.UpdateIdentityModifier) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateIdentity", trace.WithAttributes( attribute.Stringer("identity.id", i.ID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) if err := p.validateIdentity(ctx, i); err != nil { return err } o := identity.NewUpdateIdentityOptions(mods) span.SetAttributes(attribute.Bool("update.minimize_diff", o.FromDatabase() != nil)) i.NID = p.NetworkID(ctx) i.UpdatedAt = time.Now().UTC().Truncate(time.Microsecond) if err := sqlcon.HandleError(p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { // This returns "ErrNoRows" if the identity does not exist if err := update.Generic(WithTransaction(ctx, tx), tx, p.r.Tracer(ctx).Tracer(), i); err != nil { return err } var identityCreds map[identity.CredentialsType]identity.Credentials p.normalizeAllAddressess(ctx, i) if o.FromDatabase() != nil { if o.FromDatabase().ID != i.ID { return errors.New("mismatched identity ID: this is a bug") } var err error i.RecoveryAddresses, err = updateAssociationWith(ctx, p, o.FromDatabase().RecoveryAddresses, i.RecoveryAddresses) if err != nil { return err } i.VerifiableAddresses, err = updateAssociationWith(ctx, p, o.FromDatabase().VerifiableAddresses, i.VerifiableAddresses) if err != nil { return err } identityCreds = o.FromDatabase().Credentials } else { i.RecoveryAddresses, err = updateAssociation(ctx, p, i, i.RecoveryAddresses) if err != nil { return err } i.VerifiableAddresses, err = updateAssociation(ctx, p, i, i.VerifiableAddresses) if err != nil { return err } creds, err := QueryForCredentials(tx, Where{"identity_credentials.identity_id = ?", []interface{}{i.ID}}, Where{"identity_credentials.nid = ?", []interface{}{p.NetworkID(ctx)}}) if err != nil { return err } if c, found := creds[i.ID]; found { identityCreds = c } } oldCredentials := make([]identity.Credentials, 0, len(identityCreds)) for _, cred := range identityCreds { oldCredentials = append(oldCredentials, cred) } // Convert new credentials map to slice newCredentials := make([]identity.Credentials, 0, len(i.Credentials)) for _, cred := range i.Credentials { newCredentials = append(newCredentials, cred) } i.Credentials, err = p.updateCredentialsAssociation(ctx, i.ID, oldCredentials, newCredentials) return err })); err != nil { return err } span.AddEvent(events.NewIdentityUpdated(ctx, i.ID)) return nil } func (p *IdentityPersister) DeleteIdentity(ctx context.Context, id uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteIdentity", trace.WithAttributes( attribute.Stringer("identity.id", id), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) tableName := new(identity.Identity).TableName(ctx) if p.c.Dialect.Name() == "cockroach" { tableName += "@primary" } nid := p.NetworkID(ctx) count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf("DELETE FROM %s WHERE id = ? AND nid = ?", tableName), id, nid, ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } span.AddEvent(events.NewIdentityDeleted(ctx, id)) return nil } func (p *IdentityPersister) DeleteIdentities(ctx context.Context, ids []uuid.UUID) (err error) { // This function is only used internally to cleanup partially created identities, // when creating a batch of identities at once and some failed to be fully created. // This act should not be observable externally and thus we do not emit an event. if len(ids) == 0 { return nil } stringIDs := make([]string, len(ids)) for k, id := range ids { stringIDs[k] = id.String() } ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteIdentites", trace.WithAttributes( attribute.StringSlice("identity.ids", stringIDs), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) placeholders := strings.TrimSuffix(strings.Repeat("?, ", len(ids)), ", ") args := make([]any, 0, len(ids)+1) for _, id := range ids { args = append(args, id) } args = append(args, p.NetworkID(ctx)) tableName := new(identity.Identity).TableName(ctx) if p.c.Dialect.Name() == "cockroach" { tableName += "@primary" } count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %s WHERE id IN (%s) AND nid = ?", tableName, placeholders, ), args..., ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count != len(ids) { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *IdentityPersister) GetIdentity(ctx context.Context, id uuid.UUID, expand identity.Expandables) (_ *identity.Identity, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetIdentity", trace.WithAttributes( attribute.Stringer("identity.id", id), attribute.Stringer("network.id", p.NetworkID(ctx)), attribute.StringSlice("expand", expand.ToEager()))) defer otelx.End(span, &err) var i identity.Identity if err := p.GetConnection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&i); err != nil { return nil, sqlcon.HandleError(err) } if err := p.HydrateIdentityAssociations(ctx, &i, expand); err != nil { return nil, err } return &i, nil } func (p *IdentityPersister) GetIdentityConfidential(ctx context.Context, id uuid.UUID) (res *identity.Identity, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetIdentityConfidential") defer otelx.End(span, &err) return p.GetIdentity(ctx, id, identity.ExpandEverything) } func (p *IdentityPersister) FindIdentityByExternalID(ctx context.Context, externalID string, expand identity.Expandables) (res *identity.Identity, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindIdentityByExternalID", trace.WithAttributes( attribute.String("identity.external_id", externalID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) var i identity.Identity if err := p.GetConnection(ctx).Where("external_id = ? AND nid = ?", externalID, p.NetworkID(ctx)).First(&i); err != nil { return nil, sqlcon.HandleError(err) } if err := p.HydrateIdentityAssociations(ctx, &i, expand); err != nil { return nil, err } return &i, nil } func (p *IdentityPersister) FindVerifiableAddressByValue(ctx context.Context, via string, value string) (_ *identity.VerifiableAddress, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindVerifiableAddressByValue", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) otelx.End(span, &err) var address identity.VerifiableAddress if err := p.GetConnection(ctx).Where("nid = ? AND via = ? AND value = ?", p.NetworkID(ctx), via, stringToLowerTrim(value)).First(&address); err != nil { return nil, sqlcon.HandleError(err) } return &address, nil } func (p *IdentityPersister) FindRecoveryAddressByValue(ctx context.Context, via, value string) (_ *identity.RecoveryAddress, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindRecoveryAddressByValue", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) var address identity.RecoveryAddress if err := p.GetConnection(ctx).Where("nid = ? AND via = ? AND value = ?", p.NetworkID(ctx), via, stringToLowerTrim(value)).First(&address); err != nil { return nil, sqlcon.HandleError(err) } return &address, nil } // FindAllRecoveryAddressesForIdentityByRecoveryAddressValue returns all // recovery addresses for an identity if at least one of those addresses matches // the provided value. func (p *IdentityPersister) FindAllRecoveryAddressesForIdentityByRecoveryAddressValue(ctx context.Context, anyRecoveryAddress string) (recoveryAddresses []identity.RecoveryAddress, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FindAllRecoveryAddressesForIdentityByRecoveryAddressValue", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) // SQL explanation: // 1. Find a row (`B`) with the value matching `anyRecoveryAddress`. // This row has an identity id (`B.identity_id`). // 2. Find all rows (`A`) with this identity id. // Meaning: find all recovery addresses for this identity. // The result set includes the user provided address (`anyRecoveryAddress`). // NOTE: Should we exclude from the result set the login address for more security? // // This is all done in one query with a self-join. // We also bound the results for safety. err = p.GetConnection(ctx).RawQuery( ` SELECT A.id, A.via, A.value, A.identity_id, A.created_at, A.updated_at, A.nid FROM identity_recovery_addresses A JOIN identity_recovery_addresses B ON A.identity_id = B.identity_id AND A.nid = B.nid WHERE B.value = ? AND A.nid = ? LIMIT 10 `, stringToLowerTrim(anyRecoveryAddress), p.NetworkID(ctx), ). All(&recoveryAddresses) if err != nil { return nil, sqlcon.HandleError(err) } return recoveryAddresses, nil } func (p *IdentityPersister) VerifyAddress(ctx context.Context, code string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.VerifyAddress", trace.WithAttributes( attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) newCode, err := otp.New() if err != nil { return err } count, err := p.GetConnection(ctx).RawQuery( // #nosec G201 -- TableName is static fmt.Sprintf( "UPDATE %s SET status = ?, verified = true, verified_at = ?, code = ? WHERE nid = ? AND code = ? AND expires_at > ?", new(identity.VerifiableAddress).TableName(), ), identity.VerifiableAddressStatusCompleted, time.Now().UTC().Round(time.Second), newCode, p.NetworkID(ctx), code, time.Now().UTC(), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return sqlcon.HandleError(sqlcon.ErrNoRows) } return nil } func (p *IdentityPersister) UpdateVerifiableAddress(ctx context.Context, address *identity.VerifiableAddress, updateColumns ...string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateVerifiableAddress", trace.WithAttributes( attribute.Stringer("identity.id", address.IdentityID), attribute.Stringer("network.id", p.NetworkID(ctx)), attribute.StringSlice("columns", updateColumns))) defer otelx.End(span, &err) address.NID = p.NetworkID(ctx) address.Value = stringToLowerTrim(address.Value) return update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), address, updateColumns...) } func (p *IdentityPersister) validateIdentity(ctx context.Context, i *identity.Identity) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.validateIdentity", trace.WithAttributes( attribute.Stringer("identity.id", i.ID), attribute.Stringer("network.id", p.NetworkID(ctx)))) defer otelx.End(span, &err) if err := p.r.IdentityValidator().ValidateWithRunner(ctx, i); err != nil { if _, ok := errorsx.Cause(err).(*jsonschema.ValidationError); ok { return errors.WithStack(herodot.ErrBadRequest.WithReasonf("%s", err)) } return err } return nil } func (p *IdentityPersister) InjectTraitsSchemaURL(ctx context.Context, i *identity.Identity) (err error) { // This trace is more noisy than it's worth in diagnostic power. // ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.InjectTraitsSchemaURL") // defer otelx.End(span, &err) ss, err := p.r.IdentityTraitsSchemas(ctx) if err != nil { return err } s, err := ss.GetByID(i.SchemaID) if err != nil { return errors.WithStack(herodot.ErrMisconfiguration.WithReasonf( `The JSON Schema "%s" for this identity's traits could not be found.`, i.SchemaID)) } i.SchemaURL = s.SchemaURL(p.r.Config().SelfPublicURL(ctx)).String() return nil } var ( credentialTypesID = sync.Map{} credentialTypesName = sync.Map{} ) func FindIdentityCredentialsTypeByID(con *pop.Connection, id uuid.UUID) (identity.CredentialsType, error) { for name, cid := range identity.ConstantCredentialsTypeToId { if id.String() == cid { return name, nil } } result, found := credentialTypesID.Load(id) if !found { if err := loadCredentialTypes(con); err != nil { return "", err } result, found = credentialTypesID.Load(id) } if !found { return "", errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The SQL adapter failed to return the appropriate credentials_type for id %q. This is a bug in the code.", id)) } return result.(identity.CredentialsType), nil } func FindIdentityCredentialsTypeByName(con *pop.Connection, ct identity.CredentialsType) (uuid.UUID, error) { id, ok := identity.ConstantCredentialsTypeToId[ct] if ok { return uuid.Must(uuid.FromString(id)), nil } result, found := credentialTypesName.Load(ct) if !found { if err := loadCredentialTypes(con); err != nil { return uuid.Nil, err } result, found = credentialTypesName.Load(ct) } if !found { return uuid.Nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The SQL adapter failed to return the appropriate credentials_type for name %q. This is a bug in the code.", ct)) } return result.(uuid.UUID), nil } func loadCredentialTypes(con *pop.Connection) (err error) { ctx, span := trace.SpanFromContext(con.Context()).TracerProvider().Tracer("").Start(con.Context(), "persistence.sql.identity.loadCredentialTypes") defer otelx.End(span, &err) _ = ctx var tt []identity.CredentialsTypeTable if err := con.WithContext(ctx).All(&tt); err != nil { return sqlcon.HandleError(err) } for _, t := range tt { credentialTypesID.Store(t.ID, t.Name) credentialTypesName.Store(t.Name, t.ID) } return nil } ================================================ FILE: persistence/sql/migratest/fixtures/identity/0149ce5f-76a8-4efe-b2e3-431b8c6cceb6.json ================================================ { "id": "0149ce5f-76a8-4efe-b2e3-431b8c6cceb6", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "metadata_public": { "foo": "bar" }, "metadata_admin": { "baz": "bar" }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/0149ce5f-76a8-4efe-b2e3-431b8c6cceb7.json ================================================ { "id": "0149ce5f-76a8-4efe-b2e3-431b8c6cceb7", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbarbar@ory.sh" }, "metadata_public": { "foo": "bar" }, "metadata_admin": { "baz": "bar" }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/196d8c1e-4f04-40f0-94b3-5ec43996b28a.json ================================================ { "id": "196d8c1e-4f04-40f0-94b3-5ec43996b28a", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/28ff0031-190b-4253-bd15-14308dec013e.json ================================================ { "id": "28ff0031-190b-4253-bd15-14308dec013e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbarbarfoo@ory.sh" }, "metadata_public": { "foo": "bar" }, "metadata_admin": { "baz": "bar" }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/2ae6a5a7-2983-49e7-a4d8-7740b37c88cb.json ================================================ { "id": "2ae6a5a7-2983-49e7-a4d8-7740b37c88cb", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "d7b10@ory.sh" }, "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/308929d3-41a2-43fe-a33c-75308539d841.json ================================================ { "id": "308929d3-41a2-43fe-a33c-75308539d841", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "metadata_public": { "foo": "bar" }, "metadata_admin": { "baz": "bar" }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/359963ec-b09b-4ea0-aece-fb4dd95f304a.json ================================================ { "id": "359963ec-b09b-4ea0-aece-fb4dd95f304a", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "d7b11@ory.sh" }, "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/5ff66179-c240-4703-b0d8-494592cefff5.json ================================================ { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "credentials": { "password": { "type": "password", "identifiers": [ "foo2@ory.sh", "foo@ory.sh" ], "config": { "hashed_password": "$argon2id$v=19$m=131072,t=2,p=1$lQFPaKxXqPL56/mU7vRi4w$6aldHyBnURt8sP8+xu41Ng" }, "version": 0, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, "totp": { "type": "totp", "identifiers": [], "config": { "totp_url": "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP\u0026issuer=Example" }, "version": 0, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } }, "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/a251ebc2-880c-4f76-a8f3-38e6940eab0e.json ================================================ { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "credentials": { "password": { "type": "password", "identifiers": [ "foo1@ory.sh", "foobar@ory.sh" ], "config": { "hashed_password": "$argon2id$v=19$m=131072,t=2,p=1$lQFPaKxXqPL56/mU7vRi4w$6aldHyBnURt8sP8+xu41Ng" }, "version": 0, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } }, "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/d7b9addb-ac15-4bc2-9fa5-562e0bf48755.json ================================================ { "id": "d7b9addb-ac15-4bc2-9fa5-562e0bf48755", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "d7b9@ory.sh" }, "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity/ed253b2c-48ed-4c58-9b6f-1dc963c30a66.json ================================================ { "id": "ed253b2c-48ed-4c58-9b6f-1dc963c30a66", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "metadata_public": null, "metadata_admin": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null } ================================================ FILE: persistence/sql/migratest/fixtures/identity_recovery_address/b8293f1c-010f-45d9-b809-f3fc5365ba80.json ================================================ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/identity_verification_address/45e867e9-2745-4f16-8dd4-84334a252b61.json ================================================ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/identity_verification_address/b2d59320-8564-4400-a39f-a22a497a23f1.json ================================================ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/identity_verification_address/c2427b6d-312b-46d9-9285-536db7ae11fd.json ================================================ { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/identity_verification_address/d4718a67-aec2-418d-8173-6ebc7bde3b86.json ================================================ { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/login_code/bd292366-af32-4ba6-bdf0-11d6d1a217f3.json ================================================ { "id": "bd292366-af32-4ba6-bdf0-11d6d1a217f3", "expires_at": "2022-08-18T08:28:18Z", "issued_at": "2022-08-18T07:28:18Z", "identity_id": "28ff0031-190b-4253-bd15-14308dec013e" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/00b1517f-2467-4aaf-b0a5-82b4a27dcaf5.json ================================================ { "id": "00b1517f-2467-4aaf-b0a5-82b4a27dcaf5", "organization_id": null, "oauth2_login_challenge": "challenge data", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/0bc96cc9-dda4-4700-9e42-35731f2af91e.json ================================================ { "id": "0bc96cc9-dda4-4700-9e42-35731f2af91e", "organization_id": null, "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/1fb23c75-b809-42cc-8984-6ca2d0a1192f.json ================================================ { "id": "1fb23c75-b809-42cc-8984-6ca2d0a1192f", "organization_id": null, "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal2", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/202c1981-1e25-47f0-8764-75ad506c2bec.json ================================================ { "id": "202c1981-1e25-47f0-8764-75ad506c2bec", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/349c945a-60f8-436a-a301-7a42c92604f9.json ================================================ { "id": "349c945a-60f8-436a-a301-7a42c92604f9", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login?login_challenge=3caddfd599034bce83ffcae36f42dff7", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal2", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/38caf592-b042-4551-b92f-8d5223c2a4e2.json ================================================ { "id": "38caf592-b042-4551-b92f-8d5223c2a4e2", "organization_id": null, "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal2", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/3a9ea34f-0f12-469b-9417-3ae5795a7baa.json ================================================ { "id": "3a9ea34f-0f12-469b-9417-3ae5795a7baa", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/43c99182-bb67-47e1-b564-bb23bd8d4393.json ================================================ { "id": "43c99182-bb67-47e1-b564-bb23bd8d4393", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login?prompt=login\u0026return_to=http%3A%2F%2F127.0.0.1%3A4455%2F.ory%2Fkratos%2Fpublic%2Fself-service%2Fbrowser%2Fflows%2Fsettings%2Fstrategies%2Fprofile%3Frequest%3D74fd6c53-7651-453e-90b8-2c5adbf911bb", "return_to": "http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=74fd6c53-7651-453e-90b8-2c5adbf911bb", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": true, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/47edd3a8-0998-4779-9469-f4b8ee4430df.json ================================================ { "id": "47edd3a8-0998-4779-9469-f4b8ee4430df", "organization_id": null, "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/56d94e8b-8a5d-4b7f-8a6e-3259d2b2903e.json ================================================ { "id": "56d94e8b-8a5d-4b7f-8a6e-3259d2b2903e", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/6d387820-f2f4-4f9f-9980-a90d89e7811f.json ================================================ { "id": "6d387820-f2f4-4f9f-9980-a90d89e7811f", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/916ded11-aa64-4a27-b06e-96e221a509d7.json ================================================ { "id": "916ded11-aa64-4a27-b06e-96e221a509d7", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/99974ce6-388c-4669-a95a-7757ee724020.json ================================================ { "id": "99974ce6-388c-4669-a95a-7757ee724020", "organization_id": null, "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/b1fac7fb-d016-4a06-a7fe-e4eab2a0429f.json ================================================ { "id": "b1fac7fb-d016-4a06-a7fe-e4eab2a0429f", "organization_id": null, "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/cccccccc-dda4-4700-9e42-35731f2af911.json ================================================ { "id": "cccccccc-dda4-4700-9e42-35731f2af911", "organization_id": null, "oauth2_login_challenge": "challenge data", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/cccccccc-dda4-4700-9e42-35731f2af91e.json ================================================ { "id": "cccccccc-dda4-4700-9e42-35731f2af91e", "organization_id": null, "oauth2_login_challenge": "challenge data", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/login_flow/d6aa1f23-88c9-4b9b-a850-392f48c7f9e8.json ================================================ { "id": "d6aa1f23-88c9-4b9b-a850-392f48c7f9e8", "organization_id": null, "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/login", "ui": { "action": "", "method": "", "nodes": null }, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "refresh": false, "requested_aal": "aal1", "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_code/8f75f5d9-9cb4-4848-9a73-9344f686f8a6.json ================================================ { "id": "8f75f5d9-9cb4-4848-9a73-9344f686f8a6", "recovery_address": null, "expires_at": "2022-08-18T08:28:18Z", "issued_at": "2022-08-18T07:28:18Z", "identity_id": "308929d3-41a2-43fe-a33c-75308539d841" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_flow/0d14427f-e16d-43a5-8695-8278bf85d4eb.json ================================================ { "id": "0d14427f-e16d-43a5-8695-8278bf85d4eb", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_flow/13178936-095a-466b-abe0-36d977d3dc18.json ================================================ { "id": "13178936-095a-466b-abe0-36d977d3dc18", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_flow/4963f305-e874-4a68-8424-a00bec679e7b.json ================================================ { "id": "4963f305-e874-4a68-8424-a00bec679e7b", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/recovery", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_flow/68fb4010-84a9-4d1e-9f92-2705978ee891.json ================================================ { "id": "68fb4010-84a9-4d1e-9f92-2705978ee891", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_flow/68fb4010-84a9-4d1e-9f92-2705978ee89e.json ================================================ { "id": "68fb4010-84a9-4d1e-9f92-2705978ee89e", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/recovery", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_flow/87e871e1-a45f-4ed0-ba4e-a03063c774dc.json ================================================ { "id": "87e871e1-a45f-4ed0-ba4e-a03063c774dc", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_token/1b667e6d-8fda-4194-a765-08185185d7e4.json ================================================ { "id": "1b667e6d-8fda-4194-a765-08185185d7e4", "recovery_address": null, "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "identity_id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_token/5529d454-2946-404e-b681-d950f8657fd0.json ================================================ { "id": "5529d454-2946-404e-b681-d950f8657fd0", "recovery_address": null, "expires_at": "2000-01-01T00:00:00Z", "issued_at": "2000-01-01T00:00:00Z", "identity_id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e" } ================================================ FILE: persistence/sql/migratest/fixtures/recovery_token/77ca3f5c-cd39-488b-9f1d-cc7166d14bdc.json ================================================ { "id": "77ca3f5c-cd39-488b-9f1d-cc7166d14bdc", "recovery_address": null, "expires_at": "2000-01-01T00:00:00Z", "issued_at": "2000-01-01T00:00:00Z", "identity_id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_code/f1f66a69-ce02-4a12-9591-9e02dda30a0d.json ================================================ { "id": "f1f66a69-ce02-4a12-9591-9e02dda30a0d", "expires_at": "2022-08-18T08:28:18Z", "issued_at": "2022-08-18T07:28:18Z" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/05a7f09d-4ef3-41fb-958a-6ad74584b36a.json ================================================ { "id": "05a7f09d-4ef3-41fb-958a-6ad74584b36a", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/22d58184-b97d-44a5-bbaf-0aa8b4000d81.json ================================================ { "id": "22d58184-b97d-44a5-bbaf-0aa8b4000d81", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/2bf132e0-5d40-4df9-9a11-9106e5333735.json ================================================ { "id": "2bf132e0-5d40-4df9-9a11-9106e5333735", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/696e7022-c466-44f6-89c6-8cf93c06a62a.json ================================================ { "id": "696e7022-c466-44f6-89c6-8cf93c06a62a", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/69c80296-36cd-4afc-921a-15369cac5bf0.json ================================================ { "id": "69c80296-36cd-4afc-921a-15369cac5bf0", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge=", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/87fa3f43-5155-42b4-a1ad-174c2595fdaf.json ================================================ { "id": "87fa3f43-5155-42b4-a1ad-174c2595fdaf", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/8ef215a9-e8d5-43b3-9aa3-cb4333562e36.json ================================================ { "id": "8ef215a9-e8d5-43b3-9aa3-cb4333562e36", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/8f32efdc-f6fc-4c27-a3c2-579d109eff60.json ================================================ { "id": "8f32efdc-f6fc-4c27-a3c2-579d109eff60", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/9edcf051-1cd0-44cc-bd2f-6ac21f0c24dd.json ================================================ { "id": "9edcf051-1cd0-44cc-bd2f-6ac21f0c24dd", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/e2150cdc-23ac-4940-a240-6c79c27ab029.json ================================================ { "id": "e2150cdc-23ac-4940-a240-6c79c27ab029", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/ef18b06e-4700-4021-9949-ef783cd86be1.json ================================================ { "id": "ef18b06e-4700-4021-9949-ef783cd86be1", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge=", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/ef18b06e-4700-4021-9949-ef783cd86be8.json ================================================ { "id": "ef18b06e-4700-4021-9949-ef783cd86be8", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration?login_challenge=", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/registration_flow/f1b5ed18-113a-4a98-aae7-d4eba007199c.json ================================================ { "id": "f1b5ed18-113a-4a98-aae7-d4eba007199c", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "password", "ui": { "action": "", "method": "", "nodes": null }, "organization_id": null, "state": "" } ================================================ FILE: persistence/sql/migratest/fixtures/session/068f6bb6-d15f-436d-94f7-b3fd0489c9ef.json ================================================ { "id": "068f6bb6-d15f-436d-94f7-b3fd0489c9ef", "active": false, "expires_at": "2013-10-07T08:23:19Z", "authenticated_at": "2013-10-07T08:23:19Z", "authenticator_assurance_level": "aal2", "authentication_methods": [ { "method": "password", "aal": "", "completed_at": "0001-01-01T00:00:00Z" }, { "method": "totp", "aal": "", "completed_at": "0001-01-01T00:00:00Z" } ], "issued_at": "2013-10-07T08:23:19Z", "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "devices": [] } ================================================ FILE: persistence/sql/migratest/fixtures/session/7458af86-c1d8-401c-978a-8da89133f78b.json ================================================ { "id": "7458af86-c1d8-401c-978a-8da89133f78b", "active": true, "expires_at": "2080-10-07T08:23:19Z", "authenticated_at": "2013-10-07T08:23:19Z", "authenticator_assurance_level": "aal2", "authentication_methods": [ { "method": "password", "aal": "", "completed_at": "0001-01-01T00:00:00Z" }, { "method": "totp", "aal": "", "completed_at": "0001-01-01T00:00:00Z" } ], "issued_at": "2013-10-07T08:23:19Z", "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "devices": [] } ================================================ FILE: persistence/sql/migratest/fixtures/session/7458af86-c1d8-401c-978a-8da89133f98b.json ================================================ { "id": "7458af86-c1d8-401c-978a-8da89133f98b", "active": false, "expires_at": "2013-10-07T08:23:19Z", "authenticated_at": "2013-10-07T08:23:19Z", "authenticator_assurance_level": "aal2", "authentication_methods": [ { "method": "password", "aal": "", "completed_at": "0001-01-01T00:00:00Z" }, { "method": "totp", "aal": "", "completed_at": "0001-01-01T00:00:00Z" } ], "issued_at": "2013-10-07T08:23:19Z", "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "devices": [ { "id": "884f556e-eb3a-4b9f-bee3-11763642c6c0", "ip_address": "54.155.246.232", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", "location": "Munich, Germany" } ] } ================================================ FILE: persistence/sql/migratest/fixtures/session/8571e374-38f2-4f46-8ad3-b9d914e174d3.json ================================================ { "id": "8571e374-38f2-4f46-8ad3-b9d914e174d3", "active": false, "expires_at": "2013-10-07T08:23:19Z", "authenticated_at": "2013-10-07T08:23:19Z", "authenticator_assurance_level": "aal1", "authentication_methods": [ { "method": "v0.6_legacy_session", "aal": "", "completed_at": "0001-01-01T00:00:00Z" } ], "issued_at": "2013-10-07T08:23:19Z", "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "devices": [] } ================================================ FILE: persistence/sql/migratest/fixtures/session/dcde5aaa-f789-4d3d-ae1f-76da8d57e67c.json ================================================ { "id": "dcde5aaa-f789-4d3d-ae1f-76da8d57e67c", "active": false, "expires_at": "2013-10-07T08:23:19Z", "authenticated_at": "2013-10-07T08:23:19Z", "authenticator_assurance_level": "aal1", "authentication_methods": [ { "method": "v0.6_legacy_session", "aal": "", "completed_at": "0001-01-01T00:00:00Z" } ], "issued_at": "2013-10-07T08:23:19Z", "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "devices": [] } ================================================ FILE: persistence/sql/migratest/fixtures/session/f38cdebe-e567-42c9-a562-1bd4dee40998.json ================================================ { "id": "f38cdebe-e567-42c9-a562-1bd4dee40998", "active": false, "expires_at": "2013-10-07T08:23:19Z", "authenticated_at": "2013-10-07T08:23:19Z", "authenticator_assurance_level": "aal1", "authentication_methods": [ { "method": "v0.6_legacy_session", "aal": "", "completed_at": "0001-01-01T00:00:00Z" } ], "issued_at": "2013-10-07T08:23:19Z", "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "devices": [] } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/194c5b05-0487-4a11-bcbc-f301c9ff9678.json ================================================ { "id": "194c5b05-0487-4a11-bcbc-f301c9ff9678", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/19ede218-928c-4e02-ab49-b76e12b34f31.json ================================================ { "id": "19ede218-928c-4e02-ab49-b76e12b34f31", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/19ede218-928c-4e02-ab49-b76e12b34f32.json ================================================ { "id": "19ede218-928c-4e02-ab49-b76e12b34f32", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/21c5f714-3089-49d2-b387-f244d4dd9e00.json ================================================ { "id": "21c5f714-3089-49d2-b387-f244d4dd9e00", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/74fd6c53-7651-453e-90b8-2c5adbf911bb.json ================================================ { "id": "74fd6c53-7651-453e-90b8-2c5adbf911bb", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "5ff66179-c240-4703-b0d8-494592cefff5", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "bazbar@ory.sh" }, "verifiable_addresses": [ { "id": "45e867e9-2745-4f16-8dd4-84334a252b61", "value": "foo@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/77fe4fb3-2d4e-4532-b568-c44b0aece0aa.json ================================================ { "id": "77fe4fb3-2d4e-4532-b568-c44b0aece0aa", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/8248bb5d-8ef7-45e3-8e07-9e2003dd5352.json ================================================ { "id": "8248bb5d-8ef7-45e3-8e07-9e2003dd5352", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/90b4f970-b9ae-42bc-a0a7-73ec750e0aa1.json ================================================ { "id": "90b4f970-b9ae-42bc-a0a7-73ec750e0aa1", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/a79bfcf1-68ae-49de-8b23-4f96921b8341.json ================================================ { "id": "a79bfcf1-68ae-49de-8b23-4f96921b8341", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/aeba85bd-1a8c-44bf-8fc3-3be83c01a3dc.json ================================================ { "id": "aeba85bd-1a8c-44bf-8fc3-3be83c01a3dc", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/settings_flow/cdfd1eed-34a4-491d-ad0a-7579d3a0a7ba.json ================================================ { "id": "cdfd1eed-34a4-491d-ad0a-7579d3a0a7ba", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/settings", "active": "profile", "ui": { "action": "", "method": "", "nodes": null }, "identity": { "id": "a251ebc2-880c-4f76-a8f3-38e6940eab0e", "schema_id": "default", "schema_url": "https://www.ory.sh/schemas/ZGVmYXVsdA", "state": "active", "traits": { "email": "foobar@ory.sh" }, "verifiable_addresses": [ { "id": "b2d59320-8564-4400-a39f-a22a497a23f1", "value": "foobar+without-code@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "c2427b6d-312b-46d9-9285-536db7ae11fd", "value": "foobar@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" }, { "id": "d4718a67-aec2-418d-8173-6ebc7bde3b86", "value": "foobar+11345642c6c0@ory.sh", "verified": false, "via": "email", "status": "pending", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "recovery_addresses": [ { "id": "b8293f1c-010f-45d9-b809-f3fc5365ba80", "value": "foobar@ory.sh", "via": "email", "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z" } ], "metadata_public": null, "created_at": "2013-10-07T08:23:19Z", "updated_at": "2013-10-07T08:23:19Z", "organization_id": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/29b2c16e-2955-4faa-bd16-33af098cdf83.json ================================================ { "id": "29b2c16e-2955-4faa-bd16-33af098cdf83", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "passed_challenge" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/3631e880-ce59-4cbd-a705-0d825eea590d.json ================================================ { "id": "3631e880-ce59-4cbd-a705-0d825eea590d", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "passed_challenge" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/42f31e47-65e1-4be9-80ea-e5d8ed64b236.json ================================================ { "id": "42f31e47-65e1-4be9-80ea-e5d8ed64b236", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "passed_challenge" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/5385c962-0295-4575-9b1b-d7eef13c0eda.json ================================================ { "id": "5385c962-0295-4575-9b1b-d7eef13c0eda", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/registration", "active": "link", "ui": { "action": "", "method": "", "nodes": null }, "state": "choose_method" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/6aae3159-b880-4cfb-a863-03b114b1371b.json ================================================ { "id": "6aae3159-b880-4cfb-a863-03b114b1371b", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/7be6c72c-c868-4b61-a1f0-1130603665d1.json ================================================ { "id": "7be6c72c-c868-4b61-a1f0-1130603665d1", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/7be6c72c-c868-4b61-a1f0-1130603665d8.json ================================================ { "id": "7be6c72c-c868-4b61-a1f0-1130603665d8", "type": "api", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/81f74e5d-1fa5-4e1b-a9bf-e95119260471.json ================================================ { "id": "81f74e5d-1fa5-4e1b-a9bf-e95119260471", "type": "api", "expires_at": "2022-11-03T08:23:19Z", "issued_at": "2022-11-03T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/81f74e5d-1fa5-4e1b-a9bf-e9511926047c.json ================================================ { "id": "81f74e5d-1fa5-4e1b-a9bf-e9511926047c", "type": "api", "expires_at": "2022-11-03T08:23:19Z", "issued_at": "2022-11-03T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/a8e2b810-a561-4a9e-bbd8-37f649bc26fa.json ================================================ { "id": "a8e2b810-a561-4a9e-bbd8-37f649bc26fa", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "passed_challenge" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_flow/b37d34c2-4290-4be4-9e3a-c6263ee77082.json ================================================ { "id": "b37d34c2-4290-4be4-9e3a-c6263ee77082", "type": "browser", "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z", "request_url": "http://kratos:4433/self-service/browser/flows/verification/email", "ui": { "action": "", "method": "", "nodes": null }, "state": "show_form" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_token/ee56574d-2f0c-43f6-8d26-0062938ae330.json ================================================ { "id": "ee56574d-2f0c-43f6-8d26-0062938ae330", "verification_address": null, "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_token/ee56574d-2f1c-43f6-8d26-0062938ae330.json ================================================ { "id": "ee56574d-2f1c-43f6-8d26-0062938ae330", "verification_address": null, "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/fixtures/verification_token/f81fd924-23bb-4cdf-8fa0-56253eff6cc9.json ================================================ { "id": "f81fd924-23bb-4cdf-8fa0-56253eff6cc9", "verification_address": null, "expires_at": "2013-10-07T08:23:19Z", "issued_at": "2013-10-07T08:23:19Z" } ================================================ FILE: persistence/sql/migratest/migration_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package migratest import ( "context" "encoding/json" "os" "path/filepath" "regexp" "strings" "testing" "github.com/bradleyjkemp/cupaloy/v2" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence/sql" gomigrations "github.com/ory/kratos/persistence/sql/migrations/go" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/kratos/session" "github.com/ory/kratos/x" "github.com/ory/pop/v6" "github.com/ory/x/configx" "github.com/ory/x/dbal" "github.com/ory/x/fsx" "github.com/ory/x/logrusx" "github.com/ory/x/migratest" "github.com/ory/x/networkx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/popx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlcon/dockertest" ) func snapshotFor(paths ...string) *cupaloy.Config { return cupaloy.New( cupaloy.CreateNewAutomatically(true), cupaloy.FailOnUpdate(true), cupaloy.SnapshotFileExtension(".json"), cupaloy.SnapshotSubdirectory(filepath.Join(paths...)), ) } func CompareWithFixture(t *testing.T, actual interface{}, prefix string, id string) { s := snapshotFor("fixtures", prefix) actualJSON, err := json.MarshalIndent(actual, "", " ") require.NoError(t, err) err = s.SnapshotWithName(id, actualJSON) assert.NoErrorf(t, err, "actual = %s", string(actualJSON)) } func TestMigrations_SQLite(t *testing.T) { t.Parallel() sqlite, err := pop.NewConnection(&pop.ConnectionDetails{ URL: dbal.NewSQLiteTestDatabase(t), }) require.NoError(t, err) require.NoError(t, sqlite.Open()) testDatabase(t, "sqlite", sqlite) } func TestMigrations_Postgres(t *testing.T) { if testing.Short() { t.Skip("skipping testing in short mode") } t.Parallel() testDatabase(t, "postgres", dockertest.ConnectPop(t, dockertest.RunTestPostgreSQLWithVersion(t, "16"))) } func TestMigrations_Mysql(t *testing.T) { if testing.Short() { t.Skip("skipping testing in short mode") } t.Parallel() testDatabase(t, "mysql", dockertest.ConnectPop(t, dockertest.RunTestMySQLWithVersion(t, "8.4"))) } func TestMigrations_Cockroach(t *testing.T) { if testing.Short() { t.Skip("skipping testing in short mode") } t.Parallel() testDatabase(t, "cockroach", dockertest.ConnectPop(t, dockertest.RunTestCockroachDBWithVersion(t, "latest-v25.4"))) } func testDatabase(t *testing.T, db string, c *pop.Connection) { ctx := context.Background() l := logrusx.New("", "", logrusx.ForceLevel(logrus.DebugLevel)) url := c.URL() // workaround for https://github.com/ory/pop/issues/538 switch db { case "mysql": url = "mysql://" + url case "sqlite": url = "sqlite3://" + url case "cockroach": url = "cockroach" + strings.TrimPrefix(url, "postgres") } if db != "sqlite" { dbName := "testdb" + strings.ReplaceAll(x.NewUUID().String(), "-", "") require.NoError(t, c.RawQuery("CREATE DATABASE "+dbName).Exec()) url = regexp.MustCompile(`/[a-z0-9]+\?`).ReplaceAllString(url, "/"+dbName+"?") } t.Logf("URL: %s", url) var err error c, err = pop.NewConnection(&pop.ConnectionDetails{URL: url}) require.NoError(t, err) require.NoError(t, c.Open()) tm, err := popx.NewMigrationBox( fsx.Merge(sql.Migrations, networkx.Migrations), c, l, popx.WithGoMigrations(gomigrations.All), popx.WithTestdata(t, os.DirFS("./testdata")), ) require.NoError(t, err) err = tm.Up(ctx) // for easy breakpointing // _ = tm.DumpMigrationSchema(ctx) // uncomment to get the current state of the database after migrations have run if !assert.NoError(t, err) { assert.NoError(t, tm.DumpMigrationSchema(ctx)) t.FailNow() } opts := driver.WithConfigOptions( configx.WithValues(map[string]any{ config.ViperKeyDSN: url, config.ViperKeyPublicBaseURL: "https://www.ory.sh/", config.ViperKeyIdentitySchemas: config.Schemas{{ID: "default", URL: "file://stub/default.schema.json"}}, config.ViperKeySecretsDefault: []string{"secret"}, }), configx.SkipValidation(), ) t.Run("suite=fixtures", func(t *testing.T) { t.Run("case=identity", func(t *testing.T) { t.Parallel() d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) ids, _, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ListIdentityParameters{Expand: identity.ExpandEverything, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(1000)}}) require.NoError(t, err) require.NotEmpty(t, ids) var found []string for y, id := range ids { found = append(found, id.ID.String()) actual := &ids[y] for _, a := range actual.VerifiableAddresses { CompareWithFixture(t, a, "identity_verification_address", a.ID.String()) } for _, a := range actual.RecoveryAddresses { CompareWithFixture(t, a, "identity_recovery_address", a.ID.String()) } CompareWithFixture(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), "identity", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "identity"), found) }) t.Run("case=identity_get", func(t *testing.T) { t.Parallel() d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) ids, _, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ListIdentityParameters{Expand: identity.ExpandNothing, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(1000)}}) require.NoError(t, err) require.NotEmpty(t, ids) var found []string for _, id := range ids { actual, err := d.PrivilegedIdentityPool().GetIdentityConfidential(context.Background(), id.ID) require.NoError(t, err) found = append(found, actual.ID.String()) CompareWithFixture(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), "identity", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "identity"), found) }) t.Run("case=verification_token", func(t *testing.T) { t.Parallel() var ids []link.VerificationToken require.NoError(t, c.All(&ids)) require.NotEmpty(t, ids) for _, id := range ids { CompareWithFixture(t, id, "verification_token", id.ID.String()) } }) t.Run("case=session", func(t *testing.T) { t.Parallel() var ids []session.Session require.NoError(t, c.Select("id").All(&ids)) require.NotEmpty(t, ids) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) var found []string for _, id := range ids { found = append(found, id.ID.String()) actual, err := d.SessionPersister().GetSession(context.Background(), id.ID, session.ExpandEverything) require.NoErrorf(t, err, "Trying to get session: %s", id.ID) require.NotEmpty(t, actual.LogoutToken, "check if migrations have generated a logout token for existing sessions") CompareWithFixture(t, actual, "session", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "session"), found) }) t.Run("case=login", func(t *testing.T) { t.Parallel() var ids []login.Flow require.NoError(t, c.Select("id").All(&ids)) require.NotEmpty(t, ids) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) var found []string for _, id := range ids { found = append(found, id.ID.String()) actual, err := d.LoginFlowPersister().GetLoginFlow(context.Background(), id.ID) require.NoError(t, err) CompareWithFixture(t, actual, "login_flow", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "login_flow"), found) }) t.Run("case=registration", func(t *testing.T) { t.Parallel() var ids []registration.Flow require.NoError(t, c.Select("id").All(&ids)) require.NotEmpty(t, ids) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) var found []string for _, id := range ids { found = append(found, id.ID.String()) actual, err := d.RegistrationFlowPersister().GetRegistrationFlow(context.Background(), id.ID) require.NoError(t, err) CompareWithFixture(t, actual, "registration_flow", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "registration_flow"), found) }) t.Run("case=settings_flow", func(t *testing.T) { t.Parallel() var ids []settings.Flow require.NoError(t, c.Select("id").All(&ids)) require.NotEmpty(t, ids) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) var found []string for _, id := range ids { found = append(found, id.ID.String()) actual, err := d.SettingsFlowPersister().GetSettingsFlow(context.Background(), id.ID) require.NoError(t, err, id.ID.String()) CompareWithFixture(t, actual, "settings_flow", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "settings_flow"), found) }) t.Run("case=recovery_flow", func(t *testing.T) { t.Parallel() var ids []recovery.Flow require.NoError(t, c.Select("id").All(&ids)) require.NotEmpty(t, ids) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) var found []string for _, id := range ids { found = append(found, id.ID.String()) actual, err := d.RecoveryFlowPersister().GetRecoveryFlow(context.Background(), id.ID) require.NoError(t, err) CompareWithFixture(t, actual, "recovery_flow", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_flow"), found) }) t.Run("case=verification_flow", func(t *testing.T) { t.Parallel() var ids []verification.Flow require.NoError(t, c.Select("id").All(&ids)) require.NotEmpty(t, ids) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) var found []string for _, id := range ids { found = append(found, id.ID.String()) actual, err := d.VerificationFlowPersister().GetVerificationFlow(context.Background(), id.ID) require.NoError(t, err) CompareWithFixture(t, actual, "verification_flow", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "verification_flow"), found) }) t.Run("case=recovery_token", func(t *testing.T) { t.Parallel() var ids []link.RecoveryToken require.NoError(t, c.All(&ids)) require.NotEmpty(t, ids) var found []string for _, id := range ids { found = append(found, id.ID.String()) CompareWithFixture(t, id, "recovery_token", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_token"), found) }) t.Run("case=recovery_code", func(t *testing.T) { t.Parallel() var ids []code.RecoveryCode require.NoError(t, c.All(&ids)) require.NotEmpty(t, ids) var found []string for _, id := range ids { found = append(found, id.ID.String()) CompareWithFixture(t, id, "recovery_code", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_code"), found) }) t.Run("case=registration_code", func(t *testing.T) { t.Parallel() var ids []code.RegistrationCode require.NoError(t, c.All(&ids)) require.NotEmpty(t, ids) var found []string for _, id := range ids { found = append(found, id.ID.String()) CompareWithFixture(t, id, "registration_code", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "registration_code"), found) }) t.Run("case=login_code", func(t *testing.T) { t.Parallel() var ids []code.LoginCode require.NoError(t, c.All(&ids)) require.NotEmpty(t, ids) var found []string for _, id := range ids { found = append(found, id.ID.String()) CompareWithFixture(t, id, "login_code", id.ID.String()) } migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "login_code"), found) }) }) t.Run("suite=constraints", func(t *testing.T) { t.Cleanup(func() { // clean up test duplicates - remove identity_credential_identifiers 10985ed1-5b6e-4012-ac10-03d87df65618 - otherwise down migration later fails. require.NoError(t, c.RawQuery("DELETE FROM identity_credential_identifiers WHERE identifier = '10985ed1-5b6e-4012-ac10-03d87df65618'").Exec()) }) d, err := driver.New( context.Background(), os.Stderr, opts, ) require.NoError(t, err) sr, err := d.SettingsFlowPersister().GetSettingsFlow(context.Background(), x.ParseUUID("a79bfcf1-68ae-49de-8b23-4f96921b8341")) require.NoError(t, err) require.NoError(t, d.PrivilegedIdentityPool().DeleteIdentity(context.Background(), sr.IdentityID)) _, err = d.SettingsFlowPersister().GetSettingsFlow(context.Background(), x.ParseUUID("a79bfcf1-68ae-49de-8b23-4f96921b8341")) require.Error(t, err) require.ErrorIs(t, err, sqlcon.ErrNoRows) }) t.Run("suite=down", func(t *testing.T) { err = tm.Down(ctx, -1) // for easy breakpointing if !assert.NoError(t, err) { assert.NoError(t, tm.DumpMigrationSchema(ctx)) t.FailNow() } }) } ================================================ FILE: persistence/sql/migratest/stub/default.schema.json ================================================ { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "email": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } }, "verification": { "via": "email" }, "recovery": { "via": "email" } } } } } } } ================================================ FILE: persistence/sql/migratest/testdata/20150100000001_testdata.sql ================================================ INSERT INTO networks (id, created_at, updated_at) VALUES ('884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20191100000001_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000002_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000003_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000004_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000005_testdata.mysql.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000006_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000007_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000008_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000009_testdata.mysql.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000010_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000011_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20191100000012_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20200317160354_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20200401183443_testdata.sql ================================================ -- no test data up until v0.3.0 ================================================ FILE: persistence/sql/migratest/testdata/20200402142539_testdata.sql ================================================ INSERT INTO identities (id, traits_schema_id, traits, created_at, updated_at) VALUES ('a251ebc2-880c-4f76-a8f3-38e6940eab0e', 'default', '{"email":"foobar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identities (id, traits_schema_id, traits, created_at, updated_at) VALUES ('5ff66179-c240-4703-b0d8-494592cefff5', 'default', '{"email":"bazbar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO continuity_containers (id, identity_id, name, payload, expires_at, created_at, updated_at) VALUES ('50ba09d3-481b-4060-844a-9541a9cec39c', '5ff66179-c240-4703-b0d8-494592cefff5', 'ory_kratos_settings_profile', '{"traits":{"email":"baz@ory.sh"},"request_id":""}', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO courier_messages (id, type, status, body, subject, recipient, created_at, updated_at) VALUES ('98f45a85-0782-49f1-a7b5-8f83d160b4a5', 1, 2, 'Hi, Verify your account by opening the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/swmcFweNFSfvTSTKecmZjO6I8x0hxzZS', 'Please verify your email address', 'foo@ory.sh', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO courier_messages (id, type, status, body, subject, recipient, created_at, updated_at) VALUES ('7a9d6df4-3b60-4cae-996e-d15d78b0fc36', 1, 2, 'Hi, Verify your account by opening the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/u9ZcBr5HbRTR8f53Qj2Ng3KR8Mv1Zjdb', 'Please verify your email address', 'foobar@ory.sh', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO courier_messages (id, type, status, body, subject, recipient, created_at, updated_at) VALUES ('77fdc5e0-2260-49da-8aae-c36ba255d05b', 1, 2, 'Hi, Verify your account by opening the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/SQcSX0Jx6IVEDKqAuaLZLNEw00J4vlig', 'Please verify your email address', 'foobar@ory.sh', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO courier_messages (id, type, status, body, subject, recipient, created_at, updated_at) VALUES ('a3ea4c30-0c6e-47b1-99ba-8fa69282e166', 1, 2, 'Hi, Verify your account by opening the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/AtsREGbtXu0RlIcwv3RPpxHEZNEcq3R9', 'Please verify your email address', 'foo@ory.sh', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_credential_types (id, name) VALUES ('22bff9ae-f5aa-45d7-803b-97ec0b4e7b32', 'password'); INSERT INTO identity_credential_types (id, name) VALUES ('8071b37b-0d54-4c6f-8234-72cffb4ce784', 'totp'); INSERT INTO identity_credentials (id, config, identity_credential_type_id, identity_id, created_at, updated_at) VALUES ('35b60ecf-30f9-42d6-bf5d-47ad41148691', '{"hashed_password":"$argon2id$v=19$m=131072,t=2,p=1$lQFPaKxXqPL56/mU7vRi4w$6aldHyBnURt8sP8+xu41Ng"}', '22bff9ae-f5aa-45d7-803b-97ec0b4e7b32', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_credentials (id, config, identity_credential_type_id, identity_id, created_at, updated_at) VALUES ('74ac2d31-bccb-442f-a792-7b8bb14817f8', '{"hashed_password":"$argon2id$v=19$m=131072,t=2,p=1$lQFPaKxXqPL56/mU7vRi4w$6aldHyBnURt8sP8+xu41Ng"}', '22bff9ae-f5aa-45d7-803b-97ec0b4e7b32', '5ff66179-c240-4703-b0d8-494592cefff5', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_credential_identifiers (id, identifier, identity_credential_id, created_at, updated_at) VALUES ('f63a74f9-12da-439d-a8ce-b0157cd163b1', 'foobar@ory.sh', '35b60ecf-30f9-42d6-bf5d-47ad41148691', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_credential_identifiers (id, identifier, identity_credential_id, created_at, updated_at) VALUES ('1f51d652-8504-4d26-8615-989fd76b7ebf', 'foo@ory.sh', '74ac2d31-bccb-442f-a792-7b8bb14817f8', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_verifiable_addresses (id, code, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) VALUES ('c2427b6d-312b-46d9-9285-536db7ae11fd', 'SQcSX0Jx6IVEDKqAuaLZLNEw00J4vlig', 'pending', 'email', false, 'foobar@ory.sh', null, '2013-10-07 08:23:19', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_verifiable_addresses (id, code, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) VALUES ('45e867e9-2745-4f16-8dd4-84334a252b61', 'AtsREGbtXu0RlIcwv3RPpxHEZNEcq3R9', 'pending', 'email', false, 'foo@ory.sh', null, '2013-10-07 08:23:19', '5ff66179-c240-4703-b0d8-494592cefff5', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) VALUES ('3a9ea34f-0f12-469b-9417-3ae5795a7baa', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false); INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) VALUES ('916ded11-aa64-4a27-b06e-96e221a509d7', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'JdXCIeZGYglwUTjEC5PpKO5LFDEbeVOketqK5hdfZgFX379dVpLcRyRs+lteQJ1PxTMlyN0+btkz+5iVm4miIg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false); INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) VALUES ('99974ce6-388c-4669-a95a-7757ee724020', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'OXDnJU8yP+iXXsf2TduzRYrDLfksjC0vRdU+nN5bdo+YeQt5xGWKbqEX/gggqE1ntM7qavC/qqzaR2B1Pk88Ew==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false); INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) VALUES ('6d387820-f2f4-4f9f-9980-a90d89e7811f', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'Nrq+T0uNsSL8ztjmnbIB2V6NodqifoG+HZ4q6NSAnnWXs1ITwNoEpMqH4Rjwwf/7YIBmSX5NBj2CDHQBNJTU6Q==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false); INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) VALUES ('202c1981-1e25-47f0-8764-75ad506c2bec', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', '7esV9o+i9BJCvpBz3HXWxBPI2802lrGtt3+dBIwBZ/1M4vmqBPVBlHT3qY2xBijmLcUcXuqlNi4o7cPtbBUtYQ==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false); INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) VALUES ('43c99182-bb67-47e1-b564-bb23bd8d4393', 'http://kratos:4433/self-service/browser/flows/login?prompt=login&return_to=http%3A%2F%2F127.0.0.1%3A4455%2F.ory%2Fkratos%2Fpublic%2Fself-service%2Fbrowser%2Fflows%2Fsettings%2Fstrategies%2Fprofile%3Frequest%3D74fd6c53-7651-453e-90b8-2c5adbf911bb', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'dUD3MWqAmTdyoz2xwC9g5Dpv0g1aGUqAHOOsjy+IOh0vfPDqpKSudgZNSq9dqdxft4M0DN411z+5qSCMre5lmg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', true); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('9944614a-8f43-421d-aa7d-f051391e463f', 'password', '3a9ea34f-0f12-469b-9417-3ae5795a7baa', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=3a9ea34f-0f12-469b-9417-3ae5795a7baa","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('a2c9f597-6eb2-4ea5-972b-4f4ffa828a6c', 'oidc', '3a9ea34f-0f12-469b-9417-3ae5795a7baa', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/3a9ea34f-0f12-469b-9417-3ae5795a7baa","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('227aa2c4-acf3-489f-a239-7f18d4bc2828', 'password', '916ded11-aa64-4a27-b06e-96e221a509d7', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=916ded11-aa64-4a27-b06e-96e221a509d7","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"JdXCIeZGYglwUTjEC5PpKO5LFDEbeVOketqK5hdfZgFX379dVpLcRyRs+lteQJ1PxTMlyN0+btkz+5iVm4miIg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('bd498938-863f-41de-a60f-69af4220db36', 'oidc', '916ded11-aa64-4a27-b06e-96e221a509d7', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/916ded11-aa64-4a27-b06e-96e221a509d7","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"JdXCIeZGYglwUTjEC5PpKO5LFDEbeVOketqK5hdfZgFX379dVpLcRyRs+lteQJ1PxTMlyN0+btkz+5iVm4miIg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('9cdeaa86-1242-4c62-99b6-575d900d4c47', 'password', '99974ce6-388c-4669-a95a-7757ee724020', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=99974ce6-388c-4669-a95a-7757ee724020","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"OXDnJU8yP+iXXsf2TduzRYrDLfksjC0vRdU+nN5bdo+YeQt5xGWKbqEX/gggqE1ntM7qavC/qqzaR2B1Pk88Ew=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('b370b9bf-b785-4c8a-a013-245adc8db117', 'oidc', '99974ce6-388c-4669-a95a-7757ee724020', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/99974ce6-388c-4669-a95a-7757ee724020","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"OXDnJU8yP+iXXsf2TduzRYrDLfksjC0vRdU+nN5bdo+YeQt5xGWKbqEX/gggqE1ntM7qavC/qqzaR2B1Pk88Ew=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('df9bc6af-5b62-4f38-8bac-618937a67ff8', 'password', '6d387820-f2f4-4f9f-9980-a90d89e7811f', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=6d387820-f2f4-4f9f-9980-a90d89e7811f","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"Nrq+T0uNsSL8ztjmnbIB2V6NodqifoG+HZ4q6NSAnnWXs1ITwNoEpMqH4Rjwwf/7YIBmSX5NBj2CDHQBNJTU6Q=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('9cb49264-492d-49d2-8b19-ed1b7506d5ba', 'oidc', '6d387820-f2f4-4f9f-9980-a90d89e7811f', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/6d387820-f2f4-4f9f-9980-a90d89e7811f","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"Nrq+T0uNsSL8ztjmnbIB2V6NodqifoG+HZ4q6NSAnnWXs1ITwNoEpMqH4Rjwwf/7YIBmSX5NBj2CDHQBNJTU6Q=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('ccc3dbc3-302c-4f55-8f30-891975843ed4', 'password', '202c1981-1e25-47f0-8764-75ad506c2bec', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=202c1981-1e25-47f0-8764-75ad506c2bec","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"7esV9o+i9BJCvpBz3HXWxBPI2802lrGtt3+dBIwBZ/1M4vmqBPVBlHT3qY2xBijmLcUcXuqlNi4o7cPtbBUtYQ=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('49a44013-0650-4393-bad0-d3aaa7396fd6', 'oidc', '202c1981-1e25-47f0-8764-75ad506c2bec', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/202c1981-1e25-47f0-8764-75ad506c2bec","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"7esV9o+i9BJCvpBz3HXWxBPI2802lrGtt3+dBIwBZ/1M4vmqBPVBlHT3qY2xBijmLcUcXuqlNi4o7cPtbBUtYQ=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('6ba34380-19e5-473d-9181-e8f7def63235', 'oidc', '43c99182-bb67-47e1-b564-bb23bd8d4393', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/43c99182-bb67-47e1-b564-bb23bd8d4393","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"dUD3MWqAmTdyoz2xwC9g5Dpv0g1aGUqAHOOsjy+IOh0vfPDqpKSudgZNSq9dqdxft4M0DN411z+5qSCMre5lmg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_login_request_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('32477a6c-b6a5-482f-a218-287dc1208d8e', 'password', '43c99182-bb67-47e1-b564-bb23bd8d4393', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=43c99182-bb67-47e1-b564-bb23bd8d4393","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":"foo@ory.sh"},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"dUD3MWqAmTdyoz2xwC9g5Dpv0g1aGUqAHOOsjy+IOh0vfPDqpKSudgZNSq9dqdxft4M0DN411z+5qSCMre5lmg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) VALUES ('87fa3f43-5155-42b4-a1ad-174c2595fdaf', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) VALUES ('2bf132e0-5d40-4df9-9a11-9106e5333735', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'eyxQ2bNdAWBqzkZpRbzPQDpbDtpdqkkz2b7awuCdg6EJJi2lA4m/Lj7zhPYQb7snESM/I5vtdE6Qn8ixbEtHgg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) VALUES ('05a7f09d-4ef3-41fb-958a-6ad74584b36a', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'R3+y/kB6m+17C3HRkAYQJ+KOIpruSQnbplm+t3JiQd7mdl6iyy0ua01CSC/9de4F3IPlCTJ6jlg5y+BeknYLQg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) VALUES ('22d58184-b97d-44a5-bbaf-0aa8b4000d81', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'vtujrgwX3K+icdz8iHW3WD4VYd+QIGCb1NqjWcF+wj4f0k/yh0BpKZQ45QLlBkl6ABimTEwT5xhLSP2wIWqIog==', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('634a863d-2f86-40f3-aa82-29ac211e2484', 'password', '87fa3f43-5155-42b4-a1ad-174c2595fdaf', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=87fa3f43-5155-42b4-a1ad-174c2595fdaf","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"KeEp3OT+pHSXJpZAP+C1hR3yr4eIjUBDhg9tc/F1WI1b61SgVCoaOsMbVN9qM8HiNoqefk7KfT7PLn8AfaOcrg=="},{"name":"password","type":"password","required":true,"errors":[{"message":"the password does not fulfill the password policy because: the password has been found in data breaches and must no longer be used."}]},{"name":"traits.email","type":"text","value":"foo@ory.sh"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('08ce0a27-50ee-4e9a-aa75-2a842fb6825b', 'oidc', '87fa3f43-5155-42b4-a1ad-174c2595fdaf', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/87fa3f43-5155-42b4-a1ad-174c2595fdaf","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('fd51365b-b954-4a2a-9367-98a37cabf86c', 'password', '2bf132e0-5d40-4df9-9a11-9106e5333735', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=2bf132e0-5d40-4df9-9a11-9106e5333735","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"eyxQ2bNdAWBqzkZpRbzPQDpbDtpdqkkz2b7awuCdg6EJJi2lA4m/Lj7zhPYQb7snESM/I5vtdE6Qn8ixbEtHgg=="},{"name":"password","type":"password","required":true},{"name":"traits.email","type":"email"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('c65e1f49-d458-42a8-940b-8dc956804616', 'oidc', '2bf132e0-5d40-4df9-9a11-9106e5333735', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/2bf132e0-5d40-4df9-9a11-9106e5333735","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"eyxQ2bNdAWBqzkZpRbzPQDpbDtpdqkkz2b7awuCdg6EJJi2lA4m/Lj7zhPYQb7snESM/I5vtdE6Qn8ixbEtHgg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('39f85951-6d5d-43c8-afeb-a93d041f38e4', 'password', '05a7f09d-4ef3-41fb-958a-6ad74584b36a', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=05a7f09d-4ef3-41fb-958a-6ad74584b36a","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"R3+y/kB6m+17C3HRkAYQJ+KOIpruSQnbplm+t3JiQd7mdl6iyy0ua01CSC/9de4F3IPlCTJ6jlg5y+BeknYLQg=="},{"name":"password","type":"password","required":true},{"name":"traits.email","type":"email"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('8555b5d4-7f0c-4353-bddb-0601bc1c7387', 'oidc', '05a7f09d-4ef3-41fb-958a-6ad74584b36a', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/05a7f09d-4ef3-41fb-958a-6ad74584b36a","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"R3+y/kB6m+17C3HRkAYQJ+KOIpruSQnbplm+t3JiQd7mdl6iyy0ua01CSC/9de4F3IPlCTJ6jlg5y+BeknYLQg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('3573b093-4ea1-4f7c-bd90-2bd44275c36f', 'password', '22d58184-b97d-44a5-bbaf-0aa8b4000d81', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=22d58184-b97d-44a5-bbaf-0aa8b4000d81","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"vtujrgwX3K+icdz8iHW3WD4VYd+QIGCb1NqjWcF+wj4f0k/yh0BpKZQ45QLlBkl6ABimTEwT5xhLSP2wIWqIog=="},{"name":"password","type":"password","required":true},{"name":"traits.email","type":"email"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_request_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('c8853b14-a511-4367-a530-b79a514c701b', 'oidc', '22d58184-b97d-44a5-bbaf-0aa8b4000d81', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/auth/22d58184-b97d-44a5-bbaf-0aa8b4000d81","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"vtujrgwX3K+icdz8iHW3WD4VYd+QIGCb1NqjWcF+wj4f0k/yh0BpKZQ45QLlBkl6ABimTEwT5xhLSP2wIWqIog=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_requests (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) VALUES ('21c5f714-3089-49d2-b387-f244d4dd9e00', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile'); INSERT INTO selfservice_settings_requests (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) VALUES ('194c5b05-0487-4a11-bcbc-f301c9ff9678', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', true, 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', null); INSERT INTO selfservice_settings_requests (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) VALUES ('74fd6c53-7651-453e-90b8-2c5adbf911bb', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, '5ff66179-c240-4703-b0d8-494592cefff5', '2013-10-07 08:23:19', '2013-10-07 08:23:19', null); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('71896798-3844-4269-bdab-9a9f1f6160f3', 'profile', '21c5f714-3089-49d2-b387-f244d4dd9e00', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('e8cfd338-5112-4311-b2ec-97ebc07fd7e9', 'password', '21c5f714-3089-49d2-b387-f244d4dd9e00', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/password?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"Yohg7gsfztSw5KSEt/7+Tnbfc3QBmFA7zvqPg7DuhdgM8F5HOYmCxT3DDXTfq901/c1lShvHWFsAajdN60amVw=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('61d63a2d-ad74-433a-bcfb-2c93ae7b4d9c', 'oidc', '21c5f714-3089-49d2-b387-f244d4dd9e00', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/settings/connections?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"Yohg7gsfztSw5KSEt/7+Tnbfc3QBmFA7zvqPg7DuhdgM8F5HOYmCxT3DDXTfq901/c1lShvHWFsAajdN60amVw=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('1966b9ba-1fe2-46b9-97f9-e4f86cc8f15d', 'password', '194c5b05-0487-4a11-bcbc-f301c9ff9678', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/password?request=194c5b05-0487-4a11-bcbc-f301c9ff9678","method":"POST","fields":[{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"4apNE3S+VLL1UPu8GhyLOhdnhkzLZxrwTqZ/dx5xf3eP0nO6RigYo3h3UkxySahBnHWQctE4EpCANse5Rdlc+A=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('3095bd65-910b-4110-87fc-893e443df30c', 'oidc', '194c5b05-0487-4a11-bcbc-f301c9ff9678', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/settings/connections?request=194c5b05-0487-4a11-bcbc-f301c9ff9678","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"4apNE3S+VLL1UPu8GhyLOhdnhkzLZxrwTqZ/dx5xf3eP0nO6RigYo3h3UkxySahBnHWQctE4EpCANse5Rdlc+A=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('76da60ae-d629-48ed-92b2-5be892adbef7', 'profile', '194c5b05-0487-4a11-bcbc-f301c9ff9678', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=194c5b05-0487-4a11-bcbc-f301c9ff9678","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"IoLeYNbYJdALHIKvrgK7iYyMHA8r/rhG+6mr3z0nleRM+uDJ5E5pwYY7K1/GV5jyB54KMTGhsCY1ORMRZo+2aw=="},{"name":"traits.email","type":"text","value":"foobar@ory.sh"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('5ce98190-d1e8-4034-a361-7a3e686551eb', 'profile', '74fd6c53-7651-453e-90b8-2c5adbf911bb', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=74fd6c53-7651-453e-90b8-2c5adbf911bb","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"G/2FjcQNeTJHOFrPP0ytFfC/AWeUDDB4DQs6SsMRMjxBwYJWCilOczPWLdGiyhGufVPnZhAgrceoQbZJQXdtuw=="},{"name":"traits.email","type":"text","value":"baz@ory.sh"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('fb401162-9db6-4be4-b44c-01ce724dd817', 'password', '74fd6c53-7651-453e-90b8-2c5adbf911bb', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/password?request=74fd6c53-7651-453e-90b8-2c5adbf911bb","method":"POST","fields":[{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"4G7nFBBjlvSMDzqi3HXROK2ijGojM/2jXmoVHQPAz7i6UuDP3kehtfjhTbxB822DIE5qa6cfYBz7IJkegaaQPw=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_request_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('9068a486-20f2-42f1-b5b8-368aedbb7b89', 'oidc', '74fd6c53-7651-453e-90b8-2c5adbf911bb', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/settings/connections?request=74fd6c53-7651-453e-90b8-2c5adbf911bb","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"4G7nFBBjlvSMDzqi3HXROK2ijGojM/2jXmoVHQPAz7i6UuDP3kehtfjhTbxB822DIE5qa6cfYBz7IJkegaaQPw=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_verification_requests (id, request_url, issued_at, expires_at, form, via, csrf_token, success, created_at, updated_at) VALUES ('a8e2b810-a561-4a9e-bbd8-37f649bc26fa', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'null', 'email', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', true, '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_verification_requests (id, request_url, issued_at, expires_at, form, via, csrf_token, success, created_at, updated_at) VALUES ('b37d34c2-4290-4be4-9e3a-c6263ee77082', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/complete?request=b37d34c2-4290-4be4-9e3a-c6263ee77082","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"w+Amwv/g+QE/RaFUuc5/z7qnfFC2fxro9w9kUvoqhyBi6cqedLdMhwkMmKrUvYHthKq7w2pMnWtonTq7Gj7NvA=="},{"name":"to_verify","type":"email","required":true}]}', 'email', 'w+Amwv/g+QE/RaFUuc5/z7qnfFC2fxro9w9kUvoqhyBi6cqedLdMhwkMmKrUvYHthKq7w2pMnWtonTq7Gj7NvA==', false, '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200519101057_testdata.sql ================================================ INSERT INTO identity_recovery_addresses (id, via, value, identity_id, created_at, updated_at) VALUES ('b8293f1c-010f-45d9-b809-f3fc5365ba80', 'email', 'foobar@ory.sh', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_recovery_requests (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at) VALUES ('13178936-095a-466b-abe0-36d977d3dc18', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_recovery_request_methods (id, method, selfservice_recovery_request_id, config, created_at, updated_at) VALUES ('921462a6-8af6-4cda-97b4-dc0930ed271b', 'link', '13178936-095a-466b-abe0-36d977d3dc18', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_recovery_tokens (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_request_id, created_at, updated_at) VALUES ('5529d454-2946-404e-b681-d950f8657fd0', 'd40c167d-a7f2-41a6-86b2-a5483001a010', false, null, 'b8293f1c-010f-45d9-b809-f3fc5365ba80', '13178936-095a-466b-abe0-36d977d3dc18', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200519101058_testdata.mysql.sql ================================================ -- empty ================================================ FILE: persistence/sql/migratest/testdata/20200601101000_testdata.sql ================================================ INSERT INTO selfservice_settings_requests (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method, messages) VALUES ('a79bfcf1-68ae-49de-8b23-4f96921b8341', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '[]'); ================================================ FILE: persistence/sql/migratest/testdata/20200601101001_testdata.mysql.sql ================================================ -- empty ================================================ FILE: persistence/sql/migratest/testdata/20200605111551_testdata.sql ================================================ INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages) VALUES ('56d94e8b-8a5d-4b7f-8a6e-3259d2b2903e', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, '[]'); INSERT INTO selfservice_registration_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages) VALUES ('9edcf051-1cd0-44cc-bd2f-6ac21f0c24dd', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]'); INSERT INTO selfservice_verification_requests (id, request_url, issued_at, expires_at, form, via, csrf_token, success, created_at, updated_at, messages) VALUES ('29b2c16e-2955-4faa-bd16-33af098cdf83', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'null', 'email', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', true, '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]'); ================================================ FILE: persistence/sql/migratest/testdata/20200607165100_testdata.sql ================================================ INSERT INTO selfservice_settings_requests (id, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, messages) VALUES ('77fe4fb3-2d4e-4532-b568-c44b0aece0aa', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '[]'); ================================================ FILE: persistence/sql/migratest/testdata/20200705105359_testdata.sql ================================================ INSERT INTO identities (id, schema_id, traits, created_at, updated_at) VALUES ('d7b9addb-ac15-4bc2-9fa5-562e0bf48755', 'default', '{"email":"d7b9@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200810141652_testdata.sql ================================================ INSERT INTO selfservice_login_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages, type) VALUES ('47edd3a8-0998-4779-9469-f4b8ee4430df', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, '[]', 'api'); INSERT INTO selfservice_registration_requests (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages, type) VALUES ('696e7022-c466-44f6-89c6-8cf93c06a62a', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'api'); INSERT INTO selfservice_verification_requests (id, request_url, issued_at, expires_at, form, via, csrf_token, success, created_at, updated_at, messages, type) VALUES ('3631e880-ce59-4cbd-a705-0d825eea590d', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'null', 'email', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', true, '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'api'); INSERT INTO selfservice_settings_requests (id, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, messages) VALUES ('cdfd1eed-34a4-491d-ad0a-7579d3a0a7ba', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '[]'); INSERT INTO selfservice_recovery_requests (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type) VALUES ('87e871e1-a45f-4ed0-ba4e-a03063c774dc', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api'); ================================================ FILE: persistence/sql/migratest/testdata/20200810161022_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages, type) VALUES ('b1fac7fb-d016-4a06-a7fe-e4eab2a0429f', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, '[]', 'api'); INSERT INTO selfservice_login_flow_methods (id, method, selfservice_login_request_id, config, created_at, updated_at) VALUES ('8ca78d66-772a-4408-804e-f5d3d9fe696e', 'password', 'b1fac7fb-d016-4a06-a7fe-e4eab2a0429f', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=3a9ea34f-0f12-469b-9417-3ae5795a7baa","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_flows (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages, type) VALUES ('8ef215a9-e8d5-43b3-9aa3-cb4333562e36', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'api'); INSERT INTO selfservice_registration_flow_methods (id, method, selfservice_registration_request_id, config, created_at, updated_at) VALUES ('356019d1-900e-4c6d-b3f1-564c86930979', 'password', '8ef215a9-e8d5-43b3-9aa3-cb4333562e36', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=87fa3f43-5155-42b4-a1ad-174c2595fdaf","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"KeEp3OT+pHSXJpZAP+C1hR3yr4eIjUBDhg9tc/F1WI1b61SgVCoaOsMbVN9qM8HiNoqefk7KfT7PLn8AfaOcrg=="},{"name":"password","type":"password","required":true,"errors":[{"message":"the password does not fulfill the password policy because: the password has been found in data breaches and must no longer be used."}]},{"name":"traits.email","type":"text","value":"foo@ory.sh"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_flows (id, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, messages) VALUES ('90b4f970-b9ae-42bc-a0a7-73ec750e0aa1', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '[]'); INSERT INTO selfservice_settings_flow_methods (id, method, selfservice_settings_request_id, config, created_at, updated_at) VALUES ('547e8444-b3b0-4de7-9fdf-b80d6a8de15f', 'profile', '90b4f970-b9ae-42bc-a0a7-73ec750e0aa1', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_verification_flows (id, request_url, issued_at, expires_at, form, via, csrf_token, success, created_at, updated_at, messages, type) VALUES ('42f31e47-65e1-4be9-80ea-e5d8ed64b236', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'null', 'email', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', true, '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'api'); INSERT INTO selfservice_recovery_flows (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type) VALUES ('0d14427f-e16d-43a5-8695-8278bf85d4eb', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api'); INSERT INTO selfservice_recovery_flow_methods (id, method, selfservice_recovery_request_id, config, created_at, updated_at) VALUES ('1a45ce9b-b527-4535-ad31-b808e481d280', 'link', '0d14427f-e16d-43a5-8695-8278bf85d4eb', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200810162450_testdata.sql ================================================ INSERT INTO selfservice_login_flow_methods (id, method, selfservice_login_flow_id, config, created_at, updated_at) VALUES ('12aca2ae-248d-448c-a33c-5efb1b165238', 'password', 'b1fac7fb-d016-4a06-a7fe-e4eab2a0429f', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=3a9ea34f-0f12-469b-9417-3ae5795a7baa","method":"POST","fields":[{"name":"identifier","type":"text","required":true,"value":""},{"name":"password","type":"password","required":true},{"name":"csrf_token","type":"hidden","required":true,"value":"fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg=="}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_registration_flow_methods (id, method, selfservice_registration_flow_id, config, created_at, updated_at) VALUES ('4849e60b-bd37-4e51-9361-3b6c27f7b9f8', 'password', '8ef215a9-e8d5-43b3-9aa3-cb4333562e36', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=87fa3f43-5155-42b4-a1ad-174c2595fdaf","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"KeEp3OT+pHSXJpZAP+C1hR3yr4eIjUBDhg9tc/F1WI1b61SgVCoaOsMbVN9qM8HiNoqefk7KfT7PLn8AfaOcrg=="},{"name":"password","type":"password","required":true,"errors":[{"message":"the password does not fulfill the password policy because: the password has been found in data breaches and must no longer be used."}]},{"name":"traits.email","type":"text","value":"foo@ory.sh"}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_settings_flow_methods (id, method, selfservice_settings_flow_id, config, created_at, updated_at) VALUES ('d3501777-80cb-4fc6-b1ec-04c8d4d5b187', 'profile', '90b4f970-b9ae-42bc-a0a7-73ec750e0aa1', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_recovery_flow_methods (id, method, selfservice_recovery_flow_id, config, created_at, updated_at) VALUES ('1d53af7f-8379-4ad0-9e00-16a2963bea83', 'link', '0d14427f-e16d-43a5-8695-8278bf85d4eb', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?request=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200812124254_testdata.sql ================================================ INSERT INTO sessions (id, issued_at, expires_at, authenticated_at,created_at,updated_at, token, identity_id) VALUES ('8571e374-38f2-4f46-8ad3-b9d914e174d3','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19', '1001ba7ddd644cb68478e8947e4jfha', '5ff66179-c240-4703-b0d8-494592cefff5') ================================================ FILE: persistence/sql/migratest/testdata/20200812160551_testdata.sql ================================================ INSERT INTO sessions (id, issued_at, expires_at, authenticated_at,created_at,updated_at, token, identity_id, active) VALUES ('f38cdebe-e567-42c9-a562-1bd4dee40998','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19', '1001ba7ddd644cb68478e8947e4jfhb', '5ff66179-c240-4703-b0d8-494592cefff5', true) ================================================ FILE: persistence/sql/migratest/testdata/20200830121710_testdata.sql ================================================ INSERT INTO identity_recovery_tokens (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at) VALUES ('77ca3f5c-cd39-488b-9f1d-cc7166d14bdc', 'd4c66613-c4cd-47c7-8e26-a70ecc5f5b5f', false, null, 'b8293f1c-010f-45d9-b809-f3fc5365ba80', '13178936-095a-466b-abe0-36d977d3dc18', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200830130642_testdata.sql ================================================ -- nothing do to ================================================ FILE: persistence/sql/migratest/testdata/20200830130643_testdata.sql ================================================ -- nothing do to ================================================ FILE: persistence/sql/migratest/testdata/20200830130644_testdata.sql ================================================ -- nothing do to ================================================ FILE: persistence/sql/migratest/testdata/20200830130645_testdata.sql ================================================ -- nothing do to ================================================ FILE: persistence/sql/migratest/testdata/20200830130646_testdata.sql ================================================ INSERT INTO selfservice_verification_flows (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, created_at, updated_at) VALUES ('5385c962-0295-4575-9b1b-d7eef13c0eda', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '[]', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) VALUES ('42021826-ad73-44f1-9365-6e2ba455c883', 'link', '5385c962-0295-4575-9b1b-d7eef13c0eda', '{"action":"http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/settings/strategies/profile?flow=21c5f714-3089-49d2-b387-f244d4dd9e00","method":"POST","fields":[{"name":"csrf_token","type":"hidden","required":true,"value":"yDwSg0quCmc4kBl7lBqYwGh4W8awrc+TpeWiigZs3iemRCwqeDhGdrW3sIv8T7u742pN+Kryx/NrdRpEXcT9qA=="},{"name":"traits.email","type":"text","value":"foo","errors":[{"message":"validation failed"},{"message":"foo is not valid email"},{"message":"foo is not valid email"}]}]}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200830154602_testdata.sql ================================================ INSERT INTO identity_verification_tokens (id, token, used, used_at, identity_verifiable_address_id, selfservice_verification_flow_id, created_at, updated_at, expires_at, issued_at) VALUES ('ee56574d-2f0c-43f6-8d26-0062938ae330', '1001ba7ddd644cb68478e8947e4jfhc', false, null, '45e867e9-2745-4f16-8dd4-84334a252b61', '5385c962-0295-4575-9b1b-d7eef13c0eda', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_verification_tokens (id, token, used, used_at, identity_verifiable_address_id, selfservice_verification_flow_id, created_at, updated_at, expires_at, issued_at) VALUES ('f81fd924-23bb-4cdf-8fa0-56253eff6cc9', '1001ba7ddd644cb68478e8947e4jfhd', false, null, '45e867e9-2745-4f16-8dd4-84334a252b61', NULL, '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200830172221_testdata.sql ================================================ INSERT INTO identity_recovery_tokens (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) VALUES ('1b667e6d-8fda-4194-a765-08185185d7e4', '1001ba7ddd644cb68478e8947e4jfhe', false, null, 'b8293f1c-010f-45d9-b809-f3fc5365ba80', '13178936-095a-466b-abe0-36d977d3dc18', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20200831110752_testdata.sql ================================================ INSERT INTO identity_verifiable_addresses (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) VALUES ('b2d59320-8564-4400-a39f-a22a497a23f1', 'pending', 'email', false, 'foobar+without-code@ory.sh', null, 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20201201161451_testdata.sql ================================================ SELECT * FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migratest/testdata/20210118113234_testdata.sql ================================================ ================================================ FILE: persistence/sql/migratest/testdata/20210126114619_testdata.sql ================================================ ================================================ FILE: persistence/sql/migratest/testdata/20210307130558_testdata.sql ================================================ INSERT INTO courier_messages (id, type, status, body, subject, recipient, created_at, updated_at) VALUES ('34948489-31dc-454a-ab3b-b2dcc75a787f', 1, 2, 'Hi, Verify your account by opening the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/AtsREGbtXu0RlIcwv3RPpxHEZNEcq3R9', 'Please verify your email address', 'foo@ory.sh', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20210307130559_testdata.sql ================================================ INSERT INTO courier_messages (id, status, type, recipient, body, subject, template_type, template_data, created_at, updated_at) VALUES ('b2d59320-8564-4400-a39f-a22a497a23f1', 1, 1, 'foo@bar.com', 'body', 'subject', 'recovery_invalid', 'binary_data', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20210311102338_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui) VALUES ('0bc96cc9-dda4-4700-9e42-35731f2af91e', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}'); INSERT INTO selfservice_registration_flows (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui) VALUES ('e2150cdc-23ac-4940-a240-6c79c27ab029', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); INSERT INTO selfservice_settings_flows (id, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, ui) VALUES ('aeba85bd-1a8c-44bf-8fc3-3be83c01a3dc', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '{}'); INSERT INTO selfservice_verification_flows (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, ui) VALUES ('6aae3159-b880-4cfb-a863-03b114b1371b', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); INSERT INTO selfservice_recovery_flows (id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui) VALUES ('4963f305-e874-4a68-8424-a00bec679e7b', 'http://kratos:4433/self-service/browser/flows/recovery', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); ================================================ FILE: persistence/sql/migratest/testdata/20210410175418_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui) VALUES ('d6aa1f23-88c9-4b9b-a850-392f48c7f9e8', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}'); INSERT INTO selfservice_registration_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui) VALUES ('f1b5ed18-113a-4a98-aae7-d4eba007199c', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); INSERT INTO selfservice_settings_flows (id, nid, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, ui) VALUES ('19ede218-928c-4e02-ab49-b76e12b34f31', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '{}'); INSERT INTO selfservice_verification_flows (id, nid, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, ui) VALUES ('7be6c72c-c868-4b61-a1f0-1130603665d8', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); INSERT INTO selfservice_recovery_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui) VALUES ('68fb4010-84a9-4d1e-9f92-2705978ee89e', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/recovery', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); INSERT INTO courier_messages (id, nid, status, type, recipient, body, subject, template_type, template_data, created_at, updated_at) VALUES ('b821adf0-a067-4b3c-9f90-cac496d02a92', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 1, 1, 'foo@bar.com', 'body', 'subject', 'recovery_invalid', 'binary_data', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identity_verifiable_addresses (id, nid, status, via, verified, value, verified_at, identity_id, created_at, updated_at) VALUES ('d4718a67-aec2-418d-8173-6ebc7bde3b86', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'pending', 'email', false, 'foobar+11345642c6c0@ory.sh', null, 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at) VALUES ('196d8c1e-4f04-40f0-94b3-5ec43996b28a', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"foobar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at) VALUES ('ed253b2c-48ed-4c58-9b6f-1dc963c30a66', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"bazbar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20210504121624_testdata.sql ================================================ INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at, state) VALUES ('2ae6a5a7-2983-49e7-a4d8-7740b37c88cb','884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"d7b10@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'active'); INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at) VALUES ('359963ec-b09b-4ea0-aece-fb4dd95f304a','884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"d7b11@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19'); ================================================ FILE: persistence/sql/migratest/testdata/20210618103120_testdata.sql ================================================ INSERT INTO sessions (id, nid, issued_at, expires_at, authenticated_at,created_at,updated_at, token, identity_id, active, logout_token) VALUES ('dcde5aaa-f789-4d3d-ae1f-76da8d57e67c','884f556e-eb3a-4b9f-bee3-11345642c6c0','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19','2013-10-07 08:23:19', 'eVwBt7UWPweVwBt7UWPw', '5ff66179-c240-4703-b0d8-494592cefff5', true, '123eVwBt7UWPweVwBt7UWPw') ================================================ FILE: persistence/sql/migratest/testdata/20210805112414_testdata.sql ================================================ INSERT INTO selfservice_settings_flows (id, nid, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, ui, internal_context) VALUES ('8248bb5d-8ef7-45e3-8e07-9e2003dd5352', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '{}', '{"foo":"bar"}'); ================================================ FILE: persistence/sql/migratest/testdata/20210805122535_testdata.sql ================================================ -- nothing to do here because the schema did not change ================================================ FILE: persistence/sql/migratest/testdata/20210810153530_testdata.sql ================================================ INSERT INTO sessions (id, nid, issued_at, expires_at, authenticated_at, created_at, updated_at, token, identity_id, active, logout_token, aal, authentication_methods) VALUES ('7458af86-c1d8-401c-978a-8da89133f78b', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', '2080-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'eVwBt7UAAAAVwBt7UWPw', '5ff66179-c240-4703-b0d8-494592cefff5', true, '123eVwBt7UAAAeVwBt7UWPw', 'aal2', '[{"method":"password"},{"method":"totp"}]'); INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, requested_aal) VALUES ('1fb23c75-b809-42cc-8984-6ca2d0a1192f', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}', 'aal2'); ================================================ FILE: persistence/sql/migratest/testdata/20210813150152_testdata.sql ================================================ -- nothing to do here because the schema did not change ================================================ FILE: persistence/sql/migratest/testdata/20210816113956_testdata.sql ================================================ -- nothing to do here because the schema did not change ================================================ FILE: persistence/sql/migratest/testdata/20210816142650_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, requested_aal, internal_context) VALUES ('38caf592-b042-4551-b92f-8d5223c2a4e2', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}', 'aal2', '{"foo":"bar"}'); INSERT INTO selfservice_registration_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, internal_context) VALUES ('8f32efdc-f6fc-4c27-a3c2-579d109eff60', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/registration', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}', '{"foo":"bar"}'); ================================================ FILE: persistence/sql/migratest/testdata/20210817181232_testdata.sql ================================================ INSERT INTO identity_credential_identifiers (id, nid, identifier, identity_credential_id, created_at, updated_at, identity_credential_type_id) VALUES ('2672f198-4795-437c-8e14-56459b1d941a', '884f556e-eb3a-4b9f-bee3-11345642c6c0','foo1@ory.sh', '35b60ecf-30f9-42d6-bf5d-47ad41148691', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '22bff9ae-f5aa-45d7-803b-97ec0b4e7b32'); INSERT INTO identity_credential_identifiers (id, nid, identifier, identity_credential_id, created_at, updated_at, identity_credential_type_id) VALUES ('10985ed1-5b6e-4012-ac10-03d87df65618', '884f556e-eb3a-4b9f-bee3-11345642c6c0','foo2@ory.sh', '74ac2d31-bccb-442f-a792-7b8bb14817f8', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '6b213fa0-e6ad-46cb-8878-b088d2ce2e3c'); ================================================ FILE: persistence/sql/migratest/testdata/20210829131458_testdata.sql ================================================ -- empty because it is just an SQL insert ================================================ FILE: persistence/sql/migratest/testdata/20210913095309_testdata.sql ================================================ UPDATE identity_recovery_tokens SET identity_id = 'a251ebc2-880c-4f76-a8f3-38e6940eab0e' WHERE id = '5529d454-2946-404e-b681-d950f8657fd0'; UPDATE identity_recovery_tokens SET identity_id = 'a251ebc2-880c-4f76-a8f3-38e6940eab0e' WHERE id = '77ca3f5c-cd39-488b-9f1d-cc7166d14bdc'; UPDATE identity_recovery_tokens SET identity_id = 'a251ebc2-880c-4f76-a8f3-38e6940eab0e' WHERE id = '1b667e6d-8fda-4194-a765-08185185d7e4'; ================================================ FILE: persistence/sql/migratest/testdata/20220118104539_testdata.sql ================================================ ================================================ FILE: persistence/sql/migratest/testdata/20220301102701_testdata.sql ================================================ INSERT INTO identity_credentials (id, nid, config, identity_credential_type_id, identity_id, created_at, updated_at, version) VALUES ('4cefc264-4291-4abc-8f26-cc0217874f14', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '{"totp_url":"otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"}', '8071b37b-0d54-4c6f-8234-72cffb4ce784', '5ff66179-c240-4703-b0d8-494592cefff5', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 0); ================================================ FILE: persistence/sql/migratest/testdata/20220420102701_testdata.sql ================================================ INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at, metadata_public, metadata_admin) VALUES ('308929d3-41a2-43fe-a33c-75308539d841', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"bazbar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '{"foo":"bar"}', '{"baz":"bar"}'); ================================================ FILE: persistence/sql/migratest/testdata/20220607000001_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, requested_aal, internal_context, oauth2_login_challenge) VALUES ('349c945a-60f8-436a-a301-7a42c92604f9', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login?login_challenge=3caddfd599034bce83ffcae36f42dff7', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'browser', '{}', 'aal2', '{"foo":"bar"}', '3caddfd5-9903-4bce-83ff-cae36f42dff7'); INSERT INTO selfservice_registration_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, internal_context, oauth2_login_challenge) VALUES ('ef18b06e-4700-4021-9949-ef783cd86be8', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/registration?login_challenge=', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'browser', '{}', '{"foo":"bar"}', '3caddfd5-9903-4bce-83ff-cae36f42dff7'); ================================================ FILE: persistence/sql/migratest/testdata/20220902141902_testdata.sql ================================================ INSERT INTO identity_verification_tokens (id, token, used, used_at, identity_verifiable_address_id, selfservice_verification_flow_id, created_at, updated_at, expires_at, issued_at, nid) VALUES ('ee56574d-2f1c-43f6-8d26-0062938ae330', '1001ba7ddd644cb68478e8947e4jfhe', false, null, '45e867e9-2745-4f16-8dd4-84334a252b61', '5385c962-0295-4575-9b1b-d7eef13c0eda', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '884f556e-eb3a-4b9f-bee3-11345642c6c0'); ================================================ FILE: persistence/sql/migratest/testdata/20220907132836_testdata.sql ================================================ INSERT INTO sessions (id, nid, issued_at, expires_at, authenticated_at, created_at, updated_at, token, identity_id, active, logout_token, aal, authentication_methods) VALUES ('7458af86-c1d8-401c-978a-8da89133f98b', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'eVwBt7UAAAAVwBt7XAMw', '5ff66179-c240-4703-b0d8-494592cefff5', true, '123eVwBt7UAAAeVwBt7XAMw', 'aal2', '[{"method":"password"},{"method":"totp"}]'); INSERT INTO session_devices (id, nid, session_id, ip_address, user_agent, location, created_at, updated_at) VALUES ('884f556e-eb3a-4b9f-bee3-11763642c6c0', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '7458af86-c1d8-401c-978a-8da89133f98b', '54.155.246.232', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', 'Munich, Germany', '2022-08-07 08:23:19', '2022-08-09 08:35:19'); ================================================ FILE: persistence/sql/migratest/testdata/20220929124401_testdata.sql ================================================ INSERT INTO identity_recovery_codes (id, code, used_at, identity_recovery_address_id, code_type, expires_at, issued_at, selfservice_recovery_flow_id, created_at, updated_at, nid, identity_id) VALUES ( '8f75f5d9-9cb4-4848-9a73-9344f686f8a6', '7eb71370d8497734ec78dfe613bf0f08967e206d2b5c2fc1243be823cfcd57a7', null, 'b8293f1c-010f-45d9-b809-f3fc5365ba80', 1, '2022-08-18 08:28:18', '2022-08-18 07:28:18', '68fb4010-84a9-4d1e-9f92-2705978ee89e', '2022-08-18 07:28:18', '2022-08-18 07:28:18', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '308929d3-41a2-43fe-a33c-75308539d841' ) ================================================ FILE: persistence/sql/migratest/testdata/20221103120601_testdata.sql ================================================ INSERT INTO selfservice_verification_flows ( id, nid, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, ui, submit_count ) VALUES ( '81f74e5d-1fa5-4e1b-a9bf-e9511926047c', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/verification/email', '2022-11-03 08:23:19', '2022-11-03 08:23:19', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', '2022-11-03 08:23:19', '2022-11-03 08:23:19', 'api', '{}', 0 ); INSERT INTO identity_verification_codes ( id, code_hmac, used_at, identity_verifiable_address_id, expires_at, issued_at, selfservice_verification_flow_id, created_at, updated_at, nid ) VALUES ( '5ab4e72b-332a-48ab-b1e9-3b1ef1ba5b60', '7eb71370d8497734ec78dfe613bf0f08967e206d2b5c2fc1243be823cfcd57a7', null, '45e867e9-2745-4f16-8dd4-84334a252b61', '2022-11-03 08:28:18', '2022-11-03 08:28:18', '81f74e5d-1fa5-4e1b-a9bf-e9511926047c', '2022-11-03 08:28:18', '2022-11-03 08:28:18', '884f556e-eb3a-4b9f-bee3-11345642c6c0' ); ================================================ FILE: persistence/sql/migratest/testdata/20221205095201_testdata.sql ================================================ INSERT INTO courier_messages ( id, type, status, body, subject, recipient, created_at, updated_at, template_type, nid, send_count ) VALUES ( 'd9d4401c-08a1-434c-8ab5-4a7edefde351', 1, 2, 'Hi, Verify your account by opening the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/u9ZcBr5HbRTR8f53Qj2Ng3KR8Mv1Zjdb', 'Please verify your email address', 'foobar@ory.sh', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'verification_valid', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 4 ); INSERT INTO courier_message_dispatches VALUES ( 'ea0c9a03-44ce-43a8-8c5d-52f468991cb9', 'd9d4401c-08a1-434c-8ab5-4a7edefde351', 'success', '{}', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', '2013-10-07 08:23:19' ) ================================================ FILE: persistence/sql/migratest/testdata/20230313141439_testdata.sql ================================================ INSERT INTO sessions (id, nid, issued_at, expires_at, authenticated_at, created_at, updated_at, token, identity_id, active, logout_token, aal, authentication_methods) VALUES ('068f6bb6-d15f-436d-94f7-b3fd0489c9ef', '884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'ory_lo_5e5aad0f7a4143452df3d23733a68e3', '5ff66179-c240-4703-b0d8-494592cefff5', true, 'ory_st_5e5aad0f7a4143452df3d23733a68e2', 'aal2', '[{"method":"password"},{"method":"totp"}]'); ================================================ FILE: persistence/sql/migratest/testdata/20230614205200_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, internal_context, oauth2_login_challenge_data) VALUES ('cccccccc-dda4-4700-9e42-35731f2af91e', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}', '{"foo":"bar"}', 'challenge data'); ================================================ FILE: persistence/sql/migratest/testdata/20230705000000_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, internal_context, oauth2_login_challenge_data) VALUES ('cccccccc-dda4-4700-9e42-35731f2af911', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}', '{"foo":"bar"}', 'challenge data'); INSERT INTO selfservice_verification_flows (id, nid, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, ui, submit_count) VALUES ('81f74e5d-1fa5-4e1b-a9bf-e95119260471', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email/self-service/browser/flows/verification/email', '2022-11-03 08:23:19', '2022-11-03 08:23:19', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', '2022-11-03 08:23:19', '2022-11-03 08:23:19', 'api', '{}', 0); INSERT INTO selfservice_registration_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, internal_context, oauth2_login_challenge) VALUES ('ef18b06e-4700-4021-9949-ef783cd86be1', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge=', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'browser', '{}', '{"foo":"bar"}', '3caddfd5-9903-4bce-83ff-cae36f42dff7'); INSERT INTO selfservice_settings_flows (id, nid, request_url, issued_at, expires_at, state, identity_id, created_at, updated_at, active_method, ui,internal_context) VALUES ('19ede218-928c-4e02-ab49-b76e12b34f32', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings/self-service/browser/flows/settings', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'show_form', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'profile', '{}', '{}'); INSERT INTO selfservice_verification_flows (id, nid, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, ui) VALUES ('7be6c72c-c868-4b61-a1f0-1130603665d1', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/verification/email', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '8xoIMa1+UkDqTt+tIHmIEHztQkk0AWk2PJhWWYDmB6dSE+RtJinnxtwH5lNNCnYyQuCF2ugy7rWjCgiwYPJNOw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); INSERT INTO selfservice_recovery_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui) VALUES ('68fb4010-84a9-4d1e-9f92-2705978ee891', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery/self-service/browser/flows/recovery', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'link', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', 'choose_method', 'a251ebc2-880c-4f76-a8f3-38e6940eab0e', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'api', '{}'); ================================================ FILE: persistence/sql/migratest/testdata/20230706000000_testdata.sql ================================================ INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at, metadata_public, metadata_admin, available_aal) VALUES ('0149ce5f-76a8-4efe-b2e3-431b8c6cceb6', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"bazbar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '{"foo":"bar"}', '{"baz":"bar"}', 'aal1'); INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at, metadata_public, metadata_admin, available_aal) VALUES ('0149ce5f-76a8-4efe-b2e3-431b8c6cceb7', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"bazbarbar@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '{"foo":"bar"}', '{"baz":"bar"}', NULL); ================================================ FILE: persistence/sql/migratest/testdata/20230707133700_testdata.sql ================================================ INSERT INTO selfservice_login_flows (id,nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, internal_context, oauth2_login_challenge_data, state) VALUES ('00b1517f-2467-4aaf-b0a5-82b4a27dcaf5', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '', 'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}', '{"foo":"bar"}', 'challenge data', 'choose_method'); INSERT INTO identities (id, nid, schema_id, traits, created_at, updated_at, metadata_public, metadata_admin, available_aal) VALUES ('28ff0031-190b-4253-bd15-14308dec013e', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'default', '{"email":"bazbarbarfoo@ory.sh"}', '2013-10-07 08:23:19', '2013-10-07 08:23:19', '{"foo":"bar"}', '{"baz":"bar"}', NULL); INSERT INTO identity_login_codes (id, code, address, address_type, used_at, expires_at, issued_at, selfservice_login_flow_id, identity_id, created_at, updated_at, nid) VALUES ('bd292366-af32-4ba6-bdf0-11d6d1a217f3', '7eb71370d8497734ec78dfe613bf0f08967e206d2b5c2fc1243be823cfcd57a7', 'bazbarbarfoo@ory.com', 'email', null, '2022-08-18 08:28:18', '2022-08-18 07:28:18', '00b1517f-2467-4aaf-b0a5-82b4a27dcaf5', '28ff0031-190b-4253-bd15-14308dec013e', '2022-08-18 07:28:18', '2022-08-18 07:28:18', '884f556e-eb3a-4b9f-bee3-11345642c6c0' ) ================================================ FILE: persistence/sql/migratest/testdata/20230707133701_testdata.sql ================================================ INSERT INTO selfservice_registration_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, internal_context, oauth2_login_challenge, state) VALUES ('69c80296-36cd-4afc-921a-15369cac5bf0', '884f556e-eb3a-4b9f-bee3-11345642c6c0', 'http://kratos:4433/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge=', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==', '2013-10-07 08:23:19', '2013-10-07 08:23:19', 'browser', '{}', '{"foo":"bar"}', '3caddfd5-9903-4bce-83ff-cae36f42dff7', 'choose_method'); INSERT INTO identity_registration_codes (id, address, address_type, code, used_at, expires_at, issued_at, selfservice_registration_flow_id, created_at, updated_at, nid) VALUES ('f1f66a69-ce02-4a12-9591-9e02dda30a0d', 'example@example.com', 'email', '7eb71370d8497734ec78dfe613bf0f08967e206d2b5c2fc1243be823cfcd57a7', null, '2022-08-18 08:28:18', '2022-08-18 07:28:18', '69c80296-36cd-4afc-921a-15369cac5bf0', '2022-08-18 07:28:18', '2022-08-18 07:28:18', '884f556e-eb3a-4b9f-bee3-11345642c6c0' ) ================================================ FILE: persistence/sql/migrations/go/20251105000000000000_identity_id_not_null_fks.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package gomigrations import ( "fmt" "github.com/pkg/errors" "github.com/ory/pop/v6" "github.com/ory/x/popx" ) var backfillIdentityID = []popx.Migration{ { Version: "20251105000000000001", Path: path(), Name: "Backfill column identity_id in identity_credential_identifiers", Direction: "up", Type: "go", DBType: "cockroach", Autocommit: true, Runner: func(m popx.Migration, c *pop.Connection) error { _, err := c.Store.Exec("CREATE INDEX IF NOT EXISTS ici_identity_id_backfill_storing ON identity_credential_identifiers (identity_id ASC) STORING (identity_credential_id) NOT VISIBLE") if err != nil { return errors.WithStack(err) } for { res, err := c.Store.Exec(` UPDATE identity_credential_identifiers@ici_identity_id_backfill_storing ici SET identity_id = ic.identity_id FROM identity_credentials ic WHERE ici.identity_credential_id = ic.id -- AND ici.nid = ic.nid -- not needed because JOIN predicate is on primary key AND ici.identity_id IS NULL LIMIT 10000`) if err != nil { return errors.WithStack(err) } n, err := res.RowsAffected() if err != nil { return errors.WithStack(err) } if n == 0 { break } fmt.Printf("Backfilled column identity_id for %d rows in identity_credential_identifiers table\n", n) } _, err = c.Store.Exec("DROP INDEX identity_credential_identifiers@ici_identity_id_backfill_storing") return errors.WithStack(err) }, }, { Version: "20251105000000000001", Path: path(), Name: "Backfill column identity_id in identity_credential_identifiers", Direction: "up", Type: "go", DBType: "postgres", Autocommit: true, Runner: func(m popx.Migration, c *pop.Connection) error { for { res, err := c.Store.Exec(` WITH to_update AS ( SELECT ici.id, ic.identity_id FROM identity_credential_identifiers ici JOIN identity_credentials ic ON ici.identity_credential_id = ic.id AND ici.nid = ic.nid WHERE ici.identity_id IS NULL LIMIT 10000 ) UPDATE identity_credential_identifiers ici SET identity_id = to_update.identity_id FROM to_update WHERE ici.id = to_update.id`) if err != nil { return errors.WithStack(err) } n, err := res.RowsAffected() if err != nil { return errors.WithStack(err) } if n == 0 { break } fmt.Printf("Backfilled column identity_id for %d rows in identity_credential_identifiers table\n", n) } return nil }, }, { Version: "20251105000000000001", Path: path(), Name: "Backfill column identity_id in identity_credential_identifiers", Direction: "up", Type: "go", DBType: "mysql", Autocommit: true, Runner: func(m popx.Migration, c *pop.Connection) error { for { res, err := c.Store.Exec(` UPDATE identity_credential_identifiers ici JOIN ( SELECT ici2.id FROM identity_credential_identifiers ici2 JOIN identity_credentials ic ON ici2.identity_credential_id = ic.id AND ici2.nid = ic.nid WHERE ici2.identity_id IS NULL LIMIT 10000 ) t ON ici.id = t.id JOIN identity_credentials ic ON ici.identity_credential_id = ic.id SET ici.identity_id = ic.identity_id`) if err != nil { return errors.WithStack(err) } n, err := res.RowsAffected() if err != nil { return errors.WithStack(err) } if n == 0 { break } fmt.Printf("Backfilled column identity_id for %d rows in identity_credential_identifiers table\n", n) } return nil }, }, { Version: "20251105000000000001", Path: path(), Name: "Backfill column identity_id in identity_credential_identifiers (noop)", Direction: "up", Type: "go", DBType: "sqlite3", Autocommit: false, Runner: func(m popx.Migration, c *pop.Connection) error { return nil // nothing, see 20251104000000000000_identifiers_devices_identity_id.sqlite.up.sql }, }, { Version: "20251105000000000001", Path: path(), Name: "Revert backfill column identity_id in identity_credential_identifiers (noop)", Direction: "down", Type: "go", DBType: "all", Runner: func(m popx.Migration, c *pop.Connection) error { return nil }, }, { Version: "20251105000000000002", Path: path(), Name: "Backfill column identity_id in session_devices", Direction: "up", Type: "go", DBType: "cockroach", Autocommit: true, Runner: func(m popx.Migration, c *pop.Connection) error { for { res, err := c.Store.Exec(` UPDATE session_devices sd SET identity_id = s.identity_id FROM sessions s WHERE sd.session_id = s.id AND sd.nid = s.nid AND sd.identity_id IS NULL LIMIT 10000`) if err != nil { return errors.WithStack(err) } n, err := res.RowsAffected() if err != nil { return errors.WithStack(err) } if n == 0 { break } fmt.Printf("Backfilled column identity_id for %d rows in session_devices table\n", n) } return nil }, }, { Version: "20251105000000000002", Path: path(), Name: "Backfill column identity_id in session_devices", Direction: "up", Type: "go", DBType: "postgres", Autocommit: true, Runner: func(m popx.Migration, c *pop.Connection) error { for { res, err := c.Store.Exec(` WITH to_update AS ( SELECT sd.id, s.identity_id FROM session_devices sd JOIN sessions s ON sd.session_id = s.id AND sd.nid = s.nid WHERE sd.identity_id IS NULL LIMIT 10000 ) UPDATE session_devices sd SET identity_id = to_update.identity_id FROM to_update WHERE sd.id = to_update.id`) if err != nil { return errors.WithStack(err) } n, err := res.RowsAffected() if err != nil { return errors.WithStack(err) } if n == 0 { break } fmt.Printf("Backfilled column identity_id for %d rows in session_devices table\n", n) } return nil }, }, { Version: "20251105000000000002", Path: path(), Name: "Backfill column identity_id in session_devices", Direction: "up", Type: "go", DBType: "mysql", Autocommit: true, Runner: func(m popx.Migration, c *pop.Connection) error { for { res, err := c.Store.Exec(` UPDATE session_devices sd JOIN ( SELECT sd2.id FROM session_devices sd2 JOIN sessions s2 ON sd2.session_id = s2.id AND sd2.nid = s2.nid WHERE sd2.identity_id IS NULL LIMIT 10000 ) t ON sd.id = t.id JOIN sessions s ON sd.session_id = s.id SET sd.identity_id = s.identity_id`) if err != nil { return errors.WithStack(err) } n, err := res.RowsAffected() if err != nil { return errors.WithStack(err) } if n == 0 { break } fmt.Printf("Backfilled column identity_id for %d rows in session_devices table\n", n) } return nil }, }, { Version: "20251105000000000002", Path: path(), Name: "Backfill column identity_id in session_devices (noop)", Direction: "up", Type: "go", DBType: "sqlite3", Autocommit: false, Runner: func(m popx.Migration, c *pop.Connection) error { return nil // nothing, see 20251104000000000000_identifiers_devices_identity_id.sqlite.up.sql }, }, { Version: "20251105000000000002", Path: path(), Name: "Revert backfill column identity_id in session_devices (noop)", Direction: "down", Type: "go", DBType: "all", Autocommit: false, Runner: func(m popx.Migration, c *pop.Connection) error { return nil }, }, } ================================================ FILE: persistence/sql/migrations/go/gomigrations.go ================================================ // Copyright © 2025 Ory Corp // SPDX-License-Identifier: Apache-2.0 package gomigrations import ( "embed" "fmt" "path/filepath" "runtime" "slices" ) var ( All = slices.Concat( backfillIdentityID, ) //go:embed *.go Src embed.FS ) func path() string { _, file, line, _ := runtime.Caller(1) return fmt.Sprintf("%s:%d", filepath.Base(file), line) } ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credential_identifiers";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_credentials";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_credential_types";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identities";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.cockroach.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" json NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credentials_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, CONSTRAINT "identity_credentials_identity_credential_types_id_fk" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credential_identifiers_identity_credentials_id_fk" FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.mysql.down.sql ================================================ DROP TABLE `identity_credential_identifiers`; DROP TABLE `identity_credentials`; DROP TABLE `identity_credential_types`; DROP TABLE `identities`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.mysql.up.sql ================================================ CREATE TABLE `identities` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `traits_schema_id` VARCHAR (2048) NOT NULL, `traits` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; CREATE TABLE `identity_credential_types` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `name` VARCHAR (32) NOT NULL ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_credential_types_name_idx` ON `identity_credential_types` (`name`); CREATE TABLE `identity_credentials` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `config` JSON NOT NULL, `identity_credential_type_id` char(36) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade, FOREIGN KEY (`identity_credential_type_id`) REFERENCES `identity_credential_types` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `identity_credential_identifiers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identifier` VARCHAR (255) NOT NULL, `identity_credential_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_credential_id`) REFERENCES `identity_credentials` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_idx` ON `identity_credential_identifiers` (`identifier`); ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.postgres.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; DROP TABLE "identity_credentials"; DROP TABLE "identity_credential_types"; DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.postgres.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL ); CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" jsonb NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade ); CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; DROP TABLE "identity_credentials"; DROP TABLE "identity_credential_types"; DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000001_identities.sqlite3.up.sql ================================================ CREATE TABLE "identities" ( "id" TEXT PRIMARY KEY, "traits_schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); CREATE TABLE "identity_credential_types" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL ); CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); CREATE TABLE "identity_credentials" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON DELETE cascade ); CREATE TABLE "identity_credential_identifiers" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_login_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_login_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_registration_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_registration_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_profile_management_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_login_request_methods_selfservice_login_requests_id_fk" FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_registration_request_methods_selfservice_registration_requests_id_fk" FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_profile_management_requests_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_login_request_methods`; DROP TABLE `selfservice_login_requests`; DROP TABLE `selfservice_registration_request_methods`; DROP TABLE `selfservice_registration_requests`; DROP TABLE `selfservice_profile_management_requests`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_login_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; CREATE TABLE `selfservice_login_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_login_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_login_request_id`) REFERENCES `selfservice_login_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `selfservice_registration_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; CREATE TABLE `selfservice_registration_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_registration_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_registration_request_id`) REFERENCES `selfservice_registration_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `selfservice_profile_management_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `update_successful` bool NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; DROP TABLE "selfservice_login_requests"; DROP TABLE "selfservice_registration_request_methods"; DROP TABLE "selfservice_registration_requests"; DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade ); CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade ); CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; DROP TABLE "selfservice_login_requests"; DROP TABLE "selfservice_registration_request_methods"; DROP TABLE "selfservice_registration_requests"; DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000002_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); CREATE TABLE "selfservice_login_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_login_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_login_request_id) REFERENCES selfservice_login_requests (id) ON DELETE cascade ); CREATE TABLE "selfservice_registration_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); CREATE TABLE "selfservice_registration_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_registration_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_registration_request_id) REFERENCES selfservice_registration_requests (id) ON DELETE cascade ); CREATE TABLE "selfservice_profile_management_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.cockroach.down.sql ================================================ DROP TABLE "sessions";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.cockroach.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "sessions_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.mysql.down.sql ================================================ DROP TABLE `sessions`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.mysql.up.sql ================================================ CREATE TABLE `sessions` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `authenticated_at` DATETIME NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.postgres.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.postgres.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000003_sessions.sqlite3.up.sql ================================================ CREATE TABLE "sessions" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.cockroach.down.sql ================================================ DROP TABLE "selfservice_errors";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.cockroach.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" json NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.mysql.down.sql ================================================ DROP TABLE `selfservice_errors`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.mysql.up.sql ================================================ CREATE TABLE `selfservice_errors` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `errors` JSON NOT NULL, `seen_at` DATETIME NOT NULL, `was_seen` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.postgres.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.postgres.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" jsonb NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000004_errors.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME NOT NULL, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000005_identities.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255); ================================================ FILE: persistence/sql/migrations/legacy/20191100000005_identities.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) BINARY; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.cockroach.down.sql ================================================ DROP TABLE "courier_messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.cockroach.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.mysql.down.sql ================================================ DROP TABLE `courier_messages`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.mysql.up.sql ================================================ CREATE TABLE `courier_messages` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `type` INTEGER NOT NULL, `status` INTEGER NOT NULL, `body` VARCHAR (255) NOT NULL, `subject` VARCHAR (255) NOT NULL, `recipient` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.postgres.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.postgres.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000006_courier.sqlite3.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT '';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` DROP COLUMN `csrf_token`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` ADD COLUMN `csrf_token` VARCHAR (255) NOT NULL DEFAULT ""; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at) SELECT id, errors, seen_at, was_seen, created_at, updated_at FROM "selfservice_errors"; DROP TABLE "selfservice_errors"; ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000007_errors.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" TEXT NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.cockroach.down.sql ================================================ DROP TABLE "selfservice_verification_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_verifiable_addresses";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.cockroach.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verifiable_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.mysql.down.sql ================================================ DROP TABLE `selfservice_verification_requests`; DROP TABLE `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.mysql.up.sql ================================================ CREATE TABLE `identity_verifiable_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `code` VARCHAR (32) NOT NULL, `status` VARCHAR (16) NOT NULL, `via` VARCHAR (16) NOT NULL, `verified` bool NOT NULL, `value` VARCHAR (400) NOT NULL, `verified_at` DATETIME, `expires_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`); CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); CREATE UNIQUE INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses` (`via`, `value`); CREATE INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses` (`via`, `value`); CREATE TABLE `selfservice_verification_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `via` VARCHAR (16) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `success` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.postgres.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.postgres.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000008_selfservice_verification.sqlite3.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" TEXT PRIMARY KEY, "code" TEXT NOT NULL, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); CREATE TABLE "selfservice_verification_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/legacy/20191100000009_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255); ================================================ FILE: persistence/sql/migrations/legacy/20191100000009_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.cockroach.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.mysql.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.postgres.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.sqlite3.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; DROP TABLE "selfservice_errors"; ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000010_errors.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; DROP TABLE "selfservice_errors"; ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000011_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" RENAME COLUMN "body" TO "_body_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "courier_messages" ADD COLUMN "body" text;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "courier_messages" SET "body" = "_body_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "courier_messages" ALTER COLUMN "body" SET NOT NULL;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "courier_messages" DROP COLUMN "_body_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000011_courier_body_type.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/legacy/20191100000011_courier_body_type.mysql.up.sql ================================================ ALTER TABLE `courier_messages` MODIFY `body` text NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20191100000011_courier_body_type.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "body" TYPE text, ALTER COLUMN "body" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20191100000011_courier_body_type.sqlite3.up.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at) SELECT id, type, status, body, subject, recipient, created_at, updated_at FROM "courier_messages"; DROP TABLE "courier_messages"; ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `forced`; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `forced` bool NOT NULL DEFAULT false; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_login_requests"; DROP TABLE "selfservice_login_requests"; ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20191100000012_login_request_forced.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.cockroach.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" json NOT NULL DEFAULT '{}';COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_profile_management_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.cockroach.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `form` JSON; UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ALTER TABLE `selfservice_profile_management_requests` MODIFY `form` JSON; DROP TABLE `selfservice_profile_management_request_methods`; ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `active_method`; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.mysql.up.sql ================================================ CREATE TABLE `selfservice_profile_management_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_profile_management_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `active_method` VARCHAR (32); INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `form`; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" jsonb; UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ALTER TABLE "selfservice_profile_management_requests" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL; DROP TABLE "selfservice_profile_management_request_methods"; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method"; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.postgres.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32); INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form"; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods"; CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful FROM "selfservice_profile_management_requests"; DROP TABLE "selfservice_profile_management_requests"; ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200317160354_create_profile_request_forms.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_profile_management_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" TEXT; INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) SELECT id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method FROM "selfservice_profile_management_requests"; DROP TABLE "selfservice_profile_management_requests"; ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.cockroach.down.sql ================================================ DROP TABLE "continuity_containers";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.cockroach.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" json, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "continuity_containers_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.mysql.down.sql ================================================ DROP TABLE `continuity_containers`; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.mysql.up.sql ================================================ CREATE TABLE `continuity_containers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identity_id` char(36), `name` VARCHAR (255) NOT NULL, `payload` JSON, `expires_at` DATETIME NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.postgres.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.postgres.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" jsonb, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.sqlite3.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/legacy/20200401183443_continuity_containers.sqlite3.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` CHANGE `selfservice_settings_request_id` `selfservice_profile_management_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_profile_management_request_methods`; ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_profile_management_requests`; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_request_methods` CHANGE `selfservice_profile_management_request_id` `selfservice_settings_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_profile_management_request_methods` RENAME TO `selfservice_settings_request_methods`; ALTER TABLE `selfservice_profile_management_requests` RENAME TO `selfservice_settings_requests`; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200402142539_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "identity_recovery_tokens";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_recovery_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_recovery_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "identity_recovery_addresses";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" json, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_requests_identities_id_fk" FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" json NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_request_methods_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk" FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `identity_recovery_tokens`; DROP TABLE `selfservice_recovery_request_methods`; DROP TABLE `selfservice_recovery_requests`; DROP TABLE `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `identity_recovery_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `via` VARCHAR (16) NOT NULL, `value` VARCHAR (400) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses` (`via`, `value`); CREATE INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses` (`via`, `value`); CREATE TABLE `selfservice_recovery_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `messages` JSON, `active_method` VARCHAR (32), `csrf_token` VARCHAR (255) NOT NULL, `state` VARCHAR (32) NOT NULL, `recovered_identity_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`recovered_identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `selfservice_recovery_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `config` JSON NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE TABLE `identity_recovery_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `identity_recovery_address_id` char(36) NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_recovery_address_id`) REFERENCES `identity_recovery_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_recovery_addresses_code_uq_idx` ON `identity_recovery_tokens` (`token`); CREATE INDEX `identity_recovery_addresses_code_idx` ON `identity_recovery_tokens` (`token`); ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; DROP TABLE "selfservice_recovery_request_methods"; DROP TABLE "selfservice_recovery_requests"; DROP TABLE "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" jsonb, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" jsonb NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; DROP TABLE "selfservice_recovery_request_methods"; DROP TABLE "selfservice_recovery_requests"; DROP TABLE "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/legacy/20200519101057_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" TEXT PRIMARY KEY, "via" TEXT NOT NULL, "value" TEXT NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); CREATE TABLE "selfservice_recovery_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON DELETE cascade ); CREATE TABLE "selfservice_recovery_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "config" TEXT NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ); CREATE TABLE "identity_recovery_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/legacy/20200519101058_create_recovery_addresses.mysql.down.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64); ================================================ FILE: persistence/sql/migrations/legacy/20200519101058_create_recovery_addresses.mysql.up.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) BINARY; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200601101000_create_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/legacy/20200601101001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: persistence/sql/migrations/legacy/20200601101001_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(32) BINARY; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" json;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_requests` DROP COLUMN `messages`; ALTER TABLE `selfservice_login_requests` DROP COLUMN `messages`; ALTER TABLE `selfservice_registration_requests` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` ADD COLUMN `messages` JSON; ALTER TABLE `selfservice_login_requests` ADD COLUMN `messages` JSON; ALTER TABLE `selfservice_registration_requests` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages"; ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages"; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" jsonb; ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" jsonb; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ); INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, via, success FROM "selfservice_verification_requests"; DROP TABLE "selfservice_verification_requests"; ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false' ); INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced FROM "selfservice_login_requests"; DROP TABLE "selfservice_login_requests"; ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_registration_requests"; DROP TABLE "selfservice_registration_requests"; ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200605111551_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" TEXT; ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" TEXT; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `state`; ALTER TABLE `selfservice_settings_requests` ADD COLUMN `update_successful` bool NOT NULL DEFAULT false; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ALTER TABLE `selfservice_settings_requests` DROP COLUMN `update_successful`; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state"; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful"; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/legacy/20200607165100_settings.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.cockroach.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.cockroach.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.mysql.down.sql ================================================ ALTER TABLE `identities` CHANGE `schema_id` `traits_schema_id` varchar(2048) NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.mysql.up.sql ================================================ ALTER TABLE `identities` CHANGE `traits_schema_id` `schema_id` varchar(2048) NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.postgres.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.postgres.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.sqlite3.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200705105359_rename_identities_schema.sqlite3.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_registration_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_settings_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_recovery_requests` DROP COLUMN `type`; ALTER TABLE `selfservice_verification_requests` DROP COLUMN `type`; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_registration_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_settings_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_recovery_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE `selfservice_verification_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type"; ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "messages" TEXT ); INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages FROM "selfservice_login_requests"; DROP TABLE "selfservice_login_requests"; ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT ); INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages FROM "selfservice_registration_requests"; DROP TABLE "selfservice_registration_requests"; ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests"; DROP TABLE "selfservice_settings_requests"; ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; CREATE TABLE "_selfservice_recovery_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_selfservice_recovery_requests_tmp" (id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, messages, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at FROM "selfservice_recovery_requests"; DROP TABLE "selfservice_recovery_requests"; ALTER TABLE "_selfservice_recovery_requests_tmp" RENAME TO "selfservice_recovery_requests"; CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ); INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, via, success FROM "selfservice_verification_requests"; DROP TABLE "selfservice_verification_requests"; ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200810141652_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` RENAME TO `selfservice_login_requests`; ALTER TABLE `selfservice_login_flow_methods` RENAME TO `selfservice_login_request_methods`; ALTER TABLE `selfservice_registration_flow_methods` RENAME TO `selfservice_registration_request_methods`; ALTER TABLE `selfservice_registration_flows` RENAME TO `selfservice_registration_requests`; ALTER TABLE `selfservice_settings_flow_methods` RENAME TO `selfservice_settings_request_methods`; ALTER TABLE `selfservice_settings_flows` RENAME TO `selfservice_settings_requests`; ALTER TABLE `selfservice_recovery_flow_methods` RENAME TO `selfservice_recovery_request_methods`; ALTER TABLE `selfservice_recovery_flows` RENAME TO `selfservice_recovery_requests`; ALTER TABLE `selfservice_verification_flows` RENAME TO `selfservice_verification_requests`; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_request_methods` RENAME TO `selfservice_login_flow_methods`; ALTER TABLE `selfservice_login_requests` RENAME TO `selfservice_login_flows`; ALTER TABLE `selfservice_registration_request_methods` RENAME TO `selfservice_registration_flow_methods`; ALTER TABLE `selfservice_registration_requests` RENAME TO `selfservice_registration_flows`; ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_settings_flow_methods`; ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_settings_flows`; ALTER TABLE `selfservice_recovery_request_methods` RENAME TO `selfservice_recovery_flow_methods`; ALTER TABLE `selfservice_recovery_requests` RENAME TO `selfservice_recovery_flows`; ALTER TABLE `selfservice_verification_requests` RENAME TO `selfservice_verification_flows`; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/legacy/20200810161022_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_flow_id` `selfservice_login_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_flow_id` `selfservice_registration_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_flow_id` `selfservice_settings_request_id` char(36) NOT NULL; ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_request_id` `selfservice_login_flow_id` char(36) NOT NULL; ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_request_id` `selfservice_registration_flow_id` char(36) NOT NULL; ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_request_id` `selfservice_settings_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200810162450_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.cockroach.up.sql ================================================ DELETE FROM sessions;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" RENAME COLUMN "token" TO "_token_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "sessions" SET "token" = "_token_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" DROP COLUMN "_token_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "sessions_token_idx" ON "sessions" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `token`; ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.mysql.up.sql ================================================ DELETE FROM sessions; ALTER TABLE `sessions` ADD COLUMN `token` VARCHAR (32); ALTER TABLE `sessions` MODIFY `token` VARCHAR (32); CREATE UNIQUE INDEX `sessions_token_uq_idx` ON `sessions` (`token`); CREATE INDEX `sessions_token_idx` ON `sessions` (`token`); ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token"; ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.postgres.up.sql ================================================ DELETE FROM sessions;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32); ALTER TABLE "sessions" ALTER COLUMN "token" TYPE VARCHAR (32), ALTER COLUMN "token" DROP NOT NULL; CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; DROP INDEX IF EXISTS "sessions_token_idx"; CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at FROM "sessions"; DROP TABLE "sessions"; ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/legacy/20200812124254_add_session_token.sqlite3.up.sql ================================================ DELETE FROM sessions; ALTER TABLE "sessions" ADD COLUMN "token" TEXT; CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions"; DROP TABLE "sessions"; ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `active`; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `active` boolean DEFAULT false; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active"; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; DROP INDEX IF EXISTS "sessions_token_uq_idx"; CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions"; DROP TABLE "sessions"; ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/legacy/20200812160551_add_session_revoke.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" NUMERIC DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.sqlite3.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200830121710_update_recovery_token.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" json NOT NULL DEFAULT '{}';COMMIT TRANSACTION;BEGIN TRANSACTION; DROP TABLE "selfservice_verification_flow_methods";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form';COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `form` JSON; UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ALTER TABLE `selfservice_verification_flows` MODIFY `form` JSON; DROP TABLE `selfservice_verification_flow_methods`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `active_method`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `state`; ALTER TABLE `selfservice_verification_flows` ADD COLUMN `via` VARCHAR (16) NOT NULL DEFAULT 'email'; ALTER TABLE `selfservice_verification_flows` ADD COLUMN `success` bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" jsonb; UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ALTER TABLE "selfservice_verification_flows" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL; DROP TABLE "selfservice_verification_flow_methods"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state"; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email'; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form' ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser' ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" TEXT NOT NULL DEFAULT 'email'; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130642_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/legacy/20200830130643_add_verification_methods.cockroach.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130643_add_verification_methods.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/legacy/20200830130643_add_verification_methods.mysql.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130643_add_verification_methods.postgres.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130643_add_verification_methods.sqlite3.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/legacy/20200830130644_add_verification_methods.cockroach.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL );COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830130644_add_verification_methods.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/legacy/20200830130644_add_verification_methods.mysql.up.sql ================================================ CREATE TABLE `selfservice_verification_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_verification_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ALTER TABLE `selfservice_verification_flows` ADD COLUMN `active_method` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/legacy/20200830130644_add_verification_methods.postgres.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/legacy/20200830130644_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" TEXT; ================================================ FILE: persistence/sql/migrations/legacy/20200830130645_add_verification_methods.cockroach.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/legacy/20200830130645_add_verification_methods.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/legacy/20200830130645_add_verification_methods.mysql.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/legacy/20200830130645_add_verification_methods.postgres.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/legacy/20200830130645_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/legacy/20200830130646_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830130646_add_verification_methods.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/legacy/20200830130646_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `form`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `via`; ALTER TABLE `selfservice_verification_flows` DROP COLUMN `success`; ================================================ FILE: persistence/sql/migrations/legacy/20200830130646_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via"; ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success"; ================================================ FILE: persistence/sql/migrations/legacy/20200830130646_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; DROP TABLE "selfservice_verification_flows"; ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.cockroach.down.sql ================================================ DROP TABLE "identity_verification_tokens";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.cockroach.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verification_tokens_identity_verifiable_addresses_id_fk" FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_verification_tokens_selfservice_verification_flows_id_fk" FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade );COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.mysql.down.sql ================================================ DROP TABLE `identity_verification_tokens`; ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.mysql.up.sql ================================================ CREATE TABLE `identity_verification_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `expires_at` DATETIME NOT NULL, `issued_at` DATETIME NOT NULL, `identity_verifiable_address_id` char(36) NOT NULL, `selfservice_verification_flow_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_verifiable_address_id`) REFERENCES `identity_verifiable_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_verification_flow_id`) REFERENCES `selfservice_verification_flows` (`id`) ON DELETE cascade ) ENGINE=InnoDB; CREATE UNIQUE INDEX `identity_verification_tokens_token_uq_idx` ON `identity_verification_tokens` (`token`); CREATE INDEX `identity_verification_tokens_token_idx` ON `identity_verification_tokens` (`token`); CREATE INDEX `identity_verification_tokens_verifiable_address_id_idx` ON `identity_verification_tokens` (`identity_verifiable_address_id`); CREATE INDEX `identity_verification_tokens_verification_flow_id_idx` ON `identity_verification_tokens` (`selfservice_verification_flow_id`); ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.postgres.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.postgres.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.sqlite3.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/legacy/20200830154602_add_verification_token.sqlite3.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade ); CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.cockroach.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00';COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.mysql.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36) NOT NULL; ALTER TABLE `identity_recovery_tokens` DROP COLUMN `expires_at`; ALTER TABLE `identity_recovery_tokens` DROP COLUMN `issued_at`; ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `expires_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE `identity_recovery_tokens` ADD COLUMN `issued_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36); ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.postgres.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at"; ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at"; ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.sqlite3.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/legacy/20200830172221_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens"; DROP TABLE "identity_recovery_tokens"; ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE identity_verifiable_addresses SET code = substr(md5(uuid_v4()), 0, 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "code" TO "_code_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32);COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_verifiable_addresses" SET "code" = "_code_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" SET NOT NULL;COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_code_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "expires_at" TO "_expires_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp;COMMIT TRANSACTION;BEGIN TRANSACTION; UPDATE "identity_verifiable_addresses" SET "expires_at" = "_expires_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_expires_at_tmp";COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code);COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx";COMMIT TRANSACTION;BEGIN TRANSACTION; DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code";COMMIT TRANSACTION;BEGIN TRANSACTION; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at";COMMIT TRANSACTION;BEGIN TRANSACTION; ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `code` VARCHAR (32); ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `expires_at` DATETIME; UPDATE identity_verifiable_addresses SET code = LEFT(SHA2(RANDOM_BYTES(32), 256), 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ALTER TABLE `identity_verifiable_addresses` MODIFY `code` VARCHAR (32) NOT NULL; ALTER TABLE `identity_verifiable_addresses` MODIFY `expires_at` DATETIME; CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`); CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses`; DROP INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses`; ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `code`; ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `expires_at`; ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32); ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp; UPDATE identity_verifiable_addresses SET code = substr(md5(random()::text), 0, 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" TYPE VARCHAR (32), ALTER COLUMN "code" SET NOT NULL; ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "expires_at" TYPE timestamp, ALTER COLUMN "expires_at" DROP NOT NULL; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_code_uq_idx"; DROP INDEX "identity_verifiable_addresses_code_idx"; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code"; ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at"; ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" TEXT; ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" DATETIME; UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL; UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/legacy/20200831110752_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; DROP TABLE "identity_verifiable_addresses"; ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/legacy/20201201161451_credential_types_values.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.cockroach.down.sql ================================================ DROP TABLE "networks"; ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.cockroach.up.sql ================================================ CREATE TABLE "networks" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.mysql.down.sql ================================================ DROP TABLE `networks`; ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.mysql.up.sql ================================================ CREATE TABLE `networks` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.postgres.down.sql ================================================ DROP TABLE "networks"; ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.postgres.up.sql ================================================ CREATE TABLE "networks" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.sqlite3.down.sql ================================================ DROP TABLE "networks"; ================================================ FILE: persistence/sql/migrations/sql/20150100000001000000_networks.sqlite3.up.sql ================================================ CREATE TABLE "networks" ( "id" TEXT PRIMARY KEY, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.cockroach.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.cockroach.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.mysql.down.sql ================================================ DROP TABLE `identities`; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.mysql.up.sql ================================================ CREATE TABLE `identities` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `traits_schema_id` VARCHAR (2048) NOT NULL, `traits` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.postgres.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.postgres.up.sql ================================================ CREATE TABLE "identities" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "traits_schema_id" VARCHAR (2048) NOT NULL, "traits" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.sqlite3.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000000_identities.sqlite3.up.sql ================================================ CREATE TABLE "identities" ( "id" TEXT PRIMARY KEY, "traits_schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credential_types"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.cockroach.up.sql ================================================ CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.mysql.down.sql ================================================ DROP TABLE `identity_credential_types`; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.mysql.up.sql ================================================ CREATE TABLE `identity_credential_types` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `name` VARCHAR (32) NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.postgres.down.sql ================================================ DROP TABLE "identity_credential_types"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.postgres.up.sql ================================================ CREATE TABLE "identity_credential_types" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "name" VARCHAR (32) NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_types"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000001_identities.sqlite3.up.sql ================================================ CREATE TABLE "identity_credential_types" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.mysql.down.sql ================================================ DROP TABLE `identity_credentials`; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_credential_types_name_idx` ON `identity_credential_types` (`name`); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.postgres.down.sql ================================================ DROP TABLE "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000002_identities.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_types_name_idx" ON "identity_credential_types" (name); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.cockroach.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.cockroach.up.sql ================================================ CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" json NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credentials_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, CONSTRAINT "identity_credentials_identity_credential_types_id_fk" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.mysql.down.sql ================================================ DROP TABLE `identity_credential_identifiers`; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.mysql.up.sql ================================================ CREATE TABLE `identity_credentials` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `config` JSON NOT NULL, `identity_credential_type_id` char(36) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade, FOREIGN KEY (`identity_credential_type_id`) REFERENCES `identity_credential_types` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.postgres.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.postgres.up.sql ================================================ CREATE TABLE "identity_credentials" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "config" jsonb NOT NULL, "identity_credential_type_id" UUID NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000003_identities.sqlite3.up.sql ================================================ CREATE TABLE "identity_credentials" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.cockroach.up.sql ================================================ CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_credential_identifiers_identity_credentials_id_fk" FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.mysql.up.sql ================================================ CREATE TABLE `identity_credential_identifiers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identifier` VARCHAR (255) NOT NULL, `identity_credential_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_credential_id`) REFERENCES `identity_credentials` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.postgres.up.sql ================================================ CREATE TABLE "identity_credential_identifiers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identifier" VARCHAR (255) NOT NULL, "identity_credential_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_credential_id") REFERENCES "identity_credentials" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000004_identities.sqlite3.up.sql ================================================ CREATE TABLE "identity_credential_identifiers" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_idx` ON `identity_credential_identifiers` (`identifier`); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000001000005_identities.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_profile_management_requests`; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_login_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000000_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_login_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_login_request_methods_selfservice_login_requests_id_fk" FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_registration_requests`; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_login_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_login_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_login_request_id`) REFERENCES `selfservice_login_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_login_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_login_request_id") REFERENCES "selfservice_login_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000001_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_login_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_login_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_login_request_id) REFERENCES selfservice_login_requests (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_registration_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_registration_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_registration_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `active_method` VARCHAR (32) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_registration_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_registration_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "active_method" VARCHAR (32) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000002_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_registration_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_registration_request_methods_selfservice_registration_requests_id_fk" FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_login_requests`; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_registration_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_registration_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_registration_request_id`) REFERENCES `selfservice_registration_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_registration_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_registration_request_id") REFERENCES "selfservice_registration_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000003_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_registration_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_registration_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_registration_request_id) REFERENCES selfservice_registration_requests (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.cockroach.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.cockroach.up.sql ================================================ CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_profile_management_requests_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.mysql.down.sql ================================================ DROP TABLE `selfservice_login_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.mysql.up.sql ================================================ CREATE TABLE `selfservice_profile_management_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `update_successful` bool NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.postgres.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.postgres.up.sql ================================================ CREATE TABLE "selfservice_profile_management_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "update_successful" bool NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20191100000002000004_requests.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_profile_management_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.cockroach.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.cockroach.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "sessions_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.mysql.down.sql ================================================ DROP TABLE `sessions`; ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.mysql.up.sql ================================================ CREATE TABLE `sessions` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `authenticated_at` DATETIME NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.postgres.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.postgres.up.sql ================================================ CREATE TABLE "sessions" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "authenticated_at" timestamp NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20191100000003000000_sessions.sqlite3.up.sql ================================================ CREATE TABLE "sessions" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.cockroach.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.cockroach.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" json NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.mysql.down.sql ================================================ DROP TABLE `selfservice_errors`; ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.mysql.up.sql ================================================ CREATE TABLE `selfservice_errors` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `errors` JSON NOT NULL, `seen_at` DATETIME NOT NULL, `was_seen` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.postgres.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.postgres.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "errors" jsonb NOT NULL, "seen_at" timestamp NOT NULL, "was_seen" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000004000000_errors.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_errors" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME NOT NULL, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000005000000_identities.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000005000000_identities.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255) BINARY; ================================================ FILE: persistence/sql/migrations/sql/20191100000005000001_identities.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY COLUMN identifier VARCHAR(255); ================================================ FILE: persistence/sql/migrations/sql/20191100000005000001_identities.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.cockroach.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.cockroach.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.mysql.down.sql ================================================ DROP TABLE `courier_messages`; ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.mysql.up.sql ================================================ CREATE TABLE `courier_messages` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `type` INTEGER NOT NULL, `status` INTEGER NOT NULL, `body` VARCHAR (255) NOT NULL, `subject` VARCHAR (255) NOT NULL, `recipient` VARCHAR (255) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.postgres.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.postgres.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "type" int NOT NULL, "status" int NOT NULL, "body" VARCHAR (255) NOT NULL, "subject" VARCHAR (255) NOT NULL, "recipient" VARCHAR (255) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20191100000006000000_courier.sqlite3.up.sql ================================================ CREATE TABLE "courier_messages" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token"; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` DROP COLUMN `csrf_token`; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` ADD COLUMN `csrf_token` VARCHAR (255) NOT NULL DEFAULT ""; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "csrf_token"; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000000_errors.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "csrf_token" TEXT NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000001_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000001_errors.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000007000002_errors.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at) SELECT id, errors, seen_at, was_seen, created_at, updated_at FROM "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000007000002_errors.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000007000003_errors.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000007000003_errors.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.cockroach.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.cockroach.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verifiable_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.mysql.down.sql ================================================ DROP TABLE `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.mysql.up.sql ================================================ CREATE TABLE `identity_verifiable_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `code` VARCHAR (32) NOT NULL, `status` VARCHAR (16) NOT NULL, `via` VARCHAR (16) NOT NULL, `verified` bool NOT NULL, `value` VARCHAR (400) NOT NULL, `verified_at` DATETIME, `expires_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.postgres.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.postgres.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "code" VARCHAR (32) NOT NULL, "status" VARCHAR (16) NOT NULL, "via" VARCHAR (16) NOT NULL, "verified" bool NOT NULL, "value" VARCHAR (400) NOT NULL, "verified_at" timestamp, "expires_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000000_selfservice_verification.sqlite3.up.sql ================================================ CREATE TABLE "identity_verifiable_addresses" ( "id" TEXT PRIMARY KEY, "code" TEXT NOT NULL, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.cockroach.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.mysql.down.sql ================================================ DROP TABLE `selfservice_verification_requests`; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.postgres.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000001_selfservice_verification.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.mysql.up.sql ================================================ CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000002_selfservice_verification.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000003_selfservice_verification.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.mysql.up.sql ================================================ CREATE INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000004_selfservice_verification.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.cockroach.up.sql ================================================ CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" json NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.mysql.up.sql ================================================ CREATE TABLE `selfservice_verification_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `form` JSON NOT NULL, `via` VARCHAR (16) NOT NULL, `csrf_token` VARCHAR (255) NOT NULL, `success` bool NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.postgres.up.sql ================================================ CREATE TABLE "selfservice_verification_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "form" jsonb NOT NULL, "via" VARCHAR (16) NOT NULL, "csrf_token" VARCHAR (255) NOT NULL, "success" bool NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000008000005_selfservice_verification.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_verification_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "form" TEXT NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000009000000_verification.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000009000000_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: persistence/sql/migrations/sql/20191100000009000001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255); ================================================ FILE: persistence/sql/migrations/sql/20191100000009000001_verification.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `seen_at` DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "seen_at" TYPE timestamp, ALTER COLUMN "seen_at" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000000_errors.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.cockroach.down.sql ================================================ UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.mysql.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.postgres.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000001_errors.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000002_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "seen_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000002_errors.cockroach.up.sql ================================================ UPDATE "selfservice_errors" SET "seen_at" = "_seen_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000002_errors.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000002_errors.sqlite3.up.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000003_errors.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "seen_at" TO "_seen_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000003_errors.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "_seen_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000003_errors.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); ================================================ FILE: persistence/sql/migrations/sql/20191100000010000003_errors.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000004_errors.cockroach.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000004_errors.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000010000004_errors.sqlite3.down.sql ================================================ UPDATE selfservice_errors SET seen_at = '1980-01-01 00:00:00' WHERE seen_at = NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000010000004_errors.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" RENAME COLUMN "body" TO "_body_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.mysql.up.sql ================================================ ALTER TABLE `courier_messages` MODIFY `body` text NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "body" TYPE text, ALTER COLUMN "body" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000000_courier_body_type.sqlite3.up.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000011000001_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000001_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "body" text; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000001_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000001_courier_body_type.sqlite3.up.sql ================================================ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at) SELECT id, type, status, body, subject, recipient, created_at, updated_at FROM "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000002_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000002_courier_body_type.cockroach.up.sql ================================================ UPDATE "courier_messages" SET "body" = "_body_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000002_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000002_courier_body_type.sqlite3.up.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000003_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000003_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "body" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000003_courier_body_type.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000003_courier_body_type.sqlite3.up.sql ================================================ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20191100000011000004_courier_body_type.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000011000004_courier_body_type.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "_body_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced"; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `forced`; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `forced` bool NOT NULL DEFAULT false; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "forced"; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000000_login_request_forced.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "forced" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000001_login_request_forced.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000001_login_request_forced.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000012000002_login_request_forced.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20191100000012000002_login_request_forced.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20191100000012000003_login_request_forced.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20191100000012000003_login_request_forced.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.cockroach.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.cockroach.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `active_method`; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.mysql.up.sql ================================================ CREATE TABLE `selfservice_profile_management_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_profile_management_request_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "active_method"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.postgres.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_profile_management_request_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000000_create_profile_request_forms.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_profile_management_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_profile_management_request_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.cockroach.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.mysql.down.sql ================================================ DROP TABLE `selfservice_profile_management_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `active_method` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.postgres.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000001_create_profile_request_forms.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "active_method" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.cockroach.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" json NOT NULL DEFAULT '{}'; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.cockroach.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` MODIFY `form` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.mysql.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.postgres.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful, form) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, update_successful, form FROM "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000002_create_profile_request_forms.sqlite3.up.sql ================================================ INSERT INTO selfservice_profile_management_request_methods (id, method, selfservice_profile_management_request_id, config) SELECT id, 'traits', id, form FROM selfservice_profile_management_requests; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.mysql.down.sql ================================================ UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` DROP COLUMN `form`; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.postgres.down.sql ================================================ UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" DROP COLUMN "form"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "update_successful" bool NOT NULL DEFAULT 'false', "form" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000003_create_profile_request_forms.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "update_successful" bool NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000004_create_profile_request_forms.mysql.down.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` ADD COLUMN `form` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000004_create_profile_request_forms.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000004_create_profile_request_forms.postgres.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000004_create_profile_request_forms.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000004_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000004_create_profile_request_forms.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method) SELECT id, request_url, issued_at, expires_at, update_successful, identity_id, created_at, updated_at, active_method FROM "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000005_create_profile_request_forms.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000005_create_profile_request_forms.sqlite3.up.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000006_create_profile_request_forms.sqlite3.down.sql ================================================ DROP TABLE "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000006_create_profile_request_forms.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_profile_management_requests_tmp" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000007_create_profile_request_forms.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_profile_management_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful, form) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful, form FROM "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000007_create_profile_request_forms.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000008_create_profile_request_forms.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_profile_management_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "update_successful" bool NOT NULL DEFAULT 'false', "form" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000008_create_profile_request_forms.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000009_create_profile_request_forms.sqlite3.down.sql ================================================ UPDATE selfservice_profile_management_requests SET form=(SELECT * FROM (SELECT m.config FROM selfservice_profile_management_requests AS r INNER JOIN selfservice_profile_management_request_methods AS m ON r.id=m.selfservice_profile_management_request_id) as t); ================================================ FILE: persistence/sql/migrations/sql/20200317160354000009_create_profile_request_forms.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200317160354000010_create_profile_request_forms.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" ADD COLUMN "form" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200317160354000010_create_profile_request_forms.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.cockroach.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.cockroach.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" json, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "continuity_containers_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.mysql.down.sql ================================================ DROP TABLE `continuity_containers`; ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.mysql.up.sql ================================================ CREATE TABLE `continuity_containers` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `identity_id` char(36), `name` VARCHAR (255) NOT NULL, `payload` JSON, `expires_at` DATETIME NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.postgres.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.postgres.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "identity_id" UUID, "name" VARCHAR (255) NOT NULL, "payload" jsonb, "expires_at" timestamp NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.sqlite3.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20200401183443000000_continuity_containers.sqlite3.up.sql ================================================ CREATE TABLE "continuity_containers" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_profile_management_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_request_methods` CHANGE `selfservice_profile_management_request_id` `selfservice_settings_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_profile_management_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000000_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME COLUMN "selfservice_profile_management_request_id" TO "selfservice_settings_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_profile_management_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_request_methods` RENAME TO `selfservice_settings_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_profile_management_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000001_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_request_methods" RENAME TO "selfservice_settings_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.cockroach.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` CHANGE `selfservice_settings_request_id` `selfservice_profile_management_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.mysql.up.sql ================================================ ALTER TABLE `selfservice_profile_management_requests` RENAME TO `selfservice_settings_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.postgres.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_profile_management_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200402142539000002_rename_profile_flows.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_profile_management_requests" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_addresses_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `identity_recovery_addresses` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `via` VARCHAR (16) NOT NULL, `value` VARCHAR (400) NOT NULL, `identity_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "via" VARCHAR (16) NOT NULL, "value" VARCHAR (400) NOT NULL, "identity_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000000_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "identity_recovery_addresses" ( "id" TEXT PRIMARY KEY, "via" TEXT NOT NULL, "value" TEXT NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `selfservice_recovery_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000001_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "selfservice_recovery_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `selfservice_recovery_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "selfservice_recovery_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000002_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.cockroach.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" json, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_requests_identities_id_fk" FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.mysql.down.sql ================================================ DROP TABLE `identity_recovery_tokens`; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `selfservice_recovery_requests` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `request_url` VARCHAR (2048) NOT NULL, `issued_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `expires_at` DATETIME NOT NULL, `messages` JSON, `active_method` VARCHAR (32), `csrf_token` VARCHAR (255) NOT NULL, `state` VARCHAR (32) NOT NULL, `recovered_identity_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`recovered_identity_id`) REFERENCES `identities` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.postgres.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "selfservice_recovery_requests" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "request_url" VARCHAR (2048) NOT NULL, "issued_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" timestamp NOT NULL, "messages" jsonb, "active_method" VARCHAR (32), "csrf_token" VARCHAR (255) NOT NULL, "state" VARCHAR (32) NOT NULL, "recovered_identity_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("recovered_identity_id") REFERENCES "identities" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000003_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_recovery_requests" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "expires_at" DATETIME NOT NULL, "messages" TEXT, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" json NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_request_methods_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `selfservice_recovery_request_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `config` JSON NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "selfservice_recovery_request_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "config" jsonb NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000004_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_recovery_request_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "config" TEXT NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.cockroach.up.sql ================================================ CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk" FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.mysql.up.sql ================================================ CREATE TABLE `identity_recovery_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `identity_recovery_address_id` char(36) NOT NULL, `selfservice_recovery_request_id` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_recovery_address_id`) REFERENCES `identity_recovery_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_recovery_request_id`) REFERENCES `selfservice_recovery_requests` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.postgres.up.sql ================================================ CREATE TABLE "identity_recovery_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "identity_recovery_address_id" UUID NOT NULL, "selfservice_recovery_request_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_recovery_request_id") REFERENCES "selfservice_recovery_requests" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000005_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE TABLE "identity_recovery_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_request_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_recovery_request_id) REFERENCES selfservice_recovery_requests (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_recovery_addresses_code_uq_idx` ON `identity_recovery_tokens` (`token`); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000006_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_addresses_code_idx` ON `identity_recovery_tokens` (`token`); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101057000007_create_recovery_addresses.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "identity_recovery_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200519101058000000_create_recovery_addresses.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200519101058000000_create_recovery_addresses.mysql.up.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64) BINARY; ================================================ FILE: persistence/sql/migrations/sql/20200519101058000001_create_recovery_addresses.mysql.down.sql ================================================ ALTER TABLE identity_recovery_tokens MODIFY COLUMN token VARCHAR(64); ================================================ FILE: persistence/sql/migrations/sql/20200519101058000001_create_recovery_addresses.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000000_create_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000001_create_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000001_create_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200601101000000002_create_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, update_successful FROM "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200601101000000002_create_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200601101000000003_create_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "update_successful" bool NOT NULL DEFAULT 'false', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200601101000000003_create_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200601101001000000_verification.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200601101001000000_verification.mysql.up.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(32) BINARY; ================================================ FILE: persistence/sql/migrations/sql/20200601101001000001_verification.mysql.down.sql ================================================ ALTER TABLE identity_verifiable_addresses MODIFY COLUMN code VARCHAR(255) BINARY; ================================================ FILE: persistence/sql/migrations/sql/20200601101001000001_verification.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_requests` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000000_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000001_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_requests` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_requests` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at FROM "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000002_messages.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000003_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200605111551000003_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000004_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000004_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000005_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000005_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000006_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced FROM "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000006_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000007_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false' ); ================================================ FILE: persistence/sql/migrations/sql/20200605111551000007_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000008_messages.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000008_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000009_messages.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000009_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000010_messages.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, nid, form, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, nid, form, via, success FROM "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000010_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000011_messages.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "_selfservice_verification_requests_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200605111551000011_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000012_messages.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "form" TEXT, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ); ================================================ FILE: persistence/sql/migrations/sql/20200605111551000012_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200605111551000013_messages.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_verification_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200605111551000013_messages.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `update_successful` bool NOT NULL DEFAULT false; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "update_successful" bool NOT NULL DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000000_settings.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `state`; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `update_successful`; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "state"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "update_successful"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000001_settings.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200607165100000002_settings.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000002_settings.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages, state FROM "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000003_settings.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, messages FROM "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000003_settings.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200607165100000004_settings.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "messages" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200607165100000004_settings.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.cockroach.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.cockroach.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.mysql.down.sql ================================================ ALTER TABLE `identities` CHANGE `schema_id` `traits_schema_id` varchar(2048) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.mysql.up.sql ================================================ ALTER TABLE `identities` CHANGE `traits_schema_id` `schema_id` varchar(2048) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.postgres.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.postgres.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.sqlite3.down.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "schema_id" TO "traits_schema_id"; ================================================ FILE: persistence/sql/migrations/sql/20200705105359000000_rename_identities_schema.sqlite3.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "traits_schema_id" TO "schema_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_requests` DROP COLUMN `type`; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_requests_tmp" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000000_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_requests` DROP COLUMN `type`; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000001_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_requests` DROP COLUMN `type`; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_requests_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, nid, messages, form, via, success) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, nid, messages, form, via, success FROM "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000002_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_requests` DROP COLUMN `type`; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "_selfservice_verification_requests_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000003_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_requests` DROP COLUMN `type`; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` ADD COLUMN `type` VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_requests" DROP COLUMN "type"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" VARCHAR (16) NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "messages" TEXT, "form" TEXT, "via" TEXT NOT NULL DEFAULT 'email', "success" bool NOT NULL DEFAULT 'FALSE' ); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000004_flow_type.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'browser'; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000005_flow_type.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_verification_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000005_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000006_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_recovery_requests_tmp" RENAME TO "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000006_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000007_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000007_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000008_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_recovery_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, nid, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, nid, messages FROM "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000008_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000009_flow_type.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_recovery_flows_nid_idx" ON "_selfservice_recovery_requests_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000009_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000010_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_recovery_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "messages" TEXT, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000010_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000011_flow_type.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_recovery_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000011_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000012_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_requests_tmp" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000012_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000013_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000013_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000014_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_requests_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, messages) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, messages FROM "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000014_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000015_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "messages" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000015_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000016_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_requests_tmp" RENAME TO "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000016_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000017_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000017_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000018_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, messages FROM "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000018_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000019_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000019_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000020_flow_type.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_requests_tmp" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000020_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000021_flow_type.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000021_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000022_flow_type.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_requests_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, messages FROM "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810141652000022_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810141652000023_flow_type.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_requests_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "messages" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200810141652000023_flow_type.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` RENAME TO `selfservice_verification_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_request_methods` RENAME TO `selfservice_login_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME TO "selfservice_verification_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000000_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_request_methods" RENAME TO "selfservice_login_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flows` RENAME TO `selfservice_recovery_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_requests` RENAME TO `selfservice_login_flows`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME TO "selfservice_recovery_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000001_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_requests" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flow_methods` RENAME TO `selfservice_recovery_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_request_methods` RENAME TO `selfservice_registration_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME TO "selfservice_recovery_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000002_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_request_methods" RENAME TO "selfservice_registration_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` RENAME TO `selfservice_settings_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_requests` RENAME TO `selfservice_registration_flows`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME TO "selfservice_settings_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000003_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_requests" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flow_methods` RENAME TO `selfservice_settings_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_request_methods` RENAME TO `selfservice_settings_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME TO "selfservice_settings_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000004_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_request_methods" RENAME TO "selfservice_settings_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` RENAME TO `selfservice_registration_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_requests` RENAME TO `selfservice_settings_flows`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME TO "selfservice_registration_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000005_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_requests" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flow_methods` RENAME TO `selfservice_registration_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_request_methods` RENAME TO `selfservice_recovery_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME TO "selfservice_registration_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000006_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_request_methods" RENAME TO "selfservice_recovery_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` RENAME TO `selfservice_login_request_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_requests` RENAME TO `selfservice_recovery_flows`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME TO "selfservice_login_request_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000007_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_requests" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` RENAME TO `selfservice_login_requests`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_requests` RENAME TO `selfservice_verification_flows`; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME TO "selfservice_login_requests"; ================================================ FILE: persistence/sql/migrations/sql/20200810161022000008_flow_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_requests" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_request_id` `selfservice_login_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000000_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_request_id" TO "selfservice_login_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_flow_id` `selfservice_settings_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_request_id` `selfservice_registration_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_flow_id" TO "selfservice_settings_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000001_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_request_id" TO "selfservice_registration_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flow_methods` CHANGE `selfservice_registration_flow_id` `selfservice_registration_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flow_methods` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flow_methods" RENAME COLUMN "selfservice_registration_flow_id" TO "selfservice_registration_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000002_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_flow_methods" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flow_methods` CHANGE `selfservice_login_flow_id` `selfservice_login_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flow_methods` CHANGE `selfservice_settings_request_id` `selfservice_settings_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flow_methods" RENAME COLUMN "selfservice_login_flow_id" TO "selfservice_login_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200810162450000003_flow_fields_rename.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_flow_methods" RENAME COLUMN "selfservice_settings_request_id" TO "selfservice_settings_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.cockroach.up.sql ================================================ DELETE FROM sessions; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `token`; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.mysql.up.sql ================================================ DELETE FROM sessions; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "token"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.postgres.up.sql ================================================ DELETE FROM sessions; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000000_add_session_token.sqlite3.up.sql ================================================ DELETE FROM sessions; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `token` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000001_add_session_token.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" RENAME COLUMN "token" TO "_token_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.mysql.up.sql ================================================ ALTER TABLE `sessions` MODIFY `token` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.postgres.up.sql ================================================ ALTER TABLE "sessions" ALTER COLUMN "token" TYPE VARCHAR (32), ALTER COLUMN "token" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, nid) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, nid FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000002_add_session_token.sqlite3.up.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "token" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.mysql.up.sql ================================================ CREATE UNIQUE INDEX `sessions_token_uq_idx` ON `sessions` (`token`); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.postgres.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.sqlite3.down.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000003_add_session_token.sqlite3.up.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.cockroach.up.sql ================================================ UPDATE "sessions" SET "token" = "_token_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.mysql.up.sql ================================================ CREATE INDEX `sessions_token_idx` ON `sessions` (`token`); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.postgres.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000004_add_session_token.sqlite3.up.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000005_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000005_add_session_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "_token_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000005_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000005_add_session_token.sqlite3.up.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000006_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000006_add_session_token.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000006_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000006_add_session_token.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000007_add_session_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812124254000007_add_session_token.cockroach.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812124254000007_add_session_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200812124254000007_add_session_token.sqlite3.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "sessions" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `active`; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `active` boolean DEFAULT false; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "active"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" boolean DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000000_add_session_revoke.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "active" NUMERIC DEFAULT 'false'; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000001_add_session_revoke.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000001_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000002_add_session_revoke.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, nid) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, nid FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000002_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000003_add_session_revoke.sqlite3.down.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812160551000003_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000004_add_session_revoke.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200812160551000004_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000005_add_session_revoke.sqlite3.down.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200812160551000005_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000006_add_session_revoke.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200812160551000006_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000007_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000007_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000008_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000008_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200812160551000009_add_session_revoke.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200812160551000009_add_session_revoke.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_flow_id` `selfservice_recovery_request_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` CHANGE `selfservice_recovery_request_id` `selfservice_recovery_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.sqlite3.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "selfservice_recovery_request_id"; ================================================ FILE: persistence/sql/migrations/sql/20200830121710000000_update_recovery_token.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_request_id" TO "selfservice_recovery_flow_id"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `success` bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "success" bool NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000000_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'show_form'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `via` VARCHAR (16) NOT NULL DEFAULT 'email'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" VARCHAR (16) NOT NULL DEFAULT 'email'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "via" TEXT NOT NULL DEFAULT 'email'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000001_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `state`; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "state"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000002_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `active_method`; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "active_method"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000003_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.cockroach.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.mysql.down.sql ================================================ DROP TABLE `selfservice_verification_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.postgres.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, nid, messages, form) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, nid, messages, form FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000004_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" json NOT NULL DEFAULT '{}'; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` MODIFY `form` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ALTER COLUMN "form" TYPE jsonb, ALTER COLUMN "form" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "_selfservice_verification_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000005_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000006_add_verification_methods.mysql.down.sql ================================================ UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000006_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000006_add_verification_methods.postgres.down.sql ================================================ UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000006_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000006_add_verification_methods.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "nid" char(36), "messages" TEXT, "form" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000006_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000007_add_verification_methods.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `form` JSON; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000007_add_verification_methods.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000007_add_verification_methods.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000007_add_verification_methods.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000007_add_verification_methods.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_verification_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000007_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000008_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000008_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000009_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000009_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000010_add_verification_methods.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, nid, messages, form) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, nid, messages, form FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000010_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000011_add_verification_methods.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "_selfservice_verification_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000011_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000012_add_verification_methods.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "nid" char(36), "messages" TEXT, "form" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000012_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000013_add_verification_methods.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_verification_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000013_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000014_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000014_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000015_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000015_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000016_add_verification_methods.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000016_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000017_add_verification_methods.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, nid, messages, form) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, nid, messages, form FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000017_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000018_add_verification_methods.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "_selfservice_verification_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000018_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000019_add_verification_methods.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT, "nid" char(36), "messages" TEXT, "form" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000019_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000020_add_verification_methods.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_verification_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000020_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000021_add_verification_methods.sqlite3.down.sql ================================================ UPDATE selfservice_verification_flows SET form=(SELECT * FROM (SELECT m.config FROM selfservice_verification_flows AS r INNER JOIN selfservice_verification_flow_methods AS m ON r.id=m.selfservice_verification_flow_id) as t); ================================================ FILE: persistence/sql/migrations/sql/20200830130642000021_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130642000022_add_verification_methods.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "form" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200830130642000022_add_verification_methods.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.cockroach.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.mysql.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.postgres.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130643000000_add_verification_methods.sqlite3.up.sql ================================================ UPDATE selfservice_verification_flows SET state='passed_challenge' WHERE success IS TRUE; ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.cockroach.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.mysql.up.sql ================================================ CREATE TABLE `selfservice_verification_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_verification_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.postgres.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000000_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `active_method` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130644000001_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "active_method" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.cockroach.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.mysql.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.postgres.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130645000000_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO selfservice_verification_flow_methods (id, method, selfservice_verification_flow_id, config, created_at, updated_at) SELECT id, 'link', id, form, created_at, updated_at FROM selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `form`; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "form"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000000_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "via" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `via`; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "via"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000001_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, via, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `success`; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "success"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000002_add_verification_methods.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000003_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000003_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000004_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000004_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "success" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200830130646000005_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000005_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, success, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000006_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000006_add_verification_methods.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000007_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000007_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000008_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000008_add_verification_methods.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "messages" TEXT, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20200830130646000009_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000009_add_verification_methods.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, messages, type, state, active_method FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000010_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000010_add_verification_methods.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830130646000011_add_verification_methods.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830130646000011_add_verification_methods.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.cockroach.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.cockroach.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "identity_verification_tokens_identity_verifiable_addresses_id_fk" FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, CONSTRAINT "identity_verification_tokens_selfservice_verification_flows_id_fk" FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.mysql.down.sql ================================================ DROP TABLE `identity_verification_tokens`; ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.mysql.up.sql ================================================ CREATE TABLE `identity_verification_tokens` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `token` VARCHAR (64) NOT NULL, `used` bool NOT NULL DEFAULT false, `used_at` DATETIME, `expires_at` DATETIME NOT NULL, `issued_at` DATETIME NOT NULL, `identity_verifiable_address_id` char(36) NOT NULL, `selfservice_verification_flow_id` char(36), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`identity_verifiable_address_id`) REFERENCES `identity_verifiable_addresses` (`id`) ON DELETE cascade, FOREIGN KEY (`selfservice_verification_flow_id`) REFERENCES `selfservice_verification_flows` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.postgres.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.postgres.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "token" VARCHAR (64) NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" timestamp, "expires_at" timestamp NOT NULL, "issued_at" timestamp NOT NULL, "identity_verifiable_address_id" UUID NOT NULL, "selfservice_verification_flow_id" UUID, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("identity_verifiable_address_id") REFERENCES "identity_verifiable_addresses" ("id") ON DELETE cascade, FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flows" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.sqlite3.down.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830154602000000_add_verification_token.sqlite3.up.sql ================================================ CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verification_tokens_token_uq_idx` ON `identity_verification_tokens` (`token`); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000001_add_verification_token.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_token_idx` ON `identity_verification_tokens` (`token`); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000002_add_verification_token.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "identity_verification_tokens" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_verifiable_address_id_idx` ON `identity_verification_tokens` (`identity_verifiable_address_id`); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000003_add_verification_token.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_verification_flow_id_idx` ON `identity_verification_tokens` (`selfservice_verification_flow_id`); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830154602000004_add_verification_token.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` DROP COLUMN `issued_at`; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `expires_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "issued_at"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000000_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` DROP COLUMN `expires_at`; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `issued_at` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "expires_at"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000001_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00'; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `selfservice_recovery_flow_id` char(36); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" TYPE UUID, ALTER COLUMN "selfservice_recovery_flow_id" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, nid) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, nid FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000002_recovery_token_expires.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.mysql.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.postgres.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000003_recovery_token_expires.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000004_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "selfservice_recovery_flow_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000004_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000004_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000004_recovery_token_expires.sqlite3.up.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000005_recovery_token_expires.cockroach.down.sql ================================================ UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000005_recovery_token_expires.cockroach.up.sql ================================================ UPDATE "identity_recovery_tokens" SET "selfservice_recovery_flow_id" = "_selfservice_recovery_flow_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000005_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "_identity_recovery_tokens_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000005_recovery_token_expires.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000006_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "selfservice_recovery_flow_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000006_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_selfservice_recovery_flow_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000006_recovery_token_expires.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000006_recovery_token_expires.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000007_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "selfservice_recovery_flow_id" TO "_selfservice_recovery_flow_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000007_recovery_token_expires.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000007_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000007_recovery_token_expires.sqlite3.up.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000008_recovery_token_expires.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_selfservice_recovery_requests_id_fk"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000008_recovery_token_expires.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000008_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000008_recovery_token_expires.sqlite3.up.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000009_recovery_token_expires.cockroach.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000009_recovery_token_expires.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000009_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_tokens_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000009_recovery_token_expires.sqlite3.up.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000010_recovery_token_expires.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000010_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000011_recovery_token_expires.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000011_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000012_recovery_token_expires.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at, nid) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, issued_at, nid FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000012_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000013_recovery_token_expires.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000013_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000014_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000014_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000015_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "_identity_recovery_tokens_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000015_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000016_recovery_token_expires.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "nid" char(36), FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000016_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000017_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000017_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000018_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000018_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000019_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_tokens_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000019_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000020_recovery_token_expires.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000020_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000021_recovery_token_expires.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000021_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000022_recovery_token_expires.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000022_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000023_recovery_token_expires.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000023_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000024_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000024_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000025_recovery_token_expires.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "_identity_recovery_tokens_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000025_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000026_recovery_token_expires.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "nid" char(36), FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200830172221000026_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000027_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000027_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000028_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000028_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000029_recovery_token_expires.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_tokens_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000029_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200830172221000030_recovery_token_expires.sqlite3.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE selfservice_recovery_flow_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200830172221000030_recovery_token_expires.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.mysql.down.sql ================================================ CREATE INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.postgres.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_code_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000000_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.mysql.down.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_code_uq_idx` ON `identity_verifiable_addresses` (`code`); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_code_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.postgres.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_code_uq_idx" ON "identity_verifiable_addresses" (code); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000001_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_expires_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` MODIFY `expires_at` DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `code`; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "expires_at" TYPE timestamp, ALTER COLUMN "expires_at" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "code"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000002_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE "identity_verifiable_addresses" SET "expires_at" = "_expires_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` MODIFY `code` VARCHAR (32) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `expires_at`; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" TYPE VARCHAR (32), ALTER COLUMN "code" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "expires_at"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000003_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.mysql.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.postgres.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000004_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "expires_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "expires_at" TO "_expires_at_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.mysql.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = LEFT(SHA2(RANDOM_BYTES(32), 256), 32) WHERE code IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.postgres.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = substr(md5(random()::text), 0, 32) WHERE code IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000005_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_code_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `expires_at` DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000006_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "code" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `code` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000007_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, expires_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000008_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE "identity_verifiable_addresses" SET "code" = "_code_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000008_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000008_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000008_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000009_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000009_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000009_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000009_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000010_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "code" TO "_code_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000010_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000010_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000010_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000011_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000011_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000011_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000011_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000012_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = substr(md5(uuid_v4()), 0, 32) WHERE code IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000012_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000012_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, code, expires_at FROM "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000012_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000013_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000013_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000013_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000013_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000014_identity_verifiable_address_remove_code.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000014_identity_verifiable_address_remove_code.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000014_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000014_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000015_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "code" TEXT NOT NULL, "expires_at" DATETIME, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20200831110752000015_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000016_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000016_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000017_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000017_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000018_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ UPDATE identity_verifiable_addresses SET expires_at = CURRENT_TIMESTAMP WHERE expires_at IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000018_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000019_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ UPDATE identity_verifiable_addresses SET code = substr(hex(randomblob(32)), 0, 32) WHERE code IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000019_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000020_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "expires_at" DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000020_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20200831110752000021_identity_verifiable_address_remove_code.sqlite3.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "code" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20200831110752000021_identity_verifiable_address_remove_code.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'password' OR name = 'oidc'; ================================================ FILE: persistence/sql/migrations/sql/20201201161451000000_credential_types_values.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '78c1b41d-8341-4507-aa60-aff1d4369670', 'password' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'password'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20201201161451000001_credential_types_values.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6fa5e2e0-bfce-4631-b62b-cf2b0252b289', 'oidc' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'oidc'); ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.cockroach.up.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "courier_messages" (status); ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.mysql.down.sql ================================================ DROP INDEX `courier_messages_status_idx` ON `courier_messages`; ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.mysql.up.sql ================================================ CREATE INDEX `courier_messages_status_idx` ON `courier_messages` (`status`); ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.postgres.down.sql ================================================ DROP INDEX "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.postgres.up.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "courier_messages" (status); ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210307130558000000_courier_status_index.sqlite3.up.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "courier_messages" (status); ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.cockroach.down.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "template_data"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "template_type" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.mysql.down.sql ================================================ ALTER TABLE `courier_messages` DROP COLUMN `template_data`; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.mysql.up.sql ================================================ ALTER TABLE `courier_messages` ADD COLUMN `template_type` VARCHAR (255) NOT NULL DEFAULT ""; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.postgres.down.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "template_data"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "template_type" VARCHAR (255) NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.sqlite3.down.sql ================================================ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000000_courier_message_template.sqlite3.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "template_type" TEXT NOT NULL DEFAULT ''; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.cockroach.down.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "template_type"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "template_data" BYTES; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.mysql.down.sql ================================================ ALTER TABLE `courier_messages` DROP COLUMN `template_type`; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.mysql.up.sql ================================================ ALTER TABLE `courier_messages` ADD COLUMN `template_data` BLOB; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.postgres.down.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "template_type"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "template_data" bytea; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000001_courier_message_template.sqlite3.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "template_data" BLOB; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000002_courier_message_template.sqlite3.down.sql ================================================ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at) SELECT id, type, status, body, subject, recipient, created_at, updated_at FROM "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000002_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000003_courier_message_template.sqlite3.down.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "_courier_messages_tmp" (status); ================================================ FILE: persistence/sql/migrations/sql/20210307130559000003_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000004_courier_message_template.sqlite3.down.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20210307130559000004_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000005_courier_message_template.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000005_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000006_courier_message_template.sqlite3.down.sql ================================================ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000006_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000007_courier_message_template.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000007_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000008_courier_message_template.sqlite3.down.sql ================================================ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at, template_data) SELECT id, type, status, body, subject, recipient, created_at, updated_at, template_data FROM "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000008_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000009_courier_message_template.sqlite3.down.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "_courier_messages_tmp" (status); ================================================ FILE: persistence/sql/migrations/sql/20210307130559000009_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000010_courier_message_template.sqlite3.down.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "template_data" BLOB ); ================================================ FILE: persistence/sql/migrations/sql/20210307130559000010_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210307130559000011_courier_message_template.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210307130559000011_courier_message_template.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.cockroach.up.sql ================================================ DROP TABLE "selfservice_login_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.mysql.up.sql ================================================ DROP TABLE `selfservice_login_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.postgres.up.sql ================================================ DROP TABLE "selfservice_login_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000000_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_login_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `ui`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000001_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser' ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.cockroach.down.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_verification_flow_methods_selfservice_verification_flow_methods_id_fk" FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.mysql.down.sql ================================================ CREATE TABLE `selfservice_verification_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_verification_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_verification_flow_id`) REFERENCES `selfservice_verification_flow_methods` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD COLUMN `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.postgres.down.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_verification_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_verification_flow_id") REFERENCES "selfservice_verification_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "ui" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.sqlite3.down.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000002_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.cockroach.up.sql ================================================ UPDATE selfservice_login_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flows` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.mysql.up.sql ================================================ UPDATE selfservice_login_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.postgres.up.sql ================================================ UPDATE selfservice_login_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, nid) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, nid FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000003_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME COLUMN "ui" TO "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_recovery_flows` DROP COLUMN `ui`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` MODIFY `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ALTER COLUMN "ui" TYPE jsonb, ALTER COLUMN "ui" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "_selfservice_verification_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000004_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.cockroach.down.sql ================================================ CREATE TABLE "selfservice_recovery_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_recovery_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_recovery_flow_methods_selfservice_recovery_flow_methods_id_fk" FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.mysql.down.sql ================================================ CREATE TABLE `selfservice_recovery_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_recovery_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_recovery_flow_id`) REFERENCES `selfservice_recovery_flow_methods` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.mysql.up.sql ================================================ DROP TABLE `selfservice_registration_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.postgres.down.sql ================================================ CREATE TABLE "selfservice_recovery_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_recovery_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_recovery_flow_id") REFERENCES "selfservice_recovery_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.postgres.up.sql ================================================ DROP TABLE "selfservice_registration_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000005_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "ui" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.cockroach.up.sql ================================================ UPDATE "selfservice_login_flows" SET "ui" = "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_verification_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000006_form_refactoring.sqlite3.up.sql ================================================ UPDATE selfservice_login_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` DROP COLUMN `ui`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` ADD COLUMN `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "ui" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "selfservice_verification_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flow_methods (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000007_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.cockroach.down.sql ================================================ CREATE TABLE "selfservice_settings_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_settings_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_settings_flow_methods_selfservice_settings_flow_methods_id_fk" FOREIGN KEY ("selfservice_settings_flow_id") REFERENCES "selfservice_settings_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.cockroach.up.sql ================================================ DROP TABLE "selfservice_registration_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.mysql.down.sql ================================================ CREATE TABLE `selfservice_settings_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_settings_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_settings_flow_id`) REFERENCES `selfservice_settings_flow_methods` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.mysql.up.sql ================================================ UPDATE selfservice_registration_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.postgres.down.sql ================================================ CREATE TABLE "selfservice_settings_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_settings_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_settings_flow_id") REFERENCES "selfservice_settings_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.postgres.up.sql ================================================ UPDATE selfservice_registration_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000008_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` MODIFY `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ALTER COLUMN "ui" TYPE jsonb, ALTER COLUMN "ui" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_recovery_flows_tmp" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000009_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` DROP COLUMN `ui`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.mysql.up.sql ================================================ DROP TABLE `selfservice_settings_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.postgres.up.sql ================================================ DROP TABLE "selfservice_settings_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.sqlite3.down.sql ================================================ DROP TABLE "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000010_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.cockroach.down.sql ================================================ CREATE TABLE "selfservice_registration_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_registration_flow_methods_selfservice_registration_flow_methods_id_fk" FOREIGN KEY ("selfservice_registration_flow_id") REFERENCES "selfservice_registration_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.cockroach.up.sql ================================================ UPDATE selfservice_registration_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.mysql.down.sql ================================================ CREATE TABLE `selfservice_registration_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_registration_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_registration_flow_id`) REFERENCES `selfservice_registration_flow_methods` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.postgres.down.sql ================================================ CREATE TABLE "selfservice_registration_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_registration_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_registration_flow_id") REFERENCES "selfservice_registration_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_recovery_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, nid) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, nid FROM "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000011_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_registration_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "messages" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME COLUMN "ui" TO "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD COLUMN `messages` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` ADD COLUMN `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "messages" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "ui" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_recovery_flows_nid_idx" ON "_selfservice_recovery_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000012_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser' ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP COLUMN `ui`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.mysql.up.sql ================================================ UPDATE selfservice_settings_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "ui"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.postgres.up.sql ================================================ UPDATE selfservice_settings_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_recovery_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "nid" char(36), FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000013_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.cockroach.down.sql ================================================ CREATE TABLE "selfservice_login_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_flow_id" UUID NOT NULL, "config" json NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "selfservice_login_flow_methods_selfservice_login_flow_methods_id_fk" FOREIGN KEY ("selfservice_login_flow_id") REFERENCES "selfservice_login_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.cockroach.up.sql ================================================ UPDATE "selfservice_registration_flows" SET "ui" = "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.mysql.down.sql ================================================ CREATE TABLE `selfservice_login_flow_methods` ( `id` char(36) NOT NULL, PRIMARY KEY(`id`), `method` VARCHAR (32) NOT NULL, `selfservice_login_flow_id` char(36) NOT NULL, `config` JSON NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`selfservice_login_flow_id`) REFERENCES `selfservice_login_flow_methods` (`id`) ON DELETE cascade ) ENGINE=InnoDB; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` MODIFY `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.postgres.down.sql ================================================ CREATE TABLE "selfservice_login_flow_methods" ( "id" UUID NOT NULL, PRIMARY KEY("id"), "method" VARCHAR (32) NOT NULL, "selfservice_login_flow_id" UUID NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, FOREIGN KEY ("selfservice_login_flow_id") REFERENCES "selfservice_login_flow_methods" ("id") ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ALTER COLUMN "ui" TYPE jsonb, ALTER COLUMN "ui" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_recovery_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000014_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.mysql.up.sql ================================================ DROP TABLE `selfservice_recovery_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.postgres.up.sql ================================================ DROP TABLE "selfservice_recovery_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "selfservice_recovery_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_recovery_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flow_methods (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000015_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.cockroach.up.sql ================================================ DROP TABLE "selfservice_settings_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flows` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000016_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "ui" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flows` ADD COLUMN `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "ui" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000017_form_refactoring.sqlite3.up.sql ================================================ UPDATE selfservice_registration_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.mysql.up.sql ================================================ UPDATE selfservice_recovery_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.postgres.up.sql ================================================ UPDATE selfservice_recovery_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000018_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.cockroach.up.sql ================================================ UPDATE selfservice_settings_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flows` MODIFY `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ALTER COLUMN "ui" TYPE jsonb, ALTER COLUMN "ui" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000019_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME COLUMN "ui" TO "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.mysql.up.sql ================================================ DROP TABLE `selfservice_verification_flow_methods`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.postgres.up.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000020_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` DROP COLUMN `messages`; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "selfservice_settings_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_settings_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_settings_flow_id) REFERENCES selfservice_settings_flow_methods (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000021_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.cockroach.up.sql ================================================ UPDATE "selfservice_settings_flows" SET "ui" = "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "ui" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000022_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.mysql.up.sql ================================================ UPDATE selfservice_verification_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.postgres.up.sql ================================================ UPDATE selfservice_verification_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000023_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.cockroach.up.sql ================================================ DROP TABLE "selfservice_recovery_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` MODIFY `ui` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ALTER COLUMN "ui" TYPE jsonb, ALTER COLUMN "ui" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000024_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000025_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000025_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000025_form_refactoring.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000025_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000026_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000026_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000026_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser' ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000026_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000027_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000027_form_refactoring.cockroach.up.sql ================================================ UPDATE selfservice_recovery_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000027_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "selfservice_registration_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_registration_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_registration_flow_id) REFERENCES selfservice_registration_flow_methods (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000027_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "ui" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000028_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000028_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME COLUMN "ui" TO "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000028_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "messages" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000028_form_refactoring.sqlite3.up.sql ================================================ UPDATE selfservice_settings_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000029_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000029_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000029_form_refactoring.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000029_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000030_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000030_form_refactoring.cockroach.up.sql ================================================ UPDATE "selfservice_recovery_flows" SET "ui" = "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000030_form_refactoring.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000030_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000031_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000031_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP COLUMN "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000031_form_refactoring.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000031_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000032_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000032_form_refactoring.cockroach.up.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000032_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser' ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000032_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000033_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000033_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "messages"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000033_form_refactoring.sqlite3.down.sql ================================================ CREATE TABLE "selfservice_login_flow_methods" ( "id" TEXT PRIMARY KEY, "method" TEXT NOT NULL, "selfservice_login_flow_id" char(36) NOT NULL, "config" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (selfservice_login_flow_id) REFERENCES selfservice_login_flow_methods (id) ON DELETE cascade ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000033_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_recovery_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000034_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000034_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000034_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000034_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_recovery_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000035_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000035_form_refactoring.cockroach.up.sql ================================================ UPDATE selfservice_verification_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000035_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000035_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_recovery_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type FROM "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000036_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000036_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME COLUMN "ui" TO "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000036_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000036_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000037_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000037_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "ui" json; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000037_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000037_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_recovery_flows_tmp" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000038_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000038_form_refactoring.cockroach.up.sql ================================================ UPDATE "selfservice_verification_flows" SET "ui" = "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000038_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000038_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "ui" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000039_form_refactoring.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000039_form_refactoring.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "_ui_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000039_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000039_form_refactoring.sqlite3.up.sql ================================================ UPDATE selfservice_recovery_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000040_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000040_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_recovery_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000041_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000041_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_recovery_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui FROM "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000042_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000042_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000043_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000043_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_recovery_flows_tmp" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000044_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000044_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flow_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000045_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000045_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000046_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000046_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000047_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000047_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000048_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000048_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000049_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000049_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "ui" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000050_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000050_form_refactoring.sqlite3.up.sql ================================================ UPDATE selfservice_verification_flows SET ui='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000051_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000051_form_refactoring.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT, "ui" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20210311102338000052_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000052_form_refactoring.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, ui) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, ui FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000053_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000053_form_refactoring.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210311102338000054_form_refactoring.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210311102338000054_form_refactoring.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.cockroach.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.mysql.down.sql ================================================ CREATE INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.postgres.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.sqlite3.down.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000000_network.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.cockroach.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD CONSTRAINT "selfservice_login_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.mysql.down.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD CONSTRAINT `selfservice_login_flows_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.postgres.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD CONSTRAINT "selfservice_login_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000001_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_login_flows DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.cockroach.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.cockroach.up.sql ================================================ UPDATE selfservice_login_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.mysql.down.sql ================================================ CREATE INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.mysql.up.sql ================================================ UPDATE selfservice_login_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.postgres.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.postgres.up.sql ================================================ UPDATE selfservice_login_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000002_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_login_flows ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.cockroach.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP CONSTRAINT "selfservice_login_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.mysql.down.sql ================================================ CREATE UNIQUE INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses` (`via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.postgres.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000003_network.sqlite3.up.sql ================================================ UPDATE selfservice_login_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.cockroach.down.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.mysql.down.sql ================================================ CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_idx` ON `identity_credential_identifiers` (`identifier`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.mysql.up.sql ================================================ CREATE INDEX `selfservice_login_flows_nid_idx` ON `selfservice_login_flows` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.postgres.down.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.postgres.up.sql ================================================ CREATE INDEX "selfservice_login_flows_nid_idx" ON "selfservice_login_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "identity_credential_identifiers" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000004_network.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.cockroach.down.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.mysql.down.sql ================================================ ALTER TABLE `identity_credential_identifiers` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.postgres.down.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.sqlite3.down.sql ================================================ ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000005_network.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_nid_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.cockroach.up.sql ================================================ UPDATE "selfservice_login_flows" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.mysql.down.sql ================================================ DROP INDEX `identity_credential_identifiers_identifier_nid_uq_idx` ON `identity_credential_identifiers`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` ADD CONSTRAINT `selfservice_registration_flows_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.postgres.down.sql ================================================ DROP INDEX "identity_credential_identifiers_identifier_nid_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD CONSTRAINT "selfservice_registration_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000006_network.sqlite3.up.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.mysql.up.sql ================================================ UPDATE selfservice_registration_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.postgres.up.sql ================================================ UPDATE selfservice_registration_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.sqlite3.down.sql ================================================ INSERT INTO "_identity_credential_identifiers_tmp" (id, identifier, identity_credential_id, created_at, updated_at) SELECT id, identifier, identity_credential_id, created_at, updated_at FROM "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000007_network.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.cockroach.down.sql ================================================ ALTER TABLE "identity_credentials" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD CONSTRAINT "selfservice_login_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.mysql.down.sql ================================================ ALTER TABLE `identity_credentials` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.postgres.down.sql ================================================ ALTER TABLE "identity_credentials" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.sqlite3.down.sql ================================================ CREATE TABLE "_identity_credential_identifiers_tmp" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000008_network.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_login_flows_nid_idx" ON "selfservice_login_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.cockroach.down.sql ================================================ ALTER TABLE "identities" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.cockroach.up.sql ================================================ CREATE INDEX "selfservice_login_flows_nid_idx" ON "selfservice_login_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.mysql.down.sql ================================================ ALTER TABLE `identities` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.mysql.up.sql ================================================ CREATE INDEX `selfservice_registration_flows_nid_idx` ON `selfservice_registration_flows` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.postgres.down.sql ================================================ ALTER TABLE "identities" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.postgres.up.sql ================================================ CREATE INDEX "selfservice_registration_flows_nid_idx" ON "selfservice_registration_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_nid_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000009_network.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.sqlite3.down.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000010_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_registration_flows DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.cockroach.down.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD CONSTRAINT "selfservice_registration_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.mysql.down.sql ================================================ ALTER TABLE `courier_messages` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` ADD CONSTRAINT `selfservice_settings_flows_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.postgres.down.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD CONSTRAINT "selfservice_settings_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.sqlite3.down.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000011_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_registration_flows ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.cockroach.down.sql ================================================ ALTER TABLE "continuity_containers" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.cockroach.up.sql ================================================ UPDATE selfservice_registration_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.mysql.down.sql ================================================ ALTER TABLE `continuity_containers` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.mysql.up.sql ================================================ UPDATE selfservice_settings_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.postgres.down.sql ================================================ ALTER TABLE "continuity_containers" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.postgres.up.sql ================================================ UPDATE selfservice_settings_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.sqlite3.down.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at FROM "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000012_network.sqlite3.up.sql ================================================ UPDATE selfservice_registration_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP CONSTRAINT "selfservice_registration_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.sqlite3.down.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000013_network.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.mysql.up.sql ================================================ CREATE INDEX `selfservice_settings_flows_nid_idx` ON `selfservice_settings_flows` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.postgres.up.sql ================================================ CREATE INDEX "selfservice_settings_flows_nid_idx" ON "selfservice_settings_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.sqlite3.down.sql ================================================ ALTER TABLE "_identity_credentials_tmp" RENAME TO "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000014_network.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, nid) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, nid FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP COLUMN `nid`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "nid"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.sqlite3.down.sql ================================================ DROP TABLE "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000015_network.sqlite3.up.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.cockroach.up.sql ================================================ UPDATE "selfservice_registration_flows" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.mysql.down.sql ================================================ DROP INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` ADD CONSTRAINT `selfservice_errors_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.postgres.down.sql ================================================ DROP INDEX "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD CONSTRAINT "selfservice_errors_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.sqlite3.down.sql ================================================ INSERT INTO "_identity_credentials_tmp" (id, config, identity_credential_type_id, identity_id, created_at, updated_at) SELECT id, config, identity_credential_type_id, identity_id, created_at, updated_at FROM "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000016_network.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.mysql.down.sql ================================================ DROP INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.mysql.up.sql ================================================ UPDATE selfservice_errors SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.postgres.down.sql ================================================ DROP INDEX "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.postgres.up.sql ================================================ UPDATE selfservice_errors SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.sqlite3.down.sql ================================================ CREATE TABLE "_identity_credentials_tmp" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000017_network.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_registration_flows_nid_idx" ON "selfservice_registration_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD CONSTRAINT "selfservice_registration_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.mysql.down.sql ================================================ DROP INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_errors` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.postgres.down.sql ================================================ DROP INDEX "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_errors" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.sqlite3.down.sql ================================================ ALTER TABLE "_identities_tmp" RENAME TO "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000018_network.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.cockroach.up.sql ================================================ CREATE INDEX "selfservice_registration_flows_nid_idx" ON "selfservice_registration_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.mysql.down.sql ================================================ DROP INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.mysql.up.sql ================================================ CREATE INDEX `selfservice_errors_nid_idx` ON `selfservice_errors` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.postgres.down.sql ================================================ DROP INDEX "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.postgres.up.sql ================================================ CREATE INDEX "selfservice_errors_nid_idx" ON "selfservice_errors" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.sqlite3.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000019_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_settings_flows DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.mysql.down.sql ================================================ DROP INDEX `identity_credential_identifiers_nid_idx` ON `identity_credential_identifiers`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.mysql.up.sql ================================================ ALTER TABLE `continuity_containers` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.postgres.down.sql ================================================ DROP INDEX "identity_credential_identifiers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.postgres.up.sql ================================================ ALTER TABLE "continuity_containers" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.sqlite3.down.sql ================================================ INSERT INTO "_identities_tmp" (id, schema_id, traits, created_at, updated_at) SELECT id, schema_id, traits, created_at, updated_at FROM "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000020_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_settings_flows ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD CONSTRAINT "selfservice_settings_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.mysql.down.sql ================================================ DROP INDEX `identity_recovery_addresses_nid_idx` ON `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.mysql.up.sql ================================================ ALTER TABLE `continuity_containers` ADD CONSTRAINT `continuity_containers_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.postgres.down.sql ================================================ DROP INDEX "identity_recovery_addresses_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.postgres.up.sql ================================================ ALTER TABLE "continuity_containers" ADD CONSTRAINT "continuity_containers_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.sqlite3.down.sql ================================================ CREATE TABLE "_identities_tmp" ( "id" TEXT PRIMARY KEY, "schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000021_network.sqlite3.up.sql ================================================ UPDATE selfservice_settings_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.cockroach.up.sql ================================================ UPDATE selfservice_settings_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.mysql.down.sql ================================================ DROP INDEX `identity_verifiable_addresses_nid_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.mysql.up.sql ================================================ UPDATE continuity_containers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.postgres.down.sql ================================================ DROP INDEX "identity_verifiable_addresses_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.postgres.up.sql ================================================ UPDATE continuity_containers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000022_network.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_credentials_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP CONSTRAINT "selfservice_settings_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.mysql.down.sql ================================================ DROP INDEX `identity_credentials_nid_idx` ON `identity_credentials`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.mysql.up.sql ================================================ ALTER TABLE `continuity_containers` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.postgres.down.sql ================================================ DROP INDEX "identity_credentials_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.postgres.up.sql ================================================ ALTER TABLE "continuity_containers" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.sqlite3.down.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000023_network.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui, nid) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui, nid FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identities_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.mysql.down.sql ================================================ DROP INDEX `identities_nid_idx` ON `identities`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.mysql.up.sql ================================================ CREATE INDEX `continuity_containers_nid_idx` ON `continuity_containers` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.postgres.down.sql ================================================ DROP INDEX "identities_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.postgres.up.sql ================================================ CREATE INDEX "continuity_containers_nid_idx" ON "continuity_containers" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token FROM "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000024_network.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_errors_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.mysql.down.sql ================================================ DROP INDEX `selfservice_errors_nid_idx` ON `selfservice_errors`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.mysql.up.sql ================================================ ALTER TABLE `courier_messages` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.postgres.down.sql ================================================ DROP INDEX "selfservice_errors_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '' ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000025_network.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.cockroach.up.sql ================================================ UPDATE "selfservice_settings_flows" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.mysql.down.sql ================================================ DROP INDEX `courier_messages_nid_idx` ON `courier_messages`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.mysql.up.sql ================================================ ALTER TABLE `courier_messages` ADD CONSTRAINT `courier_messages_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.postgres.down.sql ================================================ DROP INDEX "courier_messages_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ADD CONSTRAINT "courier_messages_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.sqlite3.down.sql ================================================ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000026_network.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_settings_flows_nid_idx" ON "selfservice_settings_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "continuity_containers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.mysql.down.sql ================================================ DROP INDEX `continuity_containers_nid_idx` ON `continuity_containers`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.mysql.up.sql ================================================ UPDATE courier_messages SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.postgres.down.sql ================================================ DROP INDEX "continuity_containers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.postgres.up.sql ================================================ UPDATE courier_messages SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.sqlite3.down.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000027_network.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_settings_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD CONSTRAINT "selfservice_settings_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.mysql.down.sql ================================================ DROP INDEX `selfservice_settings_flows_nid_idx` ON `selfservice_settings_flows`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.mysql.up.sql ================================================ ALTER TABLE `courier_messages` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.postgres.down.sql ================================================ DROP INDEX "selfservice_settings_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.postgres.up.sql ================================================ ALTER TABLE "courier_messages" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.sqlite3.down.sql ================================================ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at, template_type, template_data) SELECT id, type, status, body, subject, recipient, created_at, updated_at, template_type, template_data FROM "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000028_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_errors DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_registration_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.cockroach.up.sql ================================================ CREATE INDEX "selfservice_settings_flows_nid_idx" ON "selfservice_settings_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.mysql.down.sql ================================================ DROP INDEX `selfservice_registration_flows_nid_idx` ON `selfservice_registration_flows`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.mysql.up.sql ================================================ CREATE INDEX `courier_messages_nid_idx` ON `courier_messages` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.postgres.down.sql ================================================ DROP INDEX "selfservice_registration_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.postgres.up.sql ================================================ CREATE INDEX "courier_messages_nid_idx" ON "courier_messages" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.sqlite3.down.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "_courier_messages_tmp" (status); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000029_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_errors ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_login_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.mysql.down.sql ================================================ DROP INDEX `selfservice_login_flows_nid_idx` ON `selfservice_login_flows`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.mysql.up.sql ================================================ ALTER TABLE `identities` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.postgres.down.sql ================================================ DROP INDEX "selfservice_login_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.postgres.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.sqlite3.down.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "template_type" TEXT NOT NULL DEFAULT '', "template_data" BLOB ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000030_network.sqlite3.up.sql ================================================ UPDATE selfservice_errors SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.cockroach.down.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP CONSTRAINT "identity_credential_identifiers_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD CONSTRAINT "selfservice_errors_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.mysql.down.sql ================================================ ALTER TABLE `identity_credential_identifiers` DROP FOREIGN KEY `identity_credential_identifiers_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.mysql.up.sql ================================================ ALTER TABLE `identities` ADD CONSTRAINT `identities_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.postgres.down.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP CONSTRAINT "identity_credential_identifiers_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.postgres.up.sql ================================================ ALTER TABLE "identities" ADD CONSTRAINT "identities_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000031_network.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_errors_tmp" ( "id" TEXT PRIMARY KEY, "errors" TEXT NOT NULL, "seen_at" DATETIME, "was_seen" bool NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL DEFAULT '', "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_addresses" DROP CONSTRAINT "identity_recovery_addresses_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.cockroach.up.sql ================================================ UPDATE selfservice_errors SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_addresses` DROP FOREIGN KEY `identity_recovery_addresses_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.mysql.up.sql ================================================ UPDATE identities SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_addresses" DROP CONSTRAINT "identity_recovery_addresses_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.postgres.up.sql ================================================ UPDATE identities SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.sqlite3.down.sql ================================================ ALTER TABLE "_continuity_containers_tmp" RENAME TO "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000032_network.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_errors_tmp" (id, errors, seen_at, was_seen, created_at, updated_at, csrf_token, nid) SELECT id, errors, seen_at, was_seen, created_at, updated_at, csrf_token, nid FROM "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.cockroach.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP CONSTRAINT "identity_verifiable_addresses_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" DROP CONSTRAINT "selfservice_errors_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.mysql.down.sql ================================================ ALTER TABLE `identity_verifiable_addresses` DROP FOREIGN KEY `identity_verifiable_addresses_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.mysql.up.sql ================================================ ALTER TABLE `identities` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.postgres.down.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP CONSTRAINT "identity_verifiable_addresses_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.postgres.up.sql ================================================ ALTER TABLE "identities" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.sqlite3.down.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000033_network.sqlite3.up.sql ================================================ DROP TABLE "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.cockroach.down.sql ================================================ ALTER TABLE "identity_credentials" DROP CONSTRAINT "identity_credentials_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.mysql.down.sql ================================================ ALTER TABLE `identity_credentials` DROP FOREIGN KEY `identity_credentials_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.mysql.up.sql ================================================ CREATE INDEX `identities_nid_idx` ON `identities` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.postgres.down.sql ================================================ ALTER TABLE "identity_credentials" DROP CONSTRAINT "identity_credentials_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.postgres.up.sql ================================================ CREATE INDEX "identities_nid_idx" ON "identities" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.sqlite3.down.sql ================================================ INSERT INTO "_continuity_containers_tmp" (id, identity_id, name, payload, expires_at, created_at, updated_at) SELECT id, identity_id, name, payload, expires_at, created_at, updated_at FROM "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000034_network.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_errors_tmp" RENAME TO "selfservice_errors"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.cockroach.down.sql ================================================ ALTER TABLE "identities" DROP CONSTRAINT "identities_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.mysql.down.sql ================================================ ALTER TABLE `identities` DROP FOREIGN KEY `identities_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.mysql.up.sql ================================================ ALTER TABLE `identity_credentials` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.postgres.down.sql ================================================ ALTER TABLE "identities" DROP CONSTRAINT "identities_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.postgres.up.sql ================================================ ALTER TABLE "identity_credentials" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.sqlite3.down.sql ================================================ CREATE TABLE "_continuity_containers_tmp" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000035_network.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_errors_nid_idx" ON "selfservice_errors" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP CONSTRAINT "selfservice_errors_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.cockroach.up.sql ================================================ UPDATE "selfservice_errors" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_errors` DROP FOREIGN KEY `selfservice_errors_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.mysql.up.sql ================================================ ALTER TABLE `identity_credentials` ADD CONSTRAINT `identity_credentials_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_errors" DROP CONSTRAINT "selfservice_errors_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.postgres.up.sql ================================================ ALTER TABLE "identity_credentials" ADD CONSTRAINT "identity_credentials_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000036_network.sqlite3.up.sql ================================================ ALTER TABLE "continuity_containers" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.cockroach.down.sql ================================================ ALTER TABLE "courier_messages" DROP CONSTRAINT "courier_messages_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.mysql.down.sql ================================================ ALTER TABLE `courier_messages` DROP FOREIGN KEY `courier_messages_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.mysql.up.sql ================================================ UPDATE identity_credentials SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.postgres.down.sql ================================================ ALTER TABLE "courier_messages" DROP CONSTRAINT "courier_messages_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.postgres.up.sql ================================================ UPDATE identity_credentials SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000037_network.sqlite3.up.sql ================================================ ALTER TABLE continuity_containers DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.cockroach.down.sql ================================================ ALTER TABLE "continuity_containers" DROP CONSTRAINT "continuity_containers_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_errors" ADD CONSTRAINT "selfservice_errors_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.mysql.down.sql ================================================ ALTER TABLE `continuity_containers` DROP FOREIGN KEY `continuity_containers_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.mysql.up.sql ================================================ ALTER TABLE `identity_credentials` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.postgres.down.sql ================================================ ALTER TABLE "continuity_containers" DROP CONSTRAINT "continuity_containers_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.postgres.up.sql ================================================ ALTER TABLE "identity_credentials" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000038_network.sqlite3.up.sql ================================================ ALTER TABLE continuity_containers ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP CONSTRAINT "selfservice_settings_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.cockroach.up.sql ================================================ CREATE INDEX "selfservice_errors_nid_idx" ON "selfservice_errors" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` DROP FOREIGN KEY `selfservice_settings_flows_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.mysql.up.sql ================================================ CREATE INDEX `identity_credentials_nid_idx` ON `identity_credentials` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP CONSTRAINT "selfservice_settings_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.postgres.up.sql ================================================ CREATE INDEX "identity_credentials_nid_idx" ON "identity_credentials" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000039_network.sqlite3.up.sql ================================================ UPDATE continuity_containers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP CONSTRAINT "selfservice_registration_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` DROP FOREIGN KEY `selfservice_registration_flows_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP CONSTRAINT "selfservice_registration_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.postgres.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000040_network.sqlite3.up.sql ================================================ CREATE TABLE "_continuity_containers_tmp" ( "id" TEXT PRIMARY KEY, "identity_id" char(36), "name" TEXT NOT NULL, "payload" TEXT, "expires_at" DATETIME NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP CONSTRAINT "selfservice_login_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" ADD CONSTRAINT "continuity_containers_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP FOREIGN KEY `selfservice_login_flows_nid_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` ADD CONSTRAINT `identity_credential_identifiers_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP CONSTRAINT "selfservice_login_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.postgres.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD CONSTRAINT "identity_credential_identifiers_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000041_network.sqlite3.up.sql ================================================ INSERT INTO "_continuity_containers_tmp" (id, identity_id, name, payload, expires_at, created_at, updated_at, nid) SELECT id, identity_id, name, payload, expires_at, created_at, updated_at, nid FROM "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.cockroach.up.sql ================================================ UPDATE continuity_containers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.mysql.up.sql ================================================ UPDATE identity_credential_identifiers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.postgres.up.sql ================================================ UPDATE identity_credential_identifiers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000042_network.sqlite3.up.sql ================================================ DROP TABLE "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" DROP CONSTRAINT "continuity_containers_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.postgres.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000043_network.sqlite3.up.sql ================================================ ALTER TABLE "_continuity_containers_tmp" RENAME TO "continuity_containers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.mysql.up.sql ================================================ CREATE INDEX `identity_credential_identifiers_nid_idx` ON `identity_credential_identifiers` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.postgres.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_idx" ON "identity_credential_identifiers" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000044_network.sqlite3.up.sql ================================================ CREATE INDEX "continuity_containers_nid_idx" ON "continuity_containers" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.mysql.up.sql ================================================ DROP INDEX `identity_credential_identifiers_identifier_idx` ON `identity_credential_identifiers`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.postgres.up.sql ================================================ DROP INDEX "identity_credential_identifiers_identifier_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000045_network.sqlite3.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.cockroach.up.sql ================================================ UPDATE "continuity_containers" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_nid_uq_idx` ON `identity_credential_identifiers` (`nid`, `identifier`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000046_network.sqlite3.up.sql ================================================ ALTER TABLE courier_messages DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flows` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000047_network.sqlite3.up.sql ================================================ ALTER TABLE courier_messages ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.cockroach.up.sql ================================================ ALTER TABLE "continuity_containers" ADD CONSTRAINT "continuity_containers_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flows` ADD CONSTRAINT `selfservice_recovery_flows_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD CONSTRAINT "selfservice_recovery_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000048_network.sqlite3.up.sql ================================================ UPDATE courier_messages SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.cockroach.up.sql ================================================ CREATE INDEX "continuity_containers_nid_idx" ON "continuity_containers" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.mysql.up.sql ================================================ UPDATE selfservice_recovery_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.postgres.up.sql ================================================ UPDATE selfservice_recovery_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000049_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "courier_messages_status_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_recovery_flows` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000050_network.sqlite3.up.sql ================================================ CREATE TABLE "_courier_messages_tmp" ( "id" TEXT PRIMARY KEY, "type" INTEGER NOT NULL, "status" INTEGER NOT NULL, "body" TEXT NOT NULL, "subject" TEXT NOT NULL, "recipient" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "template_type" TEXT NOT NULL DEFAULT '', "template_data" BLOB, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD CONSTRAINT "courier_messages_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.mysql.up.sql ================================================ CREATE INDEX `selfservice_recovery_flows_nid_idx` ON `selfservice_recovery_flows` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.postgres.up.sql ================================================ CREATE INDEX "selfservice_recovery_flows_nid_idx" ON "selfservice_recovery_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000051_network.sqlite3.up.sql ================================================ CREATE INDEX "courier_messages_status_idx" ON "_courier_messages_tmp" (status); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.cockroach.up.sql ================================================ UPDATE courier_messages SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_addresses` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000052_network.sqlite3.up.sql ================================================ INSERT INTO "_courier_messages_tmp" (id, type, status, body, subject, recipient, created_at, updated_at, template_type, template_data, nid) SELECT id, type, status, body, subject, recipient, created_at, updated_at, template_type, template_data, nid FROM "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" DROP CONSTRAINT "courier_messages_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_addresses` ADD CONSTRAINT `identity_recovery_addresses_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD CONSTRAINT "identity_recovery_addresses_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000053_network.sqlite3.up.sql ================================================ DROP TABLE "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.mysql.up.sql ================================================ UPDATE identity_recovery_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.postgres.up.sql ================================================ UPDATE identity_recovery_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000054_network.sqlite3.up.sql ================================================ ALTER TABLE "_courier_messages_tmp" RENAME TO "courier_messages"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_addresses` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credentials_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000055_network.sqlite3.up.sql ================================================ CREATE INDEX "courier_messages_nid_idx" ON "courier_messages" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.cockroach.up.sql ================================================ UPDATE "courier_messages" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_addresses_nid_idx` ON `identity_recovery_addresses` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_nid_idx" ON "identity_recovery_addresses" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identities_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000056_network.sqlite3.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.mysql.up.sql ================================================ DROP INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.postgres.up.sql ================================================ DROP INDEX "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_errors_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000057_network.sqlite3.up.sql ================================================ ALTER TABLE identities DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.cockroach.up.sql ================================================ ALTER TABLE "courier_messages" ADD CONSTRAINT "courier_messages_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.mysql.up.sql ================================================ DROP INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.postgres.up.sql ================================================ DROP INDEX "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "courier_messages_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000058_network.sqlite3.up.sql ================================================ ALTER TABLE identities ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.cockroach.up.sql ================================================ CREATE INDEX "courier_messages_nid_idx" ON "courier_messages" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_recovery_addresses_status_via_uq_idx` ON `identity_recovery_addresses` (`nid`, `via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "continuity_containers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000059_network.sqlite3.up.sql ================================================ UPDATE identities SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.cockroach.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_addresses_status_via_idx` ON `identity_recovery_addresses` (`nid`, `via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_settings_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000060_network.sqlite3.up.sql ================================================ CREATE TABLE "_identities_tmp" ( "id" TEXT PRIMARY KEY, "schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.cockroach.up.sql ================================================ ALTER TABLE "identities" ADD CONSTRAINT "identities_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_registration_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000061_network.sqlite3.up.sql ================================================ INSERT INTO "_identities_tmp" (id, schema_id, traits, created_at, updated_at, nid) SELECT id, schema_id, traits, created_at, updated_at, nid FROM "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.cockroach.up.sql ================================================ UPDATE identities SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD CONSTRAINT `identity_recovery_tokens_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_login_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000062_network.sqlite3.up.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.cockroach.up.sql ================================================ ALTER TABLE "identities" DROP CONSTRAINT "identities_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.mysql.up.sql ================================================ UPDATE identity_recovery_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.postgres.up.sql ================================================ UPDATE identity_recovery_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000063_network.sqlite3.up.sql ================================================ ALTER TABLE "_identities_tmp" RENAME TO "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.cockroach.up.sql ================================================ ALTER TABLE "identities" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000064_network.sqlite3.up.sql ================================================ CREATE INDEX "identities_nid_idx" ON "identities" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.cockroach.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.mysql.up.sql ================================================ CREATE INDEX `identity_recovery_tokens_nid_idx` ON `identity_recovery_tokens` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "identity_recovery_tokens" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000065_network.sqlite3.up.sql ================================================ ALTER TABLE "identity_credentials" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.cockroach.up.sql ================================================ UPDATE "identities" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000066_network.sqlite3.up.sql ================================================ ALTER TABLE identity_credentials DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.cockroach.up.sql ================================================ ALTER TABLE "identities" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` ADD CONSTRAINT `selfservice_verification_flows_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD CONSTRAINT "selfservice_verification_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000067_network.sqlite3.up.sql ================================================ ALTER TABLE identity_credentials ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.cockroach.up.sql ================================================ ALTER TABLE "identities" ADD CONSTRAINT "identities_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.mysql.up.sql ================================================ UPDATE selfservice_verification_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.postgres.up.sql ================================================ UPDATE selfservice_verification_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000068_network.sqlite3.up.sql ================================================ UPDATE identity_credentials SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.cockroach.up.sql ================================================ CREATE INDEX "identities_nid_idx" ON "identities" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.mysql.up.sql ================================================ ALTER TABLE `selfservice_verification_flows` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.postgres.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000069_network.sqlite3.up.sql ================================================ CREATE TABLE "_identity_credentials_tmp" ( "id" TEXT PRIMARY KEY, "config" TEXT NOT NULL, "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.mysql.up.sql ================================================ CREATE INDEX `selfservice_verification_flows_nid_idx` ON `selfservice_verification_flows` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.postgres.up.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "selfservice_verification_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000070_network.sqlite3.up.sql ================================================ INSERT INTO "_identity_credentials_tmp" (id, config, identity_credential_type_id, identity_id, created_at, updated_at, nid) SELECT id, config, identity_credential_type_id, identity_id, created_at, updated_at, nid FROM "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" ADD CONSTRAINT "identity_credentials_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000071_network.sqlite3.up.sql ================================================ DROP TABLE "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.cockroach.up.sql ================================================ UPDATE identity_credentials SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` ADD CONSTRAINT `identity_verifiable_addresses_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD CONSTRAINT "identity_verifiable_addresses_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000072_network.sqlite3.up.sql ================================================ ALTER TABLE "_identity_credentials_tmp" RENAME TO "identity_credentials"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" DROP CONSTRAINT "identity_credentials_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.mysql.up.sql ================================================ UPDATE identity_verifiable_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.postgres.up.sql ================================================ UPDATE identity_verifiable_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000073_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_credentials_nid_idx" ON "identity_credentials" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.mysql.up.sql ================================================ ALTER TABLE `identity_verifiable_addresses` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.postgres.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000074_network.sqlite3.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.mysql.up.sql ================================================ CREATE INDEX `identity_verifiable_addresses_nid_idx` ON `identity_verifiable_addresses` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_nid_idx" ON "identity_verifiable_addresses" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000075_network.sqlite3.up.sql ================================================ ALTER TABLE identity_credential_identifiers DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.cockroach.up.sql ================================================ UPDATE "identity_credentials" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000076_network.sqlite3.up.sql ================================================ ALTER TABLE identity_credential_identifiers ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.mysql.up.sql ================================================ DROP INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses`; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.postgres.up.sql ================================================ DROP INDEX "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000077_network.sqlite3.up.sql ================================================ UPDATE identity_credential_identifiers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credentials" ADD CONSTRAINT "identity_credentials_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_verifiable_addresses_status_via_uq_idx` ON `identity_verifiable_addresses` (`nid`, `via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000078_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.cockroach.up.sql ================================================ CREATE INDEX "identity_credentials_nid_idx" ON "identity_credentials" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.mysql.up.sql ================================================ CREATE INDEX `identity_verifiable_addresses_status_via_idx` ON `identity_verifiable_addresses` (`nid`, `via`, `value`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000079_network.sqlite3.up.sql ================================================ CREATE TABLE "_identity_credential_identifiers_tmp" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.mysql.up.sql ================================================ ALTER TABLE `identity_verification_tokens` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.postgres.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000080_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_idx" ON "_identity_credential_identifiers_tmp" (identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD CONSTRAINT "identity_credential_identifiers_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.mysql.up.sql ================================================ ALTER TABLE `identity_verification_tokens` ADD CONSTRAINT `identity_verification_tokens_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.postgres.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD CONSTRAINT "identity_verification_tokens_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000081_network.sqlite3.up.sql ================================================ INSERT INTO "_identity_credential_identifiers_tmp" (id, identifier, identity_credential_id, created_at, updated_at, nid) SELECT id, identifier, identity_credential_id, created_at, updated_at, nid FROM "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.cockroach.up.sql ================================================ UPDATE identity_credential_identifiers SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.mysql.up.sql ================================================ UPDATE identity_verification_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.postgres.up.sql ================================================ UPDATE identity_verification_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000082_network.sqlite3.up.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP CONSTRAINT "identity_credential_identifiers_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.mysql.up.sql ================================================ ALTER TABLE `identity_verification_tokens` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.postgres.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000083_network.sqlite3.up.sql ================================================ ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.mysql.up.sql ================================================ CREATE INDEX `identity_verification_tokens_nid_idx` ON `identity_verification_tokens` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.postgres.up.sql ================================================ CREATE INDEX "identity_verification_tokens_nid_idx" ON "identity_verification_tokens" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000084_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_idx" ON "identity_credential_identifiers" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000085_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.cockroach.up.sql ================================================ UPDATE "identity_credential_identifiers" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD CONSTRAINT `sessions_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD CONSTRAINT "sessions_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000086_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.mysql.up.sql ================================================ UPDATE sessions SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.postgres.up.sql ================================================ UPDATE sessions SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000087_network.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD CONSTRAINT "identity_credential_identifiers_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.mysql.up.sql ================================================ ALTER TABLE `sessions` MODIFY `nid` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.postgres.up.sql ================================================ ALTER TABLE "sessions" ALTER COLUMN "nid" TYPE UUID, ALTER COLUMN "nid" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000088_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_recovery_flows DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.cockroach.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_idx" ON "identity_credential_identifiers" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.mysql.up.sql ================================================ CREATE INDEX `sessions_nid_idx` ON `sessions` (`id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.postgres.up.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "sessions" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000089_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_recovery_flows ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000090_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000090_network.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000090_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000090_network.sqlite3.up.sql ================================================ UPDATE selfservice_recovery_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000091_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000091_network.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000091_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000091_network.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_recovery_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT, "csrf_token" TEXT NOT NULL, "state" TEXT NOT NULL, "recovered_identity_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), FOREIGN KEY (recovered_identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000092_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000092_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000092_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000092_network.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_recovery_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui, nid) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, state, recovered_identity_id, created_at, updated_at, type, ui, nid FROM "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000093_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000093_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD CONSTRAINT "selfservice_recovery_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000093_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000093_network.sqlite3.up.sql ================================================ DROP TABLE "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000094_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000094_network.cockroach.up.sql ================================================ UPDATE selfservice_recovery_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000094_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000094_network.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_recovery_flows_tmp" RENAME TO "selfservice_recovery_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000095_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000095_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP CONSTRAINT "selfservice_recovery_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000095_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000095_network.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_recovery_flows_nid_idx" ON "selfservice_recovery_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000096_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000096_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000096_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000096_network.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000097_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000097_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000097_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000097_network.sqlite3.up.sql ================================================ ALTER TABLE identity_recovery_addresses DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000098_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000098_network.cockroach.up.sql ================================================ UPDATE "selfservice_recovery_flows" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000098_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000098_network.sqlite3.up.sql ================================================ ALTER TABLE identity_recovery_addresses ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000099_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000099_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000099_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000099_network.sqlite3.up.sql ================================================ UPDATE identity_recovery_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000100_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000100_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_recovery_flows" ADD CONSTRAINT "selfservice_recovery_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000100_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000100_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000101_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000101_network.cockroach.up.sql ================================================ CREATE INDEX "selfservice_recovery_flows_nid_idx" ON "selfservice_recovery_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000101_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000101_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000102_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000102_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000102_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000102_network.sqlite3.up.sql ================================================ CREATE TABLE "_identity_recovery_addresses_tmp" ( "id" TEXT PRIMARY KEY, "via" TEXT NOT NULL, "value" TEXT NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000103_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000103_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD CONSTRAINT "identity_recovery_addresses_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000103_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000103_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "_identity_recovery_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000104_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000104_network.cockroach.up.sql ================================================ UPDATE identity_recovery_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000104_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000104_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "_identity_recovery_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000105_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000105_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" DROP CONSTRAINT "identity_recovery_addresses_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000105_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000105_network.sqlite3.up.sql ================================================ INSERT INTO "_identity_recovery_addresses_tmp" (id, via, value, identity_id, created_at, updated_at, nid) SELECT id, via, value, identity_id, created_at, updated_at, nid FROM "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000106_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000106_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000106_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000106_network.sqlite3.up.sql ================================================ DROP TABLE "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000107_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000107_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000107_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000107_network.sqlite3.up.sql ================================================ ALTER TABLE "_identity_recovery_addresses_tmp" RENAME TO "identity_recovery_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000108_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000108_network.cockroach.up.sql ================================================ UPDATE "identity_recovery_addresses" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000108_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000108_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_nid_idx" ON "identity_recovery_addresses" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000109_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000109_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000109_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000109_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000110_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000110_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_addresses" ADD CONSTRAINT "identity_recovery_addresses_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000110_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000110_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000111_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000111_network.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_nid_idx" ON "identity_recovery_addresses" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000111_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000111_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000112_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000112_network.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000112_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000112_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000113_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000113_network.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000113_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000113_network.sqlite3.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000114_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000114_network.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000114_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000114_network.sqlite3.up.sql ================================================ ALTER TABLE identity_recovery_tokens DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000115_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000115_network.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000115_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000115_network.sqlite3.up.sql ================================================ ALTER TABLE identity_recovery_tokens ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000116_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000116_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000116_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000116_network.sqlite3.up.sql ================================================ UPDATE identity_recovery_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000117_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000117_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000117_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000117_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000118_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000118_network.cockroach.up.sql ================================================ UPDATE identity_recovery_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000118_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000118_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000119_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000119_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000119_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000119_network.sqlite3.up.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "nid" char(36), FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000120_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000120_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000120_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000120_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000121_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000121_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000121_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000121_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000122_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000122_network.cockroach.up.sql ================================================ UPDATE "identity_recovery_tokens" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000122_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000122_network.sqlite3.up.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000123_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000123_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000123_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000123_network.sqlite3.up.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000124_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000124_network.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000124_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000124_network.sqlite3.up.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000125_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000125_network.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "identity_recovery_tokens" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000125_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000125_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "identity_recovery_tokens" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000126_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000126_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000126_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000126_network.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000127_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000127_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD CONSTRAINT "selfservice_verification_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000127_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000127_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_verification_flows DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000128_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000128_network.cockroach.up.sql ================================================ UPDATE selfservice_verification_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000128_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000128_network.sqlite3.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000129_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000129_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP CONSTRAINT "selfservice_verification_flows_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000129_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000129_network.sqlite3.up.sql ================================================ UPDATE selfservice_verification_flows SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000130_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000130_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000130_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000130_network.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_verification_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "state" TEXT NOT NULL DEFAULT 'show_form', "active_method" TEXT, "ui" TEXT, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000131_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000131_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000131_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000131_network.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_verification_flows_tmp" (id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, ui, nid) SELECT id, request_url, issued_at, expires_at, csrf_token, created_at, updated_at, type, state, active_method, ui, nid FROM "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000132_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000132_network.cockroach.up.sql ================================================ UPDATE "selfservice_verification_flows" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000132_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000132_network.sqlite3.up.sql ================================================ DROP TABLE "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000133_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000133_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000133_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000133_network.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_verification_flows_tmp" RENAME TO "selfservice_verification_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000134_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000134_network.cockroach.up.sql ================================================ ALTER TABLE "selfservice_verification_flows" ADD CONSTRAINT "selfservice_verification_flows_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000134_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000134_network.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "selfservice_verification_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000135_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000135_network.cockroach.up.sql ================================================ CREATE INDEX "selfservice_verification_flows_nid_idx" ON "selfservice_verification_flows" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000135_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000135_network.sqlite3.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000136_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000136_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000136_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000136_network.sqlite3.up.sql ================================================ ALTER TABLE identity_verifiable_addresses DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000137_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000137_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD CONSTRAINT "identity_verifiable_addresses_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000137_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000137_network.sqlite3.up.sql ================================================ ALTER TABLE identity_verifiable_addresses ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000138_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000138_network.cockroach.up.sql ================================================ UPDATE identity_verifiable_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000138_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000138_network.sqlite3.up.sql ================================================ UPDATE identity_verifiable_addresses SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000139_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000139_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP CONSTRAINT "identity_verifiable_addresses_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000139_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000139_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000140_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000140_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000140_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000140_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000141_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000141_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000141_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000141_network.sqlite3.up.sql ================================================ CREATE TABLE "_identity_verifiable_addresses_tmp" ( "id" TEXT PRIMARY KEY, "status" TEXT NOT NULL, "via" TEXT NOT NULL, "verified" bool NOT NULL, "value" TEXT NOT NULL, "verified_at" DATETIME, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000142_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000142_network.cockroach.up.sql ================================================ UPDATE "identity_verifiable_addresses" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000142_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000142_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000143_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000143_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000143_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000143_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "_identity_verifiable_addresses_tmp" (via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000144_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000144_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verifiable_addresses" ADD CONSTRAINT "identity_verifiable_addresses_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000144_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000144_network.sqlite3.up.sql ================================================ INSERT INTO "_identity_verifiable_addresses_tmp" (id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, nid) SELECT id, status, via, verified, value, verified_at, identity_id, created_at, updated_at, nid FROM "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000145_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000145_network.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_nid_idx" ON "identity_verifiable_addresses" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000145_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000145_network.sqlite3.up.sql ================================================ DROP TABLE "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000146_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000146_network.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000146_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000146_network.sqlite3.up.sql ================================================ ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000147_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000147_network.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000147_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000147_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_nid_idx" ON "identity_verifiable_addresses" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000148_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000148_network.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000148_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000148_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000149_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000149_network.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000149_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000149_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_status_via_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000150_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000150_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000150_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000150_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000151_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000151_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD CONSTRAINT "identity_verification_tokens_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000151_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000151_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (nid, via, value); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000152_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000152_network.cockroach.up.sql ================================================ UPDATE identity_verification_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000152_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000152_network.sqlite3.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000153_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000153_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" DROP CONSTRAINT "identity_verification_tokens_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000153_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000153_network.sqlite3.up.sql ================================================ ALTER TABLE identity_verification_tokens DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000154_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000154_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000154_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000154_network.sqlite3.up.sql ================================================ ALTER TABLE identity_verification_tokens ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000155_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000155_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000155_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000155_network.sqlite3.up.sql ================================================ UPDATE identity_verification_tokens SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000156_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000156_network.cockroach.up.sql ================================================ UPDATE "identity_verification_tokens" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000156_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000156_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verification_tokens_verification_flow_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000157_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000157_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000157_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000157_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verification_tokens_verifiable_address_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000158_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000158_network.cockroach.up.sql ================================================ ALTER TABLE "identity_verification_tokens" ADD CONSTRAINT "identity_verification_tokens_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000158_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000158_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verification_tokens_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000159_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000159_network.cockroach.up.sql ================================================ CREATE INDEX "identity_verification_tokens_nid_idx" ON "identity_verification_tokens" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000159_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000159_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_verification_tokens_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000160_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000160_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000160_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000160_network.sqlite3.up.sql ================================================ CREATE TABLE "_identity_verification_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000161_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000161_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD CONSTRAINT "sessions_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000161_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000161_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "_identity_verification_tokens_tmp" (selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000162_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000162_network.cockroach.up.sql ================================================ UPDATE sessions SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000162_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000162_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "_identity_verification_tokens_tmp" (identity_verifiable_address_id); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000163_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000163_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" DROP CONSTRAINT "sessions_nid_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000163_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000163_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_token_idx" ON "_identity_verification_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000164_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000164_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" RENAME COLUMN "nid" TO "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000164_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000164_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "_identity_verification_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000165_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000165_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "nid" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000165_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000165_network.sqlite3.up.sql ================================================ INSERT INTO "_identity_verification_tokens_tmp" (id, token, used, used_at, expires_at, issued_at, identity_verifiable_address_id, selfservice_verification_flow_id, created_at, updated_at, nid) SELECT id, token, used, used_at, expires_at, issued_at, identity_verifiable_address_id, selfservice_verification_flow_id, created_at, updated_at, nid FROM "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000166_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000166_network.cockroach.up.sql ================================================ UPDATE "sessions" SET "nid" = "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000166_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000166_network.sqlite3.up.sql ================================================ DROP TABLE "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000167_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000167_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "_nid_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000167_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000167_network.sqlite3.up.sql ================================================ ALTER TABLE "_identity_verification_tokens_tmp" RENAME TO "identity_verification_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000168_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000168_network.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD CONSTRAINT "sessions_nid_fk_idx" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000168_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000168_network.sqlite3.up.sql ================================================ CREATE INDEX "identity_verification_tokens_nid_idx" ON "identity_verification_tokens" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000169_network.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000169_network.cockroach.up.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "sessions" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000169_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000169_network.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "nid" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000170_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000170_network.sqlite3.up.sql ================================================ ALTER TABLE sessions DROP COLUMN nid; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000171_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000171_network.sqlite3.up.sql ================================================ ALTER TABLE sessions ADD COLUMN nid CHAR(36) NULL REFERENCES networks(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000172_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000172_network.sqlite3.up.sql ================================================ UPDATE sessions SET nid = (SELECT id FROM networks LIMIT 1); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000173_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000173_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000174_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000174_network.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000175_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000175_network.sqlite3.up.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "active" NUMERIC DEFAULT 'false', "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000176_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000176_network.sqlite3.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000177_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000177_network.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210410175418000178_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000178_network.sqlite3.up.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000179_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000179_network.sqlite3.up.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000180_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000180_network.sqlite3.up.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210410175418000181_network.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210410175418000181_network.sqlite3.up.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "sessions" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.cockroach.down.sql ================================================ ALTER TABLE "identities" DROP COLUMN "state_changed_at"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.cockroach.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'active'; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.mysql.down.sql ================================================ ALTER TABLE `identities` DROP COLUMN `state_changed_at`; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.mysql.up.sql ================================================ ALTER TABLE `identities` ADD COLUMN `state` VARCHAR (255) NOT NULL DEFAULT 'active'; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.postgres.down.sql ================================================ ALTER TABLE "identities" DROP COLUMN "state_changed_at"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.postgres.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "state" VARCHAR (255) NOT NULL DEFAULT 'active'; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.sqlite3.down.sql ================================================ ALTER TABLE "_identities_tmp" RENAME TO "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000000_add_identity_states.sqlite3.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "state" TEXT NOT NULL DEFAULT 'active'; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.cockroach.down.sql ================================================ ALTER TABLE "identities" DROP COLUMN "state"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.cockroach.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "state_changed_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.mysql.down.sql ================================================ ALTER TABLE `identities` DROP COLUMN `state`; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.mysql.up.sql ================================================ ALTER TABLE `identities` ADD COLUMN `state_changed_at` DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.postgres.down.sql ================================================ ALTER TABLE "identities" DROP COLUMN "state"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.postgres.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "state_changed_at" timestamp; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.sqlite3.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000001_add_identity_states.sqlite3.up.sql ================================================ ALTER TABLE "identities" ADD COLUMN "state_changed_at" DATETIME; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000002_add_identity_states.sqlite3.down.sql ================================================ INSERT INTO "_identities_tmp" (id, schema_id, traits, created_at, updated_at, nid) SELECT id, schema_id, traits, created_at, updated_at, nid FROM "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000002_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000003_add_identity_states.sqlite3.down.sql ================================================ CREATE INDEX "identities_nid_idx" ON "_identities_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210504121624000003_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000004_add_identity_states.sqlite3.down.sql ================================================ CREATE TABLE "_identities_tmp" ( "id" TEXT PRIMARY KEY, "schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210504121624000004_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000005_add_identity_states.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identities_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000005_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000006_add_identity_states.sqlite3.down.sql ================================================ ALTER TABLE "_identities_tmp" RENAME TO "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000006_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000007_add_identity_states.sqlite3.down.sql ================================================ DROP TABLE "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000007_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000008_add_identity_states.sqlite3.down.sql ================================================ INSERT INTO "_identities_tmp" (id, schema_id, traits, created_at, updated_at, nid, state_changed_at) SELECT id, schema_id, traits, created_at, updated_at, nid, state_changed_at FROM "identities"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000008_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000009_add_identity_states.sqlite3.down.sql ================================================ CREATE INDEX "identities_nid_idx" ON "_identities_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210504121624000009_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000010_add_identity_states.sqlite3.down.sql ================================================ CREATE TABLE "_identities_tmp" ( "id" TEXT PRIMARY KEY, "schema_id" TEXT NOT NULL, "traits" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "state_changed_at" DATETIME ); ================================================ FILE: persistence/sql/migrations/sql/20210504121624000010_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210504121624000011_add_identity_states.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identities_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210504121624000011_add_identity_states.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "logout_token"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "logout_token" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `logout_token`; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `logout_token` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "logout_token"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "logout_token" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000000_logout_token.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "logout_token" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.cockroach.up.sql ================================================ UPDATE sessions SET logout_token = token; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.mysql.up.sql ================================================ UPDATE sessions SET logout_token = token; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.postgres.up.sql ================================================ UPDATE sessions SET logout_token = token; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000001_logout_token.sqlite3.up.sql ================================================ UPDATE sessions SET logout_token = token; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" RENAME COLUMN "logout_token" TO "_logout_token_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.mysql.up.sql ================================================ ALTER TABLE `sessions` MODIFY `logout_token` VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.postgres.up.sql ================================================ ALTER TABLE "sessions" ALTER COLUMN "logout_token" TYPE VARCHAR (32), ALTER COLUMN "logout_token" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000002_logout_token.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "logout_token" VARCHAR (32); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.mysql.up.sql ================================================ CREATE UNIQUE INDEX `sessions_logout_token_uq_idx` ON `sessions` (`logout_token`); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.postgres.up.sql ================================================ CREATE UNIQUE INDEX "sessions_logout_token_uq_idx" ON "sessions" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.sqlite3.down.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000003_logout_token.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.cockroach.up.sql ================================================ UPDATE "sessions" SET "logout_token" = "_logout_token_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.mysql.up.sql ================================================ CREATE INDEX `sessions_logout_token_idx` ON `sessions` (`logout_token`); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.postgres.up.sql ================================================ CREATE INDEX "sessions_logout_token_idx" ON "sessions" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000004_logout_token.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000005_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000005_logout_token.cockroach.up.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "_logout_token_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000005_logout_token.sqlite3.down.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000005_logout_token.sqlite3.up.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "active" NUMERIC DEFAULT 'false', "nid" char(36), "logout_token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000006_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000006_logout_token.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "sessions_logout_token_uq_idx" ON "sessions" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000006_logout_token.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "active" NUMERIC DEFAULT 'false', "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000006_logout_token.sqlite3.up.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000007_logout_token.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000007_logout_token.cockroach.up.sql ================================================ CREATE INDEX "sessions_logout_token_idx" ON "sessions" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000007_logout_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000007_logout_token.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000008_logout_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000008_logout_token.sqlite3.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000009_logout_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000009_logout_token.sqlite3.up.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000010_logout_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000010_logout_token.sqlite3.up.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000011_logout_token.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000011_logout_token.sqlite3.up.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210618103120000012_logout_token.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000012_logout_token.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_logout_token_uq_idx" ON "sessions" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210618103120000013_logout_token.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210618103120000013_logout_token.sqlite3.up.sql ================================================ CREATE INDEX "sessions_logout_token_idx" ON "sessions" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.cockroach.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "internal_context"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "internal_context" json; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.mysql.down.sql ================================================ ALTER TABLE `selfservice_settings_flows` DROP COLUMN `internal_context`; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` ADD COLUMN `internal_context` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.postgres.down.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "internal_context"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "internal_context" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000000_settings_flow_context.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "internal_context" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.cockroach.up.sql ================================================ UPDATE selfservice_settings_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.mysql.up.sql ================================================ UPDATE selfservice_settings_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.postgres.up.sql ================================================ UPDATE selfservice_settings_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.sqlite3.down.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000001_settings_flow_context.sqlite3.up.sql ================================================ UPDATE selfservice_settings_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" RENAME COLUMN "internal_context" TO "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.mysql.up.sql ================================================ ALTER TABLE `selfservice_settings_flows` MODIFY `internal_context` JSON NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.postgres.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ALTER COLUMN "internal_context" TYPE jsonb, ALTER COLUMN "internal_context" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui, nid) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui, nid FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000002_settings_flow_context.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "selfservice_settings_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000003_settings_flow_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000003_settings_flow_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ADD COLUMN "internal_context" json; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000003_settings_flow_context.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_settings_flows_nid_idx" ON "_selfservice_settings_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210805112414000003_settings_flow_context.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), "internal_context" TEXT NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210805112414000004_settings_flow_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000004_settings_flow_context.cockroach.up.sql ================================================ UPDATE "selfservice_settings_flows" SET "internal_context" = "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000004_settings_flow_context.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_settings_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "active_method" TEXT, "state" TEXT NOT NULL DEFAULT 'show_form', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210805112414000004_settings_flow_context.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_settings_flows_nid_idx" ON "_selfservice_settings_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210805112414000005_settings_flow_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000005_settings_flow_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" ALTER COLUMN "internal_context" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000005_settings_flow_context.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_settings_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000005_settings_flow_context.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_settings_flows_tmp" (id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui, nid, internal_context) SELECT id, request_url, issued_at, expires_at, identity_id, created_at, updated_at, active_method, state, type, ui, nid, internal_context FROM "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000006_settings_flow_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000006_settings_flow_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_settings_flows" DROP COLUMN "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000006_settings_flow_context.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000006_settings_flow_context.sqlite3.up.sql ================================================ DROP TABLE "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210805112414000007_settings_flow_context.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210805112414000007_settings_flow_context.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_settings_flows_tmp" RENAME TO "selfservice_settings_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'totp'; ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '5e29b036-aa47-457f-9fe6-aa8b854a752b', 'totp' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'totp'); ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'totp'; ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '5e29b036-aa47-457f-9fe6-aa8b854a752b', 'totp' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'totp'); ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'totp'; ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '5e29b036-aa47-457f-9fe6-aa8b854a752b', 'totp' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'totp'); ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'totp'; ================================================ FILE: persistence/sql/migrations/sql/20210805122535000000_credential_types_totp.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '5e29b036-aa47-457f-9fe6-aa8b854a752b', 'totp' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'totp'); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "requested_aal"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "aal" VARCHAR (4) NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP COLUMN `requested_aal`; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `aal` VARCHAR (4) NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "requested_aal"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "aal" VARCHAR (4) NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000000_aal.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "aal" TEXT NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "aal"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "authentication_methods" json; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `aal`; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.mysql.up.sql ================================================ ALTER TABLE `sessions` ADD COLUMN `authentication_methods` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "aal"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.postgres.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "authentication_methods" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000001_aal.sqlite3.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "authentication_methods" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.cockroach.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "authentication_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.cockroach.up.sql ================================================ UPDATE sessions SET authentication_methods='[]'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.mysql.down.sql ================================================ ALTER TABLE `sessions` DROP COLUMN `authentication_methods`; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.mysql.up.sql ================================================ UPDATE sessions SET authentication_methods='[]'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.postgres.down.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "authentication_methods"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.postgres.up.sql ================================================ UPDATE sessions SET authentication_methods='[]'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000002_aal.sqlite3.up.sql ================================================ UPDATE sessions SET authentication_methods='[]'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.cockroach.up.sql ================================================ ALTER TABLE "sessions" RENAME COLUMN "authentication_methods" TO "_authentication_methods_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.mysql.up.sql ================================================ ALTER TABLE `sessions` MODIFY `authentication_methods` JSON NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.postgres.up.sql ================================================ ALTER TABLE "sessions" ALTER COLUMN "authentication_methods" TYPE jsonb, ALTER COLUMN "authentication_methods" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_login_flows_nid_idx" ON "_selfservice_login_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000003_aal.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.cockroach.up.sql ================================================ ALTER TABLE "sessions" ADD COLUMN "authentication_methods" json; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD COLUMN `requested_aal` VARCHAR (4) NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "requested_aal" VARCHAR (4) NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000004_aal.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000005_aal.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000005_aal.cockroach.up.sql ================================================ UPDATE "sessions" SET "authentication_methods" = "_authentication_methods_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000005_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_login_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000005_aal.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000006_aal.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000006_aal.cockroach.up.sql ================================================ ALTER TABLE "sessions" ALTER COLUMN "authentication_methods" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000006_aal.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000006_aal.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000007_aal.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000007_aal.cockroach.up.sql ================================================ ALTER TABLE "sessions" DROP COLUMN "_authentication_methods_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000007_aal.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000007_aal.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000008_aal.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000008_aal.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "requested_aal" VARCHAR (4) NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000008_aal.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000008_aal.sqlite3.up.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "active" NUMERIC DEFAULT 'false', "nid" char(36), "logout_token" TEXT, "aal" TEXT NOT NULL DEFAULT 'aal1', "authentication_methods" TEXT NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000009_aal.sqlite3.down.sql ================================================ CREATE INDEX "sessions_logout_token_idx" ON "_sessions_tmp" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000009_aal.sqlite3.up.sql ================================================ CREATE INDEX "sessions_logout_token_idx" ON "_sessions_tmp" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000010_aal.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_logout_token_uq_idx" ON "_sessions_tmp" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000010_aal.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_logout_token_uq_idx" ON "_sessions_tmp" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000011_aal.sqlite3.down.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000011_aal.sqlite3.up.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000012_aal.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000012_aal.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000013_aal.sqlite3.down.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000013_aal.sqlite3.up.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000014_aal.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "active" NUMERIC DEFAULT 'false', "nid" char(36), "logout_token" TEXT, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000014_aal.sqlite3.up.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token, aal, authentication_methods) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token, aal, authentication_methods FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000015_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000015_aal.sqlite3.up.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000016_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000016_aal.sqlite3.up.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000017_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000017_aal.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "requested_aal" TEXT NOT NULL DEFAULT 'aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000018_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000018_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000019_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000019_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000020_aal.sqlite3.down.sql ================================================ ALTER TABLE "_sessions_tmp" RENAME TO "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000020_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000021_aal.sqlite3.down.sql ================================================ DROP TABLE "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000021_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000022_aal.sqlite3.down.sql ================================================ INSERT INTO "_sessions_tmp" (id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token, aal) SELECT id, issued_at, expires_at, authenticated_at, identity_id, created_at, updated_at, token, active, nid, logout_token, aal FROM "sessions"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000022_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000023_aal.sqlite3.down.sql ================================================ CREATE INDEX "sessions_logout_token_idx" ON "_sessions_tmp" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000023_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000024_aal.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_logout_token_uq_idx" ON "_sessions_tmp" (logout_token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000024_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000025_aal.sqlite3.down.sql ================================================ CREATE INDEX "sessions_token_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000025_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000026_aal.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "sessions_token_uq_idx" ON "_sessions_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000026_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000027_aal.sqlite3.down.sql ================================================ CREATE INDEX "sessions_nid_idx" ON "_sessions_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000027_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000028_aal.sqlite3.down.sql ================================================ CREATE TABLE "_sessions_tmp" ( "id" TEXT PRIMARY KEY, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "authenticated_at" DATETIME NOT NULL, "identity_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "token" TEXT, "active" NUMERIC DEFAULT 'false', "nid" char(36), "logout_token" TEXT, "aal" TEXT NOT NULL DEFAULT 'aal1', FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210810153530000028_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000029_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000029_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000030_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_logout_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000030_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000031_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000031_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000032_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_token_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000032_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210810153530000033_aal.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "sessions_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210810153530000033_aal.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'lookup_secret'; ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '567a0730-7f48-4dd7-a13d-df87a51c245f', 'lookup_secret' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'lookup_secret'); ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'lookup_secret'; ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '567a0730-7f48-4dd7-a13d-df87a51c245f', 'lookup_secret' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'lookup_secret'); ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'lookup_secret'; ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '567a0730-7f48-4dd7-a13d-df87a51c245f', 'lookup_secret' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'lookup_secret'); ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'lookup_secret'; ================================================ FILE: persistence/sql/migrations/sql/20210813150152000000_credential_types_lookup.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '567a0730-7f48-4dd7-a13d-df87a51c245f', 'lookup_secret' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'lookup_secret'); ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.cockroach.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'webauthn'; ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.cockroach.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6b213fa0-e6ad-46cb-8878-b088d2ce2e3c', 'webauthn' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'webauthn'); ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.mysql.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'webauthn'; ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.mysql.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6b213fa0-e6ad-46cb-8878-b088d2ce2e3c', 'webauthn' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'webauthn'); ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.postgres.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'webauthn'; ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.postgres.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6b213fa0-e6ad-46cb-8878-b088d2ce2e3c', 'webauthn' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'webauthn'); ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.sqlite3.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'webauthn'; ================================================ FILE: persistence/sql/migrations/sql/20210816113956000000_webauthn.sqlite3.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '6b213fa0-e6ad-46cb-8878-b088d2ce2e3c', 'webauthn' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'webauthn'); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.cockroach.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "internal_context"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "internal_context" json; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.mysql.down.sql ================================================ ALTER TABLE `selfservice_registration_flows` DROP COLUMN `internal_context`; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD COLUMN `internal_context` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.postgres.down.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "internal_context"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "internal_context" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000000_flow_internal_context.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "internal_context" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "internal_context"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.cockroach.up.sql ================================================ UPDATE selfservice_login_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP COLUMN `internal_context`; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.mysql.up.sql ================================================ UPDATE selfservice_login_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "internal_context"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.postgres.up.sql ================================================ UPDATE selfservice_login_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.sqlite3.down.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000001_flow_internal_context.sqlite3.up.sql ================================================ UPDATE selfservice_login_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" RENAME COLUMN "internal_context" TO "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` MODIFY `internal_context` JSON NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ALTER COLUMN "internal_context" TYPE jsonb, ALTER COLUMN "internal_context" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, nid) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, nid FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000002_flow_internal_context.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "selfservice_login_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "internal_context" json; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` ADD COLUMN `internal_context` JSON; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "internal_context" jsonb; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_registration_flows_nid_idx" ON "_selfservice_registration_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000003_flow_internal_context.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), "requested_aal" TEXT NOT NULL DEFAULT 'aal1', "internal_context" TEXT NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.cockroach.up.sql ================================================ UPDATE "selfservice_login_flows" SET "internal_context" = "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.mysql.up.sql ================================================ UPDATE selfservice_registration_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.postgres.up.sql ================================================ UPDATE selfservice_registration_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36) ); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000004_flow_internal_context.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_login_flows_nid_idx" ON "_selfservice_login_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ALTER COLUMN "internal_context" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.mysql.up.sql ================================================ ALTER TABLE `selfservice_registration_flows` MODIFY `internal_context` JSON NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.postgres.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ALTER COLUMN "internal_context" TYPE jsonb, ALTER COLUMN "internal_context" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_registration_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000005_flow_internal_context.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid, requested_aal, internal_context) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid, requested_aal, internal_context FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000006_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000006_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000006_flow_internal_context.sqlite3.down.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000006_flow_internal_context.sqlite3.up.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000007_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000007_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "internal_context" json; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000007_flow_internal_context.sqlite3.down.sql ================================================ DROP TABLE "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000007_flow_internal_context.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_login_flows_tmp" RENAME TO "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000008_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000008_flow_internal_context.cockroach.up.sql ================================================ UPDATE selfservice_registration_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000008_flow_internal_context.sqlite3.down.sql ================================================ INSERT INTO "_selfservice_login_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid, requested_aal) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, forced, type, ui, nid, requested_aal FROM "selfservice_login_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000008_flow_internal_context.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "internal_context" TEXT; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000009_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000009_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" RENAME COLUMN "internal_context" TO "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000009_flow_internal_context.sqlite3.down.sql ================================================ CREATE INDEX "selfservice_login_flows_nid_idx" ON "_selfservice_login_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000009_flow_internal_context.sqlite3.up.sql ================================================ UPDATE selfservice_registration_flows SET internal_context='{}'; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000010_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000010_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ADD COLUMN "internal_context" json; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000010_flow_internal_context.sqlite3.down.sql ================================================ CREATE TABLE "_selfservice_login_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "forced" bool NOT NULL DEFAULT 'false', "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), "requested_aal" TEXT NOT NULL DEFAULT 'aal1' ); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000010_flow_internal_context.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "selfservice_registration_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000011_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000011_flow_internal_context.cockroach.up.sql ================================================ UPDATE "selfservice_registration_flows" SET "internal_context" = "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000011_flow_internal_context.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "selfservice_login_flows_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000011_flow_internal_context.sqlite3.up.sql ================================================ CREATE TABLE "_selfservice_registration_flows_tmp" ( "id" TEXT PRIMARY KEY, "request_url" TEXT NOT NULL, "issued_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', "expires_at" DATETIME NOT NULL, "active_method" TEXT NOT NULL, "csrf_token" TEXT NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "type" TEXT NOT NULL DEFAULT 'browser', "ui" TEXT, "nid" char(36), "internal_context" TEXT NOT NULL ); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000012_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000012_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" ALTER COLUMN "internal_context" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000012_flow_internal_context.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000012_flow_internal_context.sqlite3.up.sql ================================================ CREATE INDEX "selfservice_registration_flows_nid_idx" ON "_selfservice_registration_flows_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210816142650000013_flow_internal_context.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000013_flow_internal_context.cockroach.up.sql ================================================ ALTER TABLE "selfservice_registration_flows" DROP COLUMN "_internal_context_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000013_flow_internal_context.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000013_flow_internal_context.sqlite3.up.sql ================================================ INSERT INTO "_selfservice_registration_flows_tmp" (id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, nid, internal_context) SELECT id, request_url, issued_at, expires_at, active_method, csrf_token, created_at, updated_at, type, ui, nid, internal_context FROM "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000014_flow_internal_context.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000014_flow_internal_context.sqlite3.up.sql ================================================ DROP TABLE "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210816142650000015_flow_internal_context.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210816142650000015_flow_internal_context.sqlite3.up.sql ================================================ ALTER TABLE "_selfservice_registration_flows_tmp" RENAME TO "selfservice_registration_flows"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.cockroach.down.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.cockroach.up.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_nid_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.mysql.down.sql ================================================ CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_nid_uq_idx` ON `identity_credential_identifiers` (`nid`, `identifier`); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers DROP FOREIGN KEY identity_credential_identifiers_nid_fk_idx; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.postgres.down.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.postgres.up.sql ================================================ DROP INDEX "identity_credential_identifiers_identifier_nid_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000000_unique_credentials.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_nid_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.cockroach.down.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP COLUMN "identity_credential_type_id"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "identity_credential_type_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.mysql.down.sql ================================================ ALTER TABLE `identity_credential_identifiers` DROP COLUMN `identity_credential_type_id`; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.mysql.up.sql ================================================ DROP INDEX `identity_credential_identifiers_identifier_nid_uq_idx` ON `identity_credential_identifiers`; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.postgres.down.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP COLUMN "identity_credential_type_id"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.postgres.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "identity_credential_type_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.sqlite3.down.sql ================================================ ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000001_unique_credentials.sqlite3.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "identity_credential_type_id" char(36); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_nid_type_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD CONSTRAINT "identity_credential_identifiers_type_id_fk_idx" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.mysql.down.sql ================================================ ALTER TABLE `identity_credential_identifiers` ADD CONSTRAINT `identity_credential_identifiers_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` ADD CONSTRAINT `identity_credential_identifiers_nid_fk_idx` FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.postgres.down.sql ================================================ DROP INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.postgres.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD CONSTRAINT "identity_credential_identifiers_type_id_fk_idx" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.sqlite3.down.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000002_unique_credentials.sqlite3.up.sql ================================================ ALTER TABLE identity_credential_identifiers DROP COLUMN identity_credential_type_id; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.cockroach.up.sql ================================================ UPDATE identity_credential_identifiers SET identity_credential_type_id = (SELECT ict.id FROM identity_credential_types as ict JOIN identity_credentials AS ic ON (ic.identity_credential_type_id = ict.id) WHERE ic.id = identity_credential_id); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.mysql.down.sql ================================================ DROP INDEX `identity_credential_identifiers_identifier_nid_type_uq_idx` ON `identity_credential_identifiers`; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` ADD COLUMN `identity_credential_type_id` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.postgres.up.sql ================================================ UPDATE identity_credential_identifiers SET identity_credential_type_id = (SELECT ict.id FROM identity_credential_types as ict JOIN identity_credentials AS ic ON (ic.identity_credential_type_id = ict.id) WHERE ic.id = identity_credential_id); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.sqlite3.down.sql ================================================ INSERT INTO "_identity_credential_identifiers_tmp" (id, identifier, identity_credential_id, created_at, updated_at, nid) SELECT id, identifier, identity_credential_id, created_at, updated_at, nid FROM "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000003_unique_credentials.sqlite3.up.sql ================================================ ALTER TABLE identity_credential_identifiers ADD COLUMN identity_credential_type_id CHAR(36) NULL REFERENCES identity_credential_types(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP CONSTRAINT "identity_credential_identifiers_type_id_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers DROP FOREIGN KEY identity_credential_identifiers_type_id_fk_idx; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` ADD CONSTRAINT `identity_credential_identifiers_type_id_fk_idx` FOREIGN KEY (`identity_credential_type_id`) REFERENCES `identity_credential_types` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.postgres.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ALTER COLUMN "identity_credential_type_id" TYPE UUID, ALTER COLUMN "identity_credential_type_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.sqlite3.down.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_idx" ON "_identity_credential_identifiers_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000004_unique_credentials.sqlite3.up.sql ================================================ UPDATE identity_credential_identifiers SET identity_credential_type_id = (SELECT ict.id FROM identity_credential_types as ict JOIN identity_credentials AS ic ON (ic.identity_credential_type_id = ict.id) WHERE ic.id = identity_credential_id); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" RENAME COLUMN "identity_credential_type_id" TO "_identity_credential_type_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers DROP FOREIGN KEY identity_credential_identifiers_nid_fk_idx; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.mysql.up.sql ================================================ UPDATE identity_credential_identifiers SET identity_credential_type_id = (SELECT ict.id FROM identity_credential_types as ict JOIN identity_credentials AS ic ON (ic.identity_credential_type_id = ict.id) WHERE ic.id = identity_credential_id); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.postgres.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.sqlite3.down.sql ================================================ CREATE TABLE "_identity_credential_identifiers_tmp" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000005_unique_credentials.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000006_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000006_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD COLUMN "identity_credential_type_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000006_unique_credentials.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000006_unique_credentials.mysql.up.sql ================================================ ALTER TABLE `identity_credential_identifiers` MODIFY `identity_credential_type_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000006_unique_credentials.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000006_unique_credentials.sqlite3.up.sql ================================================ CREATE TABLE "_identity_credential_identifiers_tmp" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "identity_credential_type_id" char(36) NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000007_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000007_unique_credentials.cockroach.up.sql ================================================ UPDATE "identity_credential_identifiers" SET "identity_credential_type_id" = "_identity_credential_type_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000007_unique_credentials.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000007_unique_credentials.mysql.up.sql ================================================ CREATE UNIQUE INDEX `identity_credential_identifiers_identifier_nid_type_uq_idx` ON `identity_credential_identifiers` (`nid`, `identity_credential_type_id`, `identifier`); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000007_unique_credentials.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_identifier_nid_type_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000007_unique_credentials.sqlite3.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_idx" ON "_identity_credential_identifiers_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000008_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000008_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ALTER COLUMN "identity_credential_type_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000008_unique_credentials.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000008_unique_credentials.sqlite3.up.sql ================================================ INSERT INTO "_identity_credential_identifiers_tmp" (id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id) SELECT id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id FROM "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000009_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000009_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" DROP COLUMN "_identity_credential_type_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000009_unique_credentials.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000009_unique_credentials.sqlite3.up.sql ================================================ DROP TABLE "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000010_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000010_unique_credentials.cockroach.up.sql ================================================ ALTER TABLE "identity_credential_identifiers" ADD CONSTRAINT "identity_credential_identifiers_type_id_fk_idx" FOREIGN KEY ("identity_credential_type_id") REFERENCES "identity_credential_types" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000010_unique_credentials.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000010_unique_credentials.sqlite3.up.sql ================================================ ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; ================================================ FILE: persistence/sql/migrations/sql/20210817181232000011_unique_credentials.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000011_unique_credentials.cockroach.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210817181232000011_unique_credentials.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210817181232000011_unique_credentials.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.cockroach.down.sql ================================================ UPDATE sessions SET authentication_methods='[]' WHERE authentication_methods='[{"method":"v0.6_legacy_session"}]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.cockroach.up.sql ================================================ UPDATE sessions SET authentication_methods='[{"method":"v0.6_legacy_session"}]' WHERE authentication_methods='[]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.mysql.down.sql ================================================ UPDATE sessions SET authentication_methods='[]' WHERE authentication_methods='[{"method":"v0.6_legacy_session"}]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.mysql.up.sql ================================================ UPDATE sessions SET authentication_methods='[{"method":"v0.6_legacy_session"}]' WHERE JSON_LENGTH(authentication_methods)=0 AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.postgres.down.sql ================================================ UPDATE sessions SET authentication_methods='[]' WHERE authentication_methods='[{"method":"v0.6_legacy_session"}]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.postgres.up.sql ================================================ UPDATE sessions SET authentication_methods='[{"method":"v0.6_legacy_session"}]' WHERE authentication_methods='[]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.sqlite3.down.sql ================================================ UPDATE sessions SET authentication_methods='[]' WHERE authentication_methods='[{"method":"v0.6_legacy_session"}]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210829131458000000_session_aal_legacy.sqlite3.up.sql ================================================ UPDATE sessions SET authentication_methods='[{"method":"v0.6_legacy_session"}]' WHERE authentication_methods='[]' AND aal='aal1'; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "identity_id"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` DROP COLUMN `identity_id`; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `identity_recovery_address_id` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "identity_id"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "identity_recovery_address_id" TYPE UUID, ALTER COLUMN "identity_recovery_address_id" DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000000_identity_recovery_tokens.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_tokens_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_identity_id_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "identity_recovery_address_id" TO "_identity_recovery_address_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` DROP FOREIGN KEY `identity_recovery_tokens_identity_id_fk_idx`; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD COLUMN `identity_id` char(36); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_identity_id_fk_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "identity_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000001_identity_recovery_tokens.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk" FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON UPDATE NO ACTION ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "identity_recovery_address_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `identity_recovery_address_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.mysql.up.sql ================================================ UPDATE identity_recovery_tokens SET identity_id=(SELECT identity_id FROM identity_recovery_addresses WHERE id=identity_recovery_address_id) WHERE identity_id = '' OR identity_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.postgres.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "identity_recovery_address_id" TYPE UUID, ALTER COLUMN "identity_recovery_address_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.postgres.up.sql ================================================ UPDATE identity_recovery_tokens SET identity_id=(SELECT identity_id FROM identity_recovery_addresses WHERE id=identity_recovery_address_id) WHERE identity_id = '00000000-0000-0000-0000-000000000000' OR identity_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000002_identity_recovery_tokens.sqlite3.up.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_identity_recovery_address_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.cockroach.up.sql ================================================ UPDATE "identity_recovery_tokens" SET "identity_recovery_address_id" = "_identity_recovery_address_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.mysql.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE identity_recovery_address_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` MODIFY `identity_id` char(36) NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.postgres.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE identity_recovery_address_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "identity_id" TYPE UUID, ALTER COLUMN "identity_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "_identity_recovery_tokens_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000003_identity_recovery_tokens.sqlite3.up.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36), "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "nid" char(36), FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "identity_recovery_address_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_identity_recovery_address_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.mysql.up.sql ================================================ ALTER TABLE `identity_recovery_tokens` ADD CONSTRAINT `identity_recovery_tokens_identity_id_fk_idx` FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.postgres.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_identity_id_fk_idx" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000004_identity_recovery_tokens.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "_identity_recovery_tokens_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000005_identity_recovery_tokens.cockroach.down.sql ================================================ UPDATE "identity_recovery_tokens" SET "identity_recovery_address_id" = "_identity_recovery_address_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000005_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk" FOREIGN KEY ("identity_recovery_address_id") REFERENCES "identity_recovery_addresses" ("id") ON UPDATE NO ACTION ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000005_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000005_identity_recovery_tokens.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000006_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "identity_recovery_address_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000006_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "identity_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000006_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "nid" char(36), FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000006_identity_recovery_tokens.sqlite3.up.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000007_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "identity_recovery_address_id" TO "_identity_recovery_address_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000007_identity_recovery_tokens.cockroach.up.sql ================================================ UPDATE identity_recovery_tokens SET identity_id=(SELECT identity_id FROM identity_recovery_addresses WHERE id=identity_recovery_address_id) WHERE identity_id = '00000000-0000-0000-0000-000000000000' OR identity_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000007_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_tokens_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000007_identity_recovery_tokens.sqlite3.up.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000008_identity_recovery_tokens.cockroach.down.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP CONSTRAINT "identity_recovery_tokens_identity_recovery_addresses_id_fk"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000008_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" RENAME COLUMN "identity_id" TO "_identity_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000008_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000008_identity_recovery_tokens.sqlite3.up.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000009_identity_recovery_tokens.cockroach.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE identity_recovery_address_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000009_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD COLUMN "identity_id" UUID; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000009_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000009_identity_recovery_tokens.sqlite3.up.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000010_identity_recovery_tokens.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000010_identity_recovery_tokens.cockroach.up.sql ================================================ UPDATE "identity_recovery_tokens" SET "identity_id" = "_identity_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000010_identity_recovery_tokens.sqlite3.down.sql ================================================ ALTER TABLE "_identity_recovery_tokens_tmp" RENAME TO "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000010_identity_recovery_tokens.sqlite3.up.sql ================================================ ALTER TABLE identity_recovery_tokens ADD COLUMN identity_id CHAR(36) NULL REFERENCES identities(id) ON DELETE CASCADE ON UPDATE RESTRICT; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000011_identity_recovery_tokens.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000011_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ALTER COLUMN "identity_id" SET NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000011_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP TABLE "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000011_identity_recovery_tokens.sqlite3.up.sql ================================================ UPDATE identity_recovery_tokens SET identity_id=(SELECT identity_id FROM identity_recovery_addresses WHERE id=identity_recovery_address_id) WHERE identity_id = '' OR identity_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000012_identity_recovery_tokens.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000012_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" DROP COLUMN "_identity_id_tmp"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000012_identity_recovery_tokens.sqlite3.down.sql ================================================ INSERT INTO "_identity_recovery_tokens_tmp" (id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid, identity_id) SELECT id, token, used, used_at, identity_recovery_address_id, selfservice_recovery_flow_id, created_at, updated_at, expires_at, issued_at, nid, identity_id FROM "identity_recovery_tokens"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000012_identity_recovery_tokens.sqlite3.up.sql ================================================ DELETE FROM identity_recovery_tokens WHERE identity_recovery_address_id IS NULL AND identity_id = ''; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000013_identity_recovery_tokens.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000013_identity_recovery_tokens.cockroach.up.sql ================================================ ALTER TABLE "identity_recovery_tokens" ADD CONSTRAINT "identity_recovery_tokens_identity_id_fk_idx" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000013_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_tokens_nid_idx" ON "_identity_recovery_tokens_tmp" (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000013_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000014_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE INDEX "identity_recovery_addresses_code_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000014_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000015_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE UNIQUE INDEX "identity_recovery_addresses_code_uq_idx" ON "_identity_recovery_tokens_tmp" (token); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000015_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000016_identity_recovery_tokens.sqlite3.down.sql ================================================ CREATE TABLE "_identity_recovery_tokens_tmp" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "identity_recovery_address_id" char(36) NOT NULL, "selfservice_recovery_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "expires_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "issued_at" DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00', "nid" char(36), "identity_id" CHAR(36), FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE ); ================================================ FILE: persistence/sql/migrations/sql/20210913095309000016_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000017_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_tokens_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000017_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000018_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000018_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000019_identity_recovery_tokens.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_code_uq_idx"; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000019_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20210913095309000020_identity_recovery_tokens.sqlite3.down.sql ================================================ DELETE FROM identity_recovery_tokens WHERE identity_recovery_address_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20210913095309000020_identity_recovery_tokens.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220118104539000000_identity_fk_indexes.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000000_identity_fk_indexes.cockroach.up.sql ================================================ CREATE INDEX "identity_credentials_nid_identity_id_idx" ON "identity_credentials" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000000_identity_fk_indexes.postgres.down.sql ================================================ DROP INDEX "identity_verifiable_addresses_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000000_identity_fk_indexes.postgres.up.sql ================================================ CREATE INDEX "identity_credentials_nid_identity_id_idx" ON "identity_credentials" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000000_identity_fk_indexes.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_verifiable_addresses_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000000_identity_fk_indexes.sqlite3.up.sql ================================================ CREATE INDEX "identity_credentials_nid_identity_id_idx" ON "identity_credentials" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000001_identity_fk_indexes.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000001_identity_fk_indexes.cockroach.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_identity_credential_id_idx" ON "identity_credential_identifiers" (identity_credential_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000001_identity_fk_indexes.postgres.down.sql ================================================ DROP INDEX "identity_recovery_addresses_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000001_identity_fk_indexes.postgres.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_identity_credential_id_idx" ON "identity_credential_identifiers" (identity_credential_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000001_identity_fk_indexes.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_addresses_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000001_identity_fk_indexes.sqlite3.up.sql ================================================ CREATE INDEX "identity_credential_identifiers_nid_identity_credential_id_idx" ON "identity_credential_identifiers" (identity_credential_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000002_identity_fk_indexes.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_nid_identity_credential_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000002_identity_fk_indexes.cockroach.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_nid_identity_id_idx" ON "identity_recovery_addresses" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000002_identity_fk_indexes.postgres.down.sql ================================================ DROP INDEX "identity_credential_identifiers_nid_identity_credential_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000002_identity_fk_indexes.postgres.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_nid_identity_id_idx" ON "identity_recovery_addresses" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000002_identity_fk_indexes.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credential_identifiers_nid_identity_credential_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000002_identity_fk_indexes.sqlite3.up.sql ================================================ CREATE INDEX "identity_recovery_addresses_nid_identity_id_idx" ON "identity_recovery_addresses" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000003_identity_fk_indexes.cockroach.down.sql ================================================ DROP INDEX IF EXISTS "identity_credentials_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000003_identity_fk_indexes.cockroach.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_nid_identity_id_idx" ON "identity_verifiable_addresses" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000003_identity_fk_indexes.postgres.down.sql ================================================ DROP INDEX "identity_credentials_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000003_identity_fk_indexes.postgres.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_nid_identity_id_idx" ON "identity_verifiable_addresses" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220118104539000003_identity_fk_indexes.sqlite3.down.sql ================================================ DROP INDEX IF EXISTS "identity_credentials_nid_identity_id_idx"; ================================================ FILE: persistence/sql/migrations/sql/20220118104539000003_identity_fk_indexes.sqlite3.up.sql ================================================ CREATE INDEX "identity_verifiable_addresses_nid_identity_id_idx" ON "identity_verifiable_addresses" (identity_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.cockroach.up.sql ================================================ ALTER TABLE identity_credentials ADD version INT NOT NULL DEFAULT '0'; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.mysql.up.sql ================================================ ALTER TABLE identity_credentials ADD version INT NOT NULL DEFAULT '0'; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.postgres.up.sql ================================================ ALTER TABLE identity_credentials ADD version INT NOT NULL DEFAULT '0'; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000000_identity_credentials_version.sqlite3.up.sql ================================================ ALTER TABLE identity_credentials ADD version INT NOT NULL DEFAULT '0'; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.cockroach.down.sql ================================================ ALTER TABLE identity_credentials DROP COLUMN version; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.cockroach.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.mysql.down.sql ================================================ ALTER TABLE identity_credentials DROP COLUMN version; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.mysql.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.postgres.down.sql ================================================ ALTER TABLE identity_credentials DROP COLUMN version; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.postgres.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.sqlite3.down.sql ================================================ ALTER TABLE identity_credentials DROP COLUMN version; ================================================ FILE: persistence/sql/migrations/sql/20220301102701000001_identity_credentials_version.sqlite3.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220420102701000000_identity_metadata.down.sql ================================================ ALTER TABLE identities DROP COLUMN metadata_public; ALTER TABLE identities DROP COLUMN metadata_admin; ================================================ FILE: persistence/sql/migrations/sql/20220420102701000000_identity_metadata.mysql.up.sql ================================================ ALTER TABLE identities ADD metadata_public JSON NULL; ALTER TABLE identities ADD metadata_admin JSON NULL; ================================================ FILE: persistence/sql/migrations/sql/20220420102701000000_identity_metadata.up.sql ================================================ ALTER TABLE identities ADD metadata_public jsonb NULL; ALTER TABLE identities ADD metadata_admin jsonb NULL; ================================================ FILE: persistence/sql/migrations/sql/20220512102703000000_missing_indices.down.sql ================================================ CREATE INDEX sessions_nid_idx ON sessions (id, nid); CREATE INDEX sessions_token_idx ON sessions (token); CREATE INDEX sessions_logout_token_idx ON sessions (logout_token); CREATE INDEX identities_nid_idx ON identities (id, nid); CREATE INDEX continuity_containers_nid_idx ON continuity_containers (id, nid); CREATE INDEX courier_messages_nid_idx ON courier_messages (id, nid); CREATE INDEX identity_credential_identifiers_nid_idx ON identity_credential_identifiers (id, nid); CREATE INDEX identity_credentials_nid_idx ON identity_credentials (id, nid); CREATE INDEX identity_recovery_addresses_nid_idx ON identity_recovery_addresses (id, nid); CREATE INDEX identity_recovery_tokens_nid_idx ON identity_recovery_tokens (id, nid); CREATE INDEX identity_recovery_addresses_code_idx ON identity_recovery_tokens (token); CREATE INDEX identity_verifiable_addresses_nid_idx ON identity_verifiable_addresses (id, nid); CREATE INDEX identity_verification_tokens_nid_idx ON identity_verification_tokens (id, nid); CREATE INDEX identity_verification_tokens_token_idx ON identity_verification_tokens (token); CREATE INDEX selfservice_login_flows_nid_idx ON selfservice_login_flows (id,nid); CREATE INDEX selfservice_recovery_flows_nid_idx ON selfservice_recovery_flows (id,nid); CREATE INDEX selfservice_registration_flows_nid_idx ON selfservice_registration_flows (id,nid); CREATE INDEX selfservice_settings_flows_nid_idx ON selfservice_settings_flows (id,nid); CREATE INDEX selfservice_verification_flows_nid_idx ON selfservice_verification_flows (id,nid); DROP INDEX sessions_identity_id_nid_idx; DROP INDEX sessions_nid_id_identity_id_idx; DROP INDEX sessions_id_nid_idx; DROP INDEX sessions_token_nid_idx; DROP INDEX identities_id_nid_idx; DROP INDEX identities_nid_id_idx; DROP INDEX continuity_containers_nid_id_idx; DROP INDEX continuity_containers_id_nid_idx; DROP INDEX courier_messages_nid_id_idx; DROP INDEX courier_messages_id_nid_idx; DROP INDEX identity_credential_identifiers_nid_id_idx; DROP INDEX identity_credential_identifiers_id_nid_idx; DROP INDEX identity_credentials_nid_id_idx; DROP INDEX identity_credentials_id_nid_idx; DROP INDEX identity_recovery_addresses_nid_id_idx; DROP INDEX identity_recovery_addresses_id_nid_idx; DROP INDEX identity_recovery_tokens_nid_id_idx; DROP INDEX identity_recovery_tokens_id_nid_idx; DROP INDEX identity_recovery_tokens_selfservice_recovery_flow_id_idx; DROP INDEX identity_recovery_tokens_identity_recovery_address_id_idx; DROP INDEX identity_verification_tokens_nid_id_idx; DROP INDEX identity_verification_tokens_id_nid_idx; DROP INDEX identity_verification_tokens_token_nid_used_idx; DROP INDEX selfservice_login_flows_nid_id_idx; DROP INDEX selfservice_login_flows_id_nid_idx; DROP INDEX selfservice_recovery_flows_nid_id_idx; DROP INDEX selfservice_recovery_flows_id_nid_idx; DROP INDEX selfservice_registration_flows_nid_id_idx; DROP INDEX selfservice_registration_flows_id_nid_idx; DROP INDEX selfservice_settings_flows_nid_id_idx; DROP INDEX selfservice_settings_flows_id_nid_idx; DROP INDEX selfservice_verification_flows_nid_id_idx; DROP INDEX selfservice_verification_flows_id_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20220512102703000000_missing_indices.mysql.down.sql ================================================ -- This file has a couple more indexes added which MySQL needs for its FK constraints. Other -- databases generate those indices automatically. CREATE INDEX sessions_nid_idx ON sessions (id, nid); CREATE INDEX sessions_nid_mysqlfk_idx ON sessions (nid); CREATE INDEX sessions_token_idx ON sessions (token); CREATE INDEX sessions_mysql_identity_id_idx ON sessions (identity_id); CREATE INDEX sessions_logout_token_idx ON sessions (logout_token); CREATE INDEX identities_nid_idx ON identities (id, nid); CREATE INDEX identities_nid_mysqlfk_idx ON identities (nid); CREATE INDEX continuity_containers_nid_idx ON continuity_containers (id, nid); CREATE INDEX continuity_containers_mysqlfk_idx ON continuity_containers (nid); CREATE INDEX courier_messages_nid_idx ON courier_messages (id, nid); CREATE INDEX courier_messages_mysqlfk_idx ON courier_messages (nid); CREATE INDEX identity_credential_identifiers_nid_idx ON identity_credential_identifiers (id, nid); CREATE INDEX identity_credential_identifiers_mysqlfk_idx ON identity_credential_identifiers (nid); CREATE INDEX identity_credentials_nid_idx ON identity_credentials (id, nid); CREATE INDEX identity_credentials_mysqlfk_idx ON identity_credentials (nid); CREATE INDEX identity_recovery_addresses_nid_idx ON identity_recovery_addresses (id, nid); CREATE INDEX identity_recovery_addresses_nid_mysqlfk_idx ON identity_recovery_addresses (nid); CREATE INDEX identity_recovery_tokens_nid_idx ON identity_recovery_tokens (id, nid); CREATE INDEX identity_recovery_tokens_nid_mysqlfk_idx ON identity_recovery_tokens (nid); CREATE INDEX identity_recovery_addresses_code_idx ON identity_recovery_tokens (token); CREATE INDEX identity_recovery_tokens_srf_id_mysqlfk_idx ON identity_recovery_tokens (selfservice_recovery_flow_id); CREATE INDEX identity_recovery_tokens_ira_id_mysqlfk_idx ON identity_recovery_tokens (identity_recovery_address_id); CREATE INDEX identity_verifiable_addresses_nid_idx ON identity_verifiable_addresses (id, nid); CREATE INDEX identity_verifiable_addresses_nid_mysqlfk_idx ON identity_verifiable_addresses (nid); CREATE INDEX identity_verification_tokens_nid_idx ON identity_verification_tokens (id, nid); CREATE INDEX identity_verification_tokens_nid_mysqlfk_idx ON identity_verification_tokens (nid); CREATE INDEX identity_verification_tokens_token_idx ON identity_verification_tokens (token); CREATE INDEX selfservice_login_flows_nid_idx ON selfservice_login_flows (id, nid); CREATE INDEX selfservice_login_flows_nid_mysqlfk_idx ON selfservice_login_flows (nid); CREATE INDEX selfservice_recovery_flows_nid_idx ON selfservice_recovery_flows (id, nid); CREATE INDEX selfservice_recovery_flows_nid_mysqlfk_idx ON selfservice_recovery_flows (nid); CREATE INDEX selfservice_registration_flows_nid_idx ON selfservice_registration_flows (id, nid); CREATE INDEX selfservice_registration_flows_nid_mysqlfk_idx ON selfservice_registration_flows (nid); CREATE INDEX selfservice_settings_flows_nid_idx ON selfservice_settings_flows (id, nid); CREATE INDEX selfservice_settings_flows_nid_mysqlfk_idx ON selfservice_settings_flows (nid); CREATE INDEX selfservice_verification_flows_nid_idx ON selfservice_verification_flows (id, nid); CREATE INDEX selfservice_verification_flows_nid_mysqlfk_idx ON selfservice_verification_flows (nid); DROP INDEX sessions_nid_id_identity_id_idx ON sessions; DROP INDEX sessions_id_nid_idx ON sessions; DROP INDEX sessions_token_nid_idx ON sessions; DROP INDEX sessions_identity_id_nid_idx ON sessions; DROP INDEX identities_id_nid_idx ON identities; DROP INDEX identities_nid_id_idx ON identities; DROP INDEX continuity_containers_nid_id_idx ON continuity_containers; DROP INDEX continuity_containers_id_nid_idx ON continuity_containers; DROP INDEX courier_messages_nid_id_idx ON courier_messages; DROP INDEX courier_messages_id_nid_idx ON courier_messages; DROP INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers; DROP INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers; DROP INDEX identity_credentials_nid_id_idx ON identity_credentials; DROP INDEX identity_credentials_id_nid_idx ON identity_credentials; DROP INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses; DROP INDEX identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses; DROP INDEX identity_recovery_tokens_nid_id_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_id_nid_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_selfservice_recovery_flow_id_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_identity_recovery_address_id_idx ON identity_recovery_tokens; DROP INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens; DROP INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens; DROP INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens; DROP INDEX selfservice_login_flows_nid_id_idx ON selfservice_login_flows; DROP INDEX selfservice_login_flows_id_nid_idx ON selfservice_login_flows; DROP INDEX selfservice_recovery_flows_nid_id_idx ON selfservice_recovery_flows; DROP INDEX selfservice_recovery_flows_id_nid_idx ON selfservice_recovery_flows; DROP INDEX selfservice_registration_flows_nid_id_idx ON selfservice_registration_flows; DROP INDEX selfservice_registration_flows_id_nid_idx ON selfservice_registration_flows; DROP INDEX selfservice_settings_flows_nid_id_idx ON selfservice_settings_flows; DROP INDEX selfservice_settings_flows_id_nid_idx ON selfservice_settings_flows; DROP INDEX selfservice_verification_flows_nid_id_idx ON selfservice_verification_flows; DROP INDEX selfservice_verification_flows_id_nid_idx ON selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20220512102703000000_missing_indices.mysql.up.sql ================================================ CREATE INDEX sessions_identity_id_nid_idx ON sessions (identity_id, nid); CREATE INDEX identities_id_nid_idx ON identities (id, nid); CREATE INDEX identities_nid_id_idx ON identities (nid, id); DROP INDEX identities_nid_idx ON identities; CREATE INDEX continuity_containers_nid_id_idx ON continuity_containers (nid, id); CREATE INDEX continuity_containers_id_nid_idx ON continuity_containers (id, nid); DROP INDEX continuity_containers_nid_idx ON continuity_containers; CREATE INDEX courier_messages_nid_id_idx ON courier_messages (nid, id); CREATE INDEX courier_messages_id_nid_idx ON courier_messages (id, nid); DROP INDEX courier_messages_nid_idx ON courier_messages; CREATE INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid, id); CREATE INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id, nid); DROP INDEX identity_credential_identifiers_nid_idx ON identity_credential_identifiers; CREATE INDEX identity_credentials_nid_id_idx ON identity_credentials (nid, id); CREATE INDEX identity_credentials_id_nid_idx ON identity_credentials (id, nid); DROP INDEX identity_credentials_nid_idx ON identity_credentials; CREATE INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid, id); CREATE INDEX identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses (id, nid); DROP INDEX identity_recovery_addresses_nid_idx ON identity_recovery_addresses; CREATE INDEX identity_recovery_tokens_nid_id_idx ON identity_recovery_tokens (nid, id); CREATE INDEX identity_recovery_tokens_id_nid_idx ON identity_recovery_tokens (id, nid); CREATE INDEX identity_recovery_tokens_selfservice_recovery_flow_id_idx ON identity_recovery_tokens (selfservice_recovery_flow_id); CREATE INDEX identity_recovery_tokens_identity_recovery_address_id_idx ON identity_recovery_tokens (identity_recovery_address_id); CREATE INDEX identity_recovery_tokens_token_nid_used_idx ON identity_recovery_tokens (nid, token, used); DROP INDEX identity_recovery_tokens_nid_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_addresses_code_idx ON identity_recovery_tokens; CREATE INDEX identity_verifiable_addresses_nid_id_idx ON identity_verifiable_addresses (nid, id); CREATE INDEX identity_verifiable_addresses_id_nid_idx ON identity_verifiable_addresses (id, nid); DROP INDEX identity_verifiable_addresses_nid_idx ON identity_verifiable_addresses; CREATE INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens (nid, id); CREATE INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens (id, nid); CREATE INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens (nid, token, used); DROP INDEX identity_verification_tokens_nid_idx ON identity_verification_tokens; DROP INDEX identity_verification_tokens_token_idx ON identity_verification_tokens; CREATE INDEX selfservice_login_flows_nid_id_idx ON selfservice_login_flows (nid, id); CREATE INDEX selfservice_login_flows_id_nid_idx ON selfservice_login_flows (id, nid); DROP INDEX selfservice_login_flows_nid_idx ON selfservice_login_flows; CREATE INDEX selfservice_recovery_flows_nid_id_idx ON selfservice_recovery_flows (nid, id); CREATE INDEX selfservice_recovery_flows_id_nid_idx ON selfservice_recovery_flows (id, nid); DROP INDEX selfservice_recovery_flows_nid_idx ON selfservice_recovery_flows; CREATE INDEX selfservice_registration_flows_nid_id_idx ON selfservice_registration_flows (nid, id); CREATE INDEX selfservice_registration_flows_id_nid_idx ON selfservice_registration_flows (id, nid); DROP INDEX selfservice_registration_flows_nid_idx ON selfservice_registration_flows; CREATE INDEX selfservice_settings_flows_nid_id_idx ON selfservice_settings_flows (nid, id); CREATE INDEX selfservice_settings_flows_id_nid_idx ON selfservice_settings_flows (id, nid); DROP INDEX selfservice_settings_flows_nid_idx ON selfservice_settings_flows; CREATE INDEX selfservice_verification_flows_nid_id_idx ON selfservice_verification_flows (nid, id); CREATE INDEX selfservice_verification_flows_id_nid_idx ON selfservice_verification_flows (id, nid); DROP INDEX selfservice_verification_flows_nid_idx ON selfservice_verification_flows; CREATE INDEX sessions_nid_id_identity_id_idx ON sessions (nid, identity_id, id); CREATE INDEX sessions_id_nid_idx ON sessions (id, nid); CREATE INDEX sessions_token_nid_idx ON sessions (nid, token); DROP INDEX sessions_nid_idx ON sessions; DROP INDEX sessions_token_idx ON sessions; DROP INDEX sessions_logout_token_idx ON sessions; ================================================ FILE: persistence/sql/migrations/sql/20220512102703000000_missing_indices.up.sql ================================================ CREATE INDEX sessions_identity_id_nid_idx ON sessions (identity_id, nid); CREATE INDEX identities_id_nid_idx ON identities (id, nid); CREATE INDEX identities_nid_id_idx ON identities (nid, id); DROP INDEX identities_nid_idx; CREATE INDEX continuity_containers_nid_id_idx ON continuity_containers (nid, id); CREATE INDEX continuity_containers_id_nid_idx ON continuity_containers (id, nid); DROP INDEX continuity_containers_nid_idx; CREATE INDEX courier_messages_nid_id_idx ON courier_messages (nid, id); CREATE INDEX courier_messages_id_nid_idx ON courier_messages (id, nid); DROP INDEX courier_messages_nid_idx; CREATE INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid, id); CREATE INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id, nid); DROP INDEX identity_credential_identifiers_nid_idx; CREATE INDEX identity_credentials_nid_id_idx ON identity_credentials (nid, id); CREATE INDEX identity_credentials_id_nid_idx ON identity_credentials (id, nid); DROP INDEX identity_credentials_nid_idx; CREATE INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid, id); CREATE INDEX identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses (id, nid); DROP INDEX identity_recovery_addresses_nid_idx; CREATE INDEX identity_recovery_tokens_nid_id_idx ON identity_recovery_tokens (nid, id); CREATE INDEX identity_recovery_tokens_id_nid_idx ON identity_recovery_tokens (id, nid); CREATE INDEX identity_recovery_tokens_selfservice_recovery_flow_id_idx ON identity_recovery_tokens (selfservice_recovery_flow_id); CREATE INDEX identity_recovery_tokens_identity_recovery_address_id_idx ON identity_recovery_tokens (identity_recovery_address_id); CREATE INDEX identity_recovery_tokens_token_nid_used_idx ON identity_recovery_tokens (nid, token, used); DROP INDEX identity_recovery_addresses_code_idx; DROP INDEX identity_recovery_tokens_nid_idx; CREATE INDEX identity_verifiable_addresses_nid_id_idx ON identity_verifiable_addresses (nid, id); CREATE INDEX identity_verifiable_addresses_id_nid_idx ON identity_verifiable_addresses (id, nid); DROP INDEX identity_verifiable_addresses_nid_idx; CREATE INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens (nid, id); CREATE INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens (id, nid); CREATE INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens (nid, token, used); DROP INDEX identity_verification_tokens_nid_idx; DROP INDEX identity_verification_tokens_token_idx; CREATE INDEX selfservice_login_flows_nid_id_idx ON selfservice_login_flows (nid, id); CREATE INDEX selfservice_login_flows_id_nid_idx ON selfservice_login_flows (id, nid); DROP INDEX selfservice_login_flows_nid_idx; CREATE INDEX selfservice_recovery_flows_nid_id_idx ON selfservice_recovery_flows (nid, id); CREATE INDEX selfservice_recovery_flows_id_nid_idx ON selfservice_recovery_flows (id, nid); DROP INDEX selfservice_recovery_flows_nid_idx; CREATE INDEX selfservice_registration_flows_nid_id_idx ON selfservice_registration_flows (nid, id); CREATE INDEX selfservice_registration_flows_id_nid_idx ON selfservice_registration_flows (id, nid); DROP INDEX selfservice_registration_flows_nid_idx; CREATE INDEX selfservice_settings_flows_nid_id_idx ON selfservice_settings_flows (nid, id); CREATE INDEX selfservice_settings_flows_id_nid_idx ON selfservice_settings_flows (id, nid); DROP INDEX selfservice_settings_flows_nid_idx; CREATE INDEX selfservice_verification_flows_nid_id_idx ON selfservice_verification_flows (nid, id); CREATE INDEX selfservice_verification_flows_id_nid_idx ON selfservice_verification_flows (id, nid); DROP INDEX selfservice_verification_flows_nid_idx; CREATE INDEX sessions_nid_id_identity_id_idx ON sessions (nid, identity_id, id); CREATE INDEX sessions_id_nid_idx ON sessions (id, nid); CREATE INDEX sessions_token_nid_idx ON sessions (nid, token); DROP INDEX sessions_nid_idx; DROP INDEX sessions_token_idx; DROP INDEX sessions_logout_token_idx; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.cockroach.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "oauth2_login_challenge"; ALTER TABLE "selfservice_registration_flows" DROP COLUMN "oauth2_login_challenge"; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.cockroach.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "oauth2_login_challenge" UUID NULL; ALTER TABLE "selfservice_registration_flows" ADD COLUMN "oauth2_login_challenge" UUID NULL; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.mysql.down.sql ================================================ ALTER TABLE `selfservice_login_flows` DROP COLUMN `oauth2_login_challenge`; ALTER TABLE `selfservice_registration_flows` DROP COLUMN `oauth2_login_challenge`; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.mysql.up.sql ================================================ ALTER TABLE `selfservice_login_flows` ADD COLUMN `oauth2_login_challenge` CHAR(36) NULL; ALTER TABLE `selfservice_registration_flows` ADD COLUMN `oauth2_login_challenge` CHAR(36) NULL; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.postgres.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "oauth2_login_challenge"; ALTER TABLE "selfservice_registration_flows" DROP COLUMN "oauth2_login_challenge"; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.postgres.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "oauth2_login_challenge" UUID NULL; ALTER TABLE "selfservice_registration_flows" ADD COLUMN "oauth2_login_challenge" UUID NULL; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.sqlite3.down.sql ================================================ ALTER TABLE "selfservice_login_flows" DROP COLUMN "oauth2_login_challenge"; ALTER TABLE "selfservice_registration_flows" DROP COLUMN "oauth2_login_challenge"; ================================================ FILE: persistence/sql/migrations/sql/20220607000001000000_hydra_login_challenge.sqlite3.up.sql ================================================ ALTER TABLE "selfservice_login_flows" ADD COLUMN "oauth2_login_challenge" CHAR(36) NULL; ALTER TABLE "selfservice_registration_flows" ADD COLUMN "oauth2_login_challenge" CHAR(36) NULL; ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.cockroach.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.cockroach.up.sql ================================================ UPDATE identity_recovery_addresses SET value = LOWER(value) WHERE TRUE; UPDATE identity_verifiable_addresses SET value = LOWER(value) WHERE TRUE; ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.mysql.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.mysql.up.sql ================================================ UPDATE identity_recovery_addresses SET value = LOWER(value) WHERE TRUE; UPDATE identity_verifiable_addresses SET value = LOWER(value) WHERE TRUE; ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.postgres.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.postgres.up.sql ================================================ UPDATE identity_recovery_addresses SET value = LOWER(value) WHERE TRUE; UPDATE identity_verifiable_addresses SET value = LOWER(value) WHERE TRUE; ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220610155809000000_identity_address_casing.sqlite3.up.sql ================================================ UPDATE identity_recovery_addresses SET value = LOWER(value) WHERE TRUE; UPDATE identity_verifiable_addresses SET value = LOWER(value) WHERE TRUE; ================================================ FILE: persistence/sql/migrations/sql/20220802103909000000_courier_send_count.down.sql ================================================ ALTER TABLE courier_messages DROP COLUMN send_count; ================================================ FILE: persistence/sql/migrations/sql/20220802103909000000_courier_send_count.up.sql ================================================ ALTER TABLE courier_messages ADD send_count INT NOT NULL DEFAULT 0; ================================================ FILE: persistence/sql/migrations/sql/20220824165300000000_add_flow_type_to_identity_recovery_tokens.down.sql ================================================ ALTER TABLE identity_recovery_tokens DROP token_type; ================================================ FILE: persistence/sql/migrations/sql/20220824165300000000_add_flow_type_to_identity_recovery_tokens.up.sql ================================================ ALTER TABLE identity_recovery_tokens ADD token_type int NOT NULL DEFAULT 0; ================================================ FILE: persistence/sql/migrations/sql/20220824165300000001_populate_flow_type_in_recovery_tokens.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220824165300000001_populate_flow_type_in_recovery_tokens.up.sql ================================================ UPDATE identity_recovery_tokens SET token_type = 1 WHERE selfservice_recovery_flow_id IS NULL; UPDATE identity_recovery_tokens SET token_type = 2 WHERE selfservice_recovery_flow_id IS NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20220824165300000002_add_flow_type_check_constraint.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220824165300000002_add_flow_type_check_constraint.sqlite3.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220824165300000002_add_flow_type_check_constraint.sqlite3.up.sql ================================================ -- SQLITE does not support Check constraints in all cases ================================================ FILE: persistence/sql/migrations/sql/20220824165300000002_add_flow_type_check_constraint.up.sql ================================================ ALTER TABLE identity_recovery_tokens ADD CONSTRAINT identity_recovery_tokens_token_type_ck CHECK (token_type = 1 OR token_type = 2); ================================================ FILE: persistence/sql/migrations/sql/20220825134336000000_delete_verification_token_without_flow_id.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20220825134336000000_delete_verification_token_without_flow_id.up.sql ================================================ DELETE FROM identity_verification_tokens WHERE selfservice_verification_flow_id IS NULL; ================================================ FILE: persistence/sql/migrations/sql/20220825134336000001_not_null_constraint_verification_token_flow_id.down.sql ================================================ ALTER TABLE identity_verification_tokens ALTER selfservice_verification_flow_id DROP NOT NULL; DROP INDEX identity_verification_tokens_token_nid_used_flow_id_idx; CREATE INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens (nid, token, used); ================================================ FILE: persistence/sql/migrations/sql/20220825134336000001_not_null_constraint_verification_token_flow_id.mysql.down.sql ================================================ ALTER TABLE identity_verification_tokens MODIFY selfservice_verification_flow_id CHAR(36) NULL; DROP INDEX identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens; CREATE INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens (nid, token, used); ================================================ FILE: persistence/sql/migrations/sql/20220825134336000001_not_null_constraint_verification_token_flow_id.mysql.up.sql ================================================ ALTER TABLE identity_verification_tokens MODIFY selfservice_verification_flow_id CHAR(36) NOT NULL; DROP INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens; CREATE INDEX identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens (nid, token, used, selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/sql/20220825134336000001_not_null_constraint_verification_token_flow_id.sqlite3.down.sql ================================================ ALTER TABLE identity_verification_tokens RENAME TO identity_verification_tokens_; CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36), "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); DROP INDEX identity_verification_tokens_id_nid_idx; DROP INDEX identity_verification_tokens_nid_id_idx; DROP INDEX identity_verification_tokens_token_nid_used_flow_id_idx; DROP INDEX identity_verification_tokens_token_uq_idx; DROP INDEX identity_verification_tokens_verifiable_address_id_idx; DROP INDEX identity_verification_tokens_verification_flow_id_idx; CREATE INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens (id, nid); CREATE INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens (nid, id); CREATE INDEX identity_verification_tokens_token_nid_used_idx ON identity_verification_tokens (nid, token, used); CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); INSERT INTO identity_verification_tokens SELECT * FROM identity_verification_tokens_; DROP TABLE identity_verification_tokens_; ================================================ FILE: persistence/sql/migrations/sql/20220825134336000001_not_null_constraint_verification_token_flow_id.sqlite3.up.sql ================================================ ALTER TABLE identity_verification_tokens RENAME TO identity_verification_tokens_; CREATE TABLE "identity_verification_tokens" ( "id" TEXT PRIMARY KEY, "token" TEXT NOT NULL, "used" bool NOT NULL DEFAULT 'false', "used_at" DATETIME, "expires_at" DATETIME NOT NULL, "issued_at" DATETIME NOT NULL, "identity_verifiable_address_id" char(36) NOT NULL, "selfservice_verification_flow_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON UPDATE NO ACTION ON DELETE CASCADE ); DROP INDEX identity_verification_tokens_id_nid_idx; DROP INDEX identity_verification_tokens_nid_id_idx; DROP INDEX identity_verification_tokens_token_nid_used_idx; DROP INDEX identity_verification_tokens_token_uq_idx; DROP INDEX identity_verification_tokens_verifiable_address_id_idx; DROP INDEX identity_verification_tokens_verification_flow_id_idx; CREATE INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens (id, nid); CREATE INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens (nid, id); CREATE INDEX identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens (nid, token, used, selfservice_verification_flow_id); CREATE UNIQUE INDEX "identity_verification_tokens_token_uq_idx" ON "identity_verification_tokens" (token); CREATE INDEX "identity_verification_tokens_verifiable_address_id_idx" ON "identity_verification_tokens" (identity_verifiable_address_id); CREATE INDEX "identity_verification_tokens_verification_flow_id_idx" ON "identity_verification_tokens" (selfservice_verification_flow_id); INSERT INTO identity_verification_tokens SELECT * FROM identity_verification_tokens_; DROP TABLE identity_verification_tokens_; ================================================ FILE: persistence/sql/migrations/sql/20220825134336000001_not_null_constraint_verification_token_flow_id.up.sql ================================================ ALTER TABLE identity_verification_tokens ALTER selfservice_verification_flow_id SET NOT NULL; DROP INDEX identity_verification_tokens_token_nid_used_idx; CREATE INDEX identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens (nid, token, used, selfservice_verification_flow_id); ================================================ FILE: persistence/sql/migrations/sql/20220901123209000000_recovery_code.down.sql ================================================ DROP TABLE identity_recovery_codes; ALTER TABLE selfservice_recovery_flows DROP submit_count; ALTER TABLE selfservice_recovery_flows DROP skip_csrf_check; ================================================ FILE: persistence/sql/migrations/sql/20220901123209000000_recovery_code.mysql.down.sql ================================================ DROP TABLE identity_recovery_codes; ALTER TABLE selfservice_recovery_flows DROP submit_count; ALTER TABLE selfservice_recovery_flows DROP skip_csrf_check; ================================================ FILE: persistence/sql/migrations/sql/20220901123209000000_recovery_code.mysql.up.sql ================================================ CREATE TABLE identity_recovery_codes ( id CHAR(36) NOT NULL PRIMARY KEY, code VARCHAR (64) NOT NULL, -- HMACed value of the actual code used_at timestamp NULL DEFAULT NULL, identity_recovery_address_id CHAR(36), code_type int NOT NULL, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_recovery_flow_id CHAR(36), created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, nid CHAR(36) NOT NULL, identity_id CHAR(36) NOT NULL, CONSTRAINT identity_recovery_codes_identity_recovery_addresses_id_fk FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, CONSTRAINT identity_recovery_codes_selfservice_recovery_flows_id_fk FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON DELETE cascade, CONSTRAINT identity_recovery_tokens_identity_id_fk FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE, CONSTRAINT identity_recovery_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); CREATE INDEX identity_recovery_codes_nid_flow_id_idx ON identity_recovery_codes (nid, selfservice_recovery_flow_id); CREATE INDEX identity_recovery_codes_id_nid_idx ON identity_recovery_codes (id, nid); ALTER TABLE selfservice_recovery_flows ADD submit_count int NOT NULL DEFAULT 0; ALTER TABLE selfservice_recovery_flows ADD skip_csrf_check boolean NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/sql/20220901123209000000_recovery_code.up.sql ================================================ CREATE TABLE identity_recovery_codes ( id UUID NOT NULL PRIMARY KEY, code VARCHAR (64) NOT NULL, -- HMACed value of the actual code used_at timestamp NULL DEFAULT NULL, identity_recovery_address_id UUID, code_type INT NOT NULL, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_recovery_flow_id UUID NOT NULL, created_at timestamp NOT NULL, updated_at timestamp NOT NULL, nid UUID NOT NULL, identity_id UUID NOT NULL, CONSTRAINT identity_recovery_codes_identity_recovery_addresses_id_fk FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE cascade, CONSTRAINT identity_recovery_codes_selfservice_recovery_flows_id_fk FOREIGN KEY (selfservice_recovery_flow_id) REFERENCES selfservice_recovery_flows (id) ON DELETE cascade, CONSTRAINT identity_recovery_codes_identity_id_fk FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE, CONSTRAINT identity_recovery_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); CREATE INDEX identity_recovery_codes_nid_flow_id_idx ON identity_recovery_codes (nid, selfservice_recovery_flow_id); CREATE INDEX identity_recovery_codes_id_nid_idx ON identity_recovery_codes (id, nid); ALTER TABLE selfservice_recovery_flows ADD submit_count int NOT NULL DEFAULT 0; ALTER TABLE selfservice_recovery_flows ADD skip_csrf_check boolean NOT NULL DEFAULT FALSE; ================================================ FILE: persistence/sql/migrations/sql/20220907132836000000_add_session_devices_table.down.sql ================================================ DROP TABLE "session_devices"; ================================================ FILE: persistence/sql/migrations/sql/20220907132836000000_add_session_devices_table.mysql.down.sql ================================================ DROP TABLE session_devices; ================================================ FILE: persistence/sql/migrations/sql/20220907132836000000_add_session_devices_table.mysql.up.sql ================================================ CREATE TABLE `session_devices` ( `id` char(36) NOT NULL, PRIMARY KEY (`id`), `ip_address` VARCHAR(50) DEFAULT '', `user_agent` VARCHAR(512) DEFAULT '', `location` VARCHAR(512) DEFAULT '', `session_id` char(36) NOT NULL, `nid` char(36) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, FOREIGN KEY (`session_id`) REFERENCES `sessions` (`id`) ON DELETE cascade, FOREIGN KEY (`nid`) REFERENCES `networks` (`id`) ON DELETE cascade, CONSTRAINT unique_session_device UNIQUE (nid, session_id, ip_address, user_agent) ) ENGINE = InnoDB; CREATE INDEX `session_devices_id_nid_idx` ON `session_devices` (`id`, `nid`); CREATE INDEX `session_devices_session_id_nid_idx` ON `session_devices` (`session_id`, `nid`); ================================================ FILE: persistence/sql/migrations/sql/20220907132836000000_add_session_devices_table.up.sql ================================================ CREATE TABLE "session_devices" ( "id" UUID PRIMARY KEY NOT NULL, "ip_address" VARCHAR(50) DEFAULT '', "user_agent" VARCHAR(512) DEFAULT '', "location" VARCHAR(512) DEFAULT '', "nid" UUID NOT NULL, "session_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "session_metadata_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE cascade, CONSTRAINT "session_metadata_nid_fk" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON DELETE cascade, CONSTRAINT unique_session_device UNIQUE (nid, session_id, ip_address, user_agent) ); CREATE INDEX "session_devices_id_nid_idx" ON "session_devices" (id, nid); CREATE INDEX "session_devices_session_id_nid_idx" ON "session_devices" (session_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221024182336000000_verification_code.down.sql ================================================ DROP TABLE identity_verification_codes; ALTER TABLE selfservice_verification_flows DROP COLUMN submit_count; ================================================ FILE: persistence/sql/migrations/sql/20221024182336000000_verification_code.mysql.up.sql ================================================ CREATE TABLE identity_verification_codes ( id CHAR(36) NOT NULL PRIMARY KEY, code_hmac VARCHAR (64) NOT NULL, -- HMACed value of the actual code used_at timestamp NULL DEFAULT NULL, identity_verifiable_address_id CHAR(36), expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_verification_flow_id CHAR(36) NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, nid CHAR(36) NOT NULL, CONSTRAINT identity_verification_codes_identity_verifiable_addresses_id_fk FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, CONSTRAINT identity_verification_codes_selfservice_verification_flows_id_fk FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade, CONSTRAINT identity_verification_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); ALTER TABLE selfservice_verification_flows ADD COLUMN submit_count INT NOT NULL DEFAULT 0; CREATE INDEX identity_verification_codes_nid_flow_id_idx ON identity_verification_codes (nid, selfservice_verification_flow_id); CREATE INDEX identity_verification_codes_id_nid_idx ON identity_verification_codes (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221024182336000000_verification_code.up.sql ================================================ CREATE TABLE identity_verification_codes ( id UUID NOT NULL PRIMARY KEY, code_hmac VARCHAR (64) NOT NULL, -- HMACed value of the actual code used_at timestamp NULL DEFAULT NULL, identity_verifiable_address_id UUID, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_verification_flow_id UUID NOT NULL, created_at timestamp NOT NULL, updated_at timestamp NOT NULL, nid UUID NOT NULL, CONSTRAINT identity_verification_codes_identity_verifiable_addresses_id_fk FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses (id) ON DELETE cascade, CONSTRAINT identity_verification_codes_selfservice_verification_flows_id_fk FOREIGN KEY (selfservice_verification_flow_id) REFERENCES selfservice_verification_flows (id) ON DELETE cascade, CONSTRAINT identity_verification_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); ALTER TABLE selfservice_verification_flows ADD COLUMN submit_count INT NOT NULL DEFAULT 0; CREATE INDEX identity_verification_codes_nid_flow_id_idx ON identity_verification_codes (nid, selfservice_verification_flow_id); CREATE INDEX identity_verification_codes_id_nid_idx ON identity_verification_codes (id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.down.sql ================================================ DROP TABLE courier_message_dispatches; ================================================ FILE: persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.mysql.up.sql ================================================ CREATE TABLE courier_message_dispatches ( id CHAR(36) PRIMARY KEY, message_id CHAR(36) NOT NULL, status VARCHAR(7) NOT NULL, error JSON, nid CHAR(36) NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT courier_message_dispatches_message_id_fk FOREIGN KEY (message_id) REFERENCES courier_messages (id) ON DELETE cascade, CONSTRAINT courier_message_dispatches_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON DELETE cascade ); CREATE INDEX courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches (id, message_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.up.sql ================================================ CREATE TABLE courier_message_dispatches ( id UUID PRIMARY KEY, message_id UUID NOT NULL, status VARCHAR(7) NOT NULL, error JSON, nid UUID NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT courier_message_dispatches_message_id_fk FOREIGN KEY (message_id) REFERENCES courier_messages (id) ON DELETE cascade, CONSTRAINT courier_message_dispatches_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON DELETE cascade ); CREATE INDEX courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches (id, message_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221214101328000000_identity_delete_indices.down.sql ================================================ DROP INDEX IF EXISTS "identity_recovery_codes_identity_id_nid_idx"; DROP INDEX IF EXISTS "identity_verification_codes_verifiable_address_nid_idx"; DROP INDEX IF EXISTS "selfservice_settings_flows_identity_id_nid_idx"; DROP INDEX IF EXISTS "continuity_containers_identity_id_nid_idx"; DROP INDEX IF EXISTS "selfservice_recovery_flows_recovered_identity_id_nid_idx"; DROP INDEX IF EXISTS "identity_recovery_tokens_identity_id_nid_idx"; DROP INDEX IF EXISTS "identity_recovery_codes_identity_recovery_address_id_nid_idx"; ================================================ FILE: persistence/sql/migrations/sql/20221214101328000000_identity_delete_indices.mysql.down.sql ================================================ -- MySQL requires indexes on foreign keys and referenced keys so that foreign key checks can be fast and not require a table scan. -- In the referencing table, there must be an index where the foreign key columns are listed as the first columns in the same order. -- Such an index is created on the referencing table automatically if it does not exist. This index might be silently dropped later -- if you create another index that can be used to enforce the foreign key constraint. -- from https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html -- -> The indexes in question already existed. We have to create new ones that are just the foreign key to restore the previous state. ALTER TABLE identity_recovery_codes ADD INDEX (identity_id); DROP INDEX identity_recovery_codes_identity_id_nid_idx ON identity_recovery_codes; ALTER TABLE identity_verification_codes ADD INDEX (identity_verifiable_address_id); DROP INDEX identity_verification_codes_verifiable_address_nid_idx ON identity_verification_codes; ALTER TABLE selfservice_settings_flows ADD INDEX (identity_id); DROP INDEX selfservice_settings_flows_identity_id_nid_idx ON selfservice_settings_flows; ALTER TABLE continuity_containers ADD INDEX (identity_id); DROP INDEX continuity_containers_identity_id_nid_idx ON continuity_containers; ALTER TABLE selfservice_recovery_flows ADD INDEX (recovered_identity_id); DROP INDEX selfservice_recovery_flows_recovered_identity_id_nid_idx ON selfservice_recovery_flows; ALTER TABLE identity_recovery_tokens ADD INDEX (identity_id); DROP INDEX identity_recovery_tokens_identity_id_nid_idx ON identity_recovery_tokens; ALTER TABLE identity_recovery_codes ADD INDEX (identity_recovery_address_id); DROP INDEX identity_recovery_codes_identity_recovery_address_id_nid_idx ON identity_recovery_codes; ================================================ FILE: persistence/sql/migrations/sql/20221214101328000000_identity_delete_indices.mysql.up.sql ================================================ -- MySQL requires indexes on foreign keys and referenced keys so that foreign key checks can be fast and not require a table scan. -- In the referencing table, there must be an index where the foreign key columns are listed as the first columns in the same order. -- Such an index is created on the referencing table automatically if it does not exist. This index might be silently dropped later -- if you create another index that can be used to enforce the foreign key constraint. -- from https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html -- -> We create new indexes to be consistent with the other databases. However, dropping those will be a bit different. CREATE INDEX identity_recovery_codes_identity_id_nid_idx ON identity_recovery_codes (identity_id, nid); CREATE INDEX identity_verification_codes_verifiable_address_nid_idx ON identity_verification_codes (identity_verifiable_address_id, nid); CREATE INDEX selfservice_settings_flows_identity_id_nid_idx ON selfservice_settings_flows (identity_id, nid); CREATE INDEX continuity_containers_identity_id_nid_idx ON continuity_containers (identity_id, nid); CREATE INDEX selfservice_recovery_flows_recovered_identity_id_nid_idx ON selfservice_recovery_flows (recovered_identity_id, nid); CREATE INDEX identity_recovery_tokens_identity_id_nid_idx ON identity_recovery_tokens (identity_id, nid); CREATE INDEX identity_recovery_codes_identity_recovery_address_id_nid_idx ON identity_recovery_codes (identity_recovery_address_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221214101328000000_identity_delete_indices.up.sql ================================================ CREATE INDEX IF NOT EXISTS "identity_recovery_codes_identity_id_nid_idx" ON "identity_recovery_codes" (identity_id, nid); CREATE INDEX IF NOT EXISTS "identity_verification_codes_verifiable_address_nid_idx" ON "identity_verification_codes" (identity_verifiable_address_id, nid); CREATE INDEX IF NOT EXISTS "selfservice_settings_flows_identity_id_nid_idx" ON "selfservice_settings_flows" (identity_id, nid); CREATE INDEX IF NOT EXISTS "continuity_containers_identity_id_nid_idx" ON "continuity_containers" (identity_id, nid); CREATE INDEX IF NOT EXISTS "selfservice_recovery_flows_recovered_identity_id_nid_idx" ON "selfservice_recovery_flows" (recovered_identity_id, nid); CREATE INDEX IF NOT EXISTS "identity_recovery_tokens_identity_id_nid_idx" ON "identity_recovery_tokens" (identity_id, nid); CREATE INDEX IF NOT EXISTS "identity_recovery_codes_identity_recovery_address_id_nid_idx" ON "identity_recovery_codes" (identity_recovery_address_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20221220124639000000_errors_index.down.sql ================================================ CREATE INDEX selfservice_errors_nid_idx ON selfservice_errors (id, nid); DROP INDEX selfservice_errors_errors_nid_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.down.sql ================================================ CREATE INDEX selfservice_errors_nid_idx ON selfservice_errors (id, nid); -- needed for foreign key constraint, was there before implicitly CREATE INDEX selfservice_errors_nid_only_idx ON selfservice_errors (nid); DROP INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors; ================================================ FILE: persistence/sql/migrations/sql/20221220124639000000_errors_index.mysql.up.sql ================================================ CREATE INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors (nid, id); -- This index is not needed anymore, because the primary ID index together with the new index cover all queries. DROP INDEX selfservice_errors_nid_idx ON selfservice_errors; ================================================ FILE: persistence/sql/migrations/sql/20221220124639000000_errors_index.up.sql ================================================ CREATE INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors (nid, id); -- This index is not needed anymore, because the primary ID index together with the new index cover all queries. DROP INDEX selfservice_errors_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20230104193739000000_courier_list_index.down.sql ================================================ DROP INDEX courier_messages_nid_created_at_id_idx; DROP INDEX courier_messages_nid_status_created_at_id_idx; DROP INDEX courier_messages_nid_recipient_created_at_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20230104193739000000_courier_list_index.mysql.down.sql ================================================ DROP INDEX courier_messages_nid_created_at_id_idx ON courier_messages; DROP INDEX courier_messages_nid_status_created_at_id_idx ON courier_messages; DROP INDEX courier_messages_nid_recipient_created_at_id_idx ON courier_messages; ================================================ FILE: persistence/sql/migrations/sql/20230104193739000000_courier_list_index.up.sql ================================================ CREATE INDEX courier_messages_nid_created_at_id_idx ON courier_messages (nid, created_at DESC); CREATE INDEX courier_messages_nid_status_created_at_id_idx ON courier_messages (nid, status, created_at DESC); CREATE INDEX courier_messages_nid_recipient_created_at_id_idx ON courier_messages (nid, recipient, created_at DESC); ================================================ FILE: persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.down.sql ================================================ CREATE UNIQUE INDEX IF NOT EXISTS unique_session_device ON session_devices (nid, session_id, ip_address, user_agent); ================================================ FILE: persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.cockroach.up.sql ================================================ DROP INDEX IF EXISTS session_devices@unique_session_device CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.down.sql ================================================ CREATE UNIQUE INDEX IF NOT EXISTS unique_session_device ON session_devices (nid, session_id, ip_address, user_agent); ================================================ FILE: persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.down.sql ================================================ CREATE UNIQUE INDEX unique_session_device ON session_devices (nid, session_id, ip_address, user_agent); ================================================ FILE: persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.mysql.up.sql ================================================ ALTER TABLE session_devices DROP FOREIGN KEY session_devices_ibfk_2; ALTER TABLE session_devices DROP INDEX unique_session_device; ALTER TABLE session_devices ADD CONSTRAINT session_devices_ibfk_2 FOREIGN KEY (nid) REFERENCES networks(id) ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20230216142104000000_session_devices_index_drop.up.sql ================================================ DROP INDEX IF EXISTS session_devices.unique_session_device; ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.cockroach.down.sql ================================================ -- Downsizing is not yet supported in CockroachDB. Since this migration has no real-world impact on the application, we can safely -- not execute it. -- -- ALTER TABLE sessions ALTER COLUMN token TYPE varchar(32); -- ALTER TABLE sessions ALTER COLUMN logout_token TYPE varchar(32); ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.down.sql ================================================ ALTER TABLE sessions ALTER COLUMN token TYPE varchar(32); ALTER TABLE sessions ALTER COLUMN logout_token TYPE varchar(32); ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.down.sql ================================================ ALTER TABLE sessions MODIFY COLUMN token varchar(32) NULL; ALTER TABLE sessions MODIFY COLUMN logout_token varchar(32) NULL; ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.mysql.up.sql ================================================ ALTER TABLE sessions MODIFY COLUMN token varchar(39) NULL; ALTER TABLE sessions MODIFY COLUMN logout_token varchar(39) NULL; ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.down.sql ================================================ DROP INDEX sessions_token_uq_idx; DROP INDEX sessions_logout_token_uq_idx; DROP INDEX sessions_token_nid_idx; ALTER TABLE sessions RENAME COLUMN token TO old_token; ALTER TABLE sessions RENAME COLUMN logout_token TO old_logout_token; ALTER TABLE sessions ADD COLUMN token varchar(32) NULL; ALTER TABLE sessions ADD COLUMN logout_token varchar(32) NULL; UPDATE sessions SET token = old_token WHERE true; UPDATE sessions SET logout_token = old_logout_token WHERE true; ALTER TABLE sessions DROP COLUMN old_token; ALTER TABLE sessions DROP COLUMN old_logout_token; CREATE UNIQUE INDEX sessions_token_uq_idx ON sessions (logout_token); CREATE UNIQUE INDEX sessions_logout_token_uq_idx ON sessions (token); CREATE INDEX sessions_token_nid_idx ON sessions (nid, token); ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.sqlite3.up.sql ================================================ DROP INDEX sessions_token_uq_idx; DROP INDEX sessions_logout_token_uq_idx; DROP INDEX sessions_token_nid_idx; ALTER TABLE sessions RENAME COLUMN token TO old_token; ALTER TABLE sessions RENAME COLUMN logout_token TO old_logout_token; ALTER TABLE sessions ADD COLUMN token varchar(39) NULL; ALTER TABLE sessions ADD COLUMN logout_token varchar(39) NULL; UPDATE sessions SET token = old_token WHERE true; UPDATE sessions SET logout_token = old_logout_token WHERE true; ALTER TABLE sessions DROP COLUMN old_token; ALTER TABLE sessions DROP COLUMN old_logout_token; CREATE UNIQUE INDEX sessions_token_uq_idx ON sessions (logout_token); CREATE UNIQUE INDEX sessions_logout_token_uq_idx ON sessions (token); CREATE INDEX sessions_token_nid_idx ON sessions (nid, token); ================================================ FILE: persistence/sql/migrations/sql/20230313141439000000_session_token_length.up.sql ================================================ ALTER TABLE sessions ALTER COLUMN token TYPE varchar(39); ALTER TABLE sessions ALTER COLUMN logout_token TYPE varchar(39); ================================================ FILE: persistence/sql/migrations/sql/20230313141439000001_session_token_length.down.sql ================================================ UPDATE sessions SET token = LEFT(token, 32) WHERE TRUE; UPDATE sessions SET logout_token = LEFT(logout_token, 32) WHERE TRUE; ================================================ FILE: persistence/sql/migrations/sql/20230313141439000001_session_token_length.sqlite3.down.sql ================================================ UPDATE sessions SET token = substr(token, 0, 32) WHERE TRUE; UPDATE sessions SET logout_token = substr(logout_token, 0, 32) WHERE TRUE; ================================================ FILE: persistence/sql/migrations/sql/20230313141439000001_session_token_length.up.sql ================================================ -- ================================================ FILE: persistence/sql/migrations/sql/20230322144139000001_missing_login_index.down.sql ================================================ DROP INDEX identity_credential_identifiers_nid_i_ici_idx; ================================================ FILE: persistence/sql/migrations/sql/20230322144139000001_missing_login_index.mysql.down.sql ================================================ DROP INDEX identity_credential_identifiers_nid_i_ici_idx ON identity_credential_identifiers; ================================================ FILE: persistence/sql/migrations/sql/20230322144139000001_missing_login_index.up.sql ================================================ CREATE INDEX identity_credential_identifiers_nid_i_ici_idx ON identity_credential_identifiers (nid, identifier, identity_credential_id); ================================================ FILE: persistence/sql/migrations/sql/20230405000000000001_create_session_token_exchanges.down.sql ================================================ DROP TABLE session_token_exchanges; ================================================ FILE: persistence/sql/migrations/sql/20230405000000000001_create_session_token_exchanges.mysql.down.sql ================================================ DROP TABLE session_token_exchanges; ================================================ FILE: persistence/sql/migrations/sql/20230405000000000001_create_session_token_exchanges.mysql.up.sql ================================================ CREATE TABLE session_token_exchanges ( id CHAR(36) NOT NULL PRIMARY KEY, nid CHAR(36) NOT NULL, flow_id CHAR(36) NOT NULL, session_id CHAR(36) DEFAULT NULL, init_code VARCHAR(64) NOT NULL, return_to_code VARCHAR(64) NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Relevant query: -- SELECT * from session_token_exchanges -- WHERE nid = ? AND code = ? AND code <> '' AND session_id IS NOT NULL CREATE INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code, nid); -- Relevant query: -- UPDATE session_token_exchanges SET session_id = ? WHERE flow_id = ? AND nid = ? CREATE INDEX session_token_exchanges_nid_flow_id_idx ON session_token_exchanges (flow_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20230405000000000001_create_session_token_exchanges.up.sql ================================================ CREATE TABLE session_token_exchanges ( "id" UUID NOT NULL PRIMARY KEY, "nid" UUID NOT NULL, "flow_id" UUID NOT NULL, "session_id" UUID DEFAULT NULL, "init_code" VARCHAR(64) NOT NULL, "return_to_code" VARCHAR(64) NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL ); -- Relevant query: -- SELECT * from session_token_exchanges -- WHERE nid = ? AND code = ? AND code <> '' AND session_id IS NOT NULL CREATE INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code, nid); -- Relevant query: -- UPDATE session_token_exchanges SET session_id = ? WHERE flow_id = ? AND nid = ? CREATE INDEX session_token_exchanges_nid_flow_id_idx ON session_token_exchanges (flow_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20230614000001000000_hydra_login_challenge_format.down.sql ================================================ -- ALTER TABLE selfservice_login_flows ADD COLUMN oauth2_login_challenge UUID NULL; -- ALTER TABLE selfservice_registration_flows ADD COLUMN oauth2_login_challenge UUID NULL; ALTER TABLE selfservice_login_flows DROP COLUMN oauth2_login_challenge_data; ALTER TABLE selfservice_registration_flows DROP COLUMN oauth2_login_challenge_data; ================================================ FILE: persistence/sql/migrations/sql/20230614000001000000_hydra_login_challenge_format.mysql.down.sql ================================================ -- ALTER TABLE selfservice_login_flows ADD COLUMN oauth2_login_challenge CHAR(36) NULL; -- ALTER TABLE selfservice_registration_flows ADD COLUMN oauth2_login_challenge CHAR(36) NULL; ALTER TABLE selfservice_login_flows DROP COLUMN oauth2_login_challenge_data; ALTER TABLE selfservice_registration_flows DROP COLUMN oauth2_login_challenge_data; ================================================ FILE: persistence/sql/migrations/sql/20230614000001000000_hydra_login_challenge_format.sqlite3.down.sql ================================================ -- ALTER TABLE `selfservice_login_flows` ADD COLUMN `oauth2_login_challenge` CHAR(36) NULL; -- ALTER TABLE `selfservice_registration_flows` ADD COLUMN `oauth2_login_challenge` CHAR(36) NULL; ALTER TABLE "selfservice_login_flows" DROP COLUMN "oauth2_login_challenge_data"; ALTER TABLE "selfservice_registration_flows" DROP COLUMN "oauth2_login_challenge_data"; ================================================ FILE: persistence/sql/migrations/sql/20230614000001000000_hydra_login_challenge_format.up.sql ================================================ -- Drop columns in a later migration. -- ALTER TABLE selfservice_login_flows DROP COLUMN oauth2_login_challenge; -- ALTER TABLE selfservice_registration_flows DROP COLUMN oauth2_login_challenge; ALTER TABLE selfservice_login_flows ADD COLUMN oauth2_login_challenge_data TEXT NULL; ALTER TABLE selfservice_registration_flows ADD COLUMN oauth2_login_challenge_data TEXT NULL; ================================================ FILE: persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.down.sql ================================================ CREATE INDEX sessions_identity_id_nid_idx ON sessions (identity_id, nid); DROP INDEX sessions_identity_id_nid_sorted_idx; ================================================ FILE: persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.down.sql ================================================ CREATE INDEX sessions_identity_id_nid_idx ON sessions (identity_id, nid); DROP INDEX sessions_identity_id_nid_sorted_idx ON sessions; ================================================ FILE: persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.up.sql ================================================ CREATE INDEX sessions_identity_id_nid_sorted_idx ON sessions (identity_id, nid, authenticated_at DESC); DROP INDEX sessions_identity_id_nid_idx ON sessions; ================================================ FILE: persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.up.sql ================================================ CREATE INDEX sessions_identity_id_nid_sorted_idx ON sessions (identity_id, nid, authenticated_at DESC); DROP INDEX sessions_identity_id_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.down.sql ================================================ -- nothing ================================================ FILE: persistence/sql/migrations/sql/20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.mysql.down.sql ================================================ -- nothing: needed for foreign key anyways ================================================ FILE: persistence/sql/migrations/sql/20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.mysql.up.sql ================================================ CREATE INDEX identity_credential_identifiers_nid_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20230626000000000001_identity_credential_identifiers_nid_identity_credential_id_idx.up.sql ================================================ -- index already exists ================================================ FILE: persistence/sql/migrations/sql/20230703143600000001_selfservice_registration_login_flows_state.down.sql ================================================ ALTER table selfservice_registration_flows DROP COLUMN state; ALTER table selfservice_login_flows DROP COLUMN state; ================================================ FILE: persistence/sql/migrations/sql/20230703143600000001_selfservice_registration_login_flows_state.up.sql ================================================ ALTER table selfservice_login_flows ADD state VARCHAR(255) NULL; ALTER table selfservice_registration_flows ADD state VARCHAR(255) NULL; ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.cockroach.down.sql ================================================ UPDATE selfservice_login_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_recovery_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_registration_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_settings_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_verification_flows SET request_url = substr(request_url, 1, 1024); ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.down.sql ================================================ UPDATE selfservice_login_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_recovery_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_registration_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_settings_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_verification_flows SET request_url = substr(request_url, 1, 1024); ALTER TABLE selfservice_login_flows ALTER COLUMN request_url TYPE VARCHAR(1024); ALTER TABLE selfservice_recovery_flows ALTER COLUMN request_url TYPE VARCHAR(1024); ALTER TABLE selfservice_registration_flows ALTER COLUMN request_url TYPE VARCHAR(1024); ALTER TABLE selfservice_settings_flows ALTER COLUMN request_url TYPE VARCHAR(1024); ALTER TABLE selfservice_verification_flows ALTER COLUMN request_url TYPE VARCHAR(1024); ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.mysql.down.sql ================================================ UPDATE selfservice_login_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_recovery_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_registration_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_settings_flows SET request_url = substr(request_url, 1, 1024); UPDATE selfservice_verification_flows SET request_url = substr(request_url, 1, 1024); ALTER TABLE selfservice_login_flows MODIFY request_url VARCHAR(1024); ALTER TABLE selfservice_recovery_flows MODIFY request_url VARCHAR(1024); ALTER TABLE selfservice_registration_flows MODIFY request_url VARCHAR(1024); ALTER TABLE selfservice_settings_flows MODIFY request_url VARCHAR(1024); ALTER TABLE selfservice_verification_flows MODIFY request_url VARCHAR(1024); ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.mysql.up.sql ================================================ ALTER TABLE selfservice_login_flows MODIFY request_url TEXT; ALTER TABLE selfservice_recovery_flows MODIFY request_url TEXT; ALTER TABLE selfservice_registration_flows MODIFY request_url TEXT; ALTER TABLE selfservice_settings_flows MODIFY request_url TEXT; ALTER TABLE selfservice_verification_flows MODIFY request_url TEXT; ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.sqlite.down.sql ================================================ ALTER TABLE selfservice_login_flows ADD COLUMN request_url_new VARCHAR(1024) NOT NULL DEFAULT ''; ALTER TABLE selfservice_recovery_flows ADD COLUMN request_url_new VARCHAR(1024) NOT NULL DEFAULT ''; ALTER TABLE selfservice_registration_flows ADD COLUMN request_url_new VARCHAR(1024) NOT NULL DEFAULT ''; ALTER TABLE selfservice_settings_flows ADD COLUMN request_url_new VARCHAR(1024) NOT NULL DEFAULT ''; ALTER TABLE selfservice_verification_flows ADD COLUMN request_url_new VARCHAR(1024) NOT NULL DEFAULT ''; UPDATE selfservice_login_flows SET request_url_new = substr(request_url, 1, 1024); UPDATE selfservice_recovery_flows SET request_url_new = substr(request_url, 1, 1024); UPDATE selfservice_registration_flows SET request_url_new = substr(request_url, 1, 1024); UPDATE selfservice_settings_flows SET request_url_new = substr(request_url, 1, 1024); UPDATE selfservice_verification_flows SET request_url_new = substr(request_url, 1, 1024); ALTER TABLE selfservice_login_flows DROP COLUMN request_url; ALTER TABLE selfservice_login_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_recovery_flows DROP COLUMN request_url; ALTER TABLE selfservice_recovery_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_registration_flows DROP COLUMN request_url; ALTER TABLE selfservice_registration_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_settings_flows DROP COLUMN request_url; ALTER TABLE selfservice_settings_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_verification_flows DROP COLUMN request_url; ALTER TABLE selfservice_verification_flows RENAME COLUMN request_url_new TO request_url; ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.sqlite.up.sql ================================================ ALTER TABLE selfservice_login_flows ADD COLUMN request_url_new TEXT NOT NULL DEFAULT ''; ALTER TABLE selfservice_recovery_flows ADD COLUMN request_url_new TEXT NOT NULL DEFAULT ''; ALTER TABLE selfservice_registration_flows ADD COLUMN request_url_new TEXT NOT NULL DEFAULT ''; ALTER TABLE selfservice_settings_flows ADD COLUMN request_url_new TEXT NOT NULL DEFAULT ''; ALTER TABLE selfservice_verification_flows ADD COLUMN request_url_new TEXT NOT NULL DEFAULT ''; UPDATE selfservice_login_flows SET request_url_new = request_url; UPDATE selfservice_recovery_flows SET request_url_new = request_url; UPDATE selfservice_registration_flows SET request_url_new = request_url; UPDATE selfservice_settings_flows SET request_url_new = request_url; UPDATE selfservice_verification_flows SET request_url_new = request_url; ALTER TABLE selfservice_login_flows DROP COLUMN request_url; ALTER TABLE selfservice_login_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_recovery_flows DROP COLUMN request_url; ALTER TABLE selfservice_recovery_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_registration_flows DROP COLUMN request_url; ALTER TABLE selfservice_registration_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_settings_flows DROP COLUMN request_url; ALTER TABLE selfservice_settings_flows RENAME COLUMN request_url_new TO request_url; ALTER TABLE selfservice_verification_flows DROP COLUMN request_url; ALTER TABLE selfservice_verification_flows RENAME COLUMN request_url_new TO request_url; ================================================ FILE: persistence/sql/migrations/sql/20230705000000000001_cookie_flow_request_url.up.sql ================================================ ALTER TABLE selfservice_login_flows ALTER COLUMN request_url TYPE TEXT; ALTER TABLE selfservice_recovery_flows ALTER COLUMN request_url TYPE TEXT; ALTER TABLE selfservice_registration_flows ALTER COLUMN request_url TYPE TEXT; ALTER TABLE selfservice_settings_flows ALTER COLUMN request_url TYPE TEXT; ALTER TABLE selfservice_verification_flows ALTER COLUMN request_url TYPE TEXT; ================================================ FILE: persistence/sql/migrations/sql/20230706000000000001_available_aal.down.sql ================================================ ALTER TABLE identities DROP COLUMN available_aal; ================================================ FILE: persistence/sql/migrations/sql/20230706000000000001_available_aal.up.sql ================================================ ALTER TABLE identities ADD COLUMN available_aal VARCHAR(4) NULL; ================================================ FILE: persistence/sql/migrations/sql/20230707133700000000_identity_login_code.down.sql ================================================ DROP TABLE identity_login_codes; ALTER TABLE selfservice_login_flows DROP submit_count; ================================================ FILE: persistence/sql/migrations/sql/20230707133700000000_identity_login_code.mysql.up.sql ================================================ CREATE TABLE identity_login_codes ( id CHAR(36) NOT NULL PRIMARY KEY, code VARCHAR(64) NOT NULL, -- HMACed value of the actual code address VARCHAR(255) NOT NULL, address_type CHAR(36) NOT NULL, used_at timestamp NULL DEFAULT NULL, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_login_flow_id CHAR(36), identity_id CHAR(36) NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, nid CHAR(36) NOT NULL, CONSTRAINT identity_login_codes_selfservice_login_flows_id_fk FOREIGN KEY (selfservice_login_flow_id) REFERENCES selfservice_login_flows (id) ON DELETE cascade, CONSTRAINT identity_login_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); CREATE INDEX identity_login_codes_nid_flow_id_idx ON identity_login_codes (nid, selfservice_login_flow_id); CREATE INDEX identity_login_codes_id_nid_idx ON identity_login_codes (id, nid); ALTER TABLE selfservice_login_flows ADD submit_count int NOT NULL DEFAULT 0; ================================================ FILE: persistence/sql/migrations/sql/20230707133700000000_identity_login_code.up.sql ================================================ CREATE TABLE identity_login_codes ( id UUID NOT NULL PRIMARY KEY, code VARCHAR(64) NOT NULL, -- HMACed value of the actual code address VARCHAR(255) NOT NULL, address_type CHAR(36) NOT NULL, used_at timestamp NULL DEFAULT NULL, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_login_flow_id UUID NOT NULL, identity_id UUID NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, nid UUID NOT NULL, CONSTRAINT identity_login_codes_selfservice_login_flows_id_fk FOREIGN KEY (selfservice_login_flow_id) REFERENCES selfservice_login_flows (id) ON DELETE cascade, CONSTRAINT identity_login_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); CREATE INDEX identity_login_codes_nid_flow_id_idx ON identity_login_codes (nid, selfservice_login_flow_id); CREATE INDEX identity_login_codes_id_nid_idx ON identity_login_codes (id, nid); ALTER TABLE selfservice_login_flows ADD submit_count int NOT NULL DEFAULT 0; ================================================ FILE: persistence/sql/migrations/sql/20230707133700000001_identity_registration_code.down.sql ================================================ DROP TABLE identity_registration_codes; ALTER TABLE selfservice_registration_flows DROP submit_count; ================================================ FILE: persistence/sql/migrations/sql/20230707133700000001_identity_registration_code.mysql.up.sql ================================================ CREATE TABLE identity_registration_codes ( id CHAR(36) NOT NULL PRIMARY KEY, code VARCHAR(64) NOT NULL, -- HMACed value of the actual code address VARCHAR(255) NOT NULL, address_type CHAR(36) NOT NULL, used_at timestamp NULL DEFAULT NULL, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_registration_flow_id CHAR(36), created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, nid CHAR(36) NOT NULL, CONSTRAINT identity_registration_codes_selfservice_registration_flows_id_fk FOREIGN KEY (selfservice_registration_flow_id) REFERENCES selfservice_registration_flows (id) ON DELETE cascade, CONSTRAINT identity_registration_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); CREATE INDEX identity_registration_codes_nid_flow_id_idx ON identity_registration_codes (nid, selfservice_registration_flow_id); CREATE INDEX identity_registration_codes_id_nid_idx ON identity_registration_codes (id, nid); ALTER TABLE selfservice_registration_flows ADD submit_count int NOT NULL DEFAULT 0; ================================================ FILE: persistence/sql/migrations/sql/20230707133700000001_identity_registration_code.up.sql ================================================ CREATE TABLE identity_registration_codes ( id UUID NOT NULL PRIMARY KEY, code VARCHAR(64) NOT NULL, -- HMACed value of the actual code address VARCHAR(255) NOT NULL, address_type CHAR(36) NOT NULL, used_at timestamp NULL DEFAULT NULL, expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', selfservice_registration_flow_id UUID NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, nid UUID NOT NULL, CONSTRAINT identity_registration_codes_selfservice_registration_flows_id_fk FOREIGN KEY (selfservice_registration_flow_id) REFERENCES selfservice_registration_flows (id) ON DELETE cascade, CONSTRAINT identity_registration_codes_networks_id_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE ); CREATE INDEX identity_registration_codes_nid_flow_id_idx ON identity_registration_codes (nid, selfservice_registration_flow_id); CREATE INDEX identity_registration_codes_id_nid_idx ON identity_registration_codes (id, nid); ALTER TABLE selfservice_registration_flows ADD submit_count int NOT NULL DEFAULT 0; ================================================ FILE: persistence/sql/migrations/sql/20230712173852000000_credential_types_code.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'code'; ================================================ FILE: persistence/sql/migrations/sql/20230712173852000000_credential_types_code.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '14f3b7e2-8725-4068-be39-8a796485fd97', 'code' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'code'); ================================================ FILE: persistence/sql/migrations/sql/20230811000000000001_verification_add_oauth2_login_challenge.down.sql ================================================ ALTER TABLE selfservice_verification_flows DROP COLUMN oauth2_login_challenge; ================================================ FILE: persistence/sql/migrations/sql/20230811000000000001_verification_add_oauth2_login_challenge.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN oauth2_login_challenge TEXT NULL; ================================================ FILE: persistence/sql/migrations/sql/20230818000000000001_verification_add_oauth2_login_challenge_identity_id.down.sql ================================================ ALTER TABLE selfservice_verification_flows DROP COLUMN session_id; ================================================ FILE: persistence/sql/migrations/sql/20230818000000000001_verification_add_oauth2_login_challenge_identity_id.mysql.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN session_id VARCHAR(36); ================================================ FILE: persistence/sql/migrations/sql/20230818000000000001_verification_add_oauth2_login_challenge_identity_id.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN session_id UUID; ================================================ FILE: persistence/sql/migrations/sql/20230823000000000001_verification_add_oauth2_login_challenge_params.down.sql ================================================ ALTER TABLE selfservice_verification_flows DROP COLUMN identity_id; ALTER TABLE selfservice_verification_flows DROP COLUMN authentication_methods; ================================================ FILE: persistence/sql/migrations/sql/20230823000000000001_verification_add_oauth2_login_challenge_params.mysql.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN identity_id VARCHAR(36); ALTER TABLE selfservice_verification_flows ADD COLUMN authentication_methods JSON; ================================================ FILE: persistence/sql/migrations/sql/20230823000000000001_verification_add_oauth2_login_challenge_params.sqlite.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN identity_id VARCHAR(36); ALTER TABLE selfservice_verification_flows ADD COLUMN authentication_methods TEXT; ================================================ FILE: persistence/sql/migrations/sql/20230823000000000001_verification_add_oauth2_login_challenge_params.up.sql ================================================ ALTER TABLE selfservice_verification_flows ADD COLUMN identity_id UUID; ALTER TABLE selfservice_verification_flows ADD COLUMN authentication_methods JSON; ================================================ FILE: persistence/sql/migrations/sql/20230907085000000000_add_organization_id.down.sql ================================================ alter table selfservice_login_flows drop column organization_id; alter table selfservice_registration_flows drop column organization_id; alter table identities drop column organization_id; ================================================ FILE: persistence/sql/migrations/sql/20230907085000000000_add_organization_id.mysql.up.sql ================================================ alter table selfservice_login_flows add column organization_id char(36) null; alter table selfservice_registration_flows add column organization_id char(36) null; alter table identities add column organization_id char(36) null; ================================================ FILE: persistence/sql/migrations/sql/20230907085000000000_add_organization_id.up.sql ================================================ alter table selfservice_login_flows add column organization_id uuid null; alter table selfservice_registration_flows add column organization_id uuid null; alter table identities add column organization_id uuid null; ================================================ FILE: persistence/sql/migrations/sql/20230920171028000000_identity_search_index.cockroach.down.sql ================================================ DROP INDEX IF EXISTS identity_credential_identifiers_nid_identifier_gin; ================================================ FILE: persistence/sql/migrations/sql/20230920171028000000_identity_search_index.cockroach.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_identifier_gin ON identity_credential_identifiers USING GIN (nid, identifier gin_trgm_ops); ================================================ FILE: persistence/sql/migrations/sql/20230920171028000000_identity_search_index.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20230920171028000000_identity_search_index.postgres.down.sql ================================================ DROP INDEX identity_credential_identifiers_nid_identifier_gin; ================================================ FILE: persistence/sql/migrations/sql/20230920171028000000_identity_search_index.postgres.up.sql ================================================ CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gin; CREATE INDEX identity_credential_identifiers_nid_identifier_gin ON identity_credential_identifiers USING GIN (nid, identifier gin_trgm_ops); ================================================ FILE: persistence/sql/migrations/sql/20230920171028000000_identity_search_index.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20231108111100000000_credential_types_passkey.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'passkey'; ================================================ FILE: persistence/sql/migrations/sql/20231108111100000000_credential_types_passkey.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '8d0ca544-9bf6-45d3-bd75-0bbb3aeba3c7', 'passkey' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'passkey'); ================================================ FILE: persistence/sql/migrations/sql/20231130094628000000_courier_message_channel.down.sql ================================================ ALTER TABLE courier_messages DROP column channel; ================================================ FILE: persistence/sql/migrations/sql/20231130094628000000_courier_message_channel.up.sql ================================================ ALTER TABLE courier_messages ADD column channel VARCHAR(32) NULL; ================================================ FILE: persistence/sql/migrations/sql/20240119094628000000_sessions_created_at_index.down.sql ================================================ DROP INDEX sessions_nid_created_at_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20240119094628000000_sessions_created_at_index.mysql.down.sql ================================================ DROP INDEX sessions_nid_created_at_id_idx ON sessions; ================================================ FILE: persistence/sql/migrations/sql/20240119094628000000_sessions_created_at_index.up.sql ================================================ CREATE INDEX sessions_nid_created_at_id_idx ON sessions (nid, created_at DESC, id ASC); ================================================ FILE: persistence/sql/migrations/sql/20240213095000000000_identity_credentials_user_handle_index.cockroach.down.sql ================================================ DROP INDEX identity_credentials_user_handle_idx; ================================================ FILE: persistence/sql/migrations/sql/20240213095000000000_identity_credentials_user_handle_index.cockroach.up.sql ================================================ CREATE INVERTED INDEX identity_credentials_user_handle_idx ON identity_credentials (config) WHERE config ->> 'user_handle' IS NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20240213095000000000_identity_credentials_user_handle_index.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240213095000000000_identity_credentials_user_handle_index.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240214113828000000_courier_dispatch_indices.down.sql ================================================ CREATE INDEX IF NOT EXISTS courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches (id ASC, message_id ASC, nid ASC); DROP INDEX courier_message_dispatches_message_id_idx; DROP INDEX courier_message_dispatches_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20240214113828000000_courier_dispatch_indices.mysql.down.sql ================================================ CREATE INDEX courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches (id ASC, message_id ASC, nid ASC); -- These can't be removed because of foreign key constraints which disallow index deletion in MySQL. -- DROP INDEX courier_message_dispatches_message_id_idx ON courier_message_dispatches; -- DROP INDEX courier_message_dispatches_nid_idx ON courier_message_dispatches; ================================================ FILE: persistence/sql/migrations/sql/20240214113828000000_courier_dispatch_indices.mysql.up.sql ================================================ -- Remove unused index DROP INDEX courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches; -- For pop eager load CREATE INDEX courier_message_dispatches_message_id_idx ON courier_message_dispatches (message_id, created_at DESC); -- For delete by nid CREATE INDEX courier_message_dispatches_nid_idx ON courier_message_dispatches (nid); ================================================ FILE: persistence/sql/migrations/sql/20240214113828000000_courier_dispatch_indices.up.sql ================================================ -- Remove unused index DROP INDEX courier_message_dispatches_id_message_id_nid_idx; -- For pop eager load CREATE INDEX IF NOT EXISTS courier_message_dispatches_message_id_idx ON courier_message_dispatches (message_id, created_at DESC); -- For delete by nid CREATE INDEX IF NOT EXISTS courier_message_dispatches_nid_idx ON courier_message_dispatches (nid); ================================================ FILE: persistence/sql/migrations/sql/20240221000000000000_identity_recovery_codes_flow_id_idx.cockroach.down.sql ================================================ DROP INDEX IF EXISTS identity_login_codes@identity_login_codes_identity_id_idx; DROP INDEX IF EXISTS identity_login_codes@identity_login_codes_flow_id_idx; DROP INDEX IF EXISTS identity_recovery_codes@identity_recovery_codes_flow_id_idx; DROP INDEX IF EXISTS identity_registration_codes@identity_registration_codes_flow_id_idx; DROP INDEX IF EXISTS identity_verification_codes@identity_verification_codes_flow_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20240221000000000000_identity_recovery_codes_flow_id_idx.down.sql ================================================ DROP INDEX IF EXISTS identity_login_codes.identity_login_codes_identity_id_idx; DROP INDEX IF EXISTS identity_login_codes.identity_login_codes_flow_id_idx; DROP INDEX IF EXISTS identity_recovery_codes.identity_recovery_codes_flow_id_idx; DROP INDEX IF EXISTS identity_registration_codes.identity_registration_codes_flow_id_idx; DROP INDEX IF EXISTS identity_verification_codes.identity_verification_codes_flow_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20240221000000000000_identity_recovery_codes_flow_id_idx.mysql.down.sql ================================================ ALTER TABLE `identity_recovery_codes` DROP FOREIGN KEY `identity_recovery_codes_identity_id_fk`, ADD CONSTRAINT `identity_recovery_tokens_identity_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; ALTER TABLE `identity_login_codes` DROP FOREIGN KEY `identity_login_codes_identity_id_fk`; ================================================ FILE: persistence/sql/migrations/sql/20240221000000000000_identity_recovery_codes_flow_id_idx.mysql.up.sql ================================================ -- This FK was previously misnamed. ALTER TABLE `identity_recovery_codes` DROP FOREIGN KEY `identity_recovery_tokens_identity_id_fk`, ADD CONSTRAINT `identity_recovery_codes_identity_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; -- Missing FK ALTER TABLE `identity_login_codes` ADD CONSTRAINT `identity_login_codes_identity_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `identities` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; -- MySQL has created the remaining indices automatically together with the foreign key constraints. -- CREATE INDEX identity_login_codes_identity_id_idx ON identity_login_codes (identity_id ASC); -- CREATE INDEX identity_login_codes_flow_id_idx ON identity_login_codes (selfservice_login_flow_id ASC); -- CREATE INDEX identity_registration_codes_flow_id_idx ON identity_registration_codes (selfservice_registration_flow_id ASC); -- CREATE INDEX identity_recovery_codes_flow_id_idx ON identity_recovery_codes (selfservice_recovery_flow_id ASC); -- CREATE INDEX identity_verification_codes_flow_id_idx ON identity_verification_codes (selfservice_verification_flow_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20240221000000000000_identity_recovery_codes_flow_id_idx.sqlite.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_login_codes_identity_id_idx ON identity_login_codes (identity_id ASC); CREATE INDEX IF NOT EXISTS identity_login_codes_flow_id_idx ON identity_login_codes (selfservice_login_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_registration_codes_flow_id_idx ON identity_registration_codes (selfservice_registration_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_flow_id_idx ON identity_recovery_codes (selfservice_recovery_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_verification_codes_flow_id_idx ON identity_verification_codes (selfservice_verification_flow_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20240221000000000000_identity_recovery_codes_flow_id_idx.up.sql ================================================ ALTER TABLE identity_login_codes ADD CONSTRAINT identity_login_codes_identity_id_fk FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE CASCADE ON UPDATE RESTRICT; CREATE INDEX IF NOT EXISTS identity_login_codes_identity_id_idx ON identity_login_codes (identity_id ASC); CREATE INDEX IF NOT EXISTS identity_login_codes_flow_id_idx ON identity_login_codes (selfservice_login_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_registration_codes_flow_id_idx ON identity_registration_codes (selfservice_registration_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_flow_id_idx ON identity_recovery_codes (selfservice_recovery_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_verification_codes_flow_id_idx ON identity_verification_codes (selfservice_verification_flow_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20240318143139000000_drop_identity_search_index.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240318143139000000_drop_identity_search_index.postgres.down.sql ================================================ CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gin; CREATE INDEX identity_credential_identifiers_nid_identifier_gin ON identity_credential_identifiers USING GIN (nid, identifier gin_trgm_ops); ================================================ FILE: persistence/sql/migrations/sql/20240318143139000000_drop_identity_search_index.postgres.up.sql ================================================ DROP INDEX identity_credential_identifiers_nid_identifier_gin; ================================================ FILE: persistence/sql/migrations/sql/20240318143139000000_drop_identity_search_index.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240325153839000000_drop_identity_search_index.cockroach.down.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_identifier_gin ON identity_credential_identifiers USING GIN (nid, identifier gin_trgm_ops); ================================================ FILE: persistence/sql/migrations/sql/20240325153839000000_drop_identity_search_index.cockroach.up.sql ================================================ DROP INDEX IF EXISTS identity_credential_identifiers_nid_identifier_gin; ================================================ FILE: persistence/sql/migrations/sql/20240325153839000000_drop_identity_search_index.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240325153839000000_drop_identity_search_index.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240425095000000000_identity_credentials_fix_user_handle_index.cockroach.down.sql ================================================ DROP INDEX identity_credentials_config_user_handle_idx; ================================================ FILE: persistence/sql/migrations/sql/20240425095000000000_identity_credentials_fix_user_handle_index.cockroach.up.sql ================================================ CREATE INDEX identity_credentials_config_user_handle_idx ON identity_credentials ((config ->> 'user_handle')) WHERE config ->> 'user_handle' IS NOT NULL ; ================================================ FILE: persistence/sql/migrations/sql/20240425095000000000_identity_credentials_fix_user_handle_index.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240425095000000000_identity_credentials_fix_user_handle_index.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240425095000000001_identity_credentials_fix_user_handle_index.cockroach.down.sql ================================================ CREATE INVERTED INDEX identity_credentials_user_handle_idx ON identity_credentials (config) WHERE config ->> 'user_handle' IS NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20240425095000000001_identity_credentials_fix_user_handle_index.cockroach.up.sql ================================================ DROP INDEX identity_credentials_user_handle_idx; ================================================ FILE: persistence/sql/migrations/sql/20240425095000000001_identity_credentials_fix_user_handle_index.down.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240425095000000001_identity_credentials_fix_user_handle_index.up.sql ================================================ ================================================ FILE: persistence/sql/migrations/sql/20240923095000000001_organization_id_index.down.sql ================================================ DROP INDEX identities_nid_organization_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20240923095000000001_organization_id_index.mysql.down.sql ================================================ DROP INDEX identities_nid_organization_id_idx ON identities; ================================================ FILE: persistence/sql/migrations/sql/20240923095000000001_organization_id_index.up.sql ================================================ CREATE INDEX identities_nid_organization_id_idx ON identities (organization_id); ================================================ FILE: persistence/sql/migrations/sql/20241023142500000001_drop_unused_indices_identity_credentials.down.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credentials_id_nid_idx ON identity_credentials (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_credentials_nid_id_idx ON identity_credentials (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_credentials_nid_identity_id_idx ON identity_credentials (identity_id ASC, nid ASC); DROP INDEX IF EXISTS identity_credentials_identity_id_idx; DROP INDEX IF EXISTS identity_credentials_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000001_drop_unused_indices_identity_credentials.mysql.down.sql ================================================ CREATE INDEX identity_credentials_id_nid_idx ON identity_credentials (id ASC, nid ASC); CREATE INDEX identity_credentials_nid_id_idx ON identity_credentials (nid ASC, id ASC); DROP INDEX identity_credentials_nid_idx ON identity_credentials; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000001_drop_unused_indices_identity_credentials.mysql.up.sql ================================================ CREATE INDEX identity_credentials_nid_idx ON identity_credentials (nid ASC); DROP INDEX identity_credentials_id_nid_idx ON identity_credentials; DROP INDEX identity_credentials_nid_id_idx ON identity_credentials; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000001_drop_unused_indices_identity_credentials.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credentials_identity_id_idx ON identity_credentials (identity_id ASC); CREATE INDEX IF NOT EXISTS identity_credentials_nid_idx ON identity_credentials (nid ASC); DROP INDEX IF EXISTS identity_credentials_id_nid_idx; DROP INDEX IF EXISTS identity_credentials_nid_id_idx; DROP INDEX IF EXISTS identity_credentials_nid_identity_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000002_drop_unused_indices_sessions.down.sql ================================================ CREATE INDEX IF NOT EXISTS sessions_nid_id_identity_id_idx ON sessions(nid ASC, identity_id ASC, id ASC); CREATE INDEX IF NOT EXISTS sessions_id_nid_idx ON sessions(id ASC, nid ASC); CREATE INDEX IF NOT EXISTS sessions_token_nid_idx ON sessions(nid ASC, token ASC); CREATE INDEX IF NOT EXISTS sessions_identity_id_nid_sorted_idx ON sessions(identity_id ASC, nid ASC, authenticated_at DESC); CREATE INDEX IF NOT EXISTS sessions_nid_created_at_id_idx ON sessions(nid ASC, created_at DESC, id ASC); DROP INDEX IF EXISTS sessions_list_idx; DROP INDEX IF EXISTS sessions_list_active_idx; DROP INDEX IF EXISTS sessions_list_identity_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000002_drop_unused_indices_sessions.mysql.down.sql ================================================ CREATE INDEX sessions_nid_id_identity_id_idx ON sessions(nid ASC, identity_id ASC, id ASC); CREATE INDEX sessions_id_nid_idx ON sessions(id ASC, nid ASC); CREATE INDEX sessions_token_nid_idx ON sessions(nid ASC, token ASC); CREATE INDEX sessions_identity_id_nid_sorted_idx ON sessions(identity_id ASC, nid ASC, authenticated_at DESC); CREATE INDEX sessions_nid_created_at_id_idx ON sessions(nid ASC, created_at DESC, id ASC); DROP INDEX sessions_list_idx ON sessions; DROP INDEX sessions_list_active_idx ON sessions; DROP INDEX sessions_list_identity_idx ON sessions; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000002_drop_unused_indices_sessions.mysql.up.sql ================================================ CREATE INDEX sessions_list_idx ON sessions (nid ASC, created_at DESC, id ASC); CREATE INDEX sessions_list_active_idx ON sessions (nid ASC, expires_at ASC, active ASC, created_at DESC, id ASC); CREATE INDEX sessions_list_identity_idx ON sessions (identity_id ASC, nid ASC, created_at DESC); DROP INDEX sessions_nid_id_identity_id_idx ON sessions; DROP INDEX sessions_id_nid_idx ON sessions; DROP INDEX sessions_token_nid_idx ON sessions; DROP INDEX sessions_identity_id_nid_sorted_idx ON sessions; DROP INDEX sessions_nid_created_at_id_idx ON sessions; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000002_drop_unused_indices_sessions.up.sql ================================================ CREATE INDEX IF NOT EXISTS sessions_list_idx ON sessions (nid ASC, created_at DESC, id ASC); CREATE INDEX IF NOT EXISTS sessions_list_active_idx ON sessions (nid ASC, expires_at ASC, active ASC, created_at DESC, id ASC); CREATE INDEX IF NOT EXISTS sessions_list_identity_idx ON sessions (identity_id ASC, nid ASC, created_at DESC); DROP INDEX IF EXISTS sessions_nid_id_identity_id_idx; DROP INDEX IF EXISTS sessions_id_nid_idx; DROP INDEX IF EXISTS sessions_token_nid_idx; DROP INDEX IF EXISTS sessions_identity_id_nid_sorted_idx; DROP INDEX IF EXISTS sessions_nid_created_at_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000003_drop_unused_indices_credential_identifiers.cockroach.down.sql ================================================ -- THIS IS COCKROACH ONLY ALTER INDEX identity_credential_identifiers_identifier_nid_type_uq_idx RENAME TO identity_credential_identifiers_identifier_nid_type_uq_idx_deleteme; CREATE UNIQUE INDEX IF NOT EXISTS identity_credential_identifiers_identifier_nid_type_uq_idx ON identity_credential_identifiers(nid ASC, identity_credential_type_id ASC, identifier ASC); DROP INDEX IF EXISTS identity_credential_identifiers_identifier_nid_type_uq_idx_deleteme; -- CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC, nid ASC); DROP INDEX IF EXISTS identity_credential_identifiers_identity_credential_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000003_drop_unused_indices_credential_identifiers.cockroach.up.sql ================================================ -- THIS IS COCKROACH ONLY ALTER INDEX identity_credential_identifiers_identifier_nid_type_uq_idx RENAME TO identity_credential_identifiers_identifier_nid_type_uq_idx_deleteme; CREATE UNIQUE INDEX IF NOT EXISTS identity_credential_identifiers_identifier_nid_type_uq_idx ON identity_credential_identifiers (nid ASC, identity_credential_type_id ASC, identifier ASC) STORING (identity_credential_id); DROP INDEX IF EXISTS identity_credential_identifiers_identifier_nid_type_uq_idx_deleteme; -- CREATE INDEX IF NOT EXISTS identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC); DROP INDEX IF EXISTS identity_credential_identifiers_nid_id_idx; DROP INDEX IF EXISTS identity_credential_identifiers_id_nid_idx; DROP INDEX IF EXISTS identity_credential_identifiers_nid_identity_credential_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000003_drop_unused_indices_credential_identifiers.down.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC, nid ASC); DROP INDEX IF EXISTS identity_credential_identifiers_identity_credential_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000003_drop_unused_indices_credential_identifiers.mysql.down.sql ================================================ CREATE INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid ASC, id ASC); CREATE INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id ASC, nid ASC); CREATE INDEX identity_credential_identifiers_nid_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC, nid ASC); DROP INDEX identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000003_drop_unused_indices_credential_identifiers.mysql.up.sql ================================================ CREATE INDEX identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC); DROP INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers; DROP INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers; DROP INDEX identity_credential_identifiers_nid_identity_credential_id_idx ON identity_credential_identifiers; ================================================ FILE: persistence/sql/migrations/sql/20241023142500000003_drop_unused_indices_credential_identifiers.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC); DROP INDEX IF EXISTS identity_credential_identifiers_nid_id_idx; DROP INDEX IF EXISTS identity_credential_identifiers_id_nid_idx; DROP INDEX IF EXISTS identity_credential_identifiers_nid_identity_credential_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241029102200000001_self_service.down.sql ================================================ CREATE INDEX IF NOT EXISTS selfservice_login_flows_nid_id_idx ON selfservice_login_flows (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS selfservice_login_flows_id_nid_idx ON selfservice_login_flows (id ASC, nid ASC); DROP INDEX IF EXISTS selfservice_login_flows_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_errors_errors_nid_id_idx ON selfservice_errors (nid ASC, id ASC); DROP INDEX IF EXISTS selfservice_errors_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_recovery_flows_nid_id_idx ON selfservice_recovery_flows (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS selfservice_recovery_flows_id_nid_idx ON selfservice_recovery_flows (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS selfservice_recovery_flows_recovered_identity_id_nid_idx ON selfservice_recovery_flows (recovered_identity_id ASC, nid ASC); DROP INDEX IF EXISTS selfservice_recovery_flows_nid_idx; DROP INDEX IF EXISTS selfservice_recovery_flows_recovered_identity_id_idx; CREATE INDEX IF NOT EXISTS selfservice_registration_flows_nid_id_idx ON selfservice_registration_flows (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS selfservice_registration_flows_id_nid_idx ON selfservice_registration_flows (id ASC, nid ASC); DROP INDEX IF EXISTS selfservice_registration_flows_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_settings_flows_nid_id_idx ON selfservice_settings_flows (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS selfservice_settings_flows_id_nid_idx ON selfservice_settings_flows (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS selfservice_settings_flows_identity_id_nid_idx ON selfservice_settings_flows (identity_id ASC, nid ASC); DROP INDEX IF EXISTS selfservice_settings_flows_nid_idx; DROP INDEX IF EXISTS selfservice_settings_flows_identity_id_idx; CREATE INDEX IF NOT EXISTS selfservice_verification_flows_nid_id_idx ON selfservice_verification_flows (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS selfservice_verification_flows_id_nid_idx ON selfservice_verification_flows (id ASC, nid ASC); DROP INDEX IF EXISTS selfservice_verification_flows_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20241029102200000001_self_service.mysql.down.sql ================================================ CREATE INDEX selfservice_login_flows_nid_id_idx ON selfservice_login_flows (nid ASC, id ASC); CREATE INDEX selfservice_login_flows_id_nid_idx ON selfservice_login_flows (id ASC, nid ASC); DROP INDEX selfservice_login_flows_nid_idx ON selfservice_login_flows; CREATE INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors (nid ASC, id ASC); DROP INDEX selfservice_errors_nid_idx ON selfservice_errors; CREATE INDEX selfservice_recovery_flows_nid_id_idx ON selfservice_recovery_flows (nid ASC, id ASC); CREATE INDEX selfservice_recovery_flows_id_nid_idx ON selfservice_recovery_flows (id ASC, nid ASC); CREATE INDEX selfservice_recovery_flows_recovered_identity_id_nid_idx ON selfservice_recovery_flows (recovered_identity_id ASC, nid ASC); DROP INDEX selfservice_recovery_flows_nid_idx ON selfservice_recovery_flows; DROP INDEX selfservice_recovery_flows_recovered_identity_id_idx ON selfservice_recovery_flows; CREATE INDEX selfservice_registration_flows_nid_id_idx ON selfservice_registration_flows (nid ASC, id ASC); CREATE INDEX selfservice_registration_flows_id_nid_idx ON selfservice_registration_flows (id ASC, nid ASC); DROP INDEX selfservice_registration_flows_nid_idx ON selfservice_registration_flows; CREATE INDEX selfservice_settings_flows_nid_id_idx ON selfservice_settings_flows (nid ASC, id ASC); CREATE INDEX selfservice_settings_flows_id_nid_idx ON selfservice_settings_flows (id ASC, nid ASC); CREATE INDEX selfservice_settings_flows_identity_id_nid_idx ON selfservice_settings_flows (identity_id ASC, nid ASC); DROP INDEX selfservice_settings_flows_nid_idx ON selfservice_settings_flows; DROP INDEX selfservice_settings_flows_identity_id_idx ON selfservice_settings_flows; CREATE INDEX selfservice_verification_flows_nid_id_idx ON selfservice_verification_flows (nid ASC, id ASC); CREATE INDEX selfservice_verification_flows_id_nid_idx ON selfservice_verification_flows (id ASC, nid ASC); DROP INDEX selfservice_verification_flows_nid_idx ON selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20241029102200000001_self_service.mysql.up.sql ================================================ CREATE INDEX selfservice_login_flows_nid_idx ON selfservice_login_flows (nid ASC); DROP INDEX selfservice_login_flows_nid_id_idx ON selfservice_login_flows; DROP INDEX selfservice_login_flows_id_nid_idx ON selfservice_login_flows; CREATE INDEX selfservice_errors_nid_idx ON selfservice_errors (nid ASC); DROP INDEX selfservice_errors_errors_nid_id_idx ON selfservice_errors; CREATE INDEX selfservice_recovery_flows_nid_idx ON selfservice_recovery_flows (nid ASC); CREATE INDEX selfservice_recovery_flows_recovered_identity_id_idx ON selfservice_recovery_flows (recovered_identity_id ASC); DROP INDEX selfservice_recovery_flows_nid_id_idx ON selfservice_recovery_flows; DROP INDEX selfservice_recovery_flows_id_nid_idx ON selfservice_recovery_flows; DROP INDEX selfservice_recovery_flows_recovered_identity_id_nid_idx ON selfservice_recovery_flows; CREATE INDEX selfservice_registration_flows_nid_idx ON selfservice_registration_flows (nid ASC); DROP INDEX selfservice_registration_flows_nid_id_idx ON selfservice_registration_flows; DROP INDEX selfservice_registration_flows_id_nid_idx ON selfservice_registration_flows; CREATE INDEX selfservice_settings_flows_nid_idx ON selfservice_settings_flows (nid ASC); CREATE INDEX selfservice_settings_flows_identity_id_idx ON selfservice_settings_flows (identity_id ASC); DROP INDEX selfservice_settings_flows_nid_id_idx ON selfservice_settings_flows; DROP INDEX selfservice_settings_flows_id_nid_idx ON selfservice_settings_flows; DROP INDEX selfservice_settings_flows_identity_id_nid_idx ON selfservice_settings_flows; CREATE INDEX selfservice_verification_flows_nid_idx ON selfservice_verification_flows (nid ASC); DROP INDEX selfservice_verification_flows_nid_id_idx ON selfservice_verification_flows; DROP INDEX selfservice_verification_flows_id_nid_idx ON selfservice_verification_flows; ================================================ FILE: persistence/sql/migrations/sql/20241029102200000001_self_service.up.sql ================================================ CREATE INDEX IF NOT EXISTS selfservice_login_flows_nid_idx ON selfservice_login_flows (nid ASC); DROP INDEX IF EXISTS selfservice_login_flows_nid_id_idx; DROP INDEX IF EXISTS selfservice_login_flows_id_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_errors_nid_idx ON selfservice_errors (nid ASC); DROP INDEX IF EXISTS selfservice_errors_errors_nid_id_idx; CREATE INDEX IF NOT EXISTS selfservice_recovery_flows_recovered_identity_id_idx ON selfservice_recovery_flows (recovered_identity_id ASC); CREATE INDEX IF NOT EXISTS selfservice_recovery_flows_nid_idx ON selfservice_recovery_flows (nid ASC); DROP INDEX IF EXISTS selfservice_recovery_flows_nid_id_idx; DROP INDEX IF EXISTS selfservice_recovery_flows_id_nid_idx; DROP INDEX IF EXISTS selfservice_recovery_flows_recovered_identity_id_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_registration_flows_nid_idx ON selfservice_registration_flows (nid ASC); DROP INDEX IF EXISTS selfservice_registration_flows_nid_id_idx; DROP INDEX IF EXISTS selfservice_registration_flows_id_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_settings_flows_nid_idx ON selfservice_settings_flows (nid ASC); CREATE INDEX IF NOT EXISTS selfservice_settings_flows_identity_id_idx ON selfservice_settings_flows (identity_id ASC); DROP INDEX IF EXISTS selfservice_settings_flows_nid_id_idx; DROP INDEX IF EXISTS selfservice_settings_flows_id_nid_idx; DROP INDEX IF EXISTS selfservice_settings_flows_identity_id_nid_idx; CREATE INDEX IF NOT EXISTS selfservice_verification_flows_nid_idx ON selfservice_verification_flows (nid ASC); DROP INDEX IF EXISTS selfservice_verification_flows_nid_id_idx; DROP INDEX IF EXISTS selfservice_verification_flows_id_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20241029153900000001_identities.autocommit.down.sql ================================================ CREATE INDEX IF NOT EXISTS identities_id_nid_idx ON identities (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_recovery_addresses_status_via_idx ON identity_recovery_addresses (nid ASC, via ASC, value ASC); CREATE INDEX IF NOT EXISTS identity_recovery_addresses_nid_identity_id_idx ON identity_recovery_addresses (identity_id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses (id ASC, nid ASC); DROP INDEX IF EXISTS identity_recovery_addresses_identity_id_idx; CREATE INDEX IF NOT EXISTS identity_verifiable_addresses_status_via_idx ON identity_verifiable_addresses (nid ASC, via ASC, value ASC); CREATE INDEX IF NOT EXISTS identity_verifiable_addresses_nid_identity_id_idx ON identity_verifiable_addresses (identity_id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_verifiable_addresses_nid_id_idx ON identity_verifiable_addresses (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_verifiable_addresses_id_nid_idx ON identity_verifiable_addresses (id ASC, nid ASC); DROP INDEX IF EXISTS identity_verifiable_addresses_identity_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241029153900000001_identities.autocommit.up.sql ================================================ DROP INDEX IF EXISTS identities_id_nid_idx; CREATE INDEX IF NOT EXISTS identity_recovery_addresses_identity_id_idx ON identity_recovery_addresses(identity_id ASC); DROP INDEX IF EXISTS identity_recovery_addresses_status_via_idx; DROP INDEX IF EXISTS identity_recovery_addresses_nid_identity_id_idx; DROP INDEX IF EXISTS identity_recovery_addresses_nid_id_idx; DROP INDEX IF EXISTS identity_recovery_addresses_id_nid_idx; CREATE INDEX IF NOT EXISTS identity_verifiable_addresses_identity_id_idx ON identity_verifiable_addresses (identity_id ASC); DROP INDEX IF EXISTS identity_verifiable_addresses_status_via_idx; DROP INDEX IF EXISTS identity_verifiable_addresses_nid_identity_id_idx; DROP INDEX IF EXISTS identity_verifiable_addresses_nid_id_idx; DROP INDEX IF EXISTS identity_verifiable_addresses_id_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20241029153900000001_identities.mysql.down.sql ================================================ CREATE INDEX identities_id_nid_idx ON identities (id ASC, nid ASC); CREATE INDEX identity_recovery_addresses_status_via_idx ON identity_recovery_addresses (nid ASC, via ASC, value ASC); -- While this index did not exist in the past, it is needed in MySQL for foreign key relations. We accept -- that this index is "unaccounted" for if we execute down and then up migrations on MySQL. CREATE INDEX identity_recovery_addresses_identity_id_fk_idx ON identity_recovery_addresses (identity_id ASC); CREATE INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid ASC, id ASC); CREATE INDEX identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses (id ASC, nid ASC); DROP INDEX identity_recovery_addresses_identity_id_idx ON identity_recovery_addresses; CREATE INDEX identity_verifiable_addresses_status_via_idx ON identity_verifiable_addresses (nid ASC, via ASC, value ASC); -- While this index did not exist in the past, it is needed in MySQL for foreign key relations. We accept -- that this index is "unaccounted" for if we execute down and then up migrations on MySQL. CREATE INDEX identity_verifiable_addresses_identity_id_fk_idx ON identity_verifiable_addresses (identity_id ASC); CREATE INDEX identity_verifiable_addresses_nid_id_idx ON identity_verifiable_addresses (nid ASC, id ASC); CREATE INDEX identity_verifiable_addresses_id_nid_idx ON identity_verifiable_addresses (id ASC, nid ASC); DROP INDEX identity_verifiable_addresses_identity_id_idx ON identity_verifiable_addresses; ================================================ FILE: persistence/sql/migrations/sql/20241029153900000001_identities.mysql.up.sql ================================================ DROP INDEX identities_id_nid_idx ON identities; CREATE INDEX identity_recovery_addresses_identity_id_idx ON identity_recovery_addresses (identity_id ASC); DROP INDEX identity_recovery_addresses_status_via_idx ON identity_recovery_addresses; -- DROP INDEX identity_recovery_addresses_nid_identity_id_idx ON identity_recovery_addresses; DROP INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses; DROP INDEX identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses; CREATE INDEX identity_verifiable_addresses_identity_id_idx ON identity_verifiable_addresses (identity_id ASC); DROP INDEX identity_verifiable_addresses_status_via_idx ON identity_verifiable_addresses; -- DROP INDEX identity_verifiable_addresses_nid_identity_id_idx ON identity_verifiable_addresses; DROP INDEX identity_verifiable_addresses_nid_id_idx ON identity_verifiable_addresses; DROP INDEX identity_verifiable_addresses_id_nid_idx ON identity_verifiable_addresses; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000001_remaining_unused_indices.autocommit.down.sql ================================================ CREATE INDEX IF NOT EXISTS session_devices_id_nid_idx ON session_devices (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS session_devices_session_id_nid_idx ON session_devices (session_id ASC, nid ASC); DROP INDEX IF EXISTS session_devices_nid_idx; DROP INDEX IF EXISTS session_devices_session_id_idx; CREATE INDEX IF NOT EXISTS session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code ASC, nid ASC); CREATE INDEX IF NOT EXISTS session_token_exchanges_nid_flow_id_idx ON session_token_exchanges (flow_id ASC, nid ASC); DROP INDEX IF EXISTS session_token_exchanges_flow_id_nid_init_code_idx; DROP INDEX IF EXISTS session_token_exchanges_nid_init_code_idx; CREATE INDEX IF NOT EXISTS courier_messages_status_idx ON courier_messages (status ASC); CREATE INDEX IF NOT EXISTS courier_messages_nid_id_idx ON courier_messages (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS courier_messages_id_nid_idx ON courier_messages (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS courier_messages_nid_created_at_id_idx ON courier_messages (nid ASC, created_at DESC); DROP INDEX IF EXISTS courier_messages_status_id_idx; DROP INDEX IF EXISTS courier_messages_nid_id_created_at_idx; CREATE INDEX IF NOT EXISTS continuity_containers_nid_id_idx ON continuity_containers (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS continuity_containers_id_nid_idx ON continuity_containers (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS continuity_containers_identity_id_nid_idx ON continuity_containers (identity_id ASC, nid ASC); DROP INDEX IF EXISTS continuity_containers_identity_id_idx; DROP INDEX IF EXISTS continuity_containers_nid_idx; CREATE INDEX IF NOT EXISTS identity_verification_codes_nid_flow_id_idx ON identity_verification_codes (nid ASC, selfservice_verification_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_verification_codes_id_nid_idx ON identity_verification_codes (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_verification_codes_verifiable_address_nid_idx ON identity_verification_codes (identity_verifiable_address_id ASC, nid ASC); DROP INDEX IF EXISTS identity_verification_codes_identity_verifiable_address_id_idx; DROP INDEX IF EXISTS identity_verification_codes_nid_idx; CREATE INDEX IF NOT EXISTS identity_verification_tokens_nid_id_idx ON identity_verification_tokens (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_verification_tokens_id_nid_idx ON identity_verification_tokens (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens (nid ASC, token ASC, used ASC, selfservice_verification_flow_id ASC); DROP INDEX IF EXISTS identity_verification_tokens_nid_idx; CREATE INDEX IF NOT EXISTS identity_registration_codes_nid_flow_id_idx ON identity_registration_codes (nid ASC, selfservice_registration_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_registration_codes_id_nid_idx ON identity_registration_codes (id ASC, nid ASC); DROP INDEX IF EXISTS identity_registration_codes_nid_idx; CREATE INDEX IF NOT EXISTS identity_recovery_tokens_nid_id_idx ON identity_recovery_tokens (nid ASC, id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_tokens_id_nid_idx ON identity_recovery_tokens (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_recovery_tokens_token_nid_used_idx ON identity_recovery_tokens (nid ASC, token ASC, used ASC); CREATE INDEX IF NOT EXISTS identity_recovery_tokens_identity_id_nid_idx ON identity_recovery_tokens (identity_id ASC, nid ASC); DROP INDEX IF EXISTS identity_recovery_tokens_identity_id_idx; DROP INDEX IF EXISTS identity_recovery_tokens_nid_idx; CREATE INDEX IF NOT EXISTS identity_recovery_codes_nid_flow_id_idx ON identity_recovery_codes (nid ASC, selfservice_recovery_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_id_nid_idx ON identity_recovery_codes (id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_identity_id_nid_idx ON identity_recovery_codes (identity_id ASC, nid ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_identity_recovery_address_id_nid_idx ON identity_recovery_codes (identity_recovery_address_id ASC, nid ASC); DROP INDEX IF EXISTS identity_recovery_codes_identity_recovery_address_id_idx; DROP INDEX IF EXISTS identity_recovery_codes_identity_id_idx; DROP INDEX IF EXISTS identity_recovery_codes_nid_idx; CREATE INDEX IF NOT EXISTS identity_login_codes_nid_flow_id_idx ON identity_login_codes (nid ASC, selfservice_login_flow_id ASC); CREATE INDEX IF NOT EXISTS identity_login_codes_id_nid_idx ON identity_login_codes (id ASC, nid ASC); DROP INDEX IF EXISTS identity_login_codes_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000001_remaining_unused_indices.autocommit.up.sql ================================================ CREATE INDEX IF NOT EXISTS session_devices_nid_idx ON session_devices (nid ASC); CREATE INDEX IF NOT EXISTS session_devices_session_id_idx ON session_devices (session_id ASC); DROP INDEX IF EXISTS session_devices_id_nid_idx; DROP INDEX IF EXISTS session_devices_session_id_nid_idx; CREATE INDEX IF NOT EXISTS session_token_exchanges_flow_id_nid_init_code_idx ON session_token_exchanges (flow_id ASC, nid ASC, init_code ASC); CREATE INDEX IF NOT EXISTS session_token_exchanges_nid_init_code_idx ON session_token_exchanges (nid ASC, init_code ASC); DROP INDEX IF EXISTS session_token_exchanges_nid_code_idx; DROP INDEX IF EXISTS session_token_exchanges_nid_flow_id_idx; CREATE INDEX IF NOT EXISTS courier_messages_status_id_idx ON courier_messages (status ASC, id ASC); CREATE INDEX IF NOT EXISTS courier_messages_nid_id_created_at_idx ON courier_messages (nid ASC, id ASC, created_at DESC); DROP INDEX IF EXISTS courier_messages_status_idx; DROP INDEX IF EXISTS courier_messages_nid_id_idx; DROP INDEX IF EXISTS courier_messages_id_nid_idx; DROP INDEX IF EXISTS courier_messages_nid_created_at_id_idx; CREATE INDEX IF NOT EXISTS continuity_containers_identity_id_idx ON continuity_containers (identity_id ASC); CREATE INDEX IF NOT EXISTS continuity_containers_nid_idx ON continuity_containers (nid ASC); DROP INDEX IF EXISTS continuity_containers_nid_id_idx; DROP INDEX IF EXISTS continuity_containers_id_nid_idx; DROP INDEX IF EXISTS continuity_containers_identity_id_nid_idx; CREATE INDEX IF NOT EXISTS identity_verification_codes_identity_verifiable_address_id_idx ON identity_verification_codes (identity_verifiable_address_id ASC); CREATE INDEX IF NOT EXISTS identity_verification_codes_nid_idx ON identity_verification_codes (nid ASC); DROP INDEX IF EXISTS identity_verification_codes_nid_flow_id_idx; DROP INDEX IF EXISTS identity_verification_codes_id_nid_idx; DROP INDEX IF EXISTS identity_verification_codes_verifiable_address_nid_idx; CREATE INDEX IF NOT EXISTS identity_verification_tokens_nid_idx ON identity_verification_tokens (nid ASC); DROP INDEX IF EXISTS identity_verification_tokens_nid_id_idx; DROP INDEX IF EXISTS identity_verification_tokens_id_nid_idx; DROP INDEX IF EXISTS identity_verification_tokens_token_nid_used_flow_id_idx; CREATE INDEX IF NOT EXISTS identity_registration_codes_nid_idx ON identity_registration_codes (nid ASC); DROP INDEX IF EXISTS identity_registration_codes_nid_flow_id_idx; DROP INDEX IF EXISTS identity_registration_codes_id_nid_idx; CREATE INDEX IF NOT EXISTS identity_recovery_tokens_identity_id_idx ON identity_recovery_tokens (identity_id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_tokens_nid_idx ON identity_recovery_tokens (nid ASC); DROP INDEX IF EXISTS identity_recovery_tokens_nid_id_idx; DROP INDEX IF EXISTS identity_recovery_tokens_id_nid_idx; DROP INDEX IF EXISTS identity_recovery_tokens_token_nid_used_idx; DROP INDEX IF EXISTS identity_recovery_tokens_identity_id_nid_idx; CREATE INDEX IF NOT EXISTS identity_recovery_codes_identity_recovery_address_id_idx ON identity_recovery_codes (identity_recovery_address_id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_identity_id_idx ON identity_recovery_codes (identity_id ASC); CREATE INDEX IF NOT EXISTS identity_recovery_codes_nid_idx ON identity_recovery_codes (nid ASC); DROP INDEX IF EXISTS identity_recovery_codes_nid_flow_id_idx; DROP INDEX IF EXISTS identity_recovery_codes_id_nid_idx; DROP INDEX IF EXISTS identity_recovery_codes_identity_id_nid_idx; DROP INDEX IF EXISTS identity_recovery_codes_identity_recovery_address_id_nid_idx; CREATE INDEX IF NOT EXISTS identity_login_codes_nid_idx ON identity_login_codes (nid ASC); DROP INDEX IF EXISTS identity_login_codes_nid_flow_id_idx; DROP INDEX IF EXISTS identity_login_codes_id_nid_idx; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000001_remaining_unused_indices.mysql.down.sql ================================================ CREATE INDEX session_devices_id_nid_idx ON session_devices (nid ASC, id ASC); -- the original index is id, nid - but then we can't drop session_devices_nid_idx CREATE INDEX session_devices_session_id_nid_idx ON session_devices (session_id ASC, nid ASC); DROP INDEX session_devices_nid_idx ON session_devices; DROP INDEX session_devices_session_id_idx ON session_devices; CREATE INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code ASC, nid ASC); CREATE INDEX session_token_exchanges_nid_flow_id_idx ON session_token_exchanges (flow_id ASC, nid ASC); DROP INDEX session_token_exchanges_flow_id_nid_init_code_idx ON session_token_exchanges; DROP INDEX session_token_exchanges_nid_init_code_idx ON session_token_exchanges; CREATE INDEX courier_messages_status_idx ON courier_messages (status ASC); CREATE INDEX courier_messages_nid_id_idx ON courier_messages (nid ASC, id ASC); CREATE INDEX courier_messages_id_nid_idx ON courier_messages (id ASC, nid ASC); CREATE INDEX courier_messages_nid_created_at_id_idx ON courier_messages (nid ASC, created_at DESC); DROP INDEX courier_messages_status_id_idx ON courier_messages; DROP INDEX courier_messages_nid_id_created_at_idx ON courier_messages; CREATE INDEX continuity_containers_nid_id_idx ON continuity_containers (nid ASC, id ASC); CREATE INDEX continuity_containers_id_nid_idx ON continuity_containers (id ASC, nid ASC); CREATE INDEX continuity_containers_identity_id_nid_idx ON continuity_containers (identity_id ASC, nid ASC); DROP INDEX continuity_containers_identity_id_idx ON continuity_containers; DROP INDEX continuity_containers_nid_idx ON continuity_containers; CREATE INDEX identity_verification_codes_nid_flow_id_idx ON identity_verification_codes (nid ASC, selfservice_verification_flow_id ASC); CREATE INDEX identity_verification_codes_id_nid_idx ON identity_verification_codes (id ASC, nid ASC); CREATE INDEX identity_verification_codes_verifiable_address_nid_idx ON identity_verification_codes (identity_verifiable_address_id ASC, nid ASC); DROP INDEX identity_verification_codes_verifiable_address_idx ON identity_verification_codes; DROP INDEX identity_verification_codes_nid_idx ON identity_verification_codes; CREATE INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens (nid ASC, id ASC); CREATE INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens (id ASC, nid ASC); CREATE INDEX identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens (nid ASC, token ASC, used ASC, selfservice_verification_flow_id ASC); DROP INDEX identity_verification_tokens_nid_idx ON identity_verification_tokens; CREATE INDEX identity_registration_codes_nid_flow_id_idx ON identity_registration_codes (nid ASC, selfservice_registration_flow_id ASC); CREATE INDEX identity_registration_codes_id_nid_idx ON identity_registration_codes (id ASC, nid ASC); DROP INDEX identity_registration_codes_nid_idx ON identity_registration_codes; CREATE INDEX identity_recovery_tokens_nid_id_idx ON identity_recovery_tokens (nid ASC, id ASC); CREATE INDEX identity_recovery_tokens_id_nid_idx ON identity_recovery_tokens (id ASC, nid ASC); CREATE INDEX identity_recovery_tokens_token_nid_used_idx ON identity_recovery_tokens (nid ASC, token ASC, used ASC); CREATE INDEX identity_recovery_tokens_identity_id_nid_idx ON identity_recovery_tokens (identity_id ASC, nid ASC); DROP INDEX identity_recovery_tokens_identity_id_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_nid_idx ON identity_recovery_tokens; CREATE INDEX identity_recovery_codes_nid_flow_id_idx ON identity_recovery_codes (nid ASC, selfservice_recovery_flow_id ASC); CREATE INDEX identity_recovery_codes_id_nid_idx ON identity_recovery_codes (id ASC, nid ASC); CREATE INDEX identity_recovery_codes_identity_id_nid_idx ON identity_recovery_codes (identity_id ASC, nid ASC); CREATE INDEX identity_recovery_codes_identity_recovery_address_id_nid_idx ON identity_recovery_codes (identity_recovery_address_id ASC, nid ASC); DROP INDEX identity_recovery_codes_address_id_idx ON identity_recovery_codes; DROP INDEX identity_recovery_codes_identity_id_idx ON identity_recovery_codes; DROP INDEX identity_recovery_codes_nid_idx ON identity_recovery_codes; CREATE INDEX identity_login_codes_nid_flow_id_idx ON identity_login_codes (nid ASC, selfservice_login_flow_id ASC); CREATE INDEX identity_login_codes_id_nid_idx ON identity_login_codes (id ASC, nid ASC); DROP INDEX identity_login_codes_nid_idx ON identity_login_codes; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000001_remaining_unused_indices.mysql.up.sql ================================================ CREATE INDEX session_devices_nid_idx ON session_devices (nid ASC); CREATE INDEX session_devices_session_id_idx ON session_devices (session_id ASC); DROP INDEX session_devices_id_nid_idx ON session_devices; DROP INDEX session_devices_session_id_nid_idx ON session_devices; CREATE INDEX session_token_exchanges_flow_id_nid_init_code_idx ON session_token_exchanges (flow_id ASC, nid ASC, init_code ASC); CREATE INDEX session_token_exchanges_nid_init_code_idx ON session_token_exchanges (nid ASC, init_code ASC); DROP INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges; DROP INDEX session_token_exchanges_nid_flow_id_idx ON session_token_exchanges; CREATE INDEX courier_messages_status_id_idx ON courier_messages (status ASC, id ASC); CREATE INDEX courier_messages_nid_id_created_at_idx ON courier_messages (nid ASC, id ASC, created_at DESC); DROP INDEX courier_messages_status_idx ON courier_messages; DROP INDEX courier_messages_nid_id_idx ON courier_messages; DROP INDEX courier_messages_id_nid_idx ON courier_messages; DROP INDEX courier_messages_nid_created_at_id_idx ON courier_messages; CREATE INDEX continuity_containers_identity_id_idx ON continuity_containers (identity_id ASC); CREATE INDEX continuity_containers_nid_idx ON continuity_containers (nid ASC); DROP INDEX continuity_containers_nid_id_idx ON continuity_containers; DROP INDEX continuity_containers_id_nid_idx ON continuity_containers; DROP INDEX continuity_containers_identity_id_nid_idx ON continuity_containers; CREATE INDEX identity_verification_codes_verifiable_address_idx ON identity_verification_codes (identity_verifiable_address_id ASC); CREATE INDEX identity_verification_codes_nid_idx ON identity_verification_codes (nid ASC); DROP INDEX identity_verification_codes_nid_flow_id_idx ON identity_verification_codes; DROP INDEX identity_verification_codes_id_nid_idx ON identity_verification_codes; DROP INDEX identity_verification_codes_verifiable_address_nid_idx ON identity_verification_codes; CREATE INDEX identity_verification_tokens_nid_idx ON identity_verification_tokens (nid ASC); DROP INDEX identity_verification_tokens_nid_id_idx ON identity_verification_tokens; DROP INDEX identity_verification_tokens_id_nid_idx ON identity_verification_tokens; DROP INDEX identity_verification_tokens_token_nid_used_flow_id_idx ON identity_verification_tokens; CREATE INDEX identity_registration_codes_nid_idx ON identity_registration_codes (nid ASC); DROP INDEX identity_registration_codes_nid_flow_id_idx ON identity_registration_codes; DROP INDEX identity_registration_codes_id_nid_idx ON identity_registration_codes; CREATE INDEX identity_recovery_tokens_identity_id_idx ON identity_recovery_tokens (identity_id ASC); CREATE INDEX identity_recovery_tokens_nid_idx ON identity_recovery_tokens (nid ASC); DROP INDEX identity_recovery_tokens_nid_id_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_id_nid_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_token_nid_used_idx ON identity_recovery_tokens; DROP INDEX identity_recovery_tokens_identity_id_nid_idx ON identity_recovery_tokens; CREATE INDEX identity_recovery_codes_address_id_idx ON identity_recovery_codes (identity_recovery_address_id ASC); CREATE INDEX identity_recovery_codes_identity_id_idx ON identity_recovery_codes (identity_id ASC); CREATE INDEX identity_recovery_codes_nid_idx ON identity_recovery_codes (nid ASC); DROP INDEX identity_recovery_codes_nid_flow_id_idx ON identity_recovery_codes; DROP INDEX identity_recovery_codes_id_nid_idx ON identity_recovery_codes; DROP INDEX identity_recovery_codes_identity_id_nid_idx ON identity_recovery_codes; DROP INDEX identity_recovery_codes_identity_recovery_address_id_nid_idx ON identity_recovery_codes; CREATE INDEX identity_login_codes_nid_idx ON identity_login_codes (nid ASC); DROP INDEX identity_login_codes_nid_flow_id_idx ON identity_login_codes; DROP INDEX identity_login_codes_id_nid_idx ON identity_login_codes; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000002_foreign_key.autocommit.down.sql ================================================ ALTER TABLE session_token_exchanges DROP CONSTRAINT session_token_exchanges_nid_fk; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000002_foreign_key.autocommit.up.sql ================================================ ALTER TABLE session_token_exchanges ADD CONSTRAINT session_token_exchanges_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20241031094100000002_foreign_key.sqlite.down.sql ================================================ -- Step 1: Create a temporary table without the nid column and foreign key constraint CREATE TABLE session_token_exchanges_temp ( id TEXT NOT NULL, flow_id TEXT NOT NULL, session_id TEXT, init_code VARCHAR(64) NOT NULL, return_to_code VARCHAR(64) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, PRIMARY KEY (id) ); -- Step 2: Copy data from the original table to the temporary table (excluding the nid column) INSERT INTO session_token_exchanges_temp (id, flow_id, session_id, init_code, return_to_code, created_at, updated_at) SELECT id, flow_id, session_id, init_code, return_to_code, created_at, updated_at FROM session_token_exchanges; -- Step 3: Drop the original table DROP TABLE session_token_exchanges; -- Step 4: Rename the temporary table to the original table name ALTER TABLE session_token_exchanges_temp RENAME TO session_token_exchanges; -- Step 5: Recreate indexes as needed (excluding nid) CREATE INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code); CREATE INDEX session_token_exchanges_nid_flow_id_idx ON session_token_exchanges (flow_id); ================================================ FILE: persistence/sql/migrations/sql/20241031094100000002_foreign_key.sqlite.up.sql ================================================ -- Step 1: Create a temporary table with the new column and foreign key constraint CREATE TABLE session_token_exchanges_temp ( id TEXT NOT NULL, nid TEXT NOT NULL, flow_id TEXT NOT NULL, session_id TEXT, init_code VARCHAR(64) NOT NULL, return_to_code VARCHAR(64) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, PRIMARY KEY (id), FOREIGN KEY (nid) REFERENCES networks (id) ON DELETE CASCADE ); -- Step 2: Copy data from the original table to the temporary table INSERT INTO session_token_exchanges_temp (id, nid, flow_id, session_id, init_code, return_to_code, created_at, updated_at) SELECT id, nid, flow_id, session_id, init_code, return_to_code, created_at, updated_at FROM session_token_exchanges; -- Step 3: Drop the original table DROP TABLE session_token_exchanges; -- Step 4: Rename the temporary table to the original table name ALTER TABLE session_token_exchanges_temp RENAME TO session_token_exchanges; -- Step 5: Recreate indexes as needed CREATE INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code, nid); CREATE INDEX session_token_exchanges_nid_flow_id_idx ON session_token_exchanges (flow_id, nid); ================================================ FILE: persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.down.sql ================================================ DROP INDEX IF EXISTS identity_credential_identifiers_nid_ici_i_idx; ================================================ FILE: persistence/sql/migrations/sql/20241106142200000001_identities.autocommit.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers (nid ASC, identity_credential_id ASC, identifier ASC); ================================================ FILE: persistence/sql/migrations/sql/20241106142200000001_identities.mysql.down.sql ================================================ DROP INDEX identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers; ================================================ FILE: persistence/sql/migrations/sql/20241106142200000001_identities.mysql.up.sql ================================================ CREATE INDEX identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers (nid ASC, identity_credential_id ASC, identifier ASC); ================================================ FILE: persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.down.sql ================================================ DROP INDEX IF EXISTS identity_credential_identifiers_ici_nid_i_idx; ================================================ FILE: persistence/sql/migrations/sql/20241106142200000002_identities.autocommit.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_ici_nid_i_idx ON identity_credential_identifiers (identity_credential_id ASC, nid ASC, identifier ASC); ================================================ FILE: persistence/sql/migrations/sql/20241106142200000002_identities.mysql.down.sql ================================================ DROP INDEX identity_credential_identifiers_ici_nid_i_idx ON identity_credential_identifiers; ================================================ FILE: persistence/sql/migrations/sql/20241106142200000002_identities.mysql.up.sql ================================================ CREATE INDEX identity_credential_identifiers_ici_nid_i_idx ON identity_credential_identifiers (identity_credential_id ASC, nid ASC, identifier ASC); ================================================ FILE: persistence/sql/migrations/sql/20241108105000000001_index_cleanup.autocommit.down.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers (nid ASC, identity_credential_id ASC, identifier ASC); CREATE INDEX IF NOT EXISTS identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20241108105000000001_index_cleanup.autocommit.up.sql ================================================ -- This index is replaced by identity_credential_identifiers_ici_nid_i_idx (included in the previous OEL release) DROP INDEX IF EXISTS identity_credential_identifiers_nid_ici_i_idx; -- This index is replaced by identity_credential_identifiers_ici_nid_i_idx (included in the previous OEL release) DROP INDEX IF EXISTS identity_credential_identifiers_identity_credential_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20241108105000000001_index_cleanup.mysql.down.sql ================================================ CREATE INDEX identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers (nid ASC, identity_credential_id ASC, identifier ASC); CREATE INDEX identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers (identity_credential_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20241108105000000001_index_cleanup.mysql.up.sql ================================================ -- This index is replaced by identity_credential_identifiers_ici_nid_i_idx (included in the previous OEL release) DROP INDEX identity_credential_identifiers_nid_ici_i_idx ON identity_credential_identifiers; -- This index is replaced by identity_credential_identifiers_ici_nid_i_idx (included in the previous OEL release) DROP INDEX identity_credential_identifiers_identity_credential_id_idx ON identity_credential_identifiers; ================================================ FILE: persistence/sql/migrations/sql/20241203105600000000_saml_credential_type.down.sql ================================================ DELETE FROM identity_credential_types WHERE name = 'saml'; ================================================ FILE: persistence/sql/migrations/sql/20241203105600000000_saml_credential_type.up.sql ================================================ INSERT INTO identity_credential_types (id, name) SELECT '7bddcf6c-f50e-4a18-9b0f-429114c33419', 'saml' WHERE NOT EXISTS ( SELECT * FROM identity_credential_types WHERE name = 'saml'); ================================================ FILE: persistence/sql/migrations/sql/20250505150900000000_code_address_type.autocommit.down.sql ================================================ ALTER TABLE identity_login_codes ALTER COLUMN address_type TYPE CHAR(36); ALTER TABLE identity_registration_codes ALTER COLUMN address_type TYPE CHAR(36); ================================================ FILE: persistence/sql/migrations/sql/20250505150900000000_code_address_type.autocommit.up.sql ================================================ ALTER TABLE identity_login_codes ALTER COLUMN address_type TYPE VARCHAR(36); ALTER TABLE identity_registration_codes ALTER COLUMN address_type TYPE VARCHAR(36); ================================================ FILE: persistence/sql/migrations/sql/20250505150900000000_code_address_type.mysql.down.sql ================================================ ALTER TABLE identity_login_codes MODIFY address_type CHAR(36); ALTER TABLE identity_registration_codes MODIFY address_type CHAR(36); ================================================ FILE: persistence/sql/migrations/sql/20250505150900000000_code_address_type.mysql.up.sql ================================================ ALTER TABLE identity_login_codes MODIFY address_type VARCHAR(36); ALTER TABLE identity_registration_codes MODIFY address_type VARCHAR(36); ================================================ FILE: persistence/sql/migrations/sql/20250505150900000000_code_address_type.sqlite.down.sql ================================================ ALTER TABLE identity_login_codes ADD COLUMN address_type_old CHAR(36); UPDATE identity_login_codes SET address_type_old = address_type; ALTER TABLE identity_login_codes DROP COLUMN address_type; ALTER TABLE identity_login_codes RENAME COLUMN address_type_old TO address_type; ALTER TABLE identity_registration_codes ADD COLUMN address_type_old CHAR(36); UPDATE identity_registration_codes SET address_type_old = address_type; ALTER TABLE identity_registration_codes DROP COLUMN address_type; ALTER TABLE identity_registration_codes RENAME COLUMN address_type_old TO address_type; ================================================ FILE: persistence/sql/migrations/sql/20250505150900000000_code_address_type.sqlite.up.sql ================================================ ALTER TABLE identity_login_codes ADD COLUMN address_type_old VARCHAR(36); UPDATE identity_login_codes SET address_type_old = address_type; ALTER TABLE identity_login_codes DROP COLUMN address_type; ALTER TABLE identity_login_codes RENAME COLUMN address_type_old TO address_type; ALTER TABLE identity_registration_codes ADD COLUMN address_type_old VARCHAR(36); UPDATE identity_registration_codes SET address_type_old = address_type; ALTER TABLE identity_registration_codes DROP COLUMN address_type; ALTER TABLE identity_registration_codes RENAME COLUMN address_type_old TO address_type; ================================================ FILE: persistence/sql/migrations/sql/20250708190000000000_identities_external_id.autocommit.down.sql ================================================ ALTER TABLE identities DROP COLUMN external_id; ================================================ FILE: persistence/sql/migrations/sql/20250708190000000000_identities_external_id.autocommit.up.sql ================================================ ALTER TABLE identities ADD COLUMN external_id VARCHAR(64) NULL CHECK (external_id IS NULL OR external_id != ''); ================================================ FILE: persistence/sql/migrations/sql/20250708190000000000_identities_external_id.cockroach.autocommit.down.sql ================================================ ALTER TABLE identities DROP COLUMN IF EXISTS external_id; ================================================ FILE: persistence/sql/migrations/sql/20250708190000000000_identities_external_id.cockroach.autocommit.up.sql ================================================ ALTER TABLE identities ADD COLUMN IF NOT EXISTS external_id VARCHAR(64) NULL CHECK (external_id IS NULL OR external_id != ''); ================================================ FILE: persistence/sql/migrations/sql/20250708190000000001_identities_external_id_index.cockroach.autocommit.up.sql ================================================ CREATE UNIQUE INDEX IF NOT EXISTS identities_nid_external_id_idx ON identities (external_id, nid) USING HASH WHERE external_id IS NOT NULL AND external_id != ''; ================================================ FILE: persistence/sql/migrations/sql/20250708190000000001_identities_external_id_index.down.sql ================================================ DROP INDEX IF EXISTS identities_nid_external_id_idx; ================================================ FILE: persistence/sql/migrations/sql/20250708190000000001_identities_external_id_index.mysql.down.sql ================================================ DROP INDEX identities_nid_external_id_idx ON identities; ================================================ FILE: persistence/sql/migrations/sql/20250708190000000001_identities_external_id_index.mysql.up.sql ================================================ CREATE UNIQUE INDEX identities_nid_external_id_idx ON identities (nid, external_id); ================================================ FILE: persistence/sql/migrations/sql/20250708190000000001_identities_external_id_index.up.sql ================================================ CREATE UNIQUE INDEX IF NOT EXISTS identities_nid_external_id_idx ON identities (nid, external_id) WHERE external_id IS NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20250710085000000000_add_schema_id.cockroach.autocommit.down.sql ================================================ ALTER TABLE selfservice_login_flows DROP COLUMN IF EXISTS identity_schema_id; ALTER TABLE selfservice_registration_flows DROP COLUMN IF EXISTS identity_schema_id; ================================================ FILE: persistence/sql/migrations/sql/20250710085000000000_add_schema_id.cockroach.autocommit.up.sql ================================================ ALTER TABLE selfservice_login_flows ADD COLUMN IF NOT EXISTS identity_schema_id VARCHAR(128) NULL; ALTER TABLE selfservice_registration_flows ADD COLUMN IF NOT EXISTS identity_schema_id VARCHAR(128) NULL; ================================================ FILE: persistence/sql/migrations/sql/20250710085000000000_add_schema_id.down.sql ================================================ ALTER TABLE selfservice_login_flows DROP COLUMN identity_schema_id; ALTER TABLE selfservice_registration_flows DROP COLUMN identity_schema_id; ================================================ FILE: persistence/sql/migrations/sql/20250710085000000000_add_schema_id.up.sql ================================================ ALTER TABLE selfservice_login_flows ADD COLUMN identity_schema_id VARCHAR(128) NULL; ALTER TABLE selfservice_registration_flows ADD COLUMN identity_schema_id VARCHAR(128) NULL; ================================================ FILE: persistence/sql/migrations/sql/20251104000000000000_identifiers_devices_identity_id.down.sql ================================================ ALTER TABLE identity_credential_identifiers DROP COLUMN identity_id; ALTER TABLE session_devices DROP COLUMN identity_id; ================================================ FILE: persistence/sql/migrations/sql/20251104000000000000_identifiers_devices_identity_id.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers ADD COLUMN identity_id char(36) NULL; ALTER TABLE session_devices ADD COLUMN identity_id char(36) NULL; ================================================ FILE: persistence/sql/migrations/sql/20251104000000000000_identifiers_devices_identity_id.sqlite.down.sql ================================================ -- For SQLite, we do all operations in a single migration for simplicity. CREATE TABLE "_identity_credential_identifiers_tmp" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "identity_credential_type_id" char(36) NOT NULL, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE NO ACTION ON DELETE CASCADE ); INSERT INTO _identity_credential_identifiers_tmp (id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id) SELECT id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id FROM identity_credential_identifiers; DROP TABLE identity_credential_identifiers; ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); CREATE INDEX identity_credential_identifiers_nid_i_ici_idx ON "identity_credential_identifiers" (nid, identifier, identity_credential_id); CREATE INDEX identity_credential_identifiers_ici_nid_i_idx ON "identity_credential_identifiers" (identity_credential_id ASC, nid ASC, identifier ASC); CREATE TABLE IF NOT EXISTS "_session_devices_tmp" ( "id" UUID PRIMARY KEY NOT NULL, "ip_address" VARCHAR(50) DEFAULT '', "user_agent" VARCHAR(512) DEFAULT '', "location" VARCHAR(512) DEFAULT '', "nid" UUID NOT NULL, "session_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "session_metadata_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE cascade, CONSTRAINT "session_metadata_nid_fk" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON DELETE cascade, CONSTRAINT unique_session_device UNIQUE (nid, session_id, ip_address, user_agent) ); INSERT INTO "_session_devices_tmp" (id, ip_address, user_agent, location, nid, session_id, created_at, updated_at) SELECT id, ip_address, user_agent, location, nid, session_id, created_at, updated_at FROM session_devices; DROP TABLE session_devices; ALTER TABLE "_session_devices_tmp" RENAME TO "session_devices"; CREATE INDEX session_devices_nid_idx ON session_devices (nid ASC); CREATE INDEX session_devices_session_id_idx ON session_devices (session_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20251104000000000000_identifiers_devices_identity_id.sqlite.up.sql ================================================ -- For SQLite, we do all operations in a single migration for simplicity. CREATE TABLE IF NOT EXISTS "_identity_credential_identifiers_tmp" ( "id" TEXT PRIMARY KEY, "identifier" TEXT NOT NULL, "identity_credential_id" char(36) NOT NULL, "created_at" DATETIME NOT NULL, "updated_at" DATETIME NOT NULL, "nid" char(36), "identity_credential_type_id" char(36) NOT NULL, "identity_id" char(36) NOT NULL, FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON UPDATE RESTRICT ON DELETE CASCADE ); INSERT INTO _identity_credential_identifiers_tmp (id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id, identity_id) SELECT ici.id, ici.identifier, ici.identity_credential_id, ici.created_at, ici.updated_at, ici.nid, ici.identity_credential_type_id, ic.identity_id FROM identity_credential_identifiers ici INNER JOIN identity_credentials ic ON ici.identity_credential_id = ic.id AND ici.nid = ic.nid; DROP TABLE identity_credential_identifiers; ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); CREATE INDEX identity_credential_identifiers_nid_i_ici_idx ON "identity_credential_identifiers" (nid, identifier, identity_credential_id); CREATE INDEX identity_credential_identifiers_ici_nid_i_idx ON "identity_credential_identifiers" (identity_credential_id ASC, nid ASC, identifier ASC); CREATE TABLE IF NOT EXISTS "_session_devices_tmp" ( "id" UUID PRIMARY KEY NOT NULL, "identity_id" UUID NOT NULL, "ip_address" VARCHAR(50) DEFAULT '', "user_agent" VARCHAR(512) DEFAULT '', "location" VARCHAR(512) DEFAULT '', "nid" UUID NOT NULL, "session_id" UUID NOT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "session_metadata_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE cascade, CONSTRAINT "session_metadata_nid_fk" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON DELETE cascade, CONSTRAINT "session_devices_identity_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, CONSTRAINT unique_session_device UNIQUE (nid, session_id, ip_address, user_agent) ); INSERT INTO "_session_devices_tmp" (id, identity_id, ip_address, user_agent, location, nid, session_id, created_at, updated_at) SELECT sd.id, s.identity_id, sd.ip_address, sd.user_agent, sd.location, sd.nid, sd.session_id, sd.created_at, sd.updated_at FROM session_devices sd JOIN sessions s ON sd.session_id = s.id; DROP TABLE session_devices; ALTER TABLE "_session_devices_tmp" RENAME TO "session_devices"; CREATE INDEX session_devices_nid_idx ON session_devices (nid ASC); CREATE INDEX session_devices_session_id_idx ON session_devices (session_id ASC); ================================================ FILE: persistence/sql/migrations/sql/20251104000000000000_identifiers_devices_identity_id.up.sql ================================================ ALTER TABLE identity_credential_identifiers ADD COLUMN identity_id UUID NULL; ALTER TABLE session_devices ADD COLUMN identity_id UUID NULL; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.cockroach.up.sql ================================================ ALTER TABLE identity_credential_identifiers ALTER identity_id SET NOT NULL, ADD CONSTRAINT IF NOT EXISTS "identity_credential_identifiers_identities_id_fk" FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE; ALTER TABLE session_devices ALTER identity_id SET NOT NULL, ADD CONSTRAINT IF NOT EXISTS "session_devices_identities_id_fk" FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.down.sql ================================================ ALTER TABLE identity_credential_identifiers DROP CONSTRAINT identity_credential_identifiers_identities_id_fk, ALTER identity_id DROP NOT NULL; ALTER TABLE session_devices DROP CONSTRAINT session_devices_identities_id_fk, ALTER identity_id DROP NOT NULL; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.mysql.down.sql ================================================ ALTER TABLE identity_credential_identifiers DROP CONSTRAINT `identity_credential_identifiers_identity_id_fk`, MODIFY identity_id char(36) NULL; ALTER TABLE session_devices DROP CONSTRAINT `session_devices_identity_id_fk`, MODIFY identity_id char(36) NULL; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.mysql.up.sql ================================================ ALTER TABLE identity_credential_identifiers MODIFY identity_id char(36) NOT NULL, ADD CONSTRAINT `identity_credential_identifiers_identity_id_fk` FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE; ALTER TABLE session_devices MODIFY identity_id char(36) NOT NULL, ADD CONSTRAINT `session_devices_identity_id_fk` FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.postgres.up.sql ================================================ ALTER TABLE identity_credential_identifiers ALTER identity_id SET NOT NULL, ADD CONSTRAINT "identity_credential_identifiers_identities_id_fk" FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE; ALTER TABLE session_devices ALTER identity_id SET NOT NULL, ADD CONSTRAINT "session_devices_identities_id_fk" FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.sqlite.down.sql ================================================ -- Nothing, all done in 20251104000000000000_identifiers_devices_identity_id.sqlite.down.sql ================================================ FILE: persistence/sql/migrations/sql/20251105000000000003_identity_id_not_null_fks.sqlite.up.sql ================================================ -- Nothing, all done in 20251104000000000000_identifiers_devices_identity_id.sqlite.up.sql ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.cockroach.down.sql ================================================ DROP INDEX IF EXISTS identity_credential_identifiers@identity_credential_identifiers_identities_id_fk_idx; DROP INDEX IF EXISTS session_devices@session_devices_identities_id_fk_idx; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.cockroach.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_identities_id_fk_idx ON identity_credential_identifiers (identity_id); CREATE INDEX IF NOT EXISTS session_devices_identities_id_fk_idx ON session_devices (identity_id); ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.mysql.down.sql ================================================ -- Cannot drop index '...': needed in a foreign key constraint -- DROP INDEX identity_credential_identifiers_identity_id_fk_idx ON identity_credential_identifiers; -- DROP INDEX session_devices_identity_id_fk_idx ON session_devices; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.mysql.up.sql ================================================ CREATE INDEX identity_credential_identifiers_identity_id_fk_idx ON identity_credential_identifiers (identity_id); CREATE INDEX session_devices_identity_id_fk_idx ON session_devices (identity_id); ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.postgres.down.sql ================================================ DROP INDEX IF EXISTS identity_credential_identifiers_identities_id_fk_idx; DROP INDEX IF EXISTS session_devices_identities_id_fk_idx; ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.postgres.up.sql ================================================ CREATE INDEX IF NOT EXISTS identity_credential_identifiers_identities_id_fk_idx ON identity_credential_identifiers (identity_id); CREATE INDEX IF NOT EXISTS session_devices_identities_id_fk_idx ON session_devices (identity_id); ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.sqlite.down.sql ================================================ -- Nothing, all done in 20251104000000000000_identifiers_devices_identity_id.sqlite.down.sql ================================================ FILE: persistence/sql/migrations/sql/20251105000000000004_identity_id_not_null_fks.sqlite.up.sql ================================================ -- Nothing, all done in 20251104000000000000_identifiers_devices_identity_id.sqlite.up.sql ================================================ FILE: persistence/sql/migrations/sql/20251215000000000000_courier_messages_add_request_headers.down.sql ================================================ ALTER TABLE courier_messages DROP COLUMN IF EXISTS request_headers; ================================================ FILE: persistence/sql/migrations/sql/20251215000000000000_courier_messages_add_request_headers.mysql.down.sql ================================================ ALTER TABLE courier_messages DROP COLUMN request_headers; ================================================ FILE: persistence/sql/migrations/sql/20251215000000000000_courier_messages_add_request_headers.mysql.up.sql ================================================ ALTER TABLE courier_messages ADD COLUMN request_headers JSON; ================================================ FILE: persistence/sql/migrations/sql/20251215000000000000_courier_messages_add_request_headers.sqlite.down.sql ================================================ ALTER TABLE courier_messages DROP COLUMN request_headers; ================================================ FILE: persistence/sql/migrations/sql/20251215000000000000_courier_messages_add_request_headers.sqlite.up.sql ================================================ ALTER TABLE courier_messages ADD COLUMN request_headers JSONB; ================================================ FILE: persistence/sql/migrations/sql/20251215000000000000_courier_messages_add_request_headers.up.sql ================================================ ALTER TABLE courier_messages ADD COLUMN IF NOT EXISTS request_headers JSONB; ================================================ FILE: persistence/sql/migrations/sql/20260114175904000000_drop_identity_credentials_nid_idx.down.sql ================================================ CREATE INDEX identity_credentials_nid_idx ON identity_credentials (nid); ================================================ FILE: persistence/sql/migrations/sql/20260114175904000000_drop_identity_credentials_nid_idx.mysql.down.sql ================================================ -- CREATE INDEX identity_credentials_nid_idx ON identity_credentials (nid); -- didn't actually drop it in the up migration because "needed in a foreign key constraint" ================================================ FILE: persistence/sql/migrations/sql/20260114175904000000_drop_identity_credentials_nid_idx.mysql.up.sql ================================================ -- DROP INDEX identity_credentials_nid_idx ON identity_credentials; -- can't drop because "needed in a foreign key constraint" ================================================ FILE: persistence/sql/migrations/sql/20260114175904000000_drop_identity_credentials_nid_idx.up.sql ================================================ DROP INDEX IF EXISTS identity_credentials_nid_idx; ================================================ FILE: persistence/sql/persister.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "embed" "io/fs" "slices" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence" "github.com/ory/kratos/persistence/sql/devices" idpersistence "github.com/ory/kratos/persistence/sql/identity" gomigrations "github.com/ory/kratos/persistence/sql/migrations/go" "github.com/ory/kratos/schema" "github.com/ory/kratos/session" "github.com/ory/pop/v6" "github.com/ory/x/contextx" "github.com/ory/x/fsx" "github.com/ory/x/logrusx" "github.com/ory/x/networkx" "github.com/ory/x/otelx" "github.com/ory/x/popx" ) var _ persistence.Persister = new(Persister) //go:embed migrations/sql/*.sql var Migrations embed.FS type ( persisterDependencies interface { logrusx.Provider config.Provider contextx.Provider otelx.Provider schema.IdentitySchemaProvider identity.ValidationProvider } Persister struct { nid uuid.UUID c *pop.Connection mb *popx.MigrationBox mbs popx.MigrationStatuses r persisterDependencies identity.PrivilegedPool session.DevicePersister } ) type options struct { extraMigrations []fs.FS extraGoMigrations popx.Migrations disableLogging bool } type Option = func(o *options) func WithExtraMigrations(fss ...fs.FS) Option { return func(o *options) { o.extraMigrations = fss } } func WithExtraGoMigrations(ms ...popx.Migration) Option { return func(o *options) { o.extraGoMigrations = ms } } func WithDisabledLogging(v bool) Option { return func(o *options) { o.disableLogging = v } } func NewPersister(r persisterDependencies, c *pop.Connection, opts ...Option) (*Persister, error) { o := &options{} for _, f := range opts { f(o) } logger := r.Logger() if o.disableLogging { logger.Logrus().SetLevel(logrus.WarnLevel) } m, err := popx.NewMigrationBox( fsx.Merge(append([]fs.FS{Migrations, networkx.Migrations}, o.extraMigrations...)...), c, logger, popx.WithGoMigrations(slices.Concat(gomigrations.All, o.extraGoMigrations)), ) if err != nil { return nil, err } return &Persister{ c: c, mb: m, r: r, PrivilegedPool: idpersistence.NewPersister(r, c), DevicePersister: devices.NewPersister(r, c), }, nil } func (p *Persister) NetworkID(ctx context.Context) uuid.UUID { return p.r.Contextualizer().Network(ctx, p.nid) } func (p Persister) WithNetworkID(nid uuid.UUID) persistence.Persister { p.nid = nid if pp, ok := p.PrivilegedPool.(interface { WithNetworkID(uuid.UUID) identity.PrivilegedPool }); ok { p.PrivilegedPool = pp.WithNetworkID(nid) } if dp, ok := p.DevicePersister.(interface { WithNetworkID(uuid.UUID) session.DevicePersister }); ok { p.DevicePersister = dp.WithNetworkID(nid) } return &p } func (p *Persister) DetermineNetwork(ctx context.Context) (*networkx.Network, error) { return networkx.Determine(p.Connection(ctx)) } func (p *Persister) Connection(ctx context.Context) *pop.Connection { return p.c.WithContext(ctx) } func (p *Persister) MigrationStatus(ctx context.Context) (_ popx.MigrationStatuses, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.MigrationStatus") defer otelx.End(span, &err) if p.mbs != nil { return p.mbs, nil } status, err := p.mb.Status(ctx) if err != nil { return nil, errors.WithStack(err) } if !status.HasPending() { p.mbs = status } return status, nil } func (p *Persister) MigrateDown(ctx context.Context, steps int) error { return p.mb.Down(ctx, steps) } func (p *Persister) MigrateUp(ctx context.Context) error { return p.mb.Up(ctx) } func (p *Persister) MigrationBox() *popx.MigrationBox { return p.mb } func (p *Persister) Close(ctx context.Context) error { return errors.WithStack(p.GetConnection(ctx).Close()) } func (p *Persister) Ping(ctx context.Context) error { return errors.WithStack(p.c.Store.SQLDB().PingContext(ctx)) } func (p *Persister) CleanupDatabase(ctx context.Context, wait time.Duration, older time.Duration, batchSize int) error { currentTime := time.Now().Add(-older) p.r.Logger().Printf("Cleaning up records older than %s\n", currentTime) p.r.Logger().Println("Cleaning up expired sessions") if err := p.DeleteExpiredSessions(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired continuity containers") if err := p.DeleteExpiredContinuitySessions(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired login flows") if err := p.DeleteExpiredLoginFlows(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired recovery flows") if err := p.DeleteExpiredRecoveryFlows(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired registration flows") if err := p.DeleteExpiredRegistrationFlows(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired settings flows") if err := p.DeleteExpiredSettingsFlows(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired verification flows") if err := p.DeleteExpiredVerificationFlows(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Cleaning up expired session token exchangers") if err := p.DeleteExpiredExchangers(ctx, currentTime, batchSize); err != nil { return err } time.Sleep(wait) p.r.Logger().Println("Successfully cleaned up the latest batch of the SQL database! " + "This should be re-run periodically, to be sure that all expired data is purged.") return nil } ================================================ FILE: persistence/sql/persister_cleanup_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql_test import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/pkg" ) func TestPersister_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() ctx := context.Background() t.Run("case=should not throw error on cleanup", func(t *testing.T) { assert.Nil(t, p.CleanupDatabase(ctx, 0, 0, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.CleanupDatabase(ctx, 0, 0, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Continuity_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup continuity sessions", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredContinuitySessions(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup continuity sessions", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredContinuitySessions(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Login_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup login flows", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredLoginFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup login flows", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredLoginFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Recovery_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup recovery flows", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredRecoveryFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup recovery flows", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredRecoveryFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Registration_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup registration flows", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredRegistrationFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup registration flows", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredRegistrationFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Session_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup sessions", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredSessions(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup sessions", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredSessions(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Settings_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup setting flows", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredSettingsFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup setting flows", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredSettingsFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_Verification_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup verification flows", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredVerificationFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup verification flows", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredVerificationFlows(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } func TestPersister_SessionTokenExchange_Cleanup(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() currentTime := time.Now() ctx := context.Background() t.Run("case=should not throw error on cleanup session token exchangers", func(t *testing.T) { assert.Nil(t, p.DeleteExpiredExchangers(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) t.Run("case=should throw error on cleanup session token exchangers if DB is closed", func(t *testing.T) { require.NoError(t, p.GetConnection(ctx).Close()) assert.Error(t, p.DeleteExpiredExchangers(ctx, currentTime, reg.Config().DatabaseCleanupBatchSize(ctx))) }) } ================================================ FILE: persistence/sql/persister_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "crypto/subtle" "fmt" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) type oneTimeCodeProvider interface { GetID() uuid.UUID Validate() error TableName(ctx context.Context) string GetHMACCode() string } type codeOptions struct { IdentityID *uuid.UUID } type codeOption func(o *codeOptions) func withCheckIdentityID(id uuid.UUID) codeOption { return func(o *codeOptions) { o.IdentityID = &id } } func useOneTimeCode[P any, U interface { *P oneTimeCodeProvider }](ctx context.Context, p *Persister, flowID uuid.UUID, userProvidedCode, flowTableName, foreignKeyName string, opts ...codeOption, ) (target U, err error) { maxSubmissions := p.r.Config().SelfServiceCodeMethodMaxSubmissions(ctx) ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.useOneTimeCode", trace.WithAttributes(attribute.Int("max_submissions", maxSubmissions))) defer otelx.End(span, &err) o := new(codeOptions) for _, opt := range opts { opt(o) } // Before we do anything else, increment the submit count and check if we're // being brute-forced. This is a separate statement/transaction to the rest // of the operations so that it is correct for all transaction isolation // levels. submitCount, err := incrementOTPCodeSubmitCount(ctx, p, flowID, flowTableName) if err != nil { return nil, err } if submitCount > maxSubmissions { return nil, errors.WithStack(code.ErrCodeSubmittedTooOften) } nid := p.NetworkID(ctx) if err := p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { var codes []U codesQuery := tx.Where(fmt.Sprintf("nid = ? AND %s = ?", foreignKeyName), nid, flowID) if o.IdentityID != nil { codesQuery = codesQuery.Where("identity_id = ?", *o.IdentityID) } if err := sqlcon.HandleError(codesQuery.All(&codes)); err != nil { if errors.Is(err, sqlcon.ErrNoRows) { return errors.WithStack(code.ErrCodeNotFound) } return err } secrets: for _, secret := range p.r.Config().SecretsSession(ctx) { suppliedCode := []byte(hmacValueWithSecret(userProvidedCode, secret)) for i := range codes { c := codes[i] if subtle.ConstantTimeCompare([]byte(c.GetHMACCode()), suppliedCode) == 0 { // Not the supplied code continue } target = c break secrets } } if target.Validate() != nil { // Return no error, as that would roll back the transaction. We re-validate the code after the transaction. return nil } //#nosec G201 -- TableName is static return tx.RawQuery(fmt.Sprintf("UPDATE %s SET used_at = ? WHERE id = ? AND nid = ?", target.TableName(ctx)), time.Now().UTC(), target.GetID(), nid).Exec() }); err != nil { return nil, sqlcon.HandleError(err) } if err := target.Validate(); err != nil { return nil, err } return target, nil } func incrementOTPCodeSubmitCount(ctx context.Context, p *Persister, flowID uuid.UUID, flowTableName string) (submitCount int, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.incrementOTPCodeSubmitCount", trace.WithAttributes(attribute.Stringer("flow_id", flowID), attribute.String("flow_table_name", flowTableName))) defer otelx.End(span, &err) defer func() { span.SetAttributes(attribute.Int("submit_count", submitCount)) }() nid := p.NetworkID(ctx) // The branch below is a marginal performance optimization for databases // supporting RETURNING (one query instead of two). There is no real // security difference here, but there is an observable difference in // behavior. // // Databases supporting RETURNING will perform the increment+select // atomically. That means that always exactly 5 attempts will be allowed for // each flow, no matter how many concurrent attempts are made. // // Databases without support for RETURNING (MySQL) will perform the UPDATE // and SELECT in two queries, which are not atomic. The effect is that there // will still never be more than 5 attempts for each flow, but there may be // fewer before we reject. Under normal operation, this is never a problem // because a human will never submit their code as quickly as would be // required to trigger this race condition. // // In a very strict sense of the word, the MySQL implementation is even more // secure than the RETURNING implementation. But we're ok either way :) if p.c.Dialect.Name() == "mysql" { //#nosec G201 -- TableName is static qUpdate := fmt.Sprintf("UPDATE %s SET submit_count = submit_count + 1 WHERE id = ? AND nid = ?", flowTableName) if err := p.GetConnection(ctx).RawQuery(qUpdate, flowID, nid).Exec(); err != nil { return 0, sqlcon.HandleError(err) } //#nosec G201 -- TableName is static qSelect := fmt.Sprintf("SELECT submit_count FROM %s WHERE id = ? AND nid = ?", flowTableName) err = sqlcon.HandleError(p.GetConnection(ctx).RawQuery(qSelect, flowID, nid).First(&submitCount)) } else { //#nosec G201 -- TableName is static q := fmt.Sprintf("UPDATE %s SET submit_count = submit_count + 1 WHERE id = ? AND nid = ? RETURNING submit_count", flowTableName) err = sqlcon.HandleError(p.Connection(ctx).RawQuery(q, flowID, nid).First(&submitCount)) } if errors.Is(err, sqlcon.ErrNoRows) { return 0, errors.WithStack(code.ErrCodeNotFound) } return submitCount, err } ================================================ FILE: persistence/sql/persister_continuity.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/pkg/errors" "github.com/gofrs/uuid" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" "github.com/ory/kratos/continuity" ) var _ continuity.Persister = new(Persister) func (p *Persister) SaveContinuitySession(ctx context.Context, c *continuity.Container) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.SaveContinuitySession") defer otelx.End(span, &err) c.NID = p.NetworkID(ctx) return sqlcon.HandleError(p.GetConnection(ctx).Create(c)) } func (p *Persister) SetContinuitySessionExpiry(ctx context.Context, id uuid.UUID, expiresAt time.Time) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.SetContinuitySessionExpiry") defer otelx.End(span, &err) if rows, err := p.GetConnection(ctx). Where("id = ? AND nid = ?", id, p.NetworkID(ctx)). UpdateQuery(&continuity.Container{ ExpiresAt: expiresAt, }, "expires_at"); err != nil { return sqlcon.HandleError(err) } else if rows == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) GetContinuitySession(ctx context.Context, id uuid.UUID) (_ *continuity.Container, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetContinuitySession") defer otelx.End(span, &err) var c continuity.Container if err := p.GetConnection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&c); err != nil { return nil, sqlcon.HandleError(err) } return &c, nil } func (p *Persister) DeleteContinuitySession(ctx context.Context, id uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteContinuitySession") defer otelx.End(span, &err) if count, err := p.GetConnection(ctx).RawQuery( //#nosec G201 -- TableName is static fmt.Sprintf("DELETE FROM %s WHERE id=? AND nid=?", continuity.Container{}.TableName()), id, p.NetworkID(ctx)).ExecWithCount(); err != nil { return sqlcon.HandleError(err) } else if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) DeleteExpiredContinuitySessions(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredContinuitySessions") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", continuity.Container{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_courier.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "database/sql" "encoding/json" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/courier" "github.com/ory/kratos/persistence/sql/update" "github.com/ory/pop/v6" "github.com/ory/x/otelx" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" "github.com/ory/x/sqlcon" "github.com/ory/x/uuidx" ) var _ courier.Persister = new(Persister) func (p *Persister) AddMessage(ctx context.Context, m *courier.Message) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.AddMessage") defer otelx.End(span, &err) m.NID = p.NetworkID(ctx) m.Status = courier.MessageStatusQueued return sqlcon.HandleError(p.GetConnection(ctx).Create(m)) // do not create eager to avoid identity injection. } func (p *Persister) ListMessages(ctx context.Context, filter courier.ListCourierMessagesParameters, opts []keysetpagination.Option) (_ []courier.Message, _ *keysetpagination.Paginator, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListMessages") defer otelx.End(span, &err) q := p.GetConnection(ctx).Where("nid=?", p.NetworkID(ctx)) if filter.Status != nil { q = q.Where("status=?", *filter.Status) } if filter.Recipient != "" { q = q.Where("recipient=?", filter.Recipient) } opts = append(opts, keysetpagination.WithDefaultToken(courier.Message{}.DefaultPageToken())) opts = append(opts, keysetpagination.WithDefaultSize(10)) paginator, err := keysetpagination.NewPaginator(opts...) if err != nil { return nil, nil, err } messages := make([]courier.Message, paginator.Size()) if err := q.Scope(keysetpagination.Paginate[courier.Message](paginator)). All(&messages); err != nil { return nil, nil, sqlcon.HandleError(err) } messages, nextPage := keysetpagination.Result(messages, paginator) return messages, nextPage, nil } func (p *Persister) NextMessages(ctx context.Context, limit uint8) (messages []courier.Message, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.NextMessages") defer otelx.End(span, &err) if err := p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { var m []courier.Message if err := tx. Where("nid = ? AND status = ?", p.NetworkID(ctx), courier.MessageStatusQueued, ). Order("created_at ASC"). Limit(int(limit)). All(&m); err != nil { return err } if len(m) == 0 { return sql.ErrNoRows } for i := range m { message := &m[i] message.Status = courier.MessageStatusProcessing if err := update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), message, "status"); err != nil { return err } } messages = m return nil }); err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, errors.WithStack(courier.ErrQueueEmpty) } return nil, sqlcon.HandleError(err) } if len(messages) == 0 { return nil, errors.WithStack(courier.ErrQueueEmpty) } return messages, nil } func (p *Persister) LatestQueuedMessage(ctx context.Context) (_ *courier.Message, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.LatestQueuedMessage") defer otelx.End(span, &err) var m courier.Message if err := p.GetConnection(ctx). Where("nid = ? AND status = ?", p.NetworkID(ctx), courier.MessageStatusQueued, ). Order("created_at DESC"). First(&m); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, errors.WithStack(courier.ErrQueueEmpty) } return nil, sqlcon.HandleError(err) } return &m, nil } func (p *Persister) SetMessageStatus(ctx context.Context, id uuid.UUID, ms courier.MessageStatus) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.SetMessageStatus") defer otelx.End(span, &err) count, err := p.GetConnection(ctx).RawQuery( "UPDATE courier_messages SET status = ? WHERE id = ? AND nid = ?", ms, id, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) IncrementMessageSendCount(ctx context.Context, id uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.IncrementMessageSendCount") defer otelx.End(span, &err) count, err := p.GetConnection(ctx).RawQuery( "UPDATE courier_messages SET send_count = send_count + 1 WHERE id = ? AND nid = ?", id, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) FetchMessage(ctx context.Context, msgID uuid.UUID) (_ *courier.Message, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FetchMessage") defer otelx.End(span, &err) var message courier.Message if err := p.GetConnection(ctx). Where("id = ? AND nid = ?", msgID, p.NetworkID(ctx)). Eager(). First(&message); err != nil { return nil, sqlcon.HandleError(err) } return &message, nil } func (p *Persister) RecordDispatch(ctx context.Context, msgID uuid.UUID, status courier.CourierMessageDispatchStatus, err error) error { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RecordDispatch") defer otelx.End(span, &err) dispatch := courier.MessageDispatch{ ID: uuidx.NewV4(), MessageID: msgID, Status: status, NID: p.NetworkID(ctx), } if err != nil { // We use herodot as a carrier for the error's data her := herodot.ToDefaultError(err, "") content, mErr := json.Marshal(her) if mErr != nil { return errors.WithStack(mErr) } dispatch.Error = content } if err := p.GetConnection(ctx).Create(&dispatch); err != nil { return sqlcon.HandleError(err) } return nil } ================================================ FILE: persistence/sql/persister_errorx.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "encoding/json" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "github.com/ory/herodot" "github.com/ory/jsonschema/v3" "github.com/ory/kratos/selfservice/errorx" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) var _ errorx.Persister = new(Persister) func (p *Persister) CreateErrorContainer(ctx context.Context, csrfToken string, errs error) (containerID uuid.UUID, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateErrorContainer") defer otelx.End(span, &err) message, err := encodeSelfServiceErrors(errs) if err != nil { return uuid.Nil, err } c := &errorx.ErrorContainer{ ID: uuid.Nil, NID: p.NetworkID(ctx), CSRFToken: csrfToken, Errors: message, WasSeen: false, } if err := p.GetConnection(ctx).Create(c); err != nil { return uuid.Nil, sqlcon.HandleError(err) } span.SetAttributes(attribute.String("id", c.ID.String())) return c.ID, nil } func (p *Persister) ReadErrorContainer(ctx context.Context, id uuid.UUID) (_ *errorx.ErrorContainer, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ReadErrorContainer") defer otelx.End(span, &err) var ec errorx.ErrorContainer if err := p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { if err := c.Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&ec); err != nil { return sqlcon.HandleError(err) } if err := c.RawQuery( "UPDATE selfservice_errors SET was_seen = true, seen_at = ? WHERE id = ? AND nid = ?", time.Now().UTC(), id, p.NetworkID(ctx)).Exec(); err != nil { return sqlcon.HandleError(err) } return nil }); err != nil { return nil, err } return &ec, nil } func (p *Persister) ClearErrorContainers(ctx context.Context, olderThan time.Duration, force bool) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ClearErrorContainers") defer otelx.End(span, &err) if force { err = p.GetConnection(ctx).RawQuery( "DELETE FROM selfservice_errors WHERE nid = ? AND seen_at < ? AND seen_at IS NOT NULL", p.NetworkID(ctx), time.Now().UTC().Add(-olderThan)).Exec() } else { err = p.GetConnection(ctx).RawQuery( "DELETE FROM selfservice_errors WHERE nid = ? AND was_seen=true AND seen_at < ? AND seen_at IS NOT NULL", p.NetworkID(ctx), time.Now().UTC().Add(-olderThan)).Exec() } return sqlcon.HandleError(err) } func encodeSelfServiceErrors(e error) ([]byte, error) { if e == nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithDebug("A nil error was passed to the error manager which is most likely a code bug.")) } if c := new(herodot.DefaultError); errors.As(e, &c) { e = c } else if c := new(jsonschema.ValidationError); errors.As(e, &c) { e = c } else { e = herodot.ToDefaultError(e, "") } enc, err := json.Marshal(e) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Unable to encode error messages.").WithDebug(err.Error())) } return enc, nil } ================================================ FILE: persistence/sql/persister_hmac.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "crypto/hmac" "crypto/sha512" "fmt" ) func (p *Persister) hmacValue(ctx context.Context, value string) string { return hmacValueWithSecret(value, p.r.Config().SecretsSession(ctx)[0]) } func hmacValueWithSecret(value string, secret []byte) string { h := hmac.New(sha512.New512_256, secret) _, _ = h.Write([]byte(value)) return fmt.Sprintf("%x", h.Sum(nil)) } ================================================ FILE: persistence/sql/persister_hmac_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/embedx" "github.com/ory/kratos/identity" "github.com/ory/kratos/schema" "github.com/ory/pop/v6" "github.com/ory/x/configx" "github.com/ory/x/contextx" "github.com/ory/x/logrusx" "github.com/ory/x/otelx" ) type logRegistryOnly struct { l *logrusx.Logger c *config.Config } func (l *logRegistryOnly) Config() *config.Config { return l.c } func (l *logRegistryOnly) Contextualizer() contextx.Contextualizer { // TODO implement me panic("implement me") } func (l *logRegistryOnly) Logger() *logrusx.Logger { if l.l == nil { l.l = logrusx.New("kratos", "testing") } return l.l } func (l *logRegistryOnly) Audit() *logrusx.Logger { panic("implement me") } func (l *logRegistryOnly) Tracer(context.Context) *otelx.Tracer { return otelx.NewNoop() } func (l *logRegistryOnly) IdentityTraitsSchemas(context.Context) (schema.IdentitySchemaList, error) { panic("implement me") } func (l *logRegistryOnly) IdentityValidator() *identity.Validator { panic("implement me") } var _ persisterDependencies = &logRegistryOnly{} func TestPersisterHMAC(t *testing.T) { t.Parallel() ctx := context.Background() baseSecret := "foobarbaz" baseSecretBytes := []byte(baseSecret) opts := []configx.OptionModifier{configx.SkipValidation(), configx.WithValue(config.ViperKeySecretsDefault, []string{baseSecret})} conf := config.MustNew(t, logrusx.New("", ""), contextx.NewTestConfigProvider(embedx.ConfigSchema, opts...), opts...) c, err := pop.NewConnection(&pop.ConnectionDetails{URL: "sqlite://foo?mode=memory"}) require.NoError(t, err) p, err := NewPersister(&logRegistryOnly{c: conf}, c) require.NoError(t, err) t.Run("case=behaves deterministically", func(t *testing.T) { assert.Equal(t, hmacValueWithSecret("hashme", baseSecretBytes), p.hmacValue(ctx, "hashme")) assert.NotEqual(t, hmacValueWithSecret("notme", baseSecretBytes), p.hmacValue(ctx, "hashme")) assert.NotEqual(t, hmacValueWithSecret("hashme", baseSecretBytes), p.hmacValue(ctx, "notme")) }) hash := p.hmacValue(ctx, "hashme") newSecret := "not" + baseSecret t.Run("case=with only new sectet", func(t *testing.T) { ctx = contextx.WithConfigValue(ctx, config.ViperKeySecretsDefault, []string{newSecret}) assert.NotEqual(t, hmacValueWithSecret("hashme", baseSecretBytes), p.hmacValue(ctx, "hashme")) assert.Equal(t, hmacValueWithSecret("hashme", []byte(newSecret)), p.hmacValue(ctx, "hashme")) }) t.Run("case=with new and old secret", func(t *testing.T) { ctx = contextx.WithConfigValue(ctx, config.ViperKeySecretsDefault, []string{newSecret, baseSecret}) assert.Equal(t, hmacValueWithSecret("hashme", []byte(newSecret)), p.hmacValue(ctx, "hashme")) assert.NotEqual(t, hash, p.hmacValue(ctx, "hashme")) }) } ================================================ FILE: persistence/sql/persister_login.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/gofrs/uuid" "github.com/ory/kratos/persistence/sql/update" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) var _ login.FlowPersister = new(Persister) func (p *Persister) CreateLoginFlow(ctx context.Context, r *login.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateLoginFlow") defer otelx.End(span, &err) r.NID = p.NetworkID(ctx) r.EnsureInternalContext() return p.GetConnection(ctx).Create(r) } func (p *Persister) UpdateLoginFlow(ctx context.Context, r *login.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateLoginFlow") defer otelx.End(span, &err) r.EnsureInternalContext() cp := *r cp.NID = p.NetworkID(ctx) return update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), cp) } func (p *Persister) GetLoginFlow(ctx context.Context, id uuid.UUID) (_ *login.Flow, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetLoginFlow") defer otelx.End(span, &err) conn := p.GetConnection(ctx) var r login.Flow if err := conn.Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&r); err != nil { return nil, sqlcon.HandleError(err) } return &r, nil } func (p *Persister) ForceLoginFlow(ctx context.Context, id uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ForceLoginFlow") defer otelx.End(span, &err) return p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) error { lr, err := p.GetLoginFlow(ctx, id) if err != nil { return err } lr.Refresh = true return tx.Save(lr, "nid") }) } func (p *Persister) DeleteExpiredLoginFlows(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredLoginFlows") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", login.Flow{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_login_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "time" "github.com/gofrs/uuid" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) func (p *Persister) CreateLoginCode(ctx context.Context, params *code.CreateLoginCodeParams) (_ *code.LoginCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateLoginCode") defer otelx.End(span, &err) now := time.Now().UTC() loginCode := &code.LoginCode{ IdentityID: params.IdentityID, Address: params.Address, AddressType: params.AddressType, CodeHMAC: p.hmacValue(ctx, params.RawCode), IssuedAt: now, ExpiresAt: now.UTC().Add(p.r.Config().SelfServiceCodeMethodLifespan(ctx)), FlowID: params.FlowID, NID: p.NetworkID(ctx), ID: uuid.Nil, } if err := p.GetConnection(ctx).Create(loginCode); err != nil { return nil, sqlcon.HandleError(err) } return loginCode, nil } func (p *Persister) UseLoginCode(ctx context.Context, flowID uuid.UUID, identityID uuid.UUID, userProvidedCode string) (_ *code.LoginCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseLoginCode") defer otelx.End(span, &err) codeRow, err := useOneTimeCode[code.LoginCode](ctx, p, flowID, userProvidedCode, login.Flow{}.TableName(), "selfservice_login_flow_id", withCheckIdentityID(identityID)) if err != nil { return nil, err } return codeRow, nil } func (p *Persister) GetUsedLoginCode(ctx context.Context, flowID uuid.UUID) (_ *code.LoginCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetUsedLoginCode") defer otelx.End(span, &err) var loginCode code.LoginCode if err := p.Connection(ctx).Where("selfservice_login_flow_id = ? AND nid = ? AND used_at IS NOT NULL", flowID, p.NetworkID(ctx)).First(&loginCode); err != nil { return nil, sqlcon.HandleError(err) } return &loginCode, nil } func (p *Persister) DeleteLoginCodesOfFlow(ctx context.Context, flowID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteLoginCodesOfFlow") defer otelx.End(span, &err) return p.GetConnection(ctx).Where("selfservice_login_flow_id = ? AND nid = ?", flowID, p.NetworkID(ctx)).Delete(&code.LoginCode{}) } ================================================ FILE: persistence/sql/persister_recovery.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence/sql/update" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) var ( _ recovery.FlowPersister = new(Persister) _ link.RecoveryTokenPersister = new(Persister) ) func (p *Persister) CreateRecoveryFlow(ctx context.Context, r *recovery.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateRecoveryFlow") defer otelx.End(span, &err) r.NID = p.NetworkID(ctx) return p.GetConnection(ctx).Create(r) } func (p *Persister) GetRecoveryFlow(ctx context.Context, id uuid.UUID) (_ *recovery.Flow, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetRecoveryFlow") defer otelx.End(span, &err) var r recovery.Flow if err := p.GetConnection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&r); err != nil { return nil, sqlcon.HandleError(err) } return &r, nil } func (p *Persister) UpdateRecoveryFlow(ctx context.Context, r *recovery.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateRecoveryFlow") defer otelx.End(span, &err) cp := *r cp.NID = p.NetworkID(ctx) return update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), cp) } func (p *Persister) CreateRecoveryToken(ctx context.Context, token *link.RecoveryToken) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateRecoveryToken") defer otelx.End(span, &err) t := token.Token token.Token = p.hmacValue(ctx, t) token.NID = p.NetworkID(ctx) // This should not create the request eagerly because otherwise we might accidentally create an address that isn't // supposed to be in the database. if err := p.GetConnection(ctx).Create(token); err != nil { return err } token.Token = t return nil } func (p *Persister) UseRecoveryToken(ctx context.Context, fID uuid.UUID, token string) (_ *link.RecoveryToken, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseRecoveryToken") defer otelx.End(span, &err) var rt link.RecoveryToken nid := p.NetworkID(ctx) if err := sqlcon.HandleError(p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) (err error) { for _, secret := range p.r.Config().SecretsSession(ctx) { if err = tx.Where("token = ? AND nid = ? AND NOT used AND selfservice_recovery_flow_id = ?", hmacValueWithSecret(token, secret), nid, fID).First(&rt); err != nil { if !errors.Is(sqlcon.HandleError(err), sqlcon.ErrNoRows) { return err } } else { break } } if err != nil { return err } var ra identity.RecoveryAddress if err := tx.Where("id = ? AND nid = ?", rt.RecoveryAddressID, nid).First(&ra); err != nil { if !errors.Is(sqlcon.HandleError(err), sqlcon.ErrNoRows) { return err } } rt.RecoveryAddress = &ra //#nosec G201 -- TableName is static return tx.RawQuery(fmt.Sprintf("UPDATE %s SET used=true, used_at=? WHERE id=? AND nid = ?", rt.TableName(ctx)), time.Now().UTC(), rt.ID, nid).Exec() })); err != nil { return nil, err } return &rt, nil } func (p *Persister) DeleteRecoveryToken(ctx context.Context, token string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteRecoveryToken") defer otelx.End(span, &err) //#nosec G201 -- TableName is static return p.GetConnection(ctx).RawQuery(fmt.Sprintf("DELETE FROM %s WHERE token=? AND nid = ?", new(link.RecoveryToken).TableName(ctx)), token, p.NetworkID(ctx)).Exec() } func (p *Persister) DeleteExpiredRecoveryFlows(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredRecoveryFlows") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", recovery.Flow{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_recovery_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) func (p *Persister) CreateRecoveryCode(ctx context.Context, params *code.CreateRecoveryCodeParams) (_ *code.RecoveryCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateRecoveryCode") defer otelx.End(span, &err) now := time.Now() recoveryCode := &code.RecoveryCode{ ID: uuid.Nil, CodeHMAC: p.hmacValue(ctx, params.RawCode), ExpiresAt: now.UTC().Add(params.ExpiresIn), IssuedAt: now, CodeType: params.CodeType, FlowID: params.FlowID, NID: p.NetworkID(ctx), IdentityID: params.IdentityID, } if params.RecoveryAddress != nil { recoveryCode.RecoveryAddress = params.RecoveryAddress recoveryCode.RecoveryAddressID = uuid.NullUUID{ UUID: params.RecoveryAddress.ID, Valid: true, } } // This should not create the request eagerly because otherwise we might accidentally create an address that isn't // supposed to be in the database. if err := p.GetConnection(ctx).Create(recoveryCode); err != nil { return nil, err } return recoveryCode, nil } // UseRecoveryCode attempts to "use" the supplied code in the flow // // If the supplied code matched a code from the flow, no error is returned // If an invalid code was submitted with this flow more than 5 times, an error is returned func (p *Persister) UseRecoveryCode(ctx context.Context, flowID uuid.UUID, userProvidedCode string) (_ *code.RecoveryCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseRecoveryCode") defer otelx.End(span, &err) codeRow, err := useOneTimeCode[code.RecoveryCode](ctx, p, flowID, userProvidedCode, recovery.Flow{}.TableName(), "selfservice_recovery_flow_id") if err != nil { return nil, err } var ra identity.RecoveryAddress if err := sqlcon.HandleError(p.GetConnection(ctx).Where("id = ? AND nid = ?", codeRow.RecoveryAddressID, p.NetworkID(ctx)).First(&ra)); err != nil { if errors.Is(err, sqlcon.ErrNoRows) { // This is ok, it can happen when an administrator initiates account recovery. This works even if the // user has no recovery address! } else { return nil, err } } codeRow.RecoveryAddress = &ra return codeRow, nil } func (p *Persister) DeleteRecoveryCodesOfFlow(ctx context.Context, flowID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteRecoveryCodesOfFlow") defer otelx.End(span, &err) return p.GetConnection(ctx).Where("selfservice_recovery_flow_id = ? AND nid = ?", flowID, p.NetworkID(ctx)).Delete(&code.RecoveryCode{}) } ================================================ FILE: persistence/sql/persister_registration.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/gofrs/uuid" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" "github.com/ory/kratos/persistence/sql/update" "github.com/ory/kratos/selfservice/flow/registration" ) func (p *Persister) CreateRegistrationFlow(ctx context.Context, r *registration.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateRegistrationFlow") defer otelx.End(span, &err) r.NID = p.NetworkID(ctx) r.EnsureInternalContext() return p.GetConnection(ctx).Create(r) } func (p *Persister) UpdateRegistrationFlow(ctx context.Context, r *registration.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateRegistrationFlow") defer otelx.End(span, &err) r.EnsureInternalContext() cp := *r cp.NID = p.NetworkID(ctx) return update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), cp) } func (p *Persister) GetRegistrationFlow(ctx context.Context, id uuid.UUID) (_ *registration.Flow, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetRegistrationFlow") defer otelx.End(span, &err) var r registration.Flow if err := p.GetConnection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&r); err != nil { return nil, sqlcon.HandleError(err) } return &r, nil } func (p *Persister) DeleteExpiredRegistrationFlows(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredRegistrationFlows") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", registration.Flow{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_registration_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "time" "github.com/go-faker/faker/v4/pkg/slice" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) func (p *Persister) CreateRegistrationCode(ctx context.Context, params *code.CreateRegistrationCodeParams) (_ *code.RegistrationCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateRegistrationCode") defer otelx.End(span, &err) now := time.Now().UTC() registrationCode := &code.RegistrationCode{ Address: params.Address, AddressType: params.AddressType, CodeHMAC: p.hmacValue(ctx, params.RawCode), IssuedAt: now, ExpiresAt: now.UTC().Add(p.r.Config().SelfServiceCodeMethodLifespan(ctx)), FlowID: params.FlowID, NID: p.NetworkID(ctx), ID: uuid.Nil, } if err := p.GetConnection(ctx).Create(registrationCode); err != nil { return nil, sqlcon.HandleError(err) } return registrationCode, nil } func (p *Persister) UseRegistrationCode(ctx context.Context, flowID uuid.UUID, userProvidedCode string, addresses ...string) (_ *code.RegistrationCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseRegistrationCode") defer otelx.End(span, &err) codeRow, err := useOneTimeCode[code.RegistrationCode](ctx, p, flowID, userProvidedCode, registration.Flow{}.TableName(), "selfservice_registration_flow_id") if err != nil { return nil, err } // ensure that the identifiers extracted from the traits are contained in the registration code if !slice.Contains(addresses, codeRow.Address) { return nil, errors.WithStack(code.ErrCodeNotFound) } return codeRow, nil } func (p *Persister) GetUsedRegistrationCode(ctx context.Context, flowID uuid.UUID) (_ *code.RegistrationCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetUsedRegistrationCode") defer otelx.End(span, &err) var registrationCode code.RegistrationCode if err := p.Connection(ctx).Where("selfservice_registration_flow_id = ? AND used_at IS NOT NULL AND nid = ?", flowID, p.NetworkID(ctx)).First(®istrationCode); err != nil { return nil, sqlcon.HandleError(err) } return ®istrationCode, nil } func (p *Persister) DeleteRegistrationCodesOfFlow(ctx context.Context, flowID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteRegistrationCodesOfFlow") defer otelx.End(span, &err) return p.GetConnection(ctx).Where("selfservice_registration_flow_id = ? AND nid = ?", flowID, p.NetworkID(ctx)).Delete(&code.RegistrationCode{}) } ================================================ FILE: persistence/sql/persister_session.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" "github.com/ory/herodot" "github.com/ory/kratos/identity" "github.com/ory/kratos/session" "github.com/ory/kratos/x" "github.com/ory/kratos/x/events" "github.com/ory/pop/v6" "github.com/ory/x/dbal" "github.com/ory/x/otelx" "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/sqlcon" "github.com/ory/x/stringsx" ) var _ session.Persister = new(Persister) const ( SessionDeviceUserAgentMaxLength = 512 SessionDeviceLocationMaxLength = 512 paginationMaxItemsSize = 1000 paginationDefaultItemsSize = 250 ) func (p *Persister) GetSession(ctx context.Context, sid uuid.UUID, expandables session.Expandables) (_ *session.Session, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetSession") defer otelx.End(span, &err) var s session.Session s.Devices = make([]session.Device, 0) nid := p.NetworkID(ctx) q := p.GetConnection(ctx).Q() // if len(expandables) > 0 { if expandables.Has(session.ExpandSessionDevices) { q = q.Eager(expandables.ToEager()...) } if err := q.Where("id = ? AND nid = ?", sid, nid).First(&s); err != nil { return nil, sqlcon.HandleError(err) } if expandables.Has(session.ExpandSessionIdentity) { // This is needed because of how identities are fetched from the store (if we use eager not all fields are // available!). i, err := p.GetIdentity(ctx, s.IdentityID, identity.ExpandDefault) if err != nil { return nil, err } s.Identity = i } s.Active = s.IsActive() return &s, nil } func (p *Persister) ListSessions(ctx context.Context, active *bool, paginatorOpts []keysetpagination.Option, expandables session.Expandables) (_ []session.Session, _ *keysetpagination.Paginator, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListSessions") defer otelx.End(span, &err) s := make([]session.Session, 0) nid := p.NetworkID(ctx) paginatorOpts = append(paginatorOpts, keysetpagination.WithDefaultSize(paginationDefaultItemsSize)) paginatorOpts = append(paginatorOpts, keysetpagination.WithMaxSize(paginationMaxItemsSize)) paginatorOpts = append(paginatorOpts, keysetpagination.WithDefaultToken(new(session.Session).DefaultPageToken())) paginatorOpts = append(paginatorOpts, keysetpagination.WithColumn("created_at", "DESC")) paginator := keysetpagination.GetPaginator(paginatorOpts...) if _, err := uuid.FromString(paginator.Token().Parse("id")["id"]); err != nil { return nil, nil, errors.WithStack(x.PageTokenInvalid) } if err := p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { q := c.Where("nid = ?", nid) if active != nil { if *active { q.Where("active = ? AND expires_at >= ?", *active, time.Now().UTC()) } else { q.Where("(active = ? OR expires_at < ?)", *active, time.Now().UTC()) } } if len(expandables) > 0 { q = q.EagerPreload(expandables.ToEager()...) } // Get the paginated list of matching items if err := q.Scope(keysetpagination.Paginate[session.Session](paginator)).All(&s); err != nil { return sqlcon.HandleError(err) } return nil }); err != nil { return nil, nil, err } for k := range s { if s[k].Identity == nil { continue } if err := p.InjectTraitsSchemaURL(ctx, s[k].Identity); err != nil { return nil, nil, err } } s, nextPage := keysetpagination.Result(s, paginator) return s, nextPage, nil } // ListSessionsByIdentity retrieves sessions for an identity from the store. func (p *Persister) ListSessionsByIdentity( ctx context.Context, iID uuid.UUID, active *bool, page, perPage int, except uuid.UUID, expandables session.Expandables, ) (_ []session.Session, _ int64, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListSessionsByIdentity") defer otelx.End(span, &err) s := make([]session.Session, 0) t := int64(0) nid := p.NetworkID(ctx) if err := p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { q := c.Where("identity_id = ? AND nid = ?", iID, nid) if except != uuid.Nil { q = q.Where("id != ?", except) } if active != nil { if *active { q.Where("active = ? AND expires_at >= ?", *active, time.Now().UTC()) } else { q.Where("(active = ? OR expires_at < ?)", *active, time.Now().UTC()) } } if len(expandables) > 0 { q = q.EagerPreload(expandables.ToEager()...) } // Get the total count of matching items total, err := q.Count(new(session.Session)) if err != nil { return sqlcon.HandleError(err) } t = int64(total) q.Order("created_at DESC") // Get the paginated list of matching items if err := q.Paginate(page, perPage).All(&s); err != nil { return sqlcon.HandleError(err) } return nil }); err != nil { return nil, 0, err } return s, t, nil } // ExtendSession updates the expiry of a session. func (p *Persister) ExtendSession(ctx context.Context, sessionID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ExtendSession") defer otelx.End(span, &err) nid := p.NetworkID(ctx) s := new(session.Session) var didRefresh bool if err := errors.WithStack(p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) (err error) { lockBehavior := "" if tx.Dialect.Name() == dbal.DriverCockroachDB { // SKIP LOCKED returns no rows if the row is locked by another transaction. lockBehavior = "FOR UPDATE SKIP LOCKED" } if err := tx. Where( // We make use of the fact that CRDB supports FOR UPDATE as part of the WHERE clause. fmt.Sprintf("id = ? AND nid = ? %s", lockBehavior), sessionID, nid, ).First(s); err != nil { // This is a special case for CockroachDB. If the row is locked, we do not see the session. Therefor we return // a 404 not found error indicating to the user that the session might already be updated by someone else. if errors.Is(err, sqlcon.ErrNoRows) && tx.Dialect.Name() == dbal.DriverCockroachDB { return errors.WithStack(herodot.ErrNotFound.WithReason("The session you are trying to extend is already being extended by another request or does not exist.")) } return sqlcon.HandleError(err) } if !s.CanBeRefreshed(ctx, p.r.Config()) { // This prevents excessive writes to the database. return nil } didRefresh = true s = s.Refresh(ctx, p.r.Config()) if _, err := tx.Where("id = ? AND nid = ?", sessionID, nid).UpdateQuery(s, "expires_at"); err != nil { return sqlcon.HandleError(err) } return nil })); err != nil { return err } if didRefresh { trace.SpanFromContext(ctx).AddEvent(events.NewSessionLifespanExtended(ctx, s.ID, s.IdentityID, s.ExpiresAt)) } return nil } // UpsertSession creates a session if not found else updates. // This operation also inserts Session device records when a session is being created. // The update operation skips updating Session device records since only one record would need to be updated in this case. func (p *Persister) UpsertSession(ctx context.Context, s *session.Session) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpsertSession") defer otelx.End(span, &err) s.NID = p.NetworkID(ctx) if s.Identity != nil { s.IdentityID = s.Identity.ID } else if s.IdentityID.IsNil() { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("cannot upsert session without an identity or identity ID set")) } var updated bool defer func() { if err != nil { return } if updated { trace.SpanFromContext(ctx).AddEvent(events.NewSessionChanged(ctx, string(s.AuthenticatorAssuranceLevel), s.ID, s.IdentityID)) } else { trace.SpanFromContext(ctx).AddEvent(events.NewSessionIssued(ctx, string(s.AuthenticatorAssuranceLevel), s.ID, s.IdentityID)) } }() return errors.WithStack(p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) (err error) { updated = false exists := false if !s.ID.IsNil() { exists, err = tx.Where("id = ? AND nid = ?", s.ID, s.NID).Exists(new(session.Session)) if err != nil { return sqlcon.HandleError(err) } } if exists { // This must not be eager or identities will be created / updated // Only update session and not corresponding session device records if err := tx.Update(s, "issued_at", "identity_id", "nid"); err != nil { return sqlcon.HandleError(err) } updated = true return nil } // This must not be eager or identities will be created / updated if err := sqlcon.HandleError(tx.Create(s)); err != nil { return err } for i := range s.Devices { device := &(s.Devices[i]) device.SessionID = s.ID device.NID = s.NID device.IdentityID = new(s.IdentityID) if device.Location != nil { device.Location = new(stringsx.TruncateByteLen(*device.Location, SessionDeviceLocationMaxLength)) } if device.UserAgent != nil { device.UserAgent = new(stringsx.TruncateByteLen(*device.UserAgent, SessionDeviceUserAgentMaxLength)) } if err := p.CreateDevice(ctx, device); err != nil { return err } } return nil })) } func (p *Persister) DeleteSession(ctx context.Context, sid uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteSession") defer otelx.End(span, &err) nid := p.NetworkID(ctx) //#nosec G201 -- TableName is static count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf("DELETE FROM %s WHERE id = ? AND nid = ?", session.Session{}.TableName()), sid, nid, ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) DeleteSessionsByIdentity(ctx context.Context, identityID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteSessionsByIdentity") defer otelx.End(span, &err) //#nosec G201 -- TableName is static count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %s WHERE identity_id = ? AND nid = ?", session.Session{}.TableName(), ), identityID, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) GetSessionByToken(ctx context.Context, token string, expand session.Expandables, identityExpand identity.Expandables) (res *session.Session, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetSessionByToken") defer otelx.End(span, &err) var s session.Session s.Devices = make([]session.Device, 0) nid := p.NetworkID(ctx) con := p.GetConnection(ctx) if err := con.Where("token = ? AND nid = ?", token, nid).First(&s); err != nil { return nil, sqlcon.HandleError(err) } var ( i *identity.Identity sd []session.Device ) eg, ctx := errgroup.WithContext(ctx) if expand.Has(session.ExpandSessionDevices) { eg.Go(func() error { return sqlcon.HandleError(con.WithContext(ctx). Where("session_id = ? AND nid = ?", s.ID, nid).All(&sd)) }) } // This is needed because of how identities are fetched from the store (if we use eager not all fields are // available!). if expand.Has(session.ExpandSessionIdentity) { eg.Go(func() (err error) { i, err = p.GetIdentity(ctx, s.IdentityID, identityExpand) return err }) } if err := eg.Wait(); err != nil { return nil, err } s.Identity = i s.Devices = sd return &s, nil } func (p *Persister) DeleteSessionByToken(ctx context.Context, token string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteSessionByToken") defer otelx.End(span, &err) //#nosec G201 -- TableName is static count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %s WHERE token = ? AND nid = ?", session.Session{}.TableName(), ), token, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } func (p *Persister) RevokeSessionByToken(ctx context.Context, token string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeSessionByToken") defer otelx.End(span, &err) //#nosec G201 -- TableName is static count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( "UPDATE %s SET active = false WHERE token = ? AND nid = ?", session.Session{}.TableName(), ), token, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } // RevokeSessionById revokes a given session func (p *Persister) RevokeSessionById(ctx context.Context, sID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeSessionById") defer otelx.End(span, &err) //#nosec G201 -- TableName is static count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( "UPDATE %s SET active = false WHERE id = ? AND nid = ?", session.Session{}.TableName(), ), sID, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return sqlcon.HandleError(err) } if count == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } // RevokeSession revokes a given session. If the session does not exist or was not modified, // it effectively has been revoked already, and therefore that case does not return an error. func (p *Persister) RevokeSession(ctx context.Context, iID, sID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeSession") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "UPDATE %s SET active = false WHERE id = ? AND identity_id = ? AND nid = ?", session.Session{}.TableName(), ), sID, iID, p.NetworkID(ctx), ).Exec() if err != nil { return sqlcon.HandleError(err) } return nil } // RevokeSessionsIdentityExcept marks all except the given session of an identity inactive. func (p *Persister) RevokeSessionsIdentityExcept(ctx context.Context, iID, sID uuid.UUID) (res int, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeSessionsIdentityExcept") defer otelx.End(span, &err) //#nosec G201 -- TableName is static count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( "UPDATE %s SET active = false WHERE identity_id = ? AND id != ? AND nid = ?", session.Session{}.TableName(), ), iID, sID, p.NetworkID(ctx), ).ExecWithCount() if err != nil { return 0, sqlcon.HandleError(err) } return count, nil } func (p *Persister) DeleteExpiredSessions(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredSessions") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", session.Session{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_sessiontokenexchanger.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/kratos/selfservice/sessiontokenexchange" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/randx" "github.com/ory/x/sqlcon" ) var _ sessiontokenexchange.Persister = new(Persister) func updateLimitClause(conn *pop.Connection) string { // Not all databases support limiting in update clauses. switch conn.Dialect.Name() { case "sqlite3", "postgres": return "" default: return "LIMIT 1" } } func (p *Persister) CreateSessionTokenExchanger(ctx context.Context, flowID uuid.UUID) (e *sessiontokenexchange.Exchanger, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateSessionTokenExchanger") defer otelx.End(span, &err) e = &sessiontokenexchange.Exchanger{ NID: p.NetworkID(ctx), FlowID: flowID, InitCode: randx.MustString(64, randx.AlphaNum), ReturnToCode: randx.MustString(64, randx.AlphaNum), } return e, p.GetConnection(ctx).Create(e) } func (p *Persister) GetExchangerFromCode(ctx context.Context, initCode string, returnToCode string) (e *sessiontokenexchange.Exchanger, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetExchangerFromCode") defer otelx.End(span, &err) e = new(sessiontokenexchange.Exchanger) conn := p.GetConnection(ctx) if err = conn.Where(` nid = ? AND init_code = ? AND init_code <> '' AND return_to_code = ? AND return_to_code <> '' AND session_id IS NOT NULL`, p.NetworkID(ctx), initCode, returnToCode).First(e); err != nil { return nil, sqlcon.HandleError(err) } return e, nil } func (p *Persister) UpdateSessionOnExchanger(ctx context.Context, flowID uuid.UUID, sessionID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateSessionOnExchanger") defer otelx.End(span, &err) conn := p.GetConnection(ctx) query := fmt.Sprintf("UPDATE %s SET session_id = ? WHERE flow_id = ? AND nid = ? %s", conn.Dialect.Quote(new(sessiontokenexchange.Exchanger).TableName()), updateLimitClause(conn), ) return sqlcon.HandleError(conn.RawQuery(query, sessionID, flowID, p.NetworkID(ctx)).Exec()) } func (p *Persister) CodeForFlow(ctx context.Context, flowID uuid.UUID) (codes *sessiontokenexchange.Codes, found bool, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CodeForFlow") defer otelx.End(span, &err) var e sessiontokenexchange.Exchanger switch err = sqlcon.HandleError(p.GetConnection(ctx). Where("flow_id = ? AND nid = ? AND init_code <> '' and return_to_code <> ''", flowID, p.NetworkID(ctx)). First(&e)); { case err == nil: return &sessiontokenexchange.Codes{ InitCode: e.InitCode, ReturnToCode: e.ReturnToCode, }, true, nil case errors.Is(err, sqlcon.ErrNoRows): return nil, false, nil default: return nil, false, err } } func (p *Persister) MoveToNewFlow(ctx context.Context, oldFlow, newFlow uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.MoveToNewFlow") defer otelx.End(span, &err) conn := p.GetConnection(ctx) query := fmt.Sprintf("UPDATE %s SET flow_id = ? WHERE flow_id = ? AND nid = ? %s", conn.Dialect.Quote(new(sessiontokenexchange.Exchanger).TableName()), updateLimitClause(conn), ) return sqlcon.HandleError(conn.RawQuery(query, newFlow, oldFlow, p.NetworkID(ctx)).Exec()) } func (p *Persister) DeleteExpiredExchangers(ctx context.Context, at time.Time, limit int) error { expiredAfter := at.Add(1 * time.Hour) conn := p.GetConnection(ctx) //#nosec G201 -- TableName is static err := conn.RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE created_at <= ? and nid = ? ORDER BY created_at ASC LIMIT ?) AS s)", sessiontokenexchange.Exchanger{}.TableName(), ), expiredAfter, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_settings.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence/sql/update" "github.com/gofrs/uuid" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" "github.com/ory/kratos/selfservice/flow/settings" ) var _ settings.FlowPersister = new(Persister) func (p *Persister) CreateSettingsFlow(ctx context.Context, r *settings.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateSettingsFlow") defer otelx.End(span, &err) r.NID = p.NetworkID(ctx) r.EnsureInternalContext() return sqlcon.HandleError(p.GetConnection(ctx).Create(r)) } func (p *Persister) GetSettingsFlow(ctx context.Context, id uuid.UUID) (_ *settings.Flow, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetSettingsFlow") defer otelx.End(span, &err) var r settings.Flow err = p.GetConnection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&r) if err != nil { return nil, sqlcon.HandleError(err) } r.Identity, err = p.GetIdentity(ctx, r.IdentityID, identity.ExpandDefault) if err != nil { return nil, err } return &r, nil } func (p *Persister) UpdateSettingsFlow(ctx context.Context, r *settings.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateSettingsFlow") defer otelx.End(span, &err) r.EnsureInternalContext() cp := *r cp.NID = p.NetworkID(ctx) return update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), cp) } func (p *Persister) DeleteExpiredSettingsFlows(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredSettingsFlows") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", settings.Flow{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_test.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql_test import ( "context" "fmt" "os" "regexp" "strings" "sync" "testing" "time" "github.com/cockroachdb/cockroach-go/v2/testserver" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" continuity "github.com/ory/kratos/continuity/test" "github.com/ory/kratos/corpx" courier "github.com/ory/kratos/courier/test" "github.com/ory/kratos/driver/config" ri "github.com/ory/kratos/identity" identity "github.com/ory/kratos/identity/test" "github.com/ory/kratos/persistence/sql" "github.com/ory/kratos/persistence/sql/batch" sqltesthelpers "github.com/ory/kratos/persistence/sql/testhelpers" "github.com/ory/kratos/pkg" "github.com/ory/kratos/pkg/testhelpers" "github.com/ory/kratos/schema" errorx "github.com/ory/kratos/selfservice/errorx/test" lf "github.com/ory/kratos/selfservice/flow/login" login "github.com/ory/kratos/selfservice/flow/login/test" recovery "github.com/ory/kratos/selfservice/flow/recovery/test" registration "github.com/ory/kratos/selfservice/flow/registration/test" settings "github.com/ory/kratos/selfservice/flow/settings/test" verification "github.com/ory/kratos/selfservice/flow/verification/test" sessiontokenexchange "github.com/ory/kratos/selfservice/sessiontokenexchange/test" code "github.com/ory/kratos/selfservice/strategy/code/test" link "github.com/ory/kratos/selfservice/strategy/link/test" session "github.com/ory/kratos/session/test" "github.com/ory/kratos/x" "github.com/ory/pop/v6" "github.com/ory/pop/v6/logging" "github.com/ory/x/dbal" "github.com/ory/x/popx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlcon/dockertest" "github.com/ory/x/sqlxx" "github.com/ory/x/urlx" ) func init() { corpx.RegisterFakes() pop.SetNowFunc(func() time.Time { return time.Now().UTC().Round(time.Second) }) } func TestMain(m *testing.M) { os.Exit(m.Run()) } func pl(t testing.TB) func(lvl logging.Level, s string, args ...interface{}) { return func(lvl logging.Level, s string, args ...interface{}) { if pop.Debug == false { return } if lvl == logging.SQL { if len(args) > 0 { xargs := make([]string, len(args)) for i, a := range args { switch a.(type) { case string: xargs[i] = fmt.Sprintf("%q", a) default: xargs[i] = fmt.Sprintf("%v", a) } } s = fmt.Sprintf("%s - %s | %s", lvl, s, xargs) } else { s = fmt.Sprintf("%s - %s", lvl, s) } } else { s = fmt.Sprintf(s, args...) s = fmt.Sprintf("%s - %s", lvl, s) } t.Log(s) } } func createCleanDatabases(t testing.TB) map[string]string { conns := map[string]string{ "sqlite": dbal.NewSQLiteTestDatabase(t), } connsMtx := sync.Mutex{} if !testing.Short() { funcs := map[string]func(t testing.TB) string{ "postgres": func(t testing.TB) string { return dockertest.RunTestPostgreSQLWithVersion(t, "16") }, "mysql": func(t testing.TB) string { return dockertest.RunTestMySQLWithVersion(t, "8.4") }, "cockroach": newLocalTestCRDBServer, } var wg sync.WaitGroup wg.Add(len(funcs)) for k, f := range funcs { go func(s string, f func(t testing.TB) string) { defer wg.Done() db := f(t) connsMtx.Lock() conns[s] = db connsMtx.Unlock() }(k, f) } wg.Wait() } ps := make(map[string]string, len(conns)) psMtx := sync.Mutex{} var wg sync.WaitGroup wg.Add(len(conns)) for name, dsn := range conns { go func(name, dsn string) { defer wg.Done() if name != "sqlite" { require.EventuallyWithT(t, func(t *assert.CollectT) { c, err := pop.NewConnection(&pop.ConnectionDetails{URL: dsn}) require.NoError(t, err) require.NoError(t, c.Open()) dbName := "testdb" + strings.ReplaceAll(x.NewUUID().String(), "-", "") require.NoError(t, c.RawQuery("CREATE DATABASE "+dbName).Exec()) dsn = regexp.MustCompile(`/[a-z0-9]+\?`).ReplaceAllString(dsn, "/"+dbName+"?") }, 20*time.Second, 100*time.Millisecond) } t.Logf("Connecting to %s: %s", name, dsn) _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) p := reg.Persister().(*sql.Persister) t.Logf("Applying %s migrations", name) pop.SetLogger(pl(t)) require.NoError(t, p.MigrateUp(context.Background())) t.Logf("%s migrations applied", name) status, err := p.MigrationStatus(context.Background()) require.NoError(t, err) require.False(t, status.HasPending()) psMtx.Lock() ps[name] = dsn psMtx.Unlock() t.Logf("Database %s initialized successfully", name) }(name, dsn) } wg.Wait() return ps } func TestPersister(t *testing.T) { t.Parallel() conns := createCleanDatabases(t) ctx := testhelpers.WithDefaultIdentitySchema(context.Background(), "file://./stub/identity.schema.json") for name, dsn := range conns { t.Run(fmt.Sprintf("database=%s", name), func(t *testing.T) { t.Parallel() t.Logf("DSN: %s", dsn) t.Run("racy identity creation", func(t *testing.T) { t.Parallel() var wg sync.WaitGroup for i := range 10 { wg.Add(1) go func() { defer wg.Done() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, ps := testhelpers.NewNetwork(t, ctx, reg.Persister()) id := ri.NewIdentity("") id.SetCredentials(ri.CredentialsTypePassword, ri.Credentials{ Type: ri.CredentialsTypePassword, Identifiers: []string{fmt.Sprintf("racy identity %d", i)}, Config: sqlxx.JSONRawMessage(`{"foo":"bar"}`), }) id.Traits = ri.Traits("{}") require.NoError(t, ps.CreateIdentity(ctx, id)) }() } wg.Wait() }) t.Run("case=credential types exist", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) for _, ct := range []ri.CredentialsType{ri.CredentialsTypeOIDC, ri.CredentialsTypePassword} { require.NoError(t, p.(*sql.Persister).Connection(context.Background()).Where("name = ?", ct).First(&ri.CredentialsTypeTable{})) } }) t.Run("contract=identity.TestPool", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) identity.TestPool(ctx, p, reg.IdentityManager(), name)(t) }) t.Run("contract=registration.TestFlowPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) registration.TestFlowPersister(ctx, p)(t) }) t.Run("contract=errorx.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) errorx.TestPersister(ctx, p)(t) }) t.Run("contract=login.TestFlowPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) login.TestFlowPersister(ctx, p)(t) }) t.Run("contract=settings.TestFlowPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) settings.TestFlowPersister(ctx, p)(t) }) t.Run("contract=session.TestPersister", func(t *testing.T) { // Don't run this in parallel on SQLite as it causes table locks. if name != "sqlite" { t.Parallel() } _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) session.TestPersister(ctx, reg.Config(), p)(t) }) t.Run("contract=sessiontokenexchange.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) sessiontokenexchange.TestPersister(ctx, p)(t) }) t.Run("contract=courier.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) upsert, insert := sqltesthelpers.DefaultNetworkWrapper(p) courier.TestPersister(ctx, upsert, insert)(t) }) t.Run("contract=verification.TestFlowPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) verification.TestFlowPersister(ctx, p)(t) }) t.Run("contract=recovery.TestFlowPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) recovery.TestFlowPersister(ctx, p)(t) }) t.Run("contract=link.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) link.TestPersister(ctx, p)(t) }) t.Run("contract=code.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) code.TestPersister(ctx, p)(t) }) t.Run("contract=continuity.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) continuity.TestPersister(ctx, p)(t) }) t.Run("contract=batch.TestPersister", func(t *testing.T) { t.Parallel() _, reg := pkg.NewRegistryDefaultWithDSN(t, dsn) _, p := testhelpers.NewNetwork(t, ctx, reg.Persister()) batch.TestPersister(ctx, reg.Tracer(ctx), p)(t) }) }) } } func getErr(args ...interface{}) error { if len(args) == 0 { return nil } lastArg := args[len(args)-1] if e, ok := lastArg.(error); ok { return e } return nil } func TestPersister_Transaction(t *testing.T) { t.Parallel() _, reg := pkg.NewFastRegistryWithMocks(t) p := reg.Persister() t.Run("case=should not create identity because callback returned error", func(t *testing.T) { i := &ri.Identity{ ID: x.NewUUID(), State: ri.StateActive, Traits: ri.Traits(`{}`), } errMessage := "failing because why not" err := p.Transaction(context.Background(), func(_ context.Context, connection *pop.Connection) error { require.NoError(t, connection.Create(i)) return errors.New(errMessage) }) require.Error(t, err) assert.Contains(t, err.Error(), errMessage) _, err = p.GetIdentity(context.Background(), i.ID, ri.ExpandNothing) require.Error(t, err) assert.Equal(t, sqlcon.ErrNoRows.Error(), err.Error()) }) t.Run("case=functions should use the context connection", func(t *testing.T) { c := p.GetConnection(context.Background()) errMessage := "some stupid error you can't debug" lr := &lf.Flow{ ID: x.NewUUID(), } err := c.Transaction(func(tx *pop.Connection) error { ctx := popx.WithTransaction(context.Background(), tx) require.NoError(t, p.CreateLoginFlow(ctx, lr), "%+v", lr) require.NoError(t, getErr(p.GetLoginFlow(ctx, lr.ID)), "%+v", lr) return errors.New(errMessage) }) require.Error(t, err) assert.Contains(t, err.Error(), errMessage) _, err = p.GetLoginFlow(context.Background(), lr.ID) require.Error(t, err) assert.Equal(t, sqlcon.ErrNoRows.Error(), err.Error()) }) } func Benchmark_BatchCreateIdentities(b *testing.B) { conns := createCleanDatabases(b) ctx := context.Background() batchSizes := []int{1, 10, 100, 500, 800, 900, 1000, 2000, 3000} parallelRequests := []int{1, 4, 8, 16} for name, dsn := range conns { _, reg := pkg.NewRegistryDefaultWithDSN(b, dsn) b.Run(fmt.Sprintf("database=%s", name), func(b *testing.B) { conf := reg.Config() _, p := testhelpers.NewNetwork(b, ctx, reg.Persister()) multipleEmailsSchema := schema.Schema{ ID: "multiple_emails", URL: urlx.ParseOrPanic("file://./stub/handler/multiple_emails.schema.json"), RawURL: "file://./stub/identity-2.schema.json", } conf.MustSet(ctx, config.ViperKeyIdentitySchemas, []config.Schema{ { ID: multipleEmailsSchema.ID, URL: multipleEmailsSchema.RawURL, }, }) conf.MustSet(ctx, config.ViperKeyPublicBaseURL, "http://localhost/") run := 0 for _, batchSize := range batchSizes { b.Run(fmt.Sprintf("batch-size=%d", batchSize), func(b *testing.B) { for _, paralellism := range parallelRequests { b.Run(fmt.Sprintf("parallelism=%d", paralellism), func(b *testing.B) { start := time.Now() for i := 0; i < b.N; i++ { wg := new(errgroup.Group) for paralell := 0; paralell < paralellism; paralell++ { paralell := paralell wg.Go(func() error { identities := make([]*ri.Identity, batchSize) prefix := fmt.Sprintf("bench-insert-run-%d", run+paralell) for j := range identities { identities[j] = identity.NewTestIdentity(1, prefix, j) } return p.CreateIdentities(ctx, identities...) }) } assert.NoError(b, wg.Wait()) run += paralellism } end := time.Now() b.ReportMetric(float64(paralellism*batchSize*b.N), "identites_created") b.ReportMetric(float64(paralellism*batchSize*b.N)/end.Sub(start).Seconds(), "identities/s") }) } }) } }) } } func newLocalTestCRDBServer(t testing.TB) string { ts, err := testserver.NewTestServer(testserver.CustomVersionOpt("v25.4.1")) require.NoError(t, err) t.Cleanup(ts.Stop) require.NoError(t, ts.WaitForInit()) ts.PGURL().Scheme = "cockroach" return ts.PGURL().String() } ================================================ FILE: persistence/sql/persister_transaction_helpers.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "github.com/ory/x/popx" "github.com/ory/pop/v6" ) func (p *Persister) Transaction(ctx context.Context, callback func(ctx context.Context, connection *pop.Connection) error) error { return popx.Transaction(ctx, p.c.WithContext(ctx), callback) } func (p *Persister) GetConnection(ctx context.Context) *pop.Connection { return popx.GetConnection(ctx, p.c.WithContext(ctx)) } ================================================ FILE: persistence/sql/persister_verification.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "fmt" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/kratos/identity" "github.com/ory/kratos/persistence/sql/update" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/pop/v6" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) var _ verification.FlowPersister = new(Persister) func (p *Persister) CreateVerificationFlow(ctx context.Context, r *verification.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateVerificationFlow") defer otelx.End(span, &err) r.NID = p.NetworkID(ctx) // This should not create the request eagerly because otherwise we might accidentally create an address // that isn't supposed to be in the database. return p.GetConnection(ctx).Create(r) } func (p *Persister) GetVerificationFlow(ctx context.Context, id uuid.UUID) (_ *verification.Flow, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetVerificationFlow") defer otelx.End(span, &err) var r verification.Flow if err := p.GetConnection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).First(&r); err != nil { return nil, sqlcon.HandleError(err) } return &r, nil } func (p *Persister) UpdateVerificationFlow(ctx context.Context, r *verification.Flow) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpdateVerificationFlow") defer otelx.End(span, &err) cp := *r cp.NID = p.NetworkID(ctx) return update.Generic(ctx, p.GetConnection(ctx), p.r.Tracer(ctx).Tracer(), cp) } func (p *Persister) CreateVerificationToken(ctx context.Context, token *link.VerificationToken) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateVerificationToken") defer otelx.End(span, &err) t := token.Token token.Token = p.hmacValue(ctx, t) token.NID = p.NetworkID(ctx) // This should not create the request eagerly because otherwise we might accidentally create an address that isn't // supposed to be in the database. if err := p.GetConnection(ctx).Create(token); err != nil { return err } token.Token = t return nil } func (p *Persister) UseVerificationToken(ctx context.Context, fID uuid.UUID, token string) (_ *link.VerificationToken, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseVerificationToken") defer otelx.End(span, &err) var rt link.VerificationToken nid := p.NetworkID(ctx) if err := sqlcon.HandleError(p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) (err error) { for _, secret := range p.r.Config().SecretsSession(ctx) { if err = tx.Where("token = ? AND nid = ? AND NOT used AND selfservice_verification_flow_id = ?", hmacValueWithSecret(token, secret), nid, fID).First(&rt); err != nil { if !errors.Is(sqlcon.HandleError(err), sqlcon.ErrNoRows) { return err } } else { break } } if err != nil { return err } var va identity.VerifiableAddress if err := tx.Where("id = ? AND nid = ?", rt.VerifiableAddressID, nid).First(&va); err != nil { return sqlcon.HandleError(err) } rt.VerifiableAddress = &va //#nosec G201 -- TableName is static return tx.RawQuery(fmt.Sprintf("UPDATE %s SET used=true, used_at=? WHERE id=? AND nid = ?", rt.TableName(ctx)), time.Now().UTC(), rt.ID, nid).Exec() })); err != nil { return nil, err } return &rt, nil } func (p *Persister) DeleteVerificationToken(ctx context.Context, token string) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteVerificationToken") defer otelx.End(span, &err) nid := p.NetworkID(ctx) //#nosec G201 -- TableName is static return p.GetConnection(ctx).RawQuery(fmt.Sprintf("DELETE FROM %s WHERE token=? AND nid = ?", new(link.VerificationToken).TableName(ctx)), token, nid).Exec() } func (p *Persister) DeleteExpiredVerificationFlows(ctx context.Context, expiresAt time.Time, limit int) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteExpiredVerificationFlows") defer otelx.End(span, &err) //#nosec G201 -- TableName is static err = p.GetConnection(ctx).RawQuery(fmt.Sprintf( "DELETE FROM %[1]s WHERE id in (SELECT id FROM (SELECT id FROM %[1]s c WHERE expires_at <= ? and nid = ? ORDER BY expires_at ASC LIMIT ?) AS s)", verification.Flow{}.TableName(), ), expiresAt, p.NetworkID(ctx), limit, ).Exec() return sqlcon.HandleError(err) } ================================================ FILE: persistence/sql/persister_verification_code.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package sql import ( "context" "time" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow/verification" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) func (p *Persister) CreateVerificationCode(ctx context.Context, params *code.CreateVerificationCodeParams) (_ *code.VerificationCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateVerificationCode") defer otelx.End(span, &err) now := time.Now().UTC() verificationCode := &code.VerificationCode{ ID: uuid.Nil, CodeHMAC: p.hmacValue(ctx, params.RawCode), ExpiresAt: now.Add(params.ExpiresIn), IssuedAt: now, FlowID: params.FlowID, NID: p.NetworkID(ctx), } if params.VerifiableAddress == nil { return nil, errors.WithStack(herodot.ErrNotFound.WithReason("can't create a verification code without a verifiable address")) } verificationCode.VerifiableAddress = params.VerifiableAddress verificationCode.VerifiableAddressID = uuid.NullUUID{ UUID: params.VerifiableAddress.ID, Valid: true, } // This should not create the request eagerly because otherwise we might accidentally create an address that isn't // supposed to be in the database. if err := p.GetConnection(ctx).Create(verificationCode); err != nil { return nil, err } return verificationCode, nil } func (p *Persister) UseVerificationCode(ctx context.Context, flowID uuid.UUID, userProvidedCode string) (_ *code.VerificationCode, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseVerificationCode") defer otelx.End(span, &err) codeRow, err := useOneTimeCode[code.VerificationCode](ctx, p, flowID, userProvidedCode, verification.Flow{}.TableName(), "selfservice_verification_flow_id") if err != nil { return nil, err } var va identity.VerifiableAddress if err := p.Connection(ctx).Where("id = ? AND nid = ?", codeRow.VerifiableAddressID, p.NetworkID(ctx)).First(&va); err != nil { // This should fail on not found errors too, because the verifiable address must exist for the flow to work. return nil, sqlcon.HandleError(err) } codeRow.VerifiableAddress = &va return codeRow, nil } func (p *Persister) DeleteVerificationCodesOfFlow(ctx context.Context, fID uuid.UUID) (err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteVerificationCodesOfFlow") defer otelx.End(span, &err) return p.GetConnection(ctx).Where("selfservice_verification_flow_id = ? AND nid = ?", fID, p.NetworkID(ctx)).Delete(&code.VerificationCode{}) } ================================================ FILE: persistence/sql/stub/expand.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "traits": { "additionalProperties": false, "properties": { "email": { "format": "email", "ory.sh/kratos": { "credentials": { "password": { "identifier": true }, "webauthn": { "identifier": true }, "totp": { "account_name": true } }, "recovery": { "via": "email" }, "verification": { "via": "email" } }, "title": "Email address", "type": "string", "maxLength": 320 }, "name": { "minLength": 1, "title": "Name", "type": "string", "maxLength": 256 } }, "required": [ "email", "name" ], "type": "object" } }, "title": "Person", "type": "object" } ================================================ FILE: persistence/sql/stub/identity-2.schema.json ================================================ { "$id": "https://example.com/registration.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "bar": { "type": "string" } } } } } ================================================ FILE: persistence/sql/stub/identity.schema.json ================================================ { "$id": "https://example.com/registration.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "traits": { "type": "object", "properties": { "bar": { "type": "string" }, "email": { "type": "string", "ory.sh/kratos": { "credentials": { "password": { "identifier": true } } } } } } } } ================================================ FILE: persistence/sql/testhelpers/network.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package testhelpers import ( "context" "testing" db "github.com/gofrs/uuid" courier "github.com/ory/kratos/courier/test" "github.com/ory/kratos/persistence" "github.com/ory/kratos/pkg/testhelpers" ) func DefaultNetworkWrapper(p persistence.Persister) (courier.NetworkWrapper, courier.NetworkWrapper) { return func(t *testing.T, ctx context.Context) (db.UUID, courier.PersisterWrapper) { return testhelpers.NewNetworkUnlessExisting(t, ctx, p) }, func(t *testing.T, ctx context.Context) (db.UUID, courier.PersisterWrapper) { return testhelpers.NewNetwork(t, ctx, p) } } ================================================ FILE: persistence/sql/update/update.go ================================================ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package update import ( "context" "fmt" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" "github.com/ory/pop/v6" "github.com/ory/pop/v6/columns" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" ) func Generic(ctx context.Context, c *pop.Connection, tracer trace.Tracer, v any, columnNames ...string) (err error) { ctx, span := tracer.Start(ctx, "persistence.sql.update") defer otelx.End(span, &err) quoter, ok := c.Dialect.(interface{ Quote(key string) string }) if !ok { return errors.Errorf("store is not a quoter: %T", c.Store) } model := pop.NewModel(v, ctx) tn := model.TableName() cols := columns.Columns{} if len(columnNames) > 0 && tn == model.TableName() { cols = columns.NewColumnsWithAlias(tn, model.As, model.IDField()) cols.Add(columnNames...) } else { cols = columns.ForStructWithAlias(v, tn, model.As, model.IDField()) } //#nosec G201 -- TableName is static stmt := fmt.Sprintf("UPDATE %s AS %s SET %s WHERE (%s) AND %s.nid = :nid", quoter.Quote(model.TableName()), model.Alias(), cols.Writeable().QuotedUpdateString(quoter), model.WhereNamedID(), model.Alias(), ) if result, err := c.Store.NamedExecContext(ctx, stmt, v); err != nil { return sqlcon.HandleError(err) } else if affected, err := result.RowsAffected(); err != nil { return sqlcon.HandleError(err) } else if affected == 0 { return errors.WithStack(sqlcon.ErrNoRows) } return nil } ================================================ FILE: pkg/client-go/.gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof ================================================ FILE: pkg/client-go/.openapi-generator/FILES ================================================ .gitignore .openapi-generator-ignore .travis.yml README.md api/openapi.yaml api_courier.go api_frontend.go api_identity.go api_metadata.go client.go configuration.go docs/AuthenticatorAssuranceLevel.md docs/BatchPatchIdentitiesResponse.md docs/ConsistencyRequestParameters.md docs/ContinueWith.md docs/ContinueWithRecoveryUi.md docs/ContinueWithRecoveryUiFlow.md docs/ContinueWithRedirectBrowserTo.md docs/ContinueWithSetOrySessionToken.md docs/ContinueWithSettingsUi.md docs/ContinueWithSettingsUiFlow.md docs/ContinueWithVerificationUi.md docs/ContinueWithVerificationUiFlow.md docs/CourierAPI.md docs/CourierMessageStatus.md docs/CourierMessageType.md docs/CreateFedcmFlowResponse.md docs/CreateIdentityBody.md docs/CreateRecoveryCodeForIdentityBody.md docs/CreateRecoveryLinkForIdentityBody.md docs/DeleteMySessionsCount.md docs/ErrorAuthenticatorAssuranceLevelNotSatisfied.md docs/ErrorBrowserLocationChangeRequired.md docs/ErrorFlowReplaced.md docs/ErrorGeneric.md docs/FlowError.md docs/FrontendAPI.md docs/GenericError.md docs/GetVersion200Response.md docs/HealthNotReadyStatus.md docs/HealthStatus.md docs/Identity.md docs/IdentityAPI.md docs/IdentityCredentials.md docs/IdentityCredentialsCode.md docs/IdentityCredentialsCodeAddress.md docs/IdentityCredentialsOidc.md docs/IdentityCredentialsOidcProvider.md docs/IdentityCredentialsPassword.md docs/IdentityPatch.md docs/IdentityPatchResponse.md docs/IdentitySchemaContainer.md docs/IdentityWithCredentials.md docs/IdentityWithCredentialsOidc.md docs/IdentityWithCredentialsOidcConfig.md docs/IdentityWithCredentialsOidcConfigProvider.md docs/IdentityWithCredentialsPassword.md docs/IdentityWithCredentialsPasswordConfig.md docs/IdentityWithCredentialsSaml.md docs/IdentityWithCredentialsSamlConfig.md docs/IdentityWithCredentialsSamlConfigProvider.md docs/IsAlive200Response.md docs/IsReady503Response.md docs/JsonPatch.md docs/LoginFlow.md docs/LoginFlowState.md docs/LogoutFlow.md docs/Message.md docs/MessageDispatch.md docs/MetadataAPI.md docs/NeedsPrivilegedSessionError.md docs/OAuth2Client.md docs/OAuth2ConsentRequestOpenIDConnectContext.md docs/OAuth2LoginRequest.md docs/PatchIdentitiesBody.md docs/PerformNativeLogoutBody.md docs/Provider.md docs/RecoveryCodeForIdentity.md docs/RecoveryFlow.md docs/RecoveryFlowState.md docs/RecoveryIdentityAddress.md docs/RecoveryLinkForIdentity.md docs/RegistrationFlow.md docs/RegistrationFlowState.md docs/SelfServiceFlowExpiredError.md docs/Session.md docs/SessionAuthenticationMethod.md docs/SessionDevice.md docs/SettingsFlow.md docs/SettingsFlowState.md docs/SuccessfulCodeExchangeResponse.md docs/SuccessfulNativeLogin.md docs/SuccessfulNativeRegistration.md docs/TokenPagination.md docs/TokenPaginationHeaders.md docs/UiContainer.md docs/UiNode.md docs/UiNodeAnchorAttributes.md docs/UiNodeAttributes.md docs/UiNodeDivisionAttributes.md docs/UiNodeImageAttributes.md docs/UiNodeInputAttributes.md docs/UiNodeMeta.md docs/UiNodeScriptAttributes.md docs/UiNodeTextAttributes.md docs/UiText.md docs/UpdateFedcmFlowBody.md docs/UpdateIdentityBody.md docs/UpdateLoginFlowBody.md docs/UpdateLoginFlowWithCodeMethod.md docs/UpdateLoginFlowWithIdentifierFirstMethod.md docs/UpdateLoginFlowWithLookupSecretMethod.md docs/UpdateLoginFlowWithOidcMethod.md docs/UpdateLoginFlowWithPasskeyMethod.md docs/UpdateLoginFlowWithPasswordMethod.md docs/UpdateLoginFlowWithSamlMethod.md docs/UpdateLoginFlowWithTotpMethod.md docs/UpdateLoginFlowWithWebAuthnMethod.md docs/UpdateRecoveryFlowBody.md docs/UpdateRecoveryFlowWithCodeMethod.md docs/UpdateRecoveryFlowWithLinkMethod.md docs/UpdateRegistrationFlowBody.md docs/UpdateRegistrationFlowWithCodeMethod.md docs/UpdateRegistrationFlowWithOidcMethod.md docs/UpdateRegistrationFlowWithPasskeyMethod.md docs/UpdateRegistrationFlowWithPasswordMethod.md docs/UpdateRegistrationFlowWithProfileMethod.md docs/UpdateRegistrationFlowWithSamlMethod.md docs/UpdateRegistrationFlowWithWebAuthnMethod.md docs/UpdateSettingsFlowBody.md docs/UpdateSettingsFlowWithLookupMethod.md docs/UpdateSettingsFlowWithOidcMethod.md docs/UpdateSettingsFlowWithPasskeyMethod.md docs/UpdateSettingsFlowWithPasswordMethod.md docs/UpdateSettingsFlowWithProfileMethod.md docs/UpdateSettingsFlowWithSamlMethod.md docs/UpdateSettingsFlowWithTotpMethod.md docs/UpdateSettingsFlowWithWebAuthnMethod.md docs/UpdateVerificationFlowBody.md docs/UpdateVerificationFlowWithCodeMethod.md docs/UpdateVerificationFlowWithLinkMethod.md docs/VerifiableIdentityAddress.md docs/VerificationFlow.md docs/VerificationFlowState.md docs/Version.md git_push.sh go.mod go.sum model_authenticator_assurance_level.go model_batch_patch_identities_response.go model_consistency_request_parameters.go model_continue_with.go model_continue_with_recovery_ui.go model_continue_with_recovery_ui_flow.go model_continue_with_redirect_browser_to.go model_continue_with_set_ory_session_token.go model_continue_with_settings_ui.go model_continue_with_settings_ui_flow.go model_continue_with_verification_ui.go model_continue_with_verification_ui_flow.go model_courier_message_status.go model_courier_message_type.go model_create_fedcm_flow_response.go model_create_identity_body.go model_create_recovery_code_for_identity_body.go model_create_recovery_link_for_identity_body.go model_delete_my_sessions_count.go model_error_authenticator_assurance_level_not_satisfied.go model_error_browser_location_change_required.go model_error_flow_replaced.go model_error_generic.go model_flow_error.go model_generic_error.go model_get_version_200_response.go model_health_not_ready_status.go model_health_status.go model_identity.go model_identity_credentials.go model_identity_credentials_code.go model_identity_credentials_code_address.go model_identity_credentials_oidc.go model_identity_credentials_oidc_provider.go model_identity_credentials_password.go model_identity_patch.go model_identity_patch_response.go model_identity_schema_container.go model_identity_with_credentials.go model_identity_with_credentials_oidc.go model_identity_with_credentials_oidc_config.go model_identity_with_credentials_oidc_config_provider.go model_identity_with_credentials_password.go model_identity_with_credentials_password_config.go model_identity_with_credentials_saml.go model_identity_with_credentials_saml_config.go model_identity_with_credentials_saml_config_provider.go model_is_alive_200_response.go model_is_ready_503_response.go model_json_patch.go model_login_flow.go model_login_flow_state.go model_logout_flow.go model_message.go model_message_dispatch.go model_needs_privileged_session_error.go model_o_auth2_client.go model_o_auth2_consent_request_open_id_connect_context.go model_o_auth2_login_request.go model_patch_identities_body.go model_perform_native_logout_body.go model_provider.go model_recovery_code_for_identity.go model_recovery_flow.go model_recovery_flow_state.go model_recovery_identity_address.go model_recovery_link_for_identity.go model_registration_flow.go model_registration_flow_state.go model_self_service_flow_expired_error.go model_session.go model_session_authentication_method.go model_session_device.go model_settings_flow.go model_settings_flow_state.go model_successful_code_exchange_response.go model_successful_native_login.go model_successful_native_registration.go model_token_pagination.go model_token_pagination_headers.go model_ui_container.go model_ui_node.go model_ui_node_anchor_attributes.go model_ui_node_attributes.go model_ui_node_division_attributes.go model_ui_node_image_attributes.go model_ui_node_input_attributes.go model_ui_node_meta.go model_ui_node_script_attributes.go model_ui_node_text_attributes.go model_ui_text.go model_update_fedcm_flow_body.go model_update_identity_body.go model_update_login_flow_body.go model_update_login_flow_with_code_method.go model_update_login_flow_with_identifier_first_method.go model_update_login_flow_with_lookup_secret_method.go model_update_login_flow_with_oidc_method.go model_update_login_flow_with_passkey_method.go model_update_login_flow_with_password_method.go model_update_login_flow_with_saml_method.go model_update_login_flow_with_totp_method.go model_update_login_flow_with_web_authn_method.go model_update_recovery_flow_body.go model_update_recovery_flow_with_code_method.go model_update_recovery_flow_with_link_method.go model_update_registration_flow_body.go model_update_registration_flow_with_code_method.go model_update_registration_flow_with_oidc_method.go model_update_registration_flow_with_passkey_method.go model_update_registration_flow_with_password_method.go model_update_registration_flow_with_profile_method.go model_update_registration_flow_with_saml_method.go model_update_registration_flow_with_web_authn_method.go model_update_settings_flow_body.go model_update_settings_flow_with_lookup_method.go model_update_settings_flow_with_oidc_method.go model_update_settings_flow_with_passkey_method.go model_update_settings_flow_with_password_method.go model_update_settings_flow_with_profile_method.go model_update_settings_flow_with_saml_method.go model_update_settings_flow_with_totp_method.go model_update_settings_flow_with_web_authn_method.go model_update_verification_flow_body.go model_update_verification_flow_with_code_method.go model_update_verification_flow_with_link_method.go model_verifiable_identity_address.go model_verification_flow.go model_verification_flow_state.go model_version.go response.go test/api_courier_test.go test/api_frontend_test.go test/api_identity_test.go test/api_metadata_test.go utils.go ================================================ FILE: pkg/client-go/.openapi-generator/VERSION ================================================ 7.12.0 ================================================ FILE: pkg/client-go/.openapi-generator-ignore ================================================ # OpenAPI Generator Ignore # Generated by openapi-generator https://github.com/openapitools/openapi-generator # Use this file to prevent files from being overwritten by the generator. # The patterns follow closely to .gitignore or .dockerignore. # As an example, the C# client generator defines ApiClient.cs. # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: #ApiClient.cs # You can match any string of characters against a directory, file or extension with a single asterisk (*): #foo/*/qux # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux # You can recursively match patterns against a directory, file or extension with a double asterisk (**): #foo/**/qux # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux # You can also negate patterns with an exclamation (!). # For example, you can ignore all files in a docs folder with the file extension .md: #docs/*.md # Then explicitly reverse the ignore rule for a single file: #!docs/README.md ================================================ FILE: pkg/client-go/.travis.yml ================================================ language: go install: - go get -d -v . script: - go build -v ./ ================================================ FILE: pkg/client-go/README.md ================================================ # Go API client for client This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. ## Overview This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client. - API version: - Package version: 1.0.0 - Generator version: 7.12.0 - Build package: org.openapitools.codegen.languages.GoClientCodegen ## Installation Install the following dependencies: ```sh go get github.com/stretchr/testify/assert go get golang.org/x/net/context ``` Put the package under your project folder and add the following in import: ```go import client "github.com/ory/client-go" ``` To use a proxy, set the environment variable `HTTP_PROXY`: ```go os.Setenv("HTTP_PROXY", "http://proxy_name:proxy_port") ``` ## Configuration of Server URL Default configuration comes with `Servers` field that contains server objects as defined in the OpenAPI specification. ### Select Server Configuration For using other server than the one defined on index 0 set context value `client.ContextServerIndex` of type `int`. ```go ctx := context.WithValue(context.Background(), client.ContextServerIndex, 1) ``` ### Templated Server URL Templated server URL is formatted using default variables from configuration or from context value `client.ContextServerVariables` of type `map[string]string`. ```go ctx := context.WithValue(context.Background(), client.ContextServerVariables, map[string]string{ "basePath": "v2", }) ``` Note, enum values are always validated and all unused variables are silently ignored. ### URLs Configuration per Operation Each operation can use different server URL defined using `OperationServers` map in the `Configuration`. An operation is uniquely identified by `"{classname}Service.{nickname}"` string. Similar rules for overriding default operation server index and variables applies by using `client.ContextOperationServerIndices` and `client.ContextOperationServerVariables` context maps. ```go ctx := context.WithValue(context.Background(), client.ContextOperationServerIndices, map[string]int{ "{classname}Service.{nickname}": 2, }) ctx = context.WithValue(context.Background(), client.ContextOperationServerVariables, map[string]map[string]string{ "{classname}Service.{nickname}": { "port": "8443", }, }) ``` ## Documentation for API Endpoints All URIs are relative to *http://localhost* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- *CourierAPI* | [**GetCourierMessage**](docs/CourierAPI.md#getcouriermessage) | **Get** /admin/courier/messages/{id} | Get a Message *CourierAPI* | [**ListCourierMessages**](docs/CourierAPI.md#listcouriermessages) | **Get** /admin/courier/messages | List Messages *FrontendAPI* | [**CreateBrowserLoginFlow**](docs/FrontendAPI.md#createbrowserloginflow) | **Get** /self-service/login/browser | Create Login Flow for Browsers *FrontendAPI* | [**CreateBrowserLogoutFlow**](docs/FrontendAPI.md#createbrowserlogoutflow) | **Get** /self-service/logout/browser | Create a Logout URL for Browsers *FrontendAPI* | [**CreateBrowserRecoveryFlow**](docs/FrontendAPI.md#createbrowserrecoveryflow) | **Get** /self-service/recovery/browser | Create Recovery Flow for Browsers *FrontendAPI* | [**CreateBrowserRegistrationFlow**](docs/FrontendAPI.md#createbrowserregistrationflow) | **Get** /self-service/registration/browser | Create Registration Flow for Browsers *FrontendAPI* | [**CreateBrowserSettingsFlow**](docs/FrontendAPI.md#createbrowsersettingsflow) | **Get** /self-service/settings/browser | Create Settings Flow for Browsers *FrontendAPI* | [**CreateBrowserVerificationFlow**](docs/FrontendAPI.md#createbrowserverificationflow) | **Get** /self-service/verification/browser | Create Verification Flow for Browser Clients *FrontendAPI* | [**CreateFedcmFlow**](docs/FrontendAPI.md#createfedcmflow) | **Get** /self-service/fed-cm/parameters | Get FedCM Parameters *FrontendAPI* | [**CreateNativeLoginFlow**](docs/FrontendAPI.md#createnativeloginflow) | **Get** /self-service/login/api | Create Login Flow for Native Apps *FrontendAPI* | [**CreateNativeRecoveryFlow**](docs/FrontendAPI.md#createnativerecoveryflow) | **Get** /self-service/recovery/api | Create Recovery Flow for Native Apps *FrontendAPI* | [**CreateNativeRegistrationFlow**](docs/FrontendAPI.md#createnativeregistrationflow) | **Get** /self-service/registration/api | Create Registration Flow for Native Apps *FrontendAPI* | [**CreateNativeSettingsFlow**](docs/FrontendAPI.md#createnativesettingsflow) | **Get** /self-service/settings/api | Create Settings Flow for Native Apps *FrontendAPI* | [**CreateNativeVerificationFlow**](docs/FrontendAPI.md#createnativeverificationflow) | **Get** /self-service/verification/api | Create Verification Flow for Native Apps *FrontendAPI* | [**DisableMyOtherSessions**](docs/FrontendAPI.md#disablemyothersessions) | **Delete** /sessions | Disable my other sessions *FrontendAPI* | [**DisableMySession**](docs/FrontendAPI.md#disablemysession) | **Delete** /sessions/{id} | Disable one of my sessions *FrontendAPI* | [**ExchangeSessionToken**](docs/FrontendAPI.md#exchangesessiontoken) | **Get** /sessions/token-exchange | Exchange Session Token *FrontendAPI* | [**GetFlowError**](docs/FrontendAPI.md#getflowerror) | **Get** /self-service/errors | Get User-Flow Errors *FrontendAPI* | [**GetLoginFlow**](docs/FrontendAPI.md#getloginflow) | **Get** /self-service/login/flows | Get Login Flow *FrontendAPI* | [**GetRecoveryFlow**](docs/FrontendAPI.md#getrecoveryflow) | **Get** /self-service/recovery/flows | Get Recovery Flow *FrontendAPI* | [**GetRegistrationFlow**](docs/FrontendAPI.md#getregistrationflow) | **Get** /self-service/registration/flows | Get Registration Flow *FrontendAPI* | [**GetSettingsFlow**](docs/FrontendAPI.md#getsettingsflow) | **Get** /self-service/settings/flows | Get Settings Flow *FrontendAPI* | [**GetVerificationFlow**](docs/FrontendAPI.md#getverificationflow) | **Get** /self-service/verification/flows | Get Verification Flow *FrontendAPI* | [**GetWebAuthnJavaScript**](docs/FrontendAPI.md#getwebauthnjavascript) | **Get** /.well-known/ory/webauthn.js | Get WebAuthn JavaScript *FrontendAPI* | [**ListMySessions**](docs/FrontendAPI.md#listmysessions) | **Get** /sessions | Get My Active Sessions *FrontendAPI* | [**PerformNativeLogout**](docs/FrontendAPI.md#performnativelogout) | **Delete** /self-service/logout/api | Perform Logout for Native Apps *FrontendAPI* | [**ToSession**](docs/FrontendAPI.md#tosession) | **Get** /sessions/whoami | Check Who the Current HTTP Session Belongs To *FrontendAPI* | [**UpdateFedcmFlow**](docs/FrontendAPI.md#updatefedcmflow) | **Post** /self-service/fed-cm/token | Submit a FedCM token *FrontendAPI* | [**UpdateLoginFlow**](docs/FrontendAPI.md#updateloginflow) | **Post** /self-service/login | Submit a Login Flow *FrontendAPI* | [**UpdateLogoutFlow**](docs/FrontendAPI.md#updatelogoutflow) | **Get** /self-service/logout | Update Logout Flow *FrontendAPI* | [**UpdateRecoveryFlow**](docs/FrontendAPI.md#updaterecoveryflow) | **Post** /self-service/recovery | Update Recovery Flow *FrontendAPI* | [**UpdateRegistrationFlow**](docs/FrontendAPI.md#updateregistrationflow) | **Post** /self-service/registration | Update Registration Flow *FrontendAPI* | [**UpdateSettingsFlow**](docs/FrontendAPI.md#updatesettingsflow) | **Post** /self-service/settings | Complete Settings Flow *FrontendAPI* | [**UpdateVerificationFlow**](docs/FrontendAPI.md#updateverificationflow) | **Post** /self-service/verification | Complete Verification Flow *IdentityAPI* | [**BatchPatchIdentities**](docs/IdentityAPI.md#batchpatchidentities) | **Patch** /admin/identities | Create multiple identities *IdentityAPI* | [**CreateIdentity**](docs/IdentityAPI.md#createidentity) | **Post** /admin/identities | Create an Identity *IdentityAPI* | [**CreateRecoveryCodeForIdentity**](docs/IdentityAPI.md#createrecoverycodeforidentity) | **Post** /admin/recovery/code | Create a Recovery Code *IdentityAPI* | [**CreateRecoveryLinkForIdentity**](docs/IdentityAPI.md#createrecoverylinkforidentity) | **Post** /admin/recovery/link | Create a Recovery Link *IdentityAPI* | [**DeleteIdentity**](docs/IdentityAPI.md#deleteidentity) | **Delete** /admin/identities/{id} | Delete an Identity *IdentityAPI* | [**DeleteIdentityCredentials**](docs/IdentityAPI.md#deleteidentitycredentials) | **Delete** /admin/identities/{id}/credentials/{type} | Delete a credential for a specific identity *IdentityAPI* | [**DeleteIdentitySessions**](docs/IdentityAPI.md#deleteidentitysessions) | **Delete** /admin/identities/{id}/sessions | Delete & Invalidate an Identity's Sessions *IdentityAPI* | [**DisableSession**](docs/IdentityAPI.md#disablesession) | **Delete** /admin/sessions/{id} | Deactivate a Session *IdentityAPI* | [**ExtendSession**](docs/IdentityAPI.md#extendsession) | **Patch** /admin/sessions/{id}/extend | Extend a Session *IdentityAPI* | [**GetIdentity**](docs/IdentityAPI.md#getidentity) | **Get** /admin/identities/{id} | Get an Identity *IdentityAPI* | [**GetIdentityByExternalID**](docs/IdentityAPI.md#getidentitybyexternalid) | **Get** /admin/identities/by/external/{externalID} | Get an Identity by its External ID *IdentityAPI* | [**GetIdentitySchema**](docs/IdentityAPI.md#getidentityschema) | **Get** /schemas/{id} | Get Identity JSON Schema *IdentityAPI* | [**GetSession**](docs/IdentityAPI.md#getsession) | **Get** /admin/sessions/{id} | Get Session *IdentityAPI* | [**ListIdentities**](docs/IdentityAPI.md#listidentities) | **Get** /admin/identities | List Identities *IdentityAPI* | [**ListIdentitySchemas**](docs/IdentityAPI.md#listidentityschemas) | **Get** /schemas | Get all Identity Schemas *IdentityAPI* | [**ListIdentitySessions**](docs/IdentityAPI.md#listidentitysessions) | **Get** /admin/identities/{id}/sessions | List an Identity's Sessions *IdentityAPI* | [**ListSessions**](docs/IdentityAPI.md#listsessions) | **Get** /admin/sessions | List All Sessions *IdentityAPI* | [**PatchIdentity**](docs/IdentityAPI.md#patchidentity) | **Patch** /admin/identities/{id} | Patch an Identity *IdentityAPI* | [**UpdateIdentity**](docs/IdentityAPI.md#updateidentity) | **Put** /admin/identities/{id} | Update an Identity *MetadataAPI* | [**GetVersion**](docs/MetadataAPI.md#getversion) | **Get** /version | Return Running Software Version. *MetadataAPI* | [**IsAlive**](docs/MetadataAPI.md#isalive) | **Get** /health/alive | Check HTTP Server Status *MetadataAPI* | [**IsReady**](docs/MetadataAPI.md#isready) | **Get** /health/ready | Check HTTP Server and Database Status ## Documentation For Models - [AuthenticatorAssuranceLevel](docs/AuthenticatorAssuranceLevel.md) - [BatchPatchIdentitiesResponse](docs/BatchPatchIdentitiesResponse.md) - [ConsistencyRequestParameters](docs/ConsistencyRequestParameters.md) - [ContinueWith](docs/ContinueWith.md) - [ContinueWithRecoveryUi](docs/ContinueWithRecoveryUi.md) - [ContinueWithRecoveryUiFlow](docs/ContinueWithRecoveryUiFlow.md) - [ContinueWithRedirectBrowserTo](docs/ContinueWithRedirectBrowserTo.md) - [ContinueWithSetOrySessionToken](docs/ContinueWithSetOrySessionToken.md) - [ContinueWithSettingsUi](docs/ContinueWithSettingsUi.md) - [ContinueWithSettingsUiFlow](docs/ContinueWithSettingsUiFlow.md) - [ContinueWithVerificationUi](docs/ContinueWithVerificationUi.md) - [ContinueWithVerificationUiFlow](docs/ContinueWithVerificationUiFlow.md) - [CourierMessageStatus](docs/CourierMessageStatus.md) - [CourierMessageType](docs/CourierMessageType.md) - [CreateFedcmFlowResponse](docs/CreateFedcmFlowResponse.md) - [CreateIdentityBody](docs/CreateIdentityBody.md) - [CreateRecoveryCodeForIdentityBody](docs/CreateRecoveryCodeForIdentityBody.md) - [CreateRecoveryLinkForIdentityBody](docs/CreateRecoveryLinkForIdentityBody.md) - [DeleteMySessionsCount](docs/DeleteMySessionsCount.md) - [ErrorAuthenticatorAssuranceLevelNotSatisfied](docs/ErrorAuthenticatorAssuranceLevelNotSatisfied.md) - [ErrorBrowserLocationChangeRequired](docs/ErrorBrowserLocationChangeRequired.md) - [ErrorFlowReplaced](docs/ErrorFlowReplaced.md) - [ErrorGeneric](docs/ErrorGeneric.md) - [FlowError](docs/FlowError.md) - [GenericError](docs/GenericError.md) - [GetVersion200Response](docs/GetVersion200Response.md) - [HealthNotReadyStatus](docs/HealthNotReadyStatus.md) - [HealthStatus](docs/HealthStatus.md) - [Identity](docs/Identity.md) - [IdentityCredentials](docs/IdentityCredentials.md) - [IdentityCredentialsCode](docs/IdentityCredentialsCode.md) - [IdentityCredentialsCodeAddress](docs/IdentityCredentialsCodeAddress.md) - [IdentityCredentialsOidc](docs/IdentityCredentialsOidc.md) - [IdentityCredentialsOidcProvider](docs/IdentityCredentialsOidcProvider.md) - [IdentityCredentialsPassword](docs/IdentityCredentialsPassword.md) - [IdentityPatch](docs/IdentityPatch.md) - [IdentityPatchResponse](docs/IdentityPatchResponse.md) - [IdentitySchemaContainer](docs/IdentitySchemaContainer.md) - [IdentityWithCredentials](docs/IdentityWithCredentials.md) - [IdentityWithCredentialsOidc](docs/IdentityWithCredentialsOidc.md) - [IdentityWithCredentialsOidcConfig](docs/IdentityWithCredentialsOidcConfig.md) - [IdentityWithCredentialsOidcConfigProvider](docs/IdentityWithCredentialsOidcConfigProvider.md) - [IdentityWithCredentialsPassword](docs/IdentityWithCredentialsPassword.md) - [IdentityWithCredentialsPasswordConfig](docs/IdentityWithCredentialsPasswordConfig.md) - [IdentityWithCredentialsSaml](docs/IdentityWithCredentialsSaml.md) - [IdentityWithCredentialsSamlConfig](docs/IdentityWithCredentialsSamlConfig.md) - [IdentityWithCredentialsSamlConfigProvider](docs/IdentityWithCredentialsSamlConfigProvider.md) - [IsAlive200Response](docs/IsAlive200Response.md) - [IsReady503Response](docs/IsReady503Response.md) - [JsonPatch](docs/JsonPatch.md) - [LoginFlow](docs/LoginFlow.md) - [LoginFlowState](docs/LoginFlowState.md) - [LogoutFlow](docs/LogoutFlow.md) - [Message](docs/Message.md) - [MessageDispatch](docs/MessageDispatch.md) - [NeedsPrivilegedSessionError](docs/NeedsPrivilegedSessionError.md) - [OAuth2Client](docs/OAuth2Client.md) - [OAuth2ConsentRequestOpenIDConnectContext](docs/OAuth2ConsentRequestOpenIDConnectContext.md) - [OAuth2LoginRequest](docs/OAuth2LoginRequest.md) - [PatchIdentitiesBody](docs/PatchIdentitiesBody.md) - [PerformNativeLogoutBody](docs/PerformNativeLogoutBody.md) - [Provider](docs/Provider.md) - [RecoveryCodeForIdentity](docs/RecoveryCodeForIdentity.md) - [RecoveryFlow](docs/RecoveryFlow.md) - [RecoveryFlowState](docs/RecoveryFlowState.md) - [RecoveryIdentityAddress](docs/RecoveryIdentityAddress.md) - [RecoveryLinkForIdentity](docs/RecoveryLinkForIdentity.md) - [RegistrationFlow](docs/RegistrationFlow.md) - [RegistrationFlowState](docs/RegistrationFlowState.md) - [SelfServiceFlowExpiredError](docs/SelfServiceFlowExpiredError.md) - [Session](docs/Session.md) - [SessionAuthenticationMethod](docs/SessionAuthenticationMethod.md) - [SessionDevice](docs/SessionDevice.md) - [SettingsFlow](docs/SettingsFlow.md) - [SettingsFlowState](docs/SettingsFlowState.md) - [SuccessfulCodeExchangeResponse](docs/SuccessfulCodeExchangeResponse.md) - [SuccessfulNativeLogin](docs/SuccessfulNativeLogin.md) - [SuccessfulNativeRegistration](docs/SuccessfulNativeRegistration.md) - [TokenPagination](docs/TokenPagination.md) - [TokenPaginationHeaders](docs/TokenPaginationHeaders.md) - [UiContainer](docs/UiContainer.md) - [UiNode](docs/UiNode.md) - [UiNodeAnchorAttributes](docs/UiNodeAnchorAttributes.md) - [UiNodeAttributes](docs/UiNodeAttributes.md) - [UiNodeDivisionAttributes](docs/UiNodeDivisionAttributes.md) - [UiNodeImageAttributes](docs/UiNodeImageAttributes.md) - [UiNodeInputAttributes](docs/UiNodeInputAttributes.md) - [UiNodeMeta](docs/UiNodeMeta.md) - [UiNodeScriptAttributes](docs/UiNodeScriptAttributes.md) - [UiNodeTextAttributes](docs/UiNodeTextAttributes.md) - [UiText](docs/UiText.md) - [UpdateFedcmFlowBody](docs/UpdateFedcmFlowBody.md) - [UpdateIdentityBody](docs/UpdateIdentityBody.md) - [UpdateLoginFlowBody](docs/UpdateLoginFlowBody.md) - [UpdateLoginFlowWithCodeMethod](docs/UpdateLoginFlowWithCodeMethod.md) - [UpdateLoginFlowWithIdentifierFirstMethod](docs/UpdateLoginFlowWithIdentifierFirstMethod.md) - [UpdateLoginFlowWithLookupSecretMethod](docs/UpdateLoginFlowWithLookupSecretMethod.md) - [UpdateLoginFlowWithOidcMethod](docs/UpdateLoginFlowWithOidcMethod.md) - [UpdateLoginFlowWithPasskeyMethod](docs/UpdateLoginFlowWithPasskeyMethod.md) - [UpdateLoginFlowWithPasswordMethod](docs/UpdateLoginFlowWithPasswordMethod.md) - [UpdateLoginFlowWithSamlMethod](docs/UpdateLoginFlowWithSamlMethod.md) - [UpdateLoginFlowWithTotpMethod](docs/UpdateLoginFlowWithTotpMethod.md) - [UpdateLoginFlowWithWebAuthnMethod](docs/UpdateLoginFlowWithWebAuthnMethod.md) - [UpdateRecoveryFlowBody](docs/UpdateRecoveryFlowBody.md) - [UpdateRecoveryFlowWithCodeMethod](docs/UpdateRecoveryFlowWithCodeMethod.md) - [UpdateRecoveryFlowWithLinkMethod](docs/UpdateRecoveryFlowWithLinkMethod.md) - [UpdateRegistrationFlowBody](docs/UpdateRegistrationFlowBody.md) - [UpdateRegistrationFlowWithCodeMethod](docs/UpdateRegistrationFlowWithCodeMethod.md) - [UpdateRegistrationFlowWithOidcMethod](docs/UpdateRegistrationFlowWithOidcMethod.md) - [UpdateRegistrationFlowWithPasskeyMethod](docs/UpdateRegistrationFlowWithPasskeyMethod.md) - [UpdateRegistrationFlowWithPasswordMethod](docs/UpdateRegistrationFlowWithPasswordMethod.md) - [UpdateRegistrationFlowWithProfileMethod](docs/UpdateRegistrationFlowWithProfileMethod.md) - [UpdateRegistrationFlowWithSamlMethod](docs/UpdateRegistrationFlowWithSamlMethod.md) - [UpdateRegistrationFlowWithWebAuthnMethod](docs/UpdateRegistrationFlowWithWebAuthnMethod.md) - [UpdateSettingsFlowBody](docs/UpdateSettingsFlowBody.md) - [UpdateSettingsFlowWithLookupMethod](docs/UpdateSettingsFlowWithLookupMethod.md) - [UpdateSettingsFlowWithOidcMethod](docs/UpdateSettingsFlowWithOidcMethod.md) - [UpdateSettingsFlowWithPasskeyMethod](docs/UpdateSettingsFlowWithPasskeyMethod.md) - [UpdateSettingsFlowWithPasswordMethod](docs/UpdateSettingsFlowWithPasswordMethod.md) - [UpdateSettingsFlowWithProfileMethod](docs/UpdateSettingsFlowWithProfileMethod.md) - [UpdateSettingsFlowWithSamlMethod](docs/UpdateSettingsFlowWithSamlMethod.md) - [UpdateSettingsFlowWithTotpMethod](docs/UpdateSettingsFlowWithTotpMethod.md) - [UpdateSettingsFlowWithWebAuthnMethod](docs/UpdateSettingsFlowWithWebAuthnMethod.md) - [UpdateVerificationFlowBody](docs/UpdateVerificationFlowBody.md) - [UpdateVerificationFlowWithCodeMethod](docs/UpdateVerificationFlowWithCodeMethod.md) - [UpdateVerificationFlowWithLinkMethod](docs/UpdateVerificationFlowWithLinkMethod.md) - [VerifiableIdentityAddress](docs/VerifiableIdentityAddress.md) - [VerificationFlow](docs/VerificationFlow.md) - [VerificationFlowState](docs/VerificationFlowState.md) - [Version](docs/Version.md) ## Documentation For Authorization Authentication schemes defined for the API: ### oryAccessToken - **Type**: API key - **API key parameter name**: Authorization - **Location**: HTTP header Note, each API key must be added to a map of `map[string]APIKey` where the key is: oryAccessToken and passed in as the auth context for each request. Example ```go auth := context.WithValue( context.Background(), client.ContextAPIKeys, map[string]client.APIKey{ "oryAccessToken": {Key: "API_KEY_STRING"}, }, ) r, err := client.Service.Operation(auth, args) ``` ## Documentation for Utility Methods Due to the fact that model structure members are all pointers, this package contains a number of utility functions to easily obtain pointers to values of basic types. Each of these functions takes a value of the given basic type and returns a pointer to it: * `PtrBool` * `PtrInt` * `PtrInt32` * `PtrInt64` * `PtrFloat` * `PtrFloat32` * `PtrFloat64` * `PtrString` * `PtrTime` ## Author office@ory.sh ================================================ FILE: pkg/client-go/api_courier.go ================================================ /* Ory Identities API This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. API version: Contact: office@ory.sh */ // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. package client import ( "bytes" "context" "io" "net/http" "net/url" "strings" ) type CourierAPI interface { /* GetCourierMessage Get a Message Gets a specific messages by the given ID. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @param id MessageID is the ID of the message. @return CourierAPIGetCourierMessageRequest */ GetCourierMessage(ctx context.Context, id string) CourierAPIGetCourierMessageRequest // GetCourierMessageExecute executes the request // @return Message GetCourierMessageExecute(r CourierAPIGetCourierMessageRequest) (*Message, *http.Response, error) /* ListCourierMessages List Messages Lists all messages by given status and recipient. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return CourierAPIListCourierMessagesRequest */ ListCourierMessages(ctx context.Context) CourierAPIListCourierMessagesRequest // ListCourierMessagesExecute executes the request // @return []Message ListCourierMessagesExecute(r CourierAPIListCourierMessagesRequest) ([]Message, *http.Response, error) } // CourierAPIService CourierAPI service type CourierAPIService service type CourierAPIGetCourierMessageRequest struct { ctx context.Context ApiService CourierAPI id string } func (r CourierAPIGetCourierMessageRequest) Execute() (*Message, *http.Response, error) { return r.ApiService.GetCourierMessageExecute(r) } /* GetCourierMessage Get a Message Gets a specific messages by the given ID. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @param id MessageID is the ID of the message. @return CourierAPIGetCourierMessageRequest */ func (a *CourierAPIService) GetCourierMessage(ctx context.Context, id string) CourierAPIGetCourierMessageRequest { return CourierAPIGetCourierMessageRequest{ ApiService: a, ctx: ctx, id: id, } } // Execute executes the request // // @return Message func (a *CourierAPIService) GetCourierMessageExecute(r CourierAPIGetCourierMessageRequest) (*Message, *http.Response, error) { var ( localVarHTTPMethod = http.MethodGet localVarPostBody interface{} formFiles []formFile localVarReturnValue *Message ) localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "CourierAPIService.GetCourierMessage") if err != nil { return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} } localVarPath := localBasePath + "/admin/courier/messages/{id}" localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) localVarHeaderParams := make(map[string]string) localVarQueryParams := url.Values{} localVarFormParams := url.Values{} // to determine the Content-Type header localVarHTTPContentTypes := []string{} // set Content-Type header localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) if localVarHTTPContentType != "" { localVarHeaderParams["Content-Type"] = localVarHTTPContentType } // to determine the Accept header localVarHTTPHeaderAccepts := []string{"application/json"} // set Accept header localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) if localVarHTTPHeaderAccept != "" { localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept } if r.ctx != nil { // API Key Authentication if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok { if apiKey, ok := auth["oryAccessToken"]; ok { var key string if apiKey.Prefix != "" { key = apiKey.Prefix + " " + apiKey.Key } else { key = apiKey.Key } localVarHeaderParams["Authorization"] = key } } } req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) if err != nil { return localVarReturnValue, nil, err } localVarHTTPResponse, err := a.client.callAPI(req) if err != nil || localVarHTTPResponse == nil { return localVarReturnValue, localVarHTTPResponse, err } localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) localVarHTTPResponse.Body.Close() localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) if err != nil { return localVarReturnValue, localVarHTTPResponse, err } if localVarHTTPResponse.StatusCode >= 300 { newErr := &GenericOpenAPIError{ body: localVarBody, error: localVarHTTPResponse.Status, } if localVarHTTPResponse.StatusCode == 400 { var v ErrorGeneric err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() return localVarReturnValue, localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v return localVarReturnValue, localVarHTTPResponse, newErr } var v ErrorGeneric err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() return localVarReturnValue, localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v return localVarReturnValue, localVarHTTPResponse, newErr } err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr := &GenericOpenAPIError{ body: localVarBody, error: err.Error(), } return localVarReturnValue, localVarHTTPResponse, newErr } return localVarReturnValue, localVarHTTPResponse, nil } type CourierAPIListCourierMessagesRequest struct { ctx context.Context ApiService CourierAPI pageSize *int64 pageToken *string status *CourierMessageStatus recipient *string } // Items per Page This is the number of items per page to return. For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). func (r CourierAPIListCourierMessagesRequest) PageSize(pageSize int64) CourierAPIListCourierMessagesRequest { r.pageSize = &pageSize return r } // Next Page Token The next page token. For details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination). func (r CourierAPIListCourierMessagesRequest) PageToken(pageToken string) CourierAPIListCourierMessagesRequest { r.pageToken = &pageToken return r } // Status filters out messages based on status. If no value is provided, it doesn't take effect on filter. func (r CourierAPIListCourierMessagesRequest) Status(status CourierMessageStatus) CourierAPIListCourierMessagesRequest { r.status = &status return r } // Recipient filters out messages based on recipient. If no value is provided, it doesn't take effect on filter. func (r CourierAPIListCourierMessagesRequest) Recipient(recipient string) CourierAPIListCourierMessagesRequest { r.recipient = &recipient return r } func (r CourierAPIListCourierMessagesRequest) Execute() ([]Message, *http.Response, error) { return r.ApiService.ListCourierMessagesExecute(r) } /* ListCourierMessages List Messages Lists all messages by given status and recipient. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return CourierAPIListCourierMessagesRequest */ func (a *CourierAPIService) ListCourierMessages(ctx context.Context) CourierAPIListCourierMessagesRequest { return CourierAPIListCourierMessagesRequest{ ApiService: a, ctx: ctx, } } // Execute executes the request // // @return []Message func (a *CourierAPIService) ListCourierMessagesExecute(r CourierAPIListCourierMessagesRequest) ([]Message, *http.Response, error) { var ( localVarHTTPMethod = http.MethodGet localVarPostBody interface{} formFiles []formFile localVarReturnValue []Message ) localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "CourierAPIService.ListCourierMessages") if err != nil { return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} } localVarPath := localBasePath + "/admin/courier/messages" localVarHeaderParams := make(map[string]string) localVarQueryParams := url.Values{} localVarFormParams := url.Values{} if r.pageSize != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "page_size", r.pageSize, "form", "") } else { var defaultValue int64 = 250 r.pageSize = &defaultValue } if r.pageToken != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "page_token", r.pageToken, "form", "") } if r.status != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "status", r.status, "form", "") } if r.recipient != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "recipient", r.recipient, "form", "") } // to determine the Content-Type header localVarHTTPContentTypes := []string{} // set Content-Type header localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) if localVarHTTPContentType != "" { localVarHeaderParams["Content-Type"] = localVarHTTPContentType } // to determine the Accept header localVarHTTPHeaderAccepts := []string{"application/json"} // set Accept header localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) if localVarHTTPHeaderAccept != "" { localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept } if r.ctx != nil { // API Key Authentication if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok { if apiKey, ok := auth["oryAccessToken"]; ok { var key string if apiKey.Prefix != "" { key = apiKey.Prefix + " " + apiKey.Key } else { key = apiKey.Key } localVarHeaderParams["Authorization"] = key } } } req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) if err != nil { return localVarReturnValue, nil, err } localVarHTTPResponse, err := a.client.callAPI(req) if err != nil || localVarHTTPResponse == nil { return localVarReturnValue, localVarHTTPResponse, err } localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) localVarHTTPResponse.Body.Close() localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) if err != nil { return localVarReturnValue, localVarHTTPResponse, err } if localVarHTTPResponse.StatusCode >= 300 { newErr := &GenericOpenAPIError{ body: localVarBody, error: localVarHTTPResponse.Status, } if localVarHTTPResponse.StatusCode == 400 { var v ErrorGeneric err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() return localVarReturnValue, localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v return localVarReturnValue, localVarHTTPResponse, newErr } var v ErrorGeneric err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() return localVarReturnValue, localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v return localVarReturnValue, localVarHTTPResponse, newErr } err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr := &GenericOpenAPIError{ body: localVarBody, error: err.Error(), } return localVarReturnValue, localVarHTTPResponse, newErr } return localVarReturnValue, localVarHTTPResponse, nil } ================================================ FILE: pkg/client-go/api_frontend.go ================================================ /* Ory Identities API This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. API version: Contact: office@ory.sh */ // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. package client import ( "bytes" "context" "io" "net/http" "net/url" "strings" ) type FrontendAPI interface { /* CreateBrowserLoginFlow Create Login Flow for Browsers This endpoint initializes a browser-based user login flow. This endpoint will set the appropriate cookies and anti-CSRF measures required for browser-based flows. If this endpoint is opened as a link in the browser, it will be redirected to `selfservice.flows.login.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session exists already, the browser will be redirected to `urls.default_redirect_url` unless the query parameter `?refresh=true` was set. If this endpoint is called via an AJAX request, the response contains the flow without a redirect. In the case of an error, the `error.id` of the JSON response body can be one of: `session_already_available`: The user is already signed in. `session_aal1_required`: Multi-factor auth (e.g. 2fa) was requested but the user has no session yet. `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. `security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration! The optional query parameter login_challenge is set when using Kratos with Hydra in an OAuth2 flow. See the oauth2_provider.url configuration option. This endpoint is NOT INTENDED for clients that do not have a browser (Chrome, Firefox, ...) as cookies are needed. More information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateBrowserLoginFlowRequest */ CreateBrowserLoginFlow(ctx context.Context) FrontendAPICreateBrowserLoginFlowRequest // CreateBrowserLoginFlowExecute executes the request // @return LoginFlow CreateBrowserLoginFlowExecute(r FrontendAPICreateBrowserLoginFlowRequest) (*LoginFlow, *http.Response, error) /* CreateBrowserLogoutFlow Create a Logout URL for Browsers This endpoint initializes a browser-based user logout flow and a URL which can be used to log out the user. This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). For API clients you can call the `/self-service/logout/api` URL directly with the Ory Session Token. The URL is only valid for the currently signed in user. If no user is signed in, this endpoint returns a 401 error. When calling this endpoint from a backend, please ensure to properly forward the HTTP cookies. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateBrowserLogoutFlowRequest */ CreateBrowserLogoutFlow(ctx context.Context) FrontendAPICreateBrowserLogoutFlowRequest // CreateBrowserLogoutFlowExecute executes the request // @return LogoutFlow CreateBrowserLogoutFlowExecute(r FrontendAPICreateBrowserLogoutFlowRequest) (*LogoutFlow, *http.Response, error) /* CreateBrowserRecoveryFlow Create Recovery Flow for Browsers This endpoint initializes a browser-based account recovery flow. Once initialized, the browser will be redirected to `selfservice.flows.recovery.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session exists, the browser is returned to the configured return URL. If this endpoint is called via an AJAX request, the response contains the recovery flow without any redirects or a 400 bad request error if the user is already authenticated. This endpoint is NOT INTENDED for clients that do not have a browser (Chrome, Firefox, ...) as cookies are needed. More information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateBrowserRecoveryFlowRequest */ CreateBrowserRecoveryFlow(ctx context.Context) FrontendAPICreateBrowserRecoveryFlowRequest // CreateBrowserRecoveryFlowExecute executes the request // @return RecoveryFlow CreateBrowserRecoveryFlowExecute(r FrontendAPICreateBrowserRecoveryFlowRequest) (*RecoveryFlow, *http.Response, error) /* CreateBrowserRegistrationFlow Create Registration Flow for Browsers This endpoint initializes a browser-based user registration flow. This endpoint will set the appropriate cookies and anti-CSRF measures required for browser-based flows. If this endpoint is opened as a link in the browser, it will be redirected to `selfservice.flows.registration.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session exists already, the browser will be redirected to `urls.default_redirect_url`. If this endpoint is called via an AJAX request, the response contains the flow without a redirect. In the case of an error, the `error.id` of the JSON response body can be one of: `session_already_available`: The user is already signed in. `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. `security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration! If this endpoint is called via an AJAX request, the response contains the registration flow without a redirect. This endpoint is NOT INTENDED for clients that do not have a browser (Chrome, Firefox, ...) as cookies are needed. More information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateBrowserRegistrationFlowRequest */ CreateBrowserRegistrationFlow(ctx context.Context) FrontendAPICreateBrowserRegistrationFlowRequest // CreateBrowserRegistrationFlowExecute executes the request // @return RegistrationFlow CreateBrowserRegistrationFlowExecute(r FrontendAPICreateBrowserRegistrationFlowRequest) (*RegistrationFlow, *http.Response, error) /* CreateBrowserSettingsFlow Create Settings Flow for Browsers This endpoint initializes a browser-based user settings flow. Once initialized, the browser will be redirected to `selfservice.flows.settings.ui_url` with the flow ID set as the query parameter `?flow=`. If no valid Ory Kratos Session Cookie is included in the request, a login flow will be initialized. If this endpoint is opened as a link in the browser, it will be redirected to `selfservice.flows.settings.ui_url` with the flow ID set as the query parameter `?flow=`. If no valid user session was set, the browser will be redirected to the login endpoint. If this endpoint is called via an AJAX request, the response contains the settings flow without any redirects or a 401 forbidden error if no valid session was set. Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user to sign in with the second factor (happens automatically for server-side browser flows) or change the configuration. If this endpoint is called via an AJAX request, the response contains the flow without a redirect. In the case of an error, the `error.id` of the JSON response body can be one of: `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. `session_inactive`: No Ory Session was found - sign in a user first. `security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration! This endpoint is NOT INTENDED for clients that do not have a browser (Chrome, Firefox, ...) as cookies are needed. More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateBrowserSettingsFlowRequest */ CreateBrowserSettingsFlow(ctx context.Context) FrontendAPICreateBrowserSettingsFlowRequest // CreateBrowserSettingsFlowExecute executes the request // @return SettingsFlow CreateBrowserSettingsFlowExecute(r FrontendAPICreateBrowserSettingsFlowRequest) (*SettingsFlow, *http.Response, error) /* CreateBrowserVerificationFlow Create Verification Flow for Browser Clients This endpoint initializes a browser-based account verification flow. Once initialized, the browser will be redirected to `selfservice.flows.verification.ui_url` with the flow ID set as the query parameter `?flow=`. If this endpoint is called via an AJAX request, the response contains the recovery flow without any redirects. This endpoint is NOT INTENDED for API clients and only works with browsers (Chrome, Firefox, ...). More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateBrowserVerificationFlowRequest */ CreateBrowserVerificationFlow(ctx context.Context) FrontendAPICreateBrowserVerificationFlowRequest // CreateBrowserVerificationFlowExecute executes the request // @return VerificationFlow CreateBrowserVerificationFlowExecute(r FrontendAPICreateBrowserVerificationFlowRequest) (*VerificationFlow, *http.Response, error) /* CreateFedcmFlow Get FedCM Parameters This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateFedcmFlowRequest */ CreateFedcmFlow(ctx context.Context) FrontendAPICreateFedcmFlowRequest // CreateFedcmFlowExecute executes the request // @return CreateFedcmFlowResponse CreateFedcmFlowExecute(r FrontendAPICreateFedcmFlowRequest) (*CreateFedcmFlowResponse, *http.Response, error) /* CreateNativeLoginFlow Create Login Flow for Native Apps This endpoint initiates a login flow for native apps that do not use a browser, such as mobile devices, smart TVs, and so on. If a valid provided session cookie or session token is provided, a 400 Bad Request error will be returned unless the URL query parameter `?refresh=true` is set. To fetch an existing login flow call `/self-service/login/flows?flow=`. You MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server Pages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make you vulnerable to a variety of CSRF attacks, including CSRF login attacks. In the case of an error, the `error.id` of the JSON response body can be one of: `session_already_available`: The user is already signed in. `session_aal1_required`: Multi-factor auth (e.g. 2fa) was requested but the user has no session yet. `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. This endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...). More information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateNativeLoginFlowRequest */ CreateNativeLoginFlow(ctx context.Context) FrontendAPICreateNativeLoginFlowRequest // CreateNativeLoginFlowExecute executes the request // @return LoginFlow CreateNativeLoginFlowExecute(r FrontendAPICreateNativeLoginFlowRequest) (*LoginFlow, *http.Response, error) /* CreateNativeRecoveryFlow Create Recovery Flow for Native Apps This endpoint initiates a recovery flow for API clients such as mobile devices, smart TVs, and so on. If a valid provided session cookie or session token is provided, a 400 Bad Request error. On an existing recovery flow, use the `getRecoveryFlow` API endpoint. You MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server Pages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make you vulnerable to a variety of CSRF attacks. This endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...). More information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateNativeRecoveryFlowRequest */ CreateNativeRecoveryFlow(ctx context.Context) FrontendAPICreateNativeRecoveryFlowRequest // CreateNativeRecoveryFlowExecute executes the request // @return RecoveryFlow CreateNativeRecoveryFlowExecute(r FrontendAPICreateNativeRecoveryFlowRequest) (*RecoveryFlow, *http.Response, error) /* CreateNativeRegistrationFlow Create Registration Flow for Native Apps This endpoint initiates a registration flow for API clients such as mobile devices, smart TVs, and so on. If a valid provided session cookie or session token is provided, a 400 Bad Request error will be returned unless the URL query parameter `?refresh=true` is set. To fetch an existing registration flow call `/self-service/registration/flows?flow=`. You MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server Pages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make you vulnerable to a variety of CSRF attacks. In the case of an error, the `error.id` of the JSON response body can be one of: `session_already_available`: The user is already signed in. `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. This endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...). More information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateNativeRegistrationFlowRequest */ CreateNativeRegistrationFlow(ctx context.Context) FrontendAPICreateNativeRegistrationFlowRequest // CreateNativeRegistrationFlowExecute executes the request // @return RegistrationFlow CreateNativeRegistrationFlowExecute(r FrontendAPICreateNativeRegistrationFlowRequest) (*RegistrationFlow, *http.Response, error) /* CreateNativeSettingsFlow Create Settings Flow for Native Apps This endpoint initiates a settings flow for API clients such as mobile devices, smart TVs, and so on. You must provide a valid Ory Kratos Session Token for this endpoint to respond with HTTP 200 OK. To fetch an existing settings flow call `/self-service/settings/flows?flow=`. You MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server Pages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make you vulnerable to a variety of CSRF attacks. Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user to sign in with the second factor or change the configuration. In the case of an error, the `error.id` of the JSON response body can be one of: `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. `session_inactive`: No Ory Session was found - sign in a user first. This endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...). More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateNativeSettingsFlowRequest */ CreateNativeSettingsFlow(ctx context.Context) FrontendAPICreateNativeSettingsFlowRequest // CreateNativeSettingsFlowExecute executes the request // @return SettingsFlow CreateNativeSettingsFlowExecute(r FrontendAPICreateNativeSettingsFlowRequest) (*SettingsFlow, *http.Response, error) /* CreateNativeVerificationFlow Create Verification Flow for Native Apps This endpoint initiates a verification flow for API clients such as mobile devices, smart TVs, and so on. To fetch an existing verification flow call `/self-service/verification/flows?flow=`. You MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server Pages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make you vulnerable to a variety of CSRF attacks. This endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...). More information can be found at [Ory Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPICreateNativeVerificationFlowRequest */ CreateNativeVerificationFlow(ctx context.Context) FrontendAPICreateNativeVerificationFlowRequest // CreateNativeVerificationFlowExecute executes the request // @return VerificationFlow CreateNativeVerificationFlowExecute(r FrontendAPICreateNativeVerificationFlowRequest) (*VerificationFlow, *http.Response, error) /* DisableMyOtherSessions Disable my other sessions Calling this endpoint invalidates all except the current session that belong to the logged-in user. Session data are not deleted. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIDisableMyOtherSessionsRequest */ DisableMyOtherSessions(ctx context.Context) FrontendAPIDisableMyOtherSessionsRequest // DisableMyOtherSessionsExecute executes the request // @return DeleteMySessionsCount DisableMyOtherSessionsExecute(r FrontendAPIDisableMyOtherSessionsRequest) (*DeleteMySessionsCount, *http.Response, error) /* DisableMySession Disable one of my sessions Calling this endpoint invalidates the specified session. The current session cannot be revoked. Session data are not deleted. @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @param id ID is the session's ID. @return FrontendAPIDisableMySessionRequest */ DisableMySession(ctx context.Context, id string) FrontendAPIDisableMySessionRequest // DisableMySessionExecute executes the request DisableMySessionExecute(r FrontendAPIDisableMySessionRequest) (*http.Response, error) /* ExchangeSessionToken Exchange Session Token @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIExchangeSessionTokenRequest */ ExchangeSessionToken(ctx context.Context) FrontendAPIExchangeSessionTokenRequest // ExchangeSessionTokenExecute executes the request // @return SuccessfulNativeLogin ExchangeSessionTokenExecute(r FrontendAPIExchangeSessionTokenRequest) (*SuccessfulNativeLogin, *http.Response, error) /* GetFlowError Get User-Flow Errors This endpoint returns the error associated with a user-facing self service errors. This endpoint supports stub values to help you implement the error UI: `?id=stub:500` - returns a stub 500 (Internal Server Error) error. More information can be found at [Ory Kratos User User Facing Error Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-facing-errors). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIGetFlowErrorRequest */ GetFlowError(ctx context.Context) FrontendAPIGetFlowErrorRequest // GetFlowErrorExecute executes the request // @return FlowError GetFlowErrorExecute(r FrontendAPIGetFlowErrorRequest) (*FlowError, *http.Response, error) /* GetLoginFlow Get Login Flow This endpoint returns a login flow's context with, for example, error details and other information. Browser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header. For AJAX requests you must ensure that cookies are included in the request or requests will fail. If you use the browser-flow for server-side apps, the services need to run on a common top-level-domain and you need to forward the incoming HTTP Cookie header to this endpoint: ```js pseudo-code example router.get('/login', async function (req, res) { const flow = await client.getLoginFlow(req.header('cookie'), req.query['flow']) res.render('login', flow) }) ``` This request may fail due to several reasons. The `error.id` can be one of: `session_already_available`: The user is already signed in. `self_service_flow_expired`: The flow is expired and you should request a new one. More information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIGetLoginFlowRequest */ GetLoginFlow(ctx context.Context) FrontendAPIGetLoginFlowRequest // GetLoginFlowExecute executes the request // @return LoginFlow GetLoginFlowExecute(r FrontendAPIGetLoginFlowRequest) (*LoginFlow, *http.Response, error) /* GetRecoveryFlow Get Recovery Flow This endpoint returns a recovery flow's context with, for example, error details and other information. Browser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header. For AJAX requests you must ensure that cookies are included in the request or requests will fail. If you use the browser-flow for server-side apps, the services need to run on a common top-level-domain and you need to forward the incoming HTTP Cookie header to this endpoint: ```js pseudo-code example router.get('/recovery', async function (req, res) { const flow = await client.getRecoveryFlow(req.header('Cookie'), req.query['flow']) res.render('recovery', flow) }) ``` More information can be found at [Ory Kratos Account Recovery Documentation](../self-service/flows/account-recovery). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIGetRecoveryFlowRequest */ GetRecoveryFlow(ctx context.Context) FrontendAPIGetRecoveryFlowRequest // GetRecoveryFlowExecute executes the request // @return RecoveryFlow GetRecoveryFlowExecute(r FrontendAPIGetRecoveryFlowRequest) (*RecoveryFlow, *http.Response, error) /* GetRegistrationFlow Get Registration Flow This endpoint returns a registration flow's context with, for example, error details and other information. Browser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header. For AJAX requests you must ensure that cookies are included in the request or requests will fail. If you use the browser-flow for server-side apps, the services need to run on a common top-level-domain and you need to forward the incoming HTTP Cookie header to this endpoint: ```js pseudo-code example router.get('/registration', async function (req, res) { const flow = await client.getRegistrationFlow(req.header('cookie'), req.query['flow']) res.render('registration', flow) }) ``` This request may fail due to several reasons. The `error.id` can be one of: `session_already_available`: The user is already signed in. `self_service_flow_expired`: The flow is expired and you should request a new one. More information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIGetRegistrationFlowRequest */ GetRegistrationFlow(ctx context.Context) FrontendAPIGetRegistrationFlowRequest // GetRegistrationFlowExecute executes the request // @return RegistrationFlow GetRegistrationFlowExecute(r FrontendAPIGetRegistrationFlowRequest) (*RegistrationFlow, *http.Response, error) /* GetSettingsFlow Get Settings Flow When accessing this endpoint through Ory Kratos' Public API you must ensure that either the Ory Kratos Session Cookie or the Ory Kratos Session Token are set. Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user to sign in with the second factor or change the configuration. You can access this endpoint without credentials when using Ory Kratos' Admin API. If this endpoint is called via an AJAX request, the response contains the flow without a redirect. In the case of an error, the `error.id` of the JSON response body can be one of: `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred. `session_inactive`: No Ory Session was found - sign in a user first. `security_identity_mismatch`: The flow was interrupted with `session_refresh_required` but apparently some other identity logged in instead. More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIGetSettingsFlowRequest */ GetSettingsFlow(ctx context.Context) FrontendAPIGetSettingsFlowRequest // GetSettingsFlowExecute executes the request // @return SettingsFlow GetSettingsFlowExecute(r FrontendAPIGetSettingsFlowRequest) (*SettingsFlow, *http.Response, error) /* GetVerificationFlow Get Verification Flow This endpoint returns a verification flow's context with, for example, error details and other information. Browser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header. For AJAX requests you must ensure that cookies are included in the request or requests will fail. If you use the browser-flow for server-side apps, the services need to run on a common top-level-domain and you need to forward the incoming HTTP Cookie header to this endpoint: ```js pseudo-code example router.get('/recovery', async function (req, res) { const flow = await client.getVerificationFlow(req.header('cookie'), req.query['flow']) res.render('verification', flow) }) ``` More information can be found at [Ory Kratos Email and Phone Verification Documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @return FrontendAPIGetVerificationFlowRequest */ GetVerificationFlow(ctx context.Context) FrontendAPIGetVerificationFlowRequest // GetVerificationFlowExecute executes the request // @return VerificationFlow GetVerificationFlowExecute(r FrontendAPIGetVerificationFlowRequest) (*VerificationFlow, *http.Response, error) /* GetWebAuthnJavaScript Get WebAuthn JavaScript This endpoint provides JavaScript which is needed in order to perform WebAuthn login and registration. If you are building a JavaScript Browser App (e.g. in ReactJS or AngularJS) you will need to load this file: ```html