From 122b63d68a3ff2ad78107300869c5a6d2aa43354 Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Thu, 5 Sep 2024 14:25:59 +0000 Subject: [PATCH] fix: include duplicate credentials in account linking message (#4079) --- cmd/clidoc/main.go | 2 +- identity/manager.go | 9 +- ...dc_credentials-case=should_fail_login.json | 188 ++++++++++++++++++ ...entials-case=should_fail_registration.json | 188 ++++++++++++++++++ ...egistration_id_first_strategy_enabled.json | 188 ++++++++++++++++++ ...d_credentials-case=should_fail_login.json} | 9 +- ...ntials-case=should_fail_registration.json} | 9 +- ...gistration_id_first_strategy_enabled.json} | 9 +- ...dc_credentials-case=should_fail_login.json | 83 ++++++++ ...entials-case=should_fail_registration.json | 83 ++++++++ ...egistration_id_first_strategy_enabled.json | 83 ++++++++ ...rd_credentials-case=should_fail_login.json | 100 ++++++++++ ...entials-case=should_fail_registration.json | 100 ++++++++++ ...egistration_id_first_strategy_enabled.json | 100 ++++++++++ selfservice/strategy/oidc/strategy.go | 20 +- selfservice/strategy/oidc/strategy_test.go | 114 ++++++++--- text/message_login.go | 12 +- 17 files changed, 1244 insertions(+), 53 deletions(-) create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 rename selfservice/strategy/oidc/.snapshots/{TestStrategy-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_login.json} (88%) rename selfservice/strategy/oidc/.snapshots/{TestStrategy-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.json} (88%) rename selfservice/strategy/oidc/.snapshots/{TestStrategy-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=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} (88%) create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 create mode 100644 selfservice/strategy/oidc/.snapshots/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 diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index 6010768ecd1f..f7658bd6e086 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -121,7 +121,7 @@ func init() { "NewInfoLoginLookupLabel": text.NewInfoLoginLookupLabel(), "NewInfoLogin": text.NewInfoLogin(), "NewInfoLoginAndLink": text.NewInfoLoginAndLink(), - "NewInfoLoginLinkMessage": text.NewInfoLoginLinkMessage("{duplicateIdentifier}", "{provider}", "{newLoginUrl}"), + "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(), diff --git a/identity/manager.go b/identity/manager.go index d05fd83a4a7f..f41f0b1a33bb 100644 --- a/identity/manager.go +++ b/identity/manager.go @@ -32,10 +32,8 @@ import ( "github.com/ory/kratos/courier" ) -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.`) -) +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 { @@ -314,6 +312,9 @@ func (e *ErrDuplicateCredentials) AvailableCredentials() []string { } func (e *ErrDuplicateCredentials) AvailableOIDCProviders() []string { + if e.availableOIDCProviders == nil { + return []string{} + } slices.Sort(e.availableOIDCProviders) return e.availableOIDCProviders } diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..8b4f5fad2b43 --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,188 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "claimsViaUserInfo", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with claimsViaUserInfo", + "type": "info", + "context": { + "provider": "claimsViaUserInfo" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "invalid-issuer", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with invalid-issuer", + "type": "info", + "context": { + "provider": "invalid-issuer" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "secondProvider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with secondProvider", + "type": "info", + "context": { + "provider": "secondProvider" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "valid2", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with valid2", + "type": "info", + "context": { + "provider": "valid2" + } + } + } + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": true, + "autocomplete": "current-password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010022, + "text": "Sign in with password", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-false@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-false@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": [], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "duplicate_identifier": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..8b4f5fad2b43 --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,188 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "claimsViaUserInfo", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with claimsViaUserInfo", + "type": "info", + "context": { + "provider": "claimsViaUserInfo" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "invalid-issuer", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with invalid-issuer", + "type": "info", + "context": { + "provider": "invalid-issuer" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "secondProvider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with secondProvider", + "type": "info", + "context": { + "provider": "secondProvider" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "valid2", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with valid2", + "type": "info", + "context": { + "provider": "valid2" + } + } + } + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": true, + "autocomplete": "current-password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010022, + "text": "Sign in with password", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-false@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-false@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": [], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "duplicate_identifier": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..8b4f5fad2b43 --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,188 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "claimsViaUserInfo", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with claimsViaUserInfo", + "type": "info", + "context": { + "provider": "claimsViaUserInfo" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "invalid-issuer", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with invalid-issuer", + "type": "info", + "context": { + "provider": "invalid-issuer" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "secondProvider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with secondProvider", + "type": "info", + "context": { + "provider": "secondProvider" + } + } + } + }, + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "valid2", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with valid2", + "type": "info", + "context": { + "provider": "valid2" + } + } + } + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": true, + "autocomplete": "current-password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010022, + "text": "Sign in with password", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-false@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-false@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": [], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "duplicate_identifier": "email-exist-with-oidc-strategy-lh-false@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/TestStrategy-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_login.json b/selfservice/strategy/oidc/.snapshots/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 similarity index 88% rename from selfservice/strategy/oidc/.snapshots/TestStrategy-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_login.json rename to selfservice/strategy/oidc/.snapshots/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 index c87e844c54e3..112edcf3999b 100644 --- a/selfservice/strategy/oidc/.snapshots/TestStrategy-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_login.json +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -112,7 +112,7 @@ "attributes": { "name": "identifier", "type": "hidden", - "value": "email-exist-with-password-strategy@ory.sh", + "value": "email-exist-with-password-strategy-lh-false@ory.sh", "required": true, "disabled": false, "node_type": "input" @@ -169,10 +169,13 @@ "messages": [ { "id": 1010016, - "text": "You tried to sign in with \"email-exist-with-password-strategy@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy@ory.sh\" at \"generic\" as another way to sign in.", + "text": "You tried to sign in with \"email-exist-with-password-strategy-lh-false@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-false@ory.sh\" at \"generic\" as another way to sign in.", "type": "info", "context": { - "duplicateIdentifier": "email-exist-with-password-strategy@ory.sh", + "available_credential_types": [], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-password-strategy-lh-false@ory.sh", + "duplicate_identifier": "email-exist-with-password-strategy-lh-false@ory.sh", "provider": "generic" } } diff --git a/selfservice/strategy/oidc/.snapshots/TestStrategy-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration.json b/selfservice/strategy/oidc/.snapshots/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 similarity index 88% rename from selfservice/strategy/oidc/.snapshots/TestStrategy-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration.json rename to selfservice/strategy/oidc/.snapshots/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 index c87e844c54e3..112edcf3999b 100644 --- a/selfservice/strategy/oidc/.snapshots/TestStrategy-case=should_fail_to_register_and_return_fresh_login_flow_if_email_is_already_being_used_by_password_credentials-case=should_fail_registration.json +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -112,7 +112,7 @@ "attributes": { "name": "identifier", "type": "hidden", - "value": "email-exist-with-password-strategy@ory.sh", + "value": "email-exist-with-password-strategy-lh-false@ory.sh", "required": true, "disabled": false, "node_type": "input" @@ -169,10 +169,13 @@ "messages": [ { "id": 1010016, - "text": "You tried to sign in with \"email-exist-with-password-strategy@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy@ory.sh\" at \"generic\" as another way to sign in.", + "text": "You tried to sign in with \"email-exist-with-password-strategy-lh-false@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-false@ory.sh\" at \"generic\" as another way to sign in.", "type": "info", "context": { - "duplicateIdentifier": "email-exist-with-password-strategy@ory.sh", + "available_credential_types": [], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-password-strategy-lh-false@ory.sh", + "duplicate_identifier": "email-exist-with-password-strategy-lh-false@ory.sh", "provider": "generic" } } diff --git a/selfservice/strategy/oidc/.snapshots/TestStrategy-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 b/selfservice/strategy/oidc/.snapshots/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 similarity index 88% rename from selfservice/strategy/oidc/.snapshots/TestStrategy-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 rename to selfservice/strategy/oidc/.snapshots/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 index c87e844c54e3..112edcf3999b 100644 --- a/selfservice/strategy/oidc/.snapshots/TestStrategy-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 +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -112,7 +112,7 @@ "attributes": { "name": "identifier", "type": "hidden", - "value": "email-exist-with-password-strategy@ory.sh", + "value": "email-exist-with-password-strategy-lh-false@ory.sh", "required": true, "disabled": false, "node_type": "input" @@ -169,10 +169,13 @@ "messages": [ { "id": 1010016, - "text": "You tried to sign in with \"email-exist-with-password-strategy@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy@ory.sh\" at \"generic\" as another way to sign in.", + "text": "You tried to sign in with \"email-exist-with-password-strategy-lh-false@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-false@ory.sh\" at \"generic\" as another way to sign in.", "type": "info", "context": { - "duplicateIdentifier": "email-exist-with-password-strategy@ory.sh", + "available_credential_types": [], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-password-strategy-lh-false@ory.sh", + "duplicate_identifier": "email-exist-with-password-strategy-lh-false@ory.sh", "provider": "generic" } } diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..77bef5d097ae --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,83 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "secondProvider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with secondProvider", + "type": "info", + "context": { + "provider": "secondProvider" + } + } + } + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": ["oidc"], + "available_providers": ["secondProvider"], + "duplicateIdentifier": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "duplicate_identifier": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..77bef5d097ae --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,83 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "secondProvider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with secondProvider", + "type": "info", + "context": { + "provider": "secondProvider" + } + } + } + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": ["oidc"], + "available_providers": ["secondProvider"], + "duplicateIdentifier": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "duplicate_identifier": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..77bef5d097ae --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,83 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "oidc", + "attributes": { + "name": "provider", + "type": "submit", + "value": "secondProvider", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010018, + "text": "Confirm with secondProvider", + "type": "info", + "context": { + "provider": "secondProvider" + } + } + } + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": ["oidc"], + "available_providers": ["secondProvider"], + "duplicateIdentifier": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "duplicate_identifier": "email-exist-with-oidc-strategy-lh-true@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..93317c6e479a --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,100 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-password-strategy-lh-true@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": true, + "autocomplete": "current-password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010022, + "text": "Sign in with password", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-password-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": ["password"], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-password-strategy-lh-true@ory.sh", + "duplicate_identifier": "email-exist-with-password-strategy-lh-true@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..93317c6e479a --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,100 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-password-strategy-lh-true@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": true, + "autocomplete": "current-password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010022, + "text": "Sign in with password", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-password-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": ["password"], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-password-strategy-lh-true@ory.sh", + "duplicate_identifier": "email-exist-with-password-strategy-lh-true@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/.snapshots/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 b/selfservice/strategy/oidc/.snapshots/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 new file mode 100644 index 000000000000..93317c6e479a --- /dev/null +++ b/selfservice/strategy/oidc/.snapshots/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 @@ -0,0 +1,100 @@ +{ + "organization_id": null, + "type": "browser", + "active": "oidc", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "value": "email-exist-with-password-strategy-lh-true@ory.sh", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070004, + "text": "ID", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": true, + "autocomplete": "current-password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info" + } + } + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": { + "label": { + "id": 1010022, + "text": "Sign in with password", + "type": "info" + } + } + } + ], + "messages": [ + { + "id": 1010016, + "text": "You tried to sign in with \"email-exist-with-password-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.", + "type": "info", + "context": { + "available_credential_types": ["password"], + "available_providers": [], + "duplicateIdentifier": "email-exist-with-password-strategy-lh-true@ory.sh", + "duplicate_identifier": "email-exist-with-password-strategy-lh-true@ory.sh", + "provider": "generic" + } + } + ] + }, + "refresh": false, + "requested_aal": "aal1", + "state": "choose_method" +} + diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 14c2c26f8e50..fa493a528c2c 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -13,6 +13,7 @@ import ( "net/http" "net/url" "path/filepath" + "slices" "strings" "time" @@ -628,7 +629,7 @@ func (s *Strategy) handleError(ctx context.Context, w http.ResponseWriter, r *ht } if dc, err := flow.DuplicateCredentials(lf); err == nil && dc != nil { redirectURL = urlx.CopyWithQuery(redirectURL, url.Values{"no_org_ui": {"true"}}) - s.populateAccountLinkingUI(r.Context(), lf, usedProviderID, dc.DuplicateIdentifier, dup.AvailableCredentials()) + s.populateAccountLinkingUI(r.Context(), lf, usedProviderID, dc.DuplicateIdentifier, dup.AvailableCredentials(), dup.AvailableOIDCProviders()) if err := s.d.LoginFlowPersister().UpdateLoginFlow(r.Context(), lf); err != nil { return err } @@ -667,7 +668,7 @@ func (s *Strategy) handleError(ctx context.Context, w http.ResponseWriter, r *ht return err } -func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, usedProviderID string, duplicateIdentifier string, availableCredentials []string) { +func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, usedProviderID string, duplicateIdentifier string, availableCredentials []string, availableProviders []string) { newLoginURL := s.d.Config().SelfServiceFlowLoginUI(ctx).String() usedProviderLabel := usedProviderID provider, _ := s.provider(ctx, usedProviderID) @@ -677,6 +678,7 @@ func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, usedProviderLabel = provider.Config().Provider } } + loginHintsEnabled := s.d.Config().SelfServiceFlowRegistrationLoginHints(ctx) nodes := []*node.Node{} for _, n := range lf.UI.Nodes { // We don't want to touch nodes unecessary nodes @@ -687,8 +689,14 @@ func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, // Skip the provider that was used to get here (in case they used an OIDC provider) pID := gjson.GetBytes(n.Meta.Label.Context, "provider_id").String() - if n.Group == node.OpenIDConnectGroup && pID == usedProviderID { - continue + if n.Group == node.OpenIDConnectGroup { + if pID == usedProviderID { + continue + } + // Hide any provider that is not available for the user + if loginHintsEnabled && !slices.Contains(availableProviders, pID) { + continue + } } // Replace some labels to make it easier for the user to understand what's going on. @@ -702,7 +710,7 @@ func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, // This can happen, if login hints are disabled. In that case, we need to make sure to show all credential options. // It could in theory also happen due to a mis-configuration, and in that case, we should make sure to not delete the entire flow. - if len(availableCredentials) == 0 { + if !loginHintsEnabled { nodes = append(nodes, n) } else { // Hide nodes from credentials that are not relevant for the user @@ -727,7 +735,7 @@ func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, lf.UI.Nodes = nodes lf.UI.Messages.Clear() - lf.UI.Messages.Add(text.NewInfoLoginLinkMessage(duplicateIdentifier, usedProviderLabel, newLoginURL)) + lf.UI.Messages.Add(text.NewInfoLoginLinkMessage(duplicateIdentifier, usedProviderLabel, newLoginURL, availableCredentials, availableProviders)) } func (s *Strategy) NodeGroup() node.UiNodeGroup { diff --git a/selfservice/strategy/oidc/strategy_test.go b/selfservice/strategy/oidc/strategy_test.go index c3c8abd2ccce..ce87a6a28fbf 100644 --- a/selfservice/strategy/oidc/strategy_test.go +++ b/selfservice/strategy/oidc/strategy_test.go @@ -13,6 +13,7 @@ import ( "net/http/cookiejar" "net/http/httptest" "net/url" + "slices" "strconv" "strings" "testing" @@ -1074,42 +1075,83 @@ func TestStrategy(t *testing.T) { } }) - t.Run("case=should fail to register and return fresh login flow if email is already being used by password credentials", func(t *testing.T) { - subject = "email-exist-with-password-strategy@ory.sh" - scope = []string{"openid"} + for _, loginHintsEnabled := range []bool{true, false} { + t.Run("login-hints-enabled="+strconv.FormatBool(loginHintsEnabled), func(t *testing.T) { + t.Run("case=should fail to register and return fresh login flow if email is already being used by password credentials", func(t *testing.T) { + subject = "email-exist-with-password-strategy-lh-" + strconv.FormatBool(loginHintsEnabled) + "@ory.sh" + scope = []string{"openid"} + conf.MustSet(ctx, config.ViperKeySelfServiceRegistrationLoginHints, strconv.FormatBool(loginHintsEnabled)) + + t.Run("case=create password identity", func(t *testing.T) { + i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) + i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ + Identifiers: []string{subject}, + Config: sqlxx.JSONRawMessage(`{"hashed_password": "foo"}`), + }) + i.Traits = identity.Traits(`{"subject":"` + subject + `"}`) + + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) + }) - t.Run("case=create password identity", func(t *testing.T) { - i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID) - i.SetCredentials(identity.CredentialsTypePassword, identity.Credentials{ - Identifiers: []string{subject}, + t.Run("case=should fail registration", func(t *testing.T) { + r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "valid") + _, body := makeRequest(t, "valid", action, url.Values{}) + snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", findCsrfTokenPath(t, body), "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl", "new_login_url")) + }) + + t.Run("case=should fail registration id_first strategy enabled", func(t *testing.T) { + conf.Set(ctx, config.ViperKeySelfServiceLoginFlowStyle, "identifier_first") + r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "valid") + _, body := makeRequest(t, "valid", action, url.Values{}) + snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", findCsrfTokenPath(t, body), "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl", "new_login_url")) + }) + + t.Run("case=should fail login", func(t *testing.T) { + r := newBrowserLoginFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "valid") + _, body := makeRequest(t, "valid", action, url.Values{}) + snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", findCsrfTokenPath(t, body), "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl", "new_login_url")) + }) }) - i.Traits = identity.Traits(`{"subject":"` + subject + `"}`) - require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i)) - }) + t.Run("case=should fail to register and return fresh login flow if email is already being used by oidc credentials", func(t *testing.T) { + subject = "email-exist-with-oidc-strategy-lh-" + strconv.FormatBool(loginHintsEnabled) + "@ory.sh" + scope = []string{"openid"} + conf.MustSet(ctx, config.ViperKeySelfServiceRegistrationLoginHints, strconv.FormatBool(loginHintsEnabled)) - t.Run("case=should fail registration", func(t *testing.T) { - r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) - action := assertFormValues(t, r.ID, "valid") - _, body := makeRequest(t, "valid", action, url.Values{}) - snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", "ui.nodes.4.attributes.value", "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl")) - }) + t.Run("case=create oidc identity", func(t *testing.T) { + r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "secondProvider") + res, _ := makeRequest(t, "secondProvider", action, url.Values{}) + require.Equal(t, http.StatusOK, res.StatusCode) + }) - t.Run("case=should fail registration id_first strategy enabled", func(t *testing.T) { - conf.Set(ctx, config.ViperKeySelfServiceLoginFlowStyle, "identifier_first") - r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) - action := assertFormValues(t, r.ID, "valid") - _, body := makeRequest(t, "valid", action, url.Values{}) - snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", "ui.nodes.4.attributes.value", "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl")) - }) + t.Run("case=should fail registration", func(t *testing.T) { + r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "valid") + _, body := makeRequest(t, "valid", action, url.Values{}) + snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", findCsrfTokenPath(t, body), "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl", "new_login_url")) + }) - t.Run("case=should fail login", func(t *testing.T) { - r := newBrowserLoginFlow(t, returnTS.URL, time.Minute) - action := assertFormValues(t, r.ID, "valid") - _, body := makeRequest(t, "valid", action, url.Values{}) - snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", "ui.nodes.4.attributes.value", "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl")) + t.Run("case=should fail registration id_first strategy enabled", func(t *testing.T) { + conf.Set(ctx, config.ViperKeySelfServiceLoginFlowStyle, "identifier_first") + r := newBrowserRegistrationFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "valid") + _, body := makeRequest(t, "valid", action, url.Values{}) + snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", findCsrfTokenPath(t, body), "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl", "new_login_url")) + }) + + t.Run("case=should fail login", func(t *testing.T) { + r := newBrowserLoginFlow(t, returnTS.URL, time.Minute) + action := assertFormValues(t, r.ID, "valid") + _, body := makeRequest(t, "valid", action, url.Values{}) + snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptPaths("expires_at", "updated_at", "issued_at", "id", "created_at", "ui.action", findCsrfTokenPath(t, body), "request_url"), snapshotx.ExceptNestedKeys("newLoginUrl", "new_login_url")) + }) + }) }) - }) + } t.Run("case=should redirect to default return ts when sending authenticated login flow without forced flag", func(t *testing.T) { subject = "no-reauth-login@ory.sh" @@ -1423,6 +1465,8 @@ func TestStrategy(t *testing.T) { loginFlow := newLoginFlow(t, returnTS.URL, time.Minute, flow.TypeBrowser) var linkingLoginFlow struct{ ID string } t.Run("step=should fail login and start a new login", func(t *testing.T) { + // To test the identifier mismatch + conf.MustSet(ctx, config.ViperKeySelfServiceRegistrationLoginHints, false) res, body := loginWithOIDC(t, client, loginFlow.ID, "valid2") assertUIError(t, res, body, "You tried to sign in with \"existing-oidc-identity-1@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"existing-oidc-identity-1@ory.sh\" at \"generic\" as another way to sign in.") linkingLoginFlow.ID = gjson.GetBytes(body, "id").String() @@ -1651,3 +1695,15 @@ func TestPostEndpointRedirect(t *testing.T) { testhelpers.AssertNoCSRFCookieInResponse(t, publicTS, c, res) }) } + +func findCsrfTokenPath(t *testing.T, body []byte) string { + nodes := gjson.GetBytes(body, "ui.nodes").Array() + index := slices.IndexFunc(nodes, func(n gjson.Result) bool { + if n.Get("attributes.name").String() == "csrf_token" { + return true + } + return false + }) + require.GreaterOrEqual(t, index, 0) + return fmt.Sprintf("ui.nodes.%v.attributes.value", index) +} diff --git a/text/message_login.go b/text/message_login.go index a7a58118c813..f5ebc54d9899 100644 --- a/text/message_login.go +++ b/text/message_login.go @@ -56,7 +56,7 @@ func NewInfoLogin() *Message { } } -func NewInfoLoginLinkMessage(dupIdentifier, provider, newLoginURL string) *Message { +func NewInfoLoginLinkMessage(dupIdentifier, provider, newLoginURL string, availableCredentials, availableProviders []string) *Message { return &Message{ ID: InfoSelfServiceLoginLink, Type: Info, @@ -66,9 +66,13 @@ func NewInfoLoginLinkMessage(dupIdentifier, provider, newLoginURL string) *Messa provider, ), Context: context(map[string]any{ - "duplicateIdentifier": dupIdentifier, - "provider": provider, - "newLoginUrl": newLoginURL, + "duplicateIdentifier": dupIdentifier, + "provider": provider, + "newLoginUrl": newLoginURL, + "duplicate_identifier": dupIdentifier, + "new_login_url": newLoginURL, + "available_credential_types": availableCredentials, + "available_providers": availableProviders, }), } }