diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index ef3027a936a3..3664f7c809f9 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -104,7 +104,7 @@ func init() { "NewErrorValidationPasswordTooManyBreaches": text.NewErrorValidationPasswordTooManyBreaches(101), "NewErrorValidationInvalidCredentials": text.NewErrorValidationInvalidCredentials(), "NewErrorValidationDuplicateCredentials": text.NewErrorValidationDuplicateCredentials(), - "NewErrorValidationDuplicateCredentialsWithHints": text.NewErrorValidationDuplicateCredentialsWithHints([]string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}, "{credential_identifier_hint}"), + "NewErrorValidationDuplicateCredentialsWithHints": text.NewErrorValidationDuplicateCredentialsWithHints([]string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}, "{credential_identifier_hint}", "{flow}"), "NewErrorValidationDuplicateCredentialsOnOIDCLink": text.NewErrorValidationDuplicateCredentialsOnOIDCLink(), "NewErrorValidationTOTPVerifierWrong": text.NewErrorValidationTOTPVerifierWrong(), "NewErrorValidationLookupAlreadyUsed": text.NewErrorValidationLookupAlreadyUsed(), @@ -147,7 +147,6 @@ func init() { "NewErrorValidationRecoveryStateFailure": text.NewErrorValidationRecoveryStateFailure(), "NewInfoNodeInputEmail": text.NewInfoNodeInputEmail(), "NewInfoNodeResendOTP": text.NewInfoNodeResendOTP(), - "NewInfoNodeLoginAndLinkCredential": text.NewInfoNodeLoginAndLinkCredential(), "NewInfoNodeLabelContinue": text.NewInfoNodeLabelContinue(), "NewInfoSelfServiceSettingsRegisterWebAuthn": text.NewInfoSelfServiceSettingsRegisterWebAuthn(), "NewInfoLoginWebAuthnPasswordless": text.NewInfoLoginWebAuthnPasswordless(), diff --git a/internal/client-go/go.sum b/internal/client-go/go.sum index c966c8ddfd0d..6cc3f5911d11 100644 --- a/internal/client-go/go.sum +++ b/internal/client-go/go.sum @@ -4,6 +4,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/testhelpers/sdk.go b/internal/testhelpers/sdk.go index 849f7a73304e..a91913dfe1e0 100644 --- a/internal/testhelpers/sdk.go +++ b/internal/testhelpers/sdk.go @@ -9,12 +9,6 @@ import ( "net/http/httptest" "net/url" - "github.com/ory/kratos/text" - "github.com/ory/kratos/ui/node" - - "github.com/ory/kratos/x" - "github.com/ory/x/pointerx" - kratos "github.com/ory/kratos/internal/httpclient" ) @@ -61,73 +55,3 @@ func SDKFormFieldsToURLValues(ff []kratos.UiNode) url.Values { } return values } - -func NewFakeCSRFNode() *kratos.UiNode { - return &kratos.UiNode{ - Group: node.DefaultGroup.String(), - Type: "input", - Attributes: kratos.UiNodeInputAttributesAsUiNodeAttributes(&kratos.UiNodeInputAttributes{ - Name: "csrf_token", - Required: pointerx.Bool(true), - Type: "hidden", - Value: x.FakeCSRFToken, - }), - } -} - -func NewSDKEmailNode(group string) *kratos.UiNode { - return &kratos.UiNode{ - Type: "input", - Group: group, - Attributes: kratos.UiNodeInputAttributesAsUiNodeAttributes(&kratos.UiNodeInputAttributes{ - Name: "email", - Type: "email", - Required: pointerx.Bool(true), - Value: "email", - }), - } -} - -func NewSDKOIDCNode(name, provider string) *kratos.UiNode { - t := text.NewInfoRegistrationWith(provider) - return &kratos.UiNode{ - Group: node.OpenIDConnectGroup.String(), - Type: "input", - Attributes: kratos.UiNodeInputAttributesAsUiNodeAttributes(&kratos.UiNodeInputAttributes{ - Name: name, - Type: "submit", - Value: provider, - }), - Meta: kratos.UiNodeMeta{ - Label: &kratos.UiText{ - Id: int64(t.ID), - Text: t.Text, - Type: string(t.Type), - }, - }, - } -} - -func NewMethodSubmit(group, value string) *kratos.UiNode { - return &kratos.UiNode{ - Type: "input", - Group: group, - Attributes: kratos.UiNodeInputAttributesAsUiNodeAttributes(&kratos.UiNodeInputAttributes{ - Name: "method", - Type: "submit", - Value: value, - }), - } -} - -func NewPasswordNode() *kratos.UiNode { - return &kratos.UiNode{ - Type: "input", - Group: node.PasswordGroup.String(), - Attributes: kratos.UiNodeInputAttributesAsUiNodeAttributes(&kratos.UiNodeInputAttributes{ - Name: "password", - Type: "password", - Required: pointerx.Bool(true), - }), - } -} diff --git a/schema/errors.go b/schema/errors.go index 30c1f72976f3..26325b1210ce 100644 --- a/schema/errors.go +++ b/schema/errors.go @@ -140,7 +140,7 @@ type DuplicateCredentialsHinter interface { HasHints() bool } -func NewDuplicateCredentialsError(err error) error { +func NewDuplicateCredentialsError(err error, flow text.DuplicateCredentialsFlow) error { if hinter := DuplicateCredentialsHinter(nil); errors.As(err, &hinter) && hinter.HasHints() { return errors.WithStack(&ValidationError{ ValidationError: &jsonschema.ValidationError{ @@ -152,7 +152,7 @@ func NewDuplicateCredentialsError(err error) error { IdentifierHint: hinter.IdentifierHint(), }, }, - Messages: new(text.Messages).Add(text.NewErrorValidationDuplicateCredentialsWithHints(hinter.AvailableCredentials(), hinter.AvailableOIDCProviders(), hinter.IdentifierHint())), + Messages: new(text.Messages).Add(text.NewErrorValidationDuplicateCredentialsWithHints(hinter.AvailableCredentials(), hinter.AvailableOIDCProviders(), hinter.IdentifierHint(), flow)), }) } diff --git a/selfservice/flow/registration/error.go b/selfservice/flow/registration/error.go index b3ef3d9054df..24981d02e523 100644 --- a/selfservice/flow/registration/error.go +++ b/selfservice/flow/registration/error.go @@ -81,7 +81,7 @@ func (s *ErrorHandler) WriteFlowError( ) { if dup := new(identity.ErrDuplicateCredentials); errors.As(err, &dup) { - err = schema.NewDuplicateCredentialsError(dup) + err = schema.NewDuplicateCredentialsError(dup, text.DuplicateCredentialsSignInFlow) } s.d.Audit(). diff --git a/selfservice/flow/settings/hook.go b/selfservice/flow/settings/hook.go index b688fd0fc431..c2ca78a3ae84 100644 --- a/selfservice/flow/settings/hook.go +++ b/selfservice/flow/settings/hook.go @@ -221,7 +221,7 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, return errors.WithStack(NewFlowNeedsReAuth()) } if errors.Is(err, sqlcon.ErrUniqueViolation) { - return schema.NewDuplicateCredentialsError(err) + return schema.NewDuplicateCredentialsError(err, text.DuplicateCredentialsSettingsFlow) } return err } diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 5289b2a9b96b..1c734bc64f57 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -607,7 +607,7 @@ func (s *Strategy) handleError(w http.ResponseWriter, r *http.Request, f flow.Fl // This is kinda hacky and will probably need to be updated at some point. if dup := new(identity.ErrDuplicateCredentials); errors.As(err, &dup) { - err = schema.NewDuplicateCredentialsError(dup) + err = schema.NewDuplicateCredentialsError(dup, text.DuplicateCredentialsSignUpFlow) if validationErr := new(schema.ValidationError); errors.As(err, &validationErr) { for _, m := range validationErr.Messages { diff --git a/selfservice/strategy/oidc/strategy_settings.go b/selfservice/strategy/oidc/strategy_settings.go index 4fde3a457548..0592891276ce 100644 --- a/selfservice/strategy/oidc/strategy_settings.go +++ b/selfservice/strategy/oidc/strategy_settings.go @@ -11,6 +11,8 @@ import ( "net/http" "time" + "github.com/ory/x/stringsx" + "github.com/ory/x/sqlxx" "github.com/tidwall/sjson" @@ -173,7 +175,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity if l.Config().OrganizationID != "" { continue } - sr.UI.GetNodes().Append(NewLinkNode(l.Config().ID)) + sr.UI.GetNodes().Append(NewLinkNode(stringsx.Coalesce(l.Config().Label, l.Config().ID))) } count, err := s.d.IdentityManager().CountActiveFirstFactorCredentials(r.Context(), confidential) @@ -185,7 +187,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity // This means that we're able to remove a connection because it is the last configured credential. If it is // removed, the identity is no longer able to sign in. for _, l := range linked { - sr.UI.GetNodes().Append(NewUnlinkNode(l.Config().ID)) + sr.UI.GetNodes().Append(NewUnlinkNode(stringsx.Coalesce(l.Config().Label, l.Config().ID))) } } diff --git a/test/e2e/cypress/integration/profiles/email/registration/errors.spec.ts b/test/e2e/cypress/integration/profiles/email/registration/errors.spec.ts index 54617e8d7d2c..2f409ea2579f 100644 --- a/test/e2e/cypress/integration/profiles/email/registration/errors.spec.ts +++ b/test/e2e/cypress/integration/profiles/email/registration/errors.spec.ts @@ -247,7 +247,7 @@ describe("Registration failures with email profile", () => { cy.submitPasswordForm() cy.get('[data-testid="ui/message/4000028"]').should( "contain.text", - "You tried signing in with " + + "You tried signing up with " + email + " which is already in use by another account. You can sign in using your password.", ) diff --git a/test/e2e/cypress/integration/profiles/oidc/login/success.spec.ts b/test/e2e/cypress/integration/profiles/oidc/login/success.spec.ts index 866f4344eda6..193638de8b25 100644 --- a/test/e2e/cypress/integration/profiles/oidc/login/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/login/success.spec.ts @@ -70,7 +70,7 @@ context("Social Sign In Successes", () => { cy.visit(settings) cy.get('[value="hydra"]') .should("have.attr", "name", "unlink") - .should("contain.text", "Unlink hydra") + .should("contain.text", "Unlink Ory") }) it("should be able to sign up with redirects", () => { diff --git a/text/message_login.go b/text/message_login.go index c94db4538704..01ebdabbcb67 100644 --- a/text/message_login.go +++ b/text/message_login.go @@ -61,7 +61,7 @@ func NewInfoLoginLinkMessage(dupIdentifier, provider, newLoginURL string) *Messa ID: InfoSelfServiceLoginLink, Type: Info, Text: fmt.Sprintf( - "Signing in will link your account to %q at provider %q. If you do not wish to link that account, please start a new login flow.", + "Once signed in, your account will be linked to %s at %s. If you do not wish to link that account, please abort the flow.", dupIdentifier, provider, ), @@ -76,7 +76,7 @@ func NewInfoLoginLinkMessage(dupIdentifier, provider, newLoginURL string) *Messa func NewInfoLoginAndLink() *Message { return &Message{ ID: InfoSelfServiceLoginAndLink, - Text: "Sign in and link", + Text: "Continue", Type: Info, } } @@ -119,7 +119,7 @@ func NewInfoLoginWith(provider string) *Message { func NewInfoLoginWithAndLink(provider string) *Message { return &Message{ ID: InfoSelfServiceLoginWithAndLink, - Text: fmt.Sprintf("Sign in with %s and link credential", provider), + Text: fmt.Sprintf("Continue with %s", provider), Type: Info, Context: context(map[string]any{ "provider": provider, diff --git a/text/message_node.go b/text/message_node.go index e2dfb7d6dc32..3af122ae542b 100644 --- a/text/message_node.go +++ b/text/message_node.go @@ -109,11 +109,3 @@ func NewInfoNodeResendOTP() *Message { Type: Info, } } - -func NewInfoNodeLoginAndLinkCredential() *Message { - return &Message{ - ID: InfoNodeLabelLoginAndLinkCredential, - Text: "Login and link credential", - Type: Info, - } -} diff --git a/text/message_validation.go b/text/message_validation.go index 28396180c0c5..479919a63f02 100644 --- a/text/message_validation.go +++ b/text/message_validation.go @@ -267,7 +267,13 @@ func NewErrorValidationDuplicateCredentials() *Message { } } -func NewErrorValidationDuplicateCredentialsWithHints(availableCredentialTypes []string, availableOIDCProviders []string, credentialIdentifierHint string) *Message { +type DuplicateCredentialsFlow string + +const DuplicateCredentialsSignInFlow DuplicateCredentialsFlow = "signing in" +const DuplicateCredentialsSignUpFlow DuplicateCredentialsFlow = "signing up" +const DuplicateCredentialsSettingsFlow DuplicateCredentialsFlow = "linking" + +func NewErrorValidationDuplicateCredentialsWithHints(availableCredentialTypes []string, availableOIDCProviders []string, credentialIdentifierHint string, flow DuplicateCredentialsFlow) *Message { identifier := credentialIdentifierHint if identifier == "" { identifier = "an email, phone, or username" @@ -277,7 +283,7 @@ func NewErrorValidationDuplicateCredentialsWithHints(availableCredentialTypes [] oidcProviders = append(oidcProviders, cases.Title(language.English).String(provider)) } - reason := fmt.Sprintf("You tried signing in with %s which is already in use by another account.", identifier) + reason := fmt.Sprintf("You tried %s with %s which is already in use by another account.", flow, identifier) if len(availableCredentialTypes) > 0 { humanReadable := make(map[string]struct{}, len(availableCredentialTypes)) for _, cred := range availableCredentialTypes { @@ -299,8 +305,9 @@ func NewErrorValidationDuplicateCredentialsWithHints(availableCredentialTypes [] } reason += fmt.Sprintf(" You can sign in using %s.", strings.Join(maps.Keys(humanReadable), ", ")) } + if len(oidcProviders) > 0 { - reason += fmt.Sprintf(" You can sign in using one of the following social sign in providers: %s.", strings.Join(oidcProviders, ", ")) + reason += fmt.Sprintf(" Your social sign in providers are: %s.", strings.Join(oidcProviders, ", ")) } return &Message{