diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index f7658bd6e086..a2b1850a87fa 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -162,7 +162,7 @@ func init() { "NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(), "NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(), "NewRegistrationEmailWithCodeSent": text.NewRegistrationEmailWithCodeSent(), - "NewLoginEmailWithCodeSent": text.NewLoginEmailWithCodeSent(), + "NewLoginCodeSent": text.NewLoginCodeSent(), "NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed": text.NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed(), "NewErrorValidationLoginCodeInvalidOrAlreadyUsed": text.NewErrorValidationLoginCodeInvalidOrAlreadyUsed(), "NewErrorValidationNoCodeUser": text.NewErrorValidationNoCodeUser(), diff --git a/courier/courier.go b/courier/courier.go index 231b4007060f..cb5acede6aee 100644 --- a/courier/courier.go +++ b/courier/courier.go @@ -47,10 +47,10 @@ type ( } courier struct { - courierChannels map[string]Channel - deps Dependencies - failOnDispatchError bool - backoff backoff.BackOff + deps Dependencies + failOnDispatchError bool + backoff backoff.BackOff + newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error) } ) @@ -58,31 +58,11 @@ func NewCourier(ctx context.Context, deps Dependencies) (Courier, error) { return NewCourierWithCustomTemplates(ctx, deps, NewEmailTemplateFromMessage) } -func NewCourierWithCustomTemplates(ctx context.Context, deps Dependencies, newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error)) (Courier, error) { - cs, err := deps.CourierConfig().CourierChannels(ctx) - if err != nil { - return nil, err - } - channels := make(map[string]Channel, len(cs)) - for _, c := range cs { - switch c.Type { - case "smtp": - ch, err := NewSMTPChannelWithCustomTemplates(deps, c.SMTPConfig, newEmailTemplateFromMessage) - if err != nil { - return nil, err - } - channels[ch.ID()] = ch - case "http": - channels[c.ID] = newHttpChannel(c.ID, c.RequestConfig, deps) - default: - return nil, errors.Errorf("unknown courier channel type: %s", c.Type) - } - } - +func NewCourierWithCustomTemplates(_ context.Context, deps Dependencies, newEmailTemplateFromMessage func(d template.Dependencies, msg Message) (EmailTemplate, error)) (Courier, error) { return &courier{ - deps: deps, - backoff: backoff.NewExponentialBackOff(), - courierChannels: channels, + deps: deps, + backoff: backoff.NewExponentialBackOff(), + newEmailTemplateFromMessage: newEmailTemplateFromMessage, }, nil } diff --git a/courier/courier_dispatcher.go b/courier/courier_dispatcher.go index 47399369c3c3..a0f75954d68e 100644 --- a/courier/courier_dispatcher.go +++ b/courier/courier_dispatcher.go @@ -9,6 +9,33 @@ import ( "github.com/pkg/errors" ) +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") +} + func (c *courier) DispatchMessage(ctx context.Context, msg Message) error { logger := c.deps.Logger(). WithField("message_id", msg.ID). @@ -24,9 +51,9 @@ func (c *courier) DispatchMessage(ctx context.Context, msg Message) error { return err } - channel, ok := c.courierChannels[msg.Channel.String()] - if !ok { - return errors.Errorf("message %s has unknown channel %q", msg.ID.String(), msg.Channel) + channel, err := c.channels(ctx, msg.Channel.String()) + if err != nil { + return err } logger = logger. @@ -80,6 +107,9 @@ func (c *courier) DispatchQueue(ctx context.Context) error { 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). diff --git a/courier/sms_templates.go b/courier/sms_templates.go index b560542d53c9..12c55ceed751 100644 --- a/courier/sms_templates.go +++ b/courier/sms_templates.go @@ -40,6 +40,12 @@ func NewSMSTemplateFromMessage(d template.Dependencies, m Message) (SMSTemplate, 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) diff --git a/courier/template/courier/builtin/templates/login_code/valid/email.body.gotmpl b/courier/template/courier/builtin/templates/login_code/valid/email.body.gotmpl index 505684b9849b..0b1b3abd30b8 100644 --- a/courier/template/courier/builtin/templates/login_code/valid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/login_code/valid/email.body.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please login to your account by entering the following code: +Login to your account with the following code: {{ .LoginCode }} + +It expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/login_code/valid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/login_code/valid/email.body.plaintext.gotmpl index 505684b9849b..0b1b3abd30b8 100644 --- a/courier/template/courier/builtin/templates/login_code/valid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/login_code/valid/email.body.plaintext.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please login to your account by entering the following code: +Login to your account with the following code: {{ .LoginCode }} + +It expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/login_code/valid/email.subject.gotmpl b/courier/template/courier/builtin/templates/login_code/valid/email.subject.gotmpl index 19d7bfd57d49..0015e66086c9 100644 --- a/courier/template/courier/builtin/templates/login_code/valid/email.subject.gotmpl +++ b/courier/template/courier/builtin/templates/login_code/valid/email.subject.gotmpl @@ -1 +1 @@ -Login to your account +Use code {{ .LoginCode }} to log in diff --git a/courier/template/courier/builtin/templates/login_code/valid/sms.body.gotmpl b/courier/template/courier/builtin/templates/login_code/valid/sms.body.gotmpl index 5b88dde9a382..ca6d35ca9eac 100644 --- a/courier/template/courier/builtin/templates/login_code/valid/sms.body.gotmpl +++ b/courier/template/courier/builtin/templates/login_code/valid/sms.body.gotmpl @@ -1 +1,3 @@ Your login code is: {{ .LoginCode }} + +It expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/recovery/valid/email.body.gotmpl b/courier/template/courier/builtin/templates/recovery/valid/email.body.gotmpl index 2dbc8d53550b..c4ac7a1d6404 100644 --- a/courier/template/courier/builtin/templates/recovery/valid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/recovery/valid/email.body.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please recover access to your account by clicking the following link: +Recover access to your account by clicking the following link: {{ .RecoveryURL }} + +If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/recovery/valid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/recovery/valid/email.body.plaintext.gotmpl index d41e8963cc50..7bea438ffffa 100644 --- a/courier/template/courier/builtin/templates/recovery/valid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/recovery/valid/email.body.plaintext.gotmpl @@ -1,5 +1,6 @@ -Hi, - -please recover access to your account by clicking the following link: +Recover access to your account by clicking the following link: {{ .RecoveryURL }} + +If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. + diff --git a/courier/template/courier/builtin/templates/recovery_code/valid/email.body.gotmpl b/courier/template/courier/builtin/templates/recovery_code/valid/email.body.gotmpl index 5037d753998e..8427467b81c8 100644 --- a/courier/template/courier/builtin/templates/recovery_code/valid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/recovery_code/valid/email.body.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please recover access to your account by entering the following code: +Recover access to your account by entering the following code: {{ .RecoveryCode }} + +If this was not you, do nothing. This code expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/recovery_code/valid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/recovery_code/valid/email.body.plaintext.gotmpl index 5037d753998e..8427467b81c8 100644 --- a/courier/template/courier/builtin/templates/recovery_code/valid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/recovery_code/valid/email.body.plaintext.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please recover access to your account by entering the following code: +Recover access to your account by entering the following code: {{ .RecoveryCode }} + +If this was not you, do nothing. This code expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/recovery_code/valid/email.subject.gotmpl b/courier/template/courier/builtin/templates/recovery_code/valid/email.subject.gotmpl index 9a47d5f5814a..0890daa9df87 100644 --- a/courier/template/courier/builtin/templates/recovery_code/valid/email.subject.gotmpl +++ b/courier/template/courier/builtin/templates/recovery_code/valid/email.subject.gotmpl @@ -1 +1 @@ -Recover access to your account +Use code {{ .RecoveryCode }} to recover access to your account diff --git a/courier/template/courier/builtin/templates/registration_code/valid/email.body.gotmpl b/courier/template/courier/builtin/templates/registration_code/valid/email.body.gotmpl index 6b9c31799995..b839053d8afb 100644 --- a/courier/template/courier/builtin/templates/registration_code/valid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/registration_code/valid/email.body.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please complete your account registration by entering the following code: +Complete your account registration with the following code: {{ .RegistrationCode }} + +This code expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/registration_code/valid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/registration_code/valid/email.body.plaintext.gotmpl index 6b9c31799995..b839053d8afb 100644 --- a/courier/template/courier/builtin/templates/registration_code/valid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/registration_code/valid/email.body.plaintext.gotmpl @@ -1,5 +1,5 @@ -Hi, - -please complete your account registration by entering the following code: +Complete your account registration with the following code: {{ .RegistrationCode }} + +This code expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/registration_code/valid/email.subject.gotmpl b/courier/template/courier/builtin/templates/registration_code/valid/email.subject.gotmpl index 0f36292619ef..fd48d99a2b19 100644 --- a/courier/template/courier/builtin/templates/registration_code/valid/email.subject.gotmpl +++ b/courier/template/courier/builtin/templates/registration_code/valid/email.subject.gotmpl @@ -1 +1 @@ -Complete your account registration +Use code {{ .RegistrationCode }} to complete your account registration diff --git a/courier/template/courier/builtin/templates/registration_code/valid/sms.body.gotmpl b/courier/template/courier/builtin/templates/registration_code/valid/sms.body.gotmpl new file mode 100644 index 000000000000..24b79fff0f8a --- /dev/null +++ b/courier/template/courier/builtin/templates/registration_code/valid/sms.body.gotmpl @@ -0,0 +1,3 @@ +Your registration code is: {{ .RegistrationCode }} + +It expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/verification/invalid/email.body.gotmpl b/courier/template/courier/builtin/templates/verification/invalid/email.body.gotmpl index bb3d3c1b3d90..18565a8de01d 100644 --- a/courier/template/courier/builtin/templates/verification/invalid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/verification/invalid/email.body.gotmpl @@ -1,6 +1,4 @@ -Hi, - -someone asked to verify this email address, but we were unable to find an account for this address. +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. diff --git a/courier/template/courier/builtin/templates/verification/invalid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/verification/invalid/email.body.plaintext.gotmpl index bb3d3c1b3d90..18565a8de01d 100644 --- a/courier/template/courier/builtin/templates/verification/invalid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/verification/invalid/email.body.plaintext.gotmpl @@ -1,6 +1,4 @@ -Hi, - -someone asked to verify this email address, but we were unable to find an account for this address. +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. diff --git a/courier/template/courier/builtin/templates/verification/valid/email.body.gotmpl b/courier/template/courier/builtin/templates/verification/valid/email.body.gotmpl index d8e3168e5a78..d111cc47dd32 100644 --- a/courier/template/courier/builtin/templates/verification/valid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/verification/valid/email.body.gotmpl @@ -1,3 +1,5 @@ -Hi, please verify your account by clicking the following link: +Verify your account by opening the following link: {{ .VerificationURL }} + +If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/verification/valid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/verification/valid/email.body.plaintext.gotmpl index 4d915646c61e..ac28e51a4878 100644 --- a/courier/template/courier/builtin/templates/verification/valid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/verification/valid/email.body.plaintext.gotmpl @@ -1,4 +1,5 @@ -Hi, please verify your account by clicking the following link: +Verify your account by opening the following link: {{ .VerificationURL }} +If this was not you, do nothing. This link expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/courier/builtin/templates/verification_code/valid/email.body.gotmpl b/courier/template/courier/builtin/templates/verification_code/valid/email.body.gotmpl index f564f53378af..fc8d55ab5c0a 100644 --- a/courier/template/courier/builtin/templates/verification_code/valid/email.body.gotmpl +++ b/courier/template/courier/builtin/templates/verification_code/valid/email.body.gotmpl @@ -1,9 +1,9 @@ -Hi, - -please verify your account by entering the following code: +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. diff --git a/courier/template/courier/builtin/templates/verification_code/valid/email.body.plaintext.gotmpl b/courier/template/courier/builtin/templates/verification_code/valid/email.body.plaintext.gotmpl index 58a5f39d4410..b6dde063b3c1 100644 --- a/courier/template/courier/builtin/templates/verification_code/valid/email.body.plaintext.gotmpl +++ b/courier/template/courier/builtin/templates/verification_code/valid/email.body.plaintext.gotmpl @@ -1,9 +1,9 @@ -Hi, - -please verify your account by entering the following code: +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. diff --git a/courier/template/courier/builtin/templates/verification_code/valid/email.subject.gotmpl b/courier/template/courier/builtin/templates/verification_code/valid/email.subject.gotmpl index 3f0aceec1a72..b71c78404bde 100644 --- a/courier/template/courier/builtin/templates/verification_code/valid/email.subject.gotmpl +++ b/courier/template/courier/builtin/templates/verification_code/valid/email.subject.gotmpl @@ -1 +1 @@ -Please verify your email address +Use code {{ .VerificationCode }} to verify your account diff --git a/courier/template/courier/builtin/templates/verification_code/valid/sms.body.gotmpl b/courier/template/courier/builtin/templates/verification_code/valid/sms.body.gotmpl index 0469598d2d58..60474c5eb57b 100644 --- a/courier/template/courier/builtin/templates/verification_code/valid/sms.body.gotmpl +++ b/courier/template/courier/builtin/templates/verification_code/valid/sms.body.gotmpl @@ -1 +1,3 @@ Your verification code is: {{ .VerificationCode }} + +If this was not you, do nothing. It expires in {{ .ExpiresInMinutes }} minutes. diff --git a/courier/template/email/login_code_valid.go b/courier/template/email/login_code_valid.go index b09f70f8625e..1863ea39fbe6 100644 --- a/courier/template/email/login_code_valid.go +++ b/courier/template/email/login_code_valid.go @@ -23,6 +23,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/email/recovery_code_valid.go b/courier/template/email/recovery_code_valid.go index 4e8992da3d1d..313613ab714d 100644 --- a/courier/template/email/recovery_code_valid.go +++ b/courier/template/email/recovery_code_valid.go @@ -23,6 +23,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/email/recovery_valid.go b/courier/template/email/recovery_valid.go index f82a40b4f919..ae681b7c875b 100644 --- a/courier/template/email/recovery_valid.go +++ b/courier/template/email/recovery_valid.go @@ -23,6 +23,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/email/registration_code_valid.go b/courier/template/email/registration_code_valid.go index ec52362a8990..7994c1484afc 100644 --- a/courier/template/email/registration_code_valid.go +++ b/courier/template/email/registration_code_valid.go @@ -23,6 +23,7 @@ type ( RegistrationCode string `json:"registration_code"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/email/verification_code_valid.go b/courier/template/email/verification_code_valid.go index bd2045a03d25..d9b527901519 100644 --- a/courier/template/email/verification_code_valid.go +++ b/courier/template/email/verification_code_valid.go @@ -24,6 +24,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/email/verification_valid.go b/courier/template/email/verification_valid.go index eb9578261d83..c8d24c4372ac 100644 --- a/courier/template/email/verification_valid.go +++ b/courier/template/email/verification_valid.go @@ -23,6 +23,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/sms/login_code_valid.go b/courier/template/sms/login_code_valid.go index f365a6ba2800..f19e6e567771 100644 --- a/courier/template/sms/login_code_valid.go +++ b/courier/template/sms/login_code_valid.go @@ -22,6 +22,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/sms/login_code_valid_test.go b/courier/template/sms/login_code_valid_test.go index a2915c750c89..48e24475c8d7 100644 --- a/courier/template/sms/login_code_valid_test.go +++ b/courier/template/sms/login_code_valid_test.go @@ -25,7 +25,7 @@ func TestNewLoginCodeValid(t *testing.T) { tpl := sms.NewLoginCodeValid(reg, &sms.LoginCodeValidModel{To: expectedPhone, LoginCode: otp}) - expectedBody := fmt.Sprintf("Your login code is: %s\n", 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) diff --git a/courier/template/sms/registration_code_valid.go b/courier/template/sms/registration_code_valid.go new file mode 100644 index 000000000000..7413f75a94de --- /dev/null +++ b/courier/template/sms/registration_code_valid.go @@ -0,0 +1,55 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package sms + +import ( + "context" + "encoding/json" + "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]interface{} `json:"identity"` + RequestURL string `json:"request_url"` + TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` + } +) + +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 +} diff --git a/courier/template/sms/registration_code_valid_test.go b/courier/template/sms/registration_code_valid_test.go new file mode 100644 index 000000000000..1299c113f331 --- /dev/null +++ b/courier/template/sms/registration_code_valid_test.go @@ -0,0 +1,37 @@ +// 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/internal" +) + +func TestNewRegistrationCodeValid(t *testing.T) { + _, reg := internal.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) +} diff --git a/courier/template/sms/verification_code.go b/courier/template/sms/verification_code.go index 4204df0ac4c8..13c24be534cd 100644 --- a/courier/template/sms/verification_code.go +++ b/courier/template/sms/verification_code.go @@ -23,6 +23,7 @@ type ( Identity map[string]interface{} `json:"identity"` RequestURL string `json:"request_url"` TransientPayload map[string]interface{} `json:"transient_payload"` + ExpiresInMinutes int `json:"expires_in_minutes"` } ) diff --git a/courier/template/sms/verification_code_test.go b/courier/template/sms/verification_code_test.go index fc2bb892e6b2..4a31ae7f6ce3 100644 --- a/courier/template/sms/verification_code_test.go +++ b/courier/template/sms/verification_code_test.go @@ -25,7 +25,7 @@ func TestNewOTPMessage(t *testing.T) { tpl := sms.NewVerificationCodeValid(reg, &sms.VerificationCodeValidModel{To: expectedPhone, VerificationCode: otp}) - expectedBody := fmt.Sprintf("Your verification code is: %s\n", 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) diff --git a/driver/config/config.go b/driver/config/config.go index d1e28d166b98..dd7a6a42f00d 100644 --- a/driver/config/config.go +++ b/driver/config/config.go @@ -69,6 +69,7 @@ const ( ViperKeyCourierTemplatesVerificationCodeValidEmail = "courier.templates.verification_code.valid.email" ViperKeyCourierTemplatesVerificationCodeValidSMS = "courier.templates.verification_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" @@ -322,6 +323,7 @@ type ( CourierTemplatesRegistrationCodeValid(ctx context.Context) *CourierEmailTemplate CourierSMSTemplatesVerificationCodeValid(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 @@ -1164,6 +1166,10 @@ func (p *Config) CourierSMSTemplatesLoginCodeValid(ctx context.Context) *Courier 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) } diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 119ed3766fce..abe5a4e7ac5b 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -2043,6 +2043,9 @@ "properties": { "email": { "$ref": "#/definitions/emailCourierTemplate" + }, + "sms": { + "$ref": "#/definitions/smsCourierTemplate" } }, "required": ["email"] 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/courier.go b/internal/testhelpers/courier.go index fcb77f47005d..f4779adcd6d1 100644 --- a/internal/testhelpers/courier.go +++ b/internal/testhelpers/courier.go @@ -20,6 +20,7 @@ func CourierExpectMessage(ctx context.Context, t *testing.T, reg interface { courier.PersistenceProvider }, recipient, subject string, ) *courier.Message { + t.Helper() messages, total, _, err := reg.CourierPersister().ListMessages(ctx, courier.ListCourierMessagesParameters{ Recipient: recipient, }, []keysetpagination.Option{}) @@ -31,7 +32,7 @@ func CourierExpectMessage(ctx context.Context, t *testing.T, reg interface { }) for _, m := range messages { - if strings.EqualFold(m.Recipient, recipient) && (strings.EqualFold(m.Subject, subject) || strings.Contains(m.Body, subject)) { + if strings.EqualFold(m.Recipient, recipient) && (strings.Contains(m.Subject, subject) || strings.Contains(m.Body, subject)) { return &m } } diff --git a/persistence/sql/migratest/testdata/20200402142539_testdata.sql b/persistence/sql/migratest/testdata/20200402142539_testdata.sql index 36781f7a493a..6d67a5358c80 100644 --- a/persistence/sql/migratest/testdata/20200402142539_testdata.sql +++ b/persistence/sql/migratest/testdata/20200402142539_testdata.sql @@ -3,13 +3,13 @@ INSERT INTO identities (id, traits_schema_id, traits, created_at, updated_at) VA 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, please verify your account by clicking the following link: +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, please verify your account by clicking 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, please verify your account by clicking 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, please verify your account by clicking 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 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'); diff --git a/persistence/sql/migratest/testdata/20210307130558_testdata.sql b/persistence/sql/migratest/testdata/20210307130558_testdata.sql index e92788541f35..b256c62cb945 100644 --- a/persistence/sql/migratest/testdata/20210307130558_testdata.sql +++ b/persistence/sql/migratest/testdata/20210307130558_testdata.sql @@ -1 +1 @@ -INSERT INTO courier_messages (id, type, status, body, subject, recipient, created_at, updated_at) VALUES ('34948489-31dc-454a-ab3b-b2dcc75a787f', 1, 2, 'Hi, please verify your account by clicking 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 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'); diff --git a/persistence/sql/migratest/testdata/20221205095201_testdata.sql b/persistence/sql/migratest/testdata/20221205095201_testdata.sql index de20fc952a73..fee47fdd5e8d 100644 --- a/persistence/sql/migratest/testdata/20221205095201_testdata.sql +++ b/persistence/sql/migratest/testdata/20221205095201_testdata.sql @@ -17,7 +17,7 @@ VALUES 'd9d4401c-08a1-434c-8ab5-4a7edefde351', 1, 2, - 'Hi, please verify your account by clicking the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/u9ZcBr5HbRTR8f53Qj2Ng3KR8Mv1Zjdb', + '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', @@ -38,4 +38,4 @@ VALUES '884f556e-eb3a-4b9f-bee3-11345642c6c0', '2013-10-07 08:23:19', '2013-10-07 08:23:19' - ) \ No newline at end of file + ) diff --git a/selfservice/strategy/code/code_sender.go b/selfservice/strategy/code/code_sender.go index 02fda31f3d0a..20e392836ce7 100644 --- a/selfservice/strategy/code/code_sender.go +++ b/selfservice/strategy/code/code_sender.go @@ -101,21 +101,35 @@ func (s *Sender) SendCode(ctx context.Context, f flow.Flow, id *identity.Identit return err } - emailModel := email.RegistrationCodeValidModel{ - To: address.To, - RegistrationCode: rawCode, - Traits: model, - RequestURL: f.GetRequestURL(), - TransientPayload: transientPayload, - } - s.deps.Audit(). WithField("registration_flow_id", code.FlowID). WithField("registration_code_id", code.ID). WithSensitiveField("registration_code", rawCode). Info("Sending out registration email with code.") - if err := s.send(ctx, string(address.Via), email.NewRegistrationCodeValid(s.deps, &emailModel)); err != nil { + var t courier.Template + switch address.Via { + case identity.ChannelTypeEmail: + t = email.NewRegistrationCodeValid(s.deps, &email.RegistrationCodeValidModel{ + To: address.To, + RegistrationCode: rawCode, + Traits: model, + RequestURL: f.GetRequestURL(), + TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), + }) + case identity.ChannelTypeSMS: + t = sms.NewRegistrationCodeValid(s.deps, &sms.RegistrationCodeValidModel{ + To: address.To, + RegistrationCode: rawCode, + Identity: model, + RequestURL: f.GetRequestURL(), + TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), + }) + } + + if err := s.send(ctx, string(address.Via), t); err != nil { return errors.WithStack(err) } @@ -153,6 +167,7 @@ func (s *Sender) SendCode(ctx context.Context, f flow.Flow, id *identity.Identit Identity: model, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), }) case identity.ChannelTypeSMS: t = sms.NewLoginCodeValid(s.deps, &sms.LoginCodeValidModel{ @@ -161,6 +176,7 @@ func (s *Sender) SendCode(ctx context.Context, f flow.Flow, id *identity.Identit Identity: model, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), }) } @@ -266,6 +282,7 @@ func (s *Sender) SendRecoveryCodeTo(ctx context.Context, i *identity.Identity, c Identity: model, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), } return s.send(ctx, string(code.RecoveryAddress.Via), email.NewRecoveryCodeValid(s.deps, &emailModel)) @@ -371,6 +388,7 @@ func (s *Sender) SendVerificationCodeTo(ctx context.Context, f *verification.Flo VerificationCode: codeString, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), }) case identity.ChannelTypeSMS: t = sms.NewVerificationCodeValid(s.deps, &sms.VerificationCodeValidModel{ @@ -379,6 +397,7 @@ func (s *Sender) SendVerificationCodeTo(ctx context.Context, f *verification.Flo Identity: model, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.deps.Config().SelfServiceCodeMethodLifespan(ctx).Minutes()), }) default: return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Expected email or sms but got %s", code.VerifiableAddress.Via)) diff --git a/selfservice/strategy/code/code_sender_test.go b/selfservice/strategy/code/code_sender_test.go index e5ba75826eb5..4e306a78cf02 100644 --- a/selfservice/strategy/code/code_sender_test.go +++ b/selfservice/strategy/code/code_sender_test.go @@ -66,7 +66,7 @@ func TestSender(t *testing.T) { require.Len(t, messages, 2) assert.EqualValues(t, "tracked@ory.sh", messages[0].Recipient) - assert.Contains(t, messages[0].Subject, "Recover access to your account") + assert.Contains(t, messages[0].Subject, "Use code") assert.Regexp(t, testhelpers.CodeRegex, messages[0].Body) @@ -122,7 +122,7 @@ func TestSender(t *testing.T) { require.Len(t, messages, 2) assert.EqualValues(t, "tracked@ory.sh", messages[0].Recipient) - assert.Contains(t, messages[0].Subject, "Please verify your email address") + assert.Contains(t, messages[0].Subject, "Use code") assert.Regexp(t, testhelpers.CodeRegex, messages[0].Body) diff --git a/selfservice/strategy/code/strategy.go b/selfservice/strategy/code/strategy.go index 1e8fb5cb6af4..aaac228508fd 100644 --- a/selfservice/strategy/code/strategy.go +++ b/selfservice/strategy/code/strategy.go @@ -352,7 +352,7 @@ func (s *Strategy) populateEmailSentFlow(ctx context.Context, f flow.Flow) error case flow.LoginFlow: route = login.RouteSubmitFlow codeMetaLabel = text.NewInfoNodeLabelLoginCode() - message = text.NewLoginEmailWithCodeSent() + message = text.NewLoginCodeSent() // preserve the login identifier that was submitted // so we can retry the code flow with the same data diff --git a/selfservice/strategy/code/strategy_login_test.go b/selfservice/strategy/code/strategy_login_test.go index d93972162852..95952f45d7de 100644 --- a/selfservice/strategy/code/strategy_login_test.go +++ b/selfservice/strategy/code/strategy_login_test.go @@ -236,8 +236,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", stringsx.ToUpperInitial(s.identityEmail)) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -257,8 +257,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -284,8 +284,8 @@ func TestLoginCodeStrategy(t *testing.T) { t.Logf("s.body: %s", s.body) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -450,8 +450,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -478,8 +478,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -505,8 +505,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -564,8 +564,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -616,8 +616,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -652,8 +652,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -663,8 +663,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message = testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message = testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode2 := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode2) @@ -713,8 +713,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, loginEmail, "Login to your account") - require.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, loginEmail, "Use code") + require.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) require.NotEmpty(t, loginCode) @@ -795,8 +795,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("identifier", s.identityEmail) }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.identityEmail, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -837,8 +837,8 @@ func TestLoginCodeStrategy(t *testing.T) { v.Set("method", "code") }, false, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") loginCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, loginCode) @@ -988,8 +988,8 @@ func TestLoginCodeStrategy(t *testing.T) { var message *courier.Message if !strings.HasPrefix(identifier, "+") { // email - message = testhelpers.CourierExpectMessage(ctx, t, reg, x.GracefulNormalization(identifier), "Login to your account") - assert.Contains(t, message.Body, "please login to your account by entering the following code") + message = testhelpers.CourierExpectMessage(ctx, t, reg, x.GracefulNormalization(identifier), "Use code") + assert.Contains(t, message.Body, "Login to your account with the following code") } else { // SMS message = testhelpers.CourierExpectMessage(ctx, t, reg, x.GracefulNormalization(identifier), "Your login code is:") diff --git a/selfservice/strategy/code/strategy_recovery_test.go b/selfservice/strategy/code/strategy_recovery_test.go index 483e8dfc6f89..9b55016daebb 100644 --- a/selfservice/strategy/code/strategy_recovery_test.go +++ b/selfservice/strategy/code/strategy_recovery_test.go @@ -242,8 +242,8 @@ func TestRecovery(t *testing.T) { assert.Len(t, gjson.Get(recoverySubmissionResponse, "ui.messages").Array(), 1, "%s", recoverySubmissionResponse) assertx.EqualAsJSON(t, text.NewRecoveryEmailWithCodeSent(), json.RawMessage(gjson.Get(recoverySubmissionResponse, "ui.messages.0").Raw)) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") - assert.Contains(t, message.Body, "please recover access to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") + assert.Contains(t, message.Body, "Recover access to your account by entering") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, recoveryCode) @@ -316,7 +316,7 @@ func TestRecovery(t *testing.T) { formPayload.Set("transient_payload", templatePayload) body, _ := testhelpers.RecoveryMakeRequest(t, false, f, client, formPayload.Encode()) - message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Use code") assert.Equal(t, templatePayload, gjson.GetBytes(message.TemplateData, "transient_payload").String(), "should pass transient payload to email template") @@ -607,7 +607,7 @@ func TestRecovery(t *testing.T) { addr, err := reg.IdentityPool().FindVerifiableAddressByValue(context.Background(), identity.VerifiableAddressTypeEmail, email) assert.NoError(t, err) - emailText := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Recover access to your account") + emailText := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, emailText, 1) // Deactivate the identity @@ -646,7 +646,7 @@ func TestRecovery(t *testing.T) { actual := expectSuccessfulRecovery(t, cl, RecoveryClientTypeBrowser, func(v url.Values) { v.Set("email", email) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) cl.CheckRedirect = func(req *http.Request, via []*http.Request) error { @@ -707,7 +707,7 @@ func TestRecovery(t *testing.T) { v.Set("email", recoveryEmail) }, http.StatusOK) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) form := withCSRFToken(t, testCase.ClientType, actual, url.Values{ @@ -818,8 +818,8 @@ func TestRecovery(t *testing.T) { initialFlowId := gjson.Get(body, "id") - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") - assert.Contains(t, message.Body, "please recover access to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") + assert.Contains(t, message.Body, "Recover access to your account by entering") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) @@ -873,7 +873,7 @@ func TestRecovery(t *testing.T) { assert.True(t, gjson.Get(body, "ui.nodes.#(attributes.name==code)").Exists()) assert.Equal(t, recoveryEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) submitRecoveryCode(t, c, body, RecoveryClientTypeBrowser, recoveryCode, http.StatusOK) @@ -892,14 +892,14 @@ func TestRecovery(t *testing.T) { require.NotEmpty(t, action) assert.Equal(t, recoveryEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message1 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message1 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode1 := testhelpers.CourierExpectCodeInMessage(t, message1, 1) body = resendRecoveryCode(t, c, body, RecoveryClientTypeBrowser, http.StatusOK) assert.True(t, gjson.Get(body, "ui.nodes.#(attributes.name==code)").Exists()) assert.Equal(t, recoveryEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message2 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message2 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode2 := testhelpers.CourierExpectCodeInMessage(t, message2, 1) body = submitRecoveryCode(t, c, body, RecoveryClientTypeBrowser, recoveryCode1, http.StatusOK) @@ -943,7 +943,7 @@ func TestRecovery(t *testing.T) { v.Set("email", recoveryEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) action := gjson.Get(body, "ui.action").String() @@ -975,7 +975,7 @@ func TestRecovery(t *testing.T) { v.Set("email", recoveryEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) action := gjson.Get(body, "ui.action").String() @@ -1117,8 +1117,8 @@ func TestRecovery_WithContinueWith(t *testing.T) { assert.Len(t, gjson.Get(recoverySubmissionResponse, "ui.messages").Array(), 1, "%s", recoverySubmissionResponse) assertx.EqualAsJSON(t, text.NewRecoveryEmailWithCodeSent(), json.RawMessage(gjson.Get(recoverySubmissionResponse, "ui.messages.0").Raw)) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") - assert.Contains(t, message.Body, "please recover access to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") + assert.Contains(t, message.Body, "Recover access to your account by entering") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, recoveryCode) @@ -1435,7 +1435,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { addr, err := reg.IdentityPool().FindVerifiableAddressByValue(context.Background(), identity.VerifiableAddressTypeEmail, email) assert.NoError(t, err) - emailText := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Recover access to your account") + emailText := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, emailText, 1) // Deactivate the identity @@ -1478,7 +1478,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { actual := submitRecoveryForm(t, cl, testCase.ClientType, func(v url.Values) { v.Set("email", email) }, http.StatusOK) - message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, email, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) submitCodeAndExpectRedirectToSettings(t, cl, testCase.ClientType, recoveryCode, actual) @@ -1555,7 +1555,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { v.Set("email", recoveryEmail) }, http.StatusOK) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) form := withCSRFToken(t, testCase.ClientType, actual, url.Values{ @@ -1631,7 +1631,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { v.Set("email", recoveryEmail) }, http.StatusOK) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) action := gjson.Get(actual, "ui.action").String() @@ -1744,8 +1744,8 @@ func TestRecovery_WithContinueWith(t *testing.T) { initialFlowId := gjson.Get(body, "id") - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") - assert.Contains(t, message.Body, "please recover access to your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") + assert.Contains(t, message.Body, "Recover access to your account by entering") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) @@ -1812,7 +1812,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { assert.True(t, gjson.Get(body, "ui.nodes.#(attributes.name==code)").Exists()) assert.Equal(t, recoveryEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) submitCodeAndExpectRedirectToSettings(t, c, testCase.ClientType, recoveryCode, body) @@ -1835,14 +1835,14 @@ func TestRecovery_WithContinueWith(t *testing.T) { require.NotEmpty(t, action) assert.Equal(t, recoveryEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message1 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message1 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode1 := testhelpers.CourierExpectCodeInMessage(t, message1, 1) body = resendRecoveryCode(t, c, body, testCase.ClientType, http.StatusOK) assert.True(t, gjson.Get(body, "ui.nodes.#(attributes.name==code)").Exists()) assert.Equal(t, recoveryEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message2 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message2 := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode2 := testhelpers.CourierExpectCodeInMessage(t, message2, 1) body = submitRecoveryCode(t, c, body, testCase.ClientType, recoveryCode1, http.StatusOK) @@ -1892,7 +1892,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { v.Set("email", recoveryEmail) }, http.StatusOK) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) action := gjson.Get(body, "ui.action").String() @@ -1920,7 +1920,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { v.Set("email", recoveryEmail) }, http.StatusOK) - message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") + message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Use code") recoveryCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) action := gjson.Get(body, "ui.action").String() diff --git a/selfservice/strategy/code/strategy_registration_test.go b/selfservice/strategy/code/strategy_registration_test.go index c5988047d0a3..b5f0d6e02c9f 100644 --- a/selfservice/strategy/code/strategy_registration_test.go +++ b/selfservice/strategy/code/strategy_registration_test.go @@ -274,8 +274,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { // 2. Submit Identifier (email) state = registerNewUser(ctx, t, state, tc.apiType, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, state.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, state.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) @@ -307,8 +307,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { // 2. Submit Identifier (email) state = registerNewUser(ctx, t, state, tc.apiType, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, sourceMail, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, sourceMail, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) @@ -351,8 +351,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { require.Equal(t, "code", val, body) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) @@ -372,12 +372,12 @@ func TestRegistrationCodeStrategy(t *testing.T) { } else { require.NotEmptyf(t, csrfToken, "expected to find the csrf_token but got %s", body) } - require.Containsf(t, gjson.Get(body, "ui.messages").String(), "An email containing a code has been sent to the email address you provided.", "%s", body) + require.Containsf(t, gjson.Get(body, "ui.messages").String(), "A code has been sent to the address(es) you provided", "%s", body) }) // get the new code from email - message = testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message = testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode2 := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode2) @@ -410,8 +410,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { // 2. Submit Identifier (email) s = registerNewUser(ctx, t, s, tc.apiType, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) @@ -439,8 +439,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { // 2. Submit Identifier (email) s = registerNewUser(ctx, t, s, tc.apiType, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) @@ -573,8 +573,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { // 2. Submit Identifier (email) state = registerNewUser(ctx, t, state, tc.apiType, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, state.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, state.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) @@ -597,8 +597,8 @@ func TestRegistrationCodeStrategy(t *testing.T) { // 2. Submit Identifier (email) s = registerNewUser(ctx, t, s, tc.apiType, nil) - message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Complete your account registration") - assert.Contains(t, message.Body, "please complete your account registration by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, s.email, "Use code") + assert.Contains(t, message.Body, "Complete your account registration with the following code") registrationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) assert.NotEmpty(t, registrationCode) diff --git a/selfservice/strategy/code/strategy_verification_test.go b/selfservice/strategy/code/strategy_verification_test.go index 322dc56eabd2..5bd8417d180d 100644 --- a/selfservice/strategy/code/strategy_verification_test.go +++ b/selfservice/strategy/code/strategy_verification_test.go @@ -285,8 +285,8 @@ func TestVerification(t *testing.T) { v.Set("email", verificationEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") - assert.Contains(t, message.Body, "please verify your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") + assert.Contains(t, message.Body, "Verify your account with the following code") code := testhelpers.CourierExpectCodeInMessage(t, message, 1) @@ -310,8 +310,8 @@ func TestVerification(t *testing.T) { assert.EqualValues(t, verificationEmail, gjson.Get(actual, "ui.nodes.#(attributes.name==email).attributes.value").String(), "%s", actual) assertx.EqualAsJSON(t, text.NewVerificationEmailWithCodeSent(), json.RawMessage(gjson.Get(actual, "ui.messages.0").Raw)) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") - assert.Contains(t, message.Body, "please verify your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") + assert.Contains(t, message.Body, "Verify your account with the following code") verificationLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) @@ -374,7 +374,7 @@ func TestVerification(t *testing.T) { } expectSuccess(t, nil, false, false, values) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") verificationLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) code := testhelpers.CourierExpectCodeInMessage(t, message, 1) @@ -442,8 +442,8 @@ func TestVerification(t *testing.T) { v.Set("email", verificationEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") - assert.Contains(t, message.Body, "please verify your account by entering the following code") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") + assert.Contains(t, message.Body, "Verify your account with the following code") verificationLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) @@ -507,7 +507,7 @@ func TestVerification(t *testing.T) { v.Set("email", verificationEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") _ = testhelpers.CourierExpectCodeInMessage(t, message, 1) c := testhelpers.NewClientWithCookies(t) @@ -516,7 +516,7 @@ func TestVerification(t *testing.T) { assert.True(t, gjson.Get(body, "ui.nodes.#(attributes.name==code)").Exists()) assert.Equal(t, verificationEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message = testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message = testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") verificationCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) submitVerificationCode(t, body, c, verificationCode) @@ -527,7 +527,7 @@ func TestVerification(t *testing.T) { v.Set("email", verificationEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") firstCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) c := testhelpers.NewClientWithCookies(t) @@ -536,7 +536,7 @@ func TestVerification(t *testing.T) { assert.True(t, gjson.Get(body, "ui.nodes.#(attributes.name==code)").Exists()) assert.Equal(t, verificationEmail, gjson.Get(body, "ui.nodes.#(attributes.name==email).attributes.value").String()) - message = testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message = testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") secondCode := testhelpers.CourierExpectCodeInMessage(t, message, 1) body, res := submitVerificationCode(t, body, c, firstCode) @@ -587,7 +587,7 @@ func TestVerification(t *testing.T) { body := expectSuccess(t, nil, true, false, func(v url.Values) { v.Set("email", verificationEmail) }) - message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") code := testhelpers.CourierExpectCodeInMessage(t, message, 1) body, res := submitVerificationCode(t, body, c, code) @@ -597,7 +597,7 @@ func TestVerification(t *testing.T) { body = expectSuccess(t, nil, true, false, func(v url.Values) { v.Set("email", verificationEmail) }) - message = testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") + message = testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Use code") code = testhelpers.CourierExpectCodeInMessage(t, message, 1) body, res = submitVerificationCode(t, body, c, code) diff --git a/selfservice/strategy/link/sender.go b/selfservice/strategy/link/sender.go index 41231a721b8b..c289b657e0a1 100644 --- a/selfservice/strategy/link/sender.go +++ b/selfservice/strategy/link/sender.go @@ -202,6 +202,7 @@ func (s *Sender) SendRecoveryTokenTo(ctx context.Context, f *recovery.Flow, i *i Identity: model, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.r.Config().SelfServiceLinkMethodLifespan(ctx).Minutes()), })) } @@ -231,13 +232,14 @@ func (s *Sender) SendVerificationTokenTo(ctx context.Context, f *verification.Fl "token": {token.Token}, }).String() - if err := s.send(ctx, string(address.Via), email.NewVerificationValid(s.r, + if err := s.send(ctx, address.Via, email.NewVerificationValid(s.r, &email.VerificationValidModel{ To: address.Value, VerificationURL: verificationUrl, Identity: model, RequestURL: f.GetRequestURL(), TransientPayload: transientPayload, + ExpiresInMinutes: int(s.r.Config().SelfServiceLinkMethodLifespan(ctx).Minutes()), })); err != nil { return err } diff --git a/selfservice/strategy/link/strategy_recovery_test.go b/selfservice/strategy/link/strategy_recovery_test.go index 5cc6c510d73e..531cb4e77502 100644 --- a/selfservice/strategy/link/strategy_recovery_test.go +++ b/selfservice/strategy/link/strategy_recovery_test.go @@ -506,7 +506,7 @@ func TestRecovery(t *testing.T) { assertx.EqualAsJSON(t, text.NewRecoveryEmailSent(), json.RawMessage(gjson.Get(recoverySubmissionResponse, "ui.messages.0").Raw)) message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") - assert.Contains(t, message.Body, "please recover access to your account by clicking the following link") + assert.Contains(t, message.Body, "Recover access to your account by clicking the following link") recoveryLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) @@ -788,7 +788,7 @@ func TestRecovery(t *testing.T) { }) message := testhelpers.CourierExpectMessage(ctx, t, reg, recoveryEmail, "Recover access to your account") - assert.Contains(t, message.Body, "please recover access to your account by clicking the following link") + assert.Contains(t, message.Body, "Recover access to your account by clicking the following link") recoveryLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) diff --git a/selfservice/strategy/link/strategy_verification_test.go b/selfservice/strategy/link/strategy_verification_test.go index b2fd7c0f405a..ae0e20021338 100644 --- a/selfservice/strategy/link/strategy_verification_test.go +++ b/selfservice/strategy/link/strategy_verification_test.go @@ -248,7 +248,7 @@ func TestVerification(t *testing.T) { }) message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") - assert.Contains(t, message.Body, "Hi, please verify your account by clicking the following link") + assert.Contains(t, message.Body, "Verify your account by opening the following link") verificationLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) @@ -283,7 +283,7 @@ func TestVerification(t *testing.T) { assertx.EqualAsJSON(t, text.NewVerificationEmailSent(), json.RawMessage(gjson.Get(actual, "ui.messages.0").Raw)) message := testhelpers.CourierExpectMessage(ctx, t, reg, verificationEmail, "Please verify your email address") - assert.Contains(t, message.Body, "please verify your account by clicking the following link") + assert.Contains(t, message.Body, "Verify your account by opening the following link") verificationLink := testhelpers.CourierExpectLinkInMessage(t, message, 1) diff --git a/selfservice/strategy/profile/strategy_test.go b/selfservice/strategy/profile/strategy_test.go index d34c3f9e94f6..be9b448a0215 100644 --- a/selfservice/strategy/profile/strategy_test.go +++ b/selfservice/strategy/profile/strategy_test.go @@ -544,7 +544,7 @@ func TestStrategyTraits(t *testing.T) { m, err := reg.CourierPersister().LatestQueuedMessage(context.Background()) require.NoError(t, err) - assert.Contains(t, m.Subject, "verify your email address") + assert.Contains(t, m.Subject, "Use code") } payload := func(newEmail string) func(v url.Values) { diff --git a/test/e2e/cypress/helpers/index.ts b/test/e2e/cypress/helpers/index.ts index 52bcb339ad50..ef61a5546bba 100644 --- a/test/e2e/cypress/helpers/index.ts +++ b/test/e2e/cypress/helpers/index.ts @@ -95,7 +95,7 @@ export const appPrefix = (app) => `[data-testid="app-${app}"] ` export const codeRegex = /(\d{6})/ -export function extractRecoveryCode(body: string): string | null { +export function extractOTPCode(body: string): string | null { const result = codeRegex.exec(body) if (result != null && result.length > 0) { return result[0] diff --git a/test/e2e/cypress/integration/profiles/code/login/error.spec.ts b/test/e2e/cypress/integration/profiles/code/login/error.spec.ts index 244d40ab8132..cf929a1df6fb 100644 --- a/test/e2e/cypress/integration/profiles/code/login/error.spec.ts +++ b/test/e2e/cypress/integration/profiles/code/login/error.spec.ts @@ -78,10 +78,7 @@ context("Login error messages with code method", () => { cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1010014"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1010014"]').should("exist") cy.get(Selectors[app]["code"]).type("123456") cy.submitCodeForm(app) diff --git a/test/e2e/cypress/integration/profiles/code/registration/error.spec.ts b/test/e2e/cypress/integration/profiles/code/registration/error.spec.ts index 4570c1c5cc60..a1e10a196c02 100644 --- a/test/e2e/cypress/integration/profiles/code/registration/error.spec.ts +++ b/test/e2e/cypress/integration/profiles/code/registration/error.spec.ts @@ -69,10 +69,7 @@ context("Registration error messages with code method", () => { cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.get(Selectors[app]["code"]).type("123456") cy.submitCodeForm(app) @@ -90,10 +87,7 @@ context("Registration error messages with code method", () => { cy.get(Selectors[app]["tos"]).click() cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") if (app !== "express") { // the mobile app doesn't render hidden fields in the DOM @@ -131,10 +125,7 @@ context("Registration error messages with code method", () => { cy.get(Selectors[app]["tos"]).click() cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.removeAttribute([Selectors[app]["code"]], "required") @@ -200,10 +191,7 @@ context("Registration error messages with code method", () => { cy.get(Selectors[app]["tos"]).click() cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).then((code) => { cy.get(Selectors[app]["code"]).type(code) diff --git a/test/e2e/cypress/integration/profiles/code/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/code/registration/success.spec.ts index c715d80cd86e..91c48b760f1c 100644 --- a/test/e2e/cypress/integration/profiles/code/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/code/registration/success.spec.ts @@ -98,10 +98,7 @@ context("Registration success with code method", () => { cy.get(Selectors[app]["tos"]).click() cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).then((code) => cy.wrap(code).as("code1"), @@ -166,10 +163,7 @@ context("Registration success with code method", () => { cy.get(Selectors[app]["tos"]).click() cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).should((code) => { cy.get(Selectors[app]["code"]).type(code) @@ -216,10 +210,7 @@ context("Registration success with code method", () => { cy.get(Selectors[app]["tos"]).click() cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).should((code) => { cy.get(Selectors[app]["code"]).type(code) @@ -329,10 +320,7 @@ context("Registration success with code method", () => { cy.get(Selectors[app]["email2"]).type(email2) cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") // intentionally use email 1 to sign up for the account cy.getRegistrationCodeFromEmail(email, { expectedCount: 1 }).should( diff --git a/test/e2e/cypress/integration/profiles/oidc-provider/login.spec.ts b/test/e2e/cypress/integration/profiles/oidc-provider/login.spec.ts index 861cfd0d2a79..d7f73f44872c 100644 --- a/test/e2e/cypress/integration/profiles/oidc-provider/login.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc-provider/login.spec.ts @@ -218,14 +218,20 @@ context("OpenID Provider - change between flows", () => { }) it("switch to recovery flow with password reset", () => { + cy.updateConfigFile((config) => { + config.selfservice.flows.recovery.lifespan = "1m" + config.selfservice.methods.link.config.lifespan = "1m" + config.selfservice.flows.verification.enabled = false + if (!config.selfservice.flows.recovery) { + config.selfservice.flows.recovery = {} + } + config.selfservice.flows.recovery.enabled = true + config.selfservice.flows.settings.privileged_session_max_age = "5m" + return config + }) cy.deleteMail() - cy.longRecoveryLifespan() - cy.longLinkLifespan() - cy.disableVerification() - cy.enableRecovery() cy.useRecoveryStrategy("code") cy.notifyUnknownRecipients("recovery", false) - cy.longPrivilegedSessionTime() const identity = gen.identityWithWebsite() cy.registerApi(identity) diff --git a/test/e2e/cypress/integration/profiles/recovery/code/errors.spec.ts b/test/e2e/cypress/integration/profiles/recovery/code/errors.spec.ts index 23d877049e6c..8da454cfdfe7 100644 --- a/test/e2e/cypress/integration/profiles/recovery/code/errors.spec.ts +++ b/test/e2e/cypress/integration/profiles/recovery/code/errors.spec.ts @@ -1,7 +1,7 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { appPrefix, email, extractRecoveryCode, gen } from "../../../../helpers" +import { appPrefix, email, extractOTPCode, gen } from "../../../../helpers" import { routes as express } from "../../../../helpers/express" import { routes as react } from "../../../../helpers/react" @@ -123,7 +123,7 @@ context("Account Recovery Errors", () => { expect(message.toAddresses).to.have.length(1) expect(message.toAddresses[0].trim()).to.equal(email) - const code = extractRecoveryCode(message.body) + const code = extractOTPCode(message.body) expect(code).to.be.null }) }) diff --git a/test/e2e/cypress/integration/profiles/two-steps/registration/code.spec.ts b/test/e2e/cypress/integration/profiles/two-steps/registration/code.spec.ts index bffafb36ee03..41cc98c03eb8 100644 --- a/test/e2e/cypress/integration/profiles/two-steps/registration/code.spec.ts +++ b/test/e2e/cypress/integration/profiles/two-steps/registration/code.spec.ts @@ -104,10 +104,7 @@ context("Registration success with code method", () => { cy.submitProfileForm(app) cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).then((code) => cy.wrap(code).as("code1"), @@ -187,10 +184,7 @@ context("Registration success with code method", () => { cy.submitProfileForm(app) cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).should((code) => { cy.get(Selectors[app]["code"]).type(code) @@ -234,10 +228,7 @@ context("Registration success with code method", () => { cy.submitProfileForm(app) cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") cy.getRegistrationCodeFromEmail(email).should((code) => { cy.get(Selectors[app]["code"]).type(code) @@ -305,10 +296,7 @@ context("Registration success with code method", () => { cy.submitProfileForm(app) cy.submitCodeForm(app) - cy.get('[data-testid="ui/message/1040005"]').should( - "contain", - "An email containing a code has been sent to the email address you provided", - ) + cy.get('[data-testid="ui/message/1040005"]').should("be.visible") // intentionally use email 1 to sign up for the account cy.getRegistrationCodeFromEmail(email, { expectedCount: 1 }).should( diff --git a/test/e2e/cypress/integration/profiles/verification/registration/errors.spec.ts b/test/e2e/cypress/integration/profiles/verification/registration/errors.spec.ts index 06a68f4af237..f8436c7eae1a 100644 --- a/test/e2e/cypress/integration/profiles/verification/registration/errors.spec.ts +++ b/test/e2e/cypress/integration/profiles/verification/registration/errors.spec.ts @@ -66,7 +66,7 @@ context("Account Verification Registration Errors", () => { it("is unable to verify the email address if the code is incorrect", () => { cy.getMail({ - subject: "Please verify your email address", + body: "Verify your account", email: identity.email, }).then((mail) => { const link = parseHtml(mail.body).querySelector("a") diff --git a/test/e2e/cypress/integration/profiles/verification/settings/error.spec.ts b/test/e2e/cypress/integration/profiles/verification/settings/error.spec.ts index 287f63a931e0..91ae2c5e8259 100644 --- a/test/e2e/cypress/integration/profiles/verification/settings/error.spec.ts +++ b/test/e2e/cypress/integration/profiles/verification/settings/error.spec.ts @@ -72,7 +72,7 @@ context("Account Verification Settings Error", () => { cy.get('button[value="profile"]').click() cy.getMail({ - subject: "Please verify your email address", + body: "Verify your account", email, }).then((mail) => { const link = parseHtml(mail.body).querySelector("a") diff --git a/test/e2e/cypress/integration/profiles/verification/verify/errors.spec.ts b/test/e2e/cypress/integration/profiles/verification/verify/errors.spec.ts index 97fd908a2e22..afb8d9730413 100644 --- a/test/e2e/cypress/integration/profiles/verification/verify/errors.spec.ts +++ b/test/e2e/cypress/integration/profiles/verification/verify/errors.spec.ts @@ -70,12 +70,9 @@ context("Account Verification Error", () => { cy.getMail({ removeMail: true, - subject: "Please verify your email address", + body: "Verify your account", email: identity.email, }).then((message) => { - expect(message.subject).to.equal( - "Please verify your email address", - ) expect(message.toAddresses[0].trim()).to.equal(identity.email) const link = parseHtml(message.body).querySelector("a") @@ -136,7 +133,7 @@ context("Account Verification Error", () => { cy.getMail({ email: identity.email, - subject: "Please verify your email address", + body: "Verify your account", }).then((mail) => { const link = parseHtml(mail.body).querySelector("a") diff --git a/test/e2e/cypress/support/commands.ts b/test/e2e/cypress/support/commands.ts index 89b5c7cb15c5..2d933360c26e 100644 --- a/test/e2e/cypress/support/commands.ts +++ b/test/e2e/cypress/support/commands.ts @@ -4,7 +4,7 @@ import { APP_URL, assertVerifiableAddress, - extractRecoveryCode, + extractOTPCode, gen, KRATOS_ADMIN, KRATOS_PUBLIC, @@ -1112,9 +1112,8 @@ Cypress.Commands.add( ({ expect: { email, redirectTo }, strategy = "code" }) => { cy.getMail({ email, - subject: "Please verify your email address", + body: "Verify your account", }).then((message) => { - expect(message.subject).to.equal("Please verify your email address") expect(message.fromAddress.trim()).to.equal("no-reply@ory.kratos.sh") expect(message.toAddresses).to.have.length(1) expect(message.toAddresses[0].trim()).to.equal(email) @@ -1176,9 +1175,9 @@ Cypress.Commands.add( cy.getMail({ removeMail: true, email, - subject: "Recover access to your account", + body: "Recover access to your account", }).should((message) => { - const code = extractRecoveryCode(message.body) + const code = extractOTPCode(message.body) expect(code).to.not.be.undefined expect(code.length).to.equal(6) cy.wrap(code).as("recoveryCode") @@ -1221,7 +1220,7 @@ Cypress.Commands.add( cy.getMail({ removeMail: true, email, - subject: "Please verify your email address", + body: "Verify your account", }).should((message) => { expect(message.fromAddress.trim()).to.equal("no-reply@ory.kratos.sh") expect(message.toAddresses).to.have.length(1) @@ -1286,6 +1285,7 @@ Cypress.Commands.add( expectedCount = 1, email = undefined, subject = undefined, + body = undefined, }) => { let tries = 0 const req = () => @@ -1313,6 +1313,9 @@ Cypress.Commands.add( if (subject) { filters.push((m: MailMessage) => m.subject.includes(subject)) } + if (body) { + filters.push((m: MailMessage) => m.body.includes(body)) + } const filtered = response.body.mailItems.filter((m) => { return filters.every((f) => f(m)) }) @@ -1525,13 +1528,13 @@ Cypress.Commands.add("getVerificationCodeFromEmail", (email) => { .getMail({ removeMail: true, email, - subject: "Please verify your email address", + body: "Verify your account", }) .should((message) => { expect(message.toAddresses[0].trim()).to.equal(email) }) .then((message) => { - const code = extractRecoveryCode(message.body) + const code = extractOTPCode(message.body) expect(code).to.not.be.undefined expect(code.length).to.equal(6) return code @@ -1543,14 +1546,14 @@ Cypress.Commands.add("getRegistrationCodeFromEmail", (email, opts) => { .getMail({ removeMail: true, email, - subject: "Complete your account registration", + body: "Complete your account registration with the following code", ...opts, }) .should((message) => { expect(message.toAddresses[0].trim()).to.equal(email) }) .then((message) => { - const code = extractRecoveryCode(message.body) + const code = extractOTPCode(message.body) expect(code).to.not.be.undefined expect(code.length).to.equal(6) return code @@ -1562,14 +1565,14 @@ Cypress.Commands.add("getLoginCodeFromEmail", (email, opts) => { .getMail({ removeMail: true, email, - subject: "Login to your account", + body: "Login to your account with the following code", ...opts, }) .should((message) => { expect(message.toAddresses[0].trim()).to.equal(email) }) .then((message) => { - const code = extractRecoveryCode(message.body) + const code = extractOTPCode(message.body) expect(code).to.not.be.undefined expect(code.length).to.equal(6) return code diff --git a/test/e2e/cypress/support/index.d.ts b/test/e2e/cypress/support/index.d.ts index a6a7120937de..667360b32c66 100644 --- a/test/e2e/cypress/support/index.d.ts +++ b/test/e2e/cypress/support/index.d.ts @@ -114,6 +114,7 @@ declare global { expectedCount?: number email?: string subject?: string + body?: string }): Chainable performEmailVerification(opts?: { diff --git a/test/e2e/mock/httptarget/go.mod b/test/e2e/mock/httptarget/go.mod new file mode 100644 index 000000000000..2d66a9ff4f48 --- /dev/null +++ b/test/e2e/mock/httptarget/go.mod @@ -0,0 +1,10 @@ +module github.com/ory/mock + +go 1.22.1 + +require ( + github.com/julienschmidt/httprouter v1.3.0 + github.com/ory/graceful v0.1.3 +) + +require github.com/pkg/errors v0.9.1 // indirect diff --git a/test/e2e/mock/httptarget/go.sum b/test/e2e/mock/httptarget/go.sum new file mode 100644 index 000000000000..e44bf27060c1 --- /dev/null +++ b/test/e2e/mock/httptarget/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/ory/graceful v0.1.3 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc= +github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/test/e2e/mock/httptarget/main.go b/test/e2e/mock/httptarget/main.go new file mode 100644 index 000000000000..c95d0834fb76 --- /dev/null +++ b/test/e2e/mock/httptarget/main.go @@ -0,0 +1,79 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "cmp" + "fmt" + "io" + "log" + "net/http" + "os" + "sync" + + "github.com/julienschmidt/httprouter" + + "github.com/ory/graceful" +) + +var ( + documentsLock sync.RWMutex + documents = make(map[string][]byte) +) + +func main() { + port := cmp.Or(os.Getenv("PORT"), "4471") + server := graceful.WithDefaults(&http.Server{Addr: fmt.Sprintf(":%s", port)}) + register(server) + if err := graceful.Graceful(server.ListenAndServe, server.Shutdown); err != nil { + log.Fatalln(err) + } +} + +func register(server *http.Server) { + router := httprouter.New() + + router.GET("/health", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + _, _ = w.Write([]byte("OK")) + }) + + router.GET("/documents/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + id := ps.ByName("id") + + documentsLock.RLock() + doc, ok := documents[id] + documentsLock.RUnlock() + + if ok { + _, _ = w.Write(doc) + } else { + w.WriteHeader(http.StatusNotFound) + } + }) + + router.PUT("/documents/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + documentsLock.Lock() + defer documentsLock.Unlock() + id := ps.ByName("id") + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + documents[id] = body + }) + + router.DELETE("/documents/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + documentsLock.Lock() + defer documentsLock.Unlock() + id := ps.ByName("id") + + delete(documents, id) + w.WriteHeader(http.StatusNoContent) + }) + + server.Handler = router +} diff --git a/test/e2e/package-lock.json b/test/e2e/package-lock.json index f6452591bda9..7dd8c6416648 100644 --- a/test/e2e/package-lock.json +++ b/test/e2e/package-lock.json @@ -8,7 +8,8 @@ "name": "@ory/kratos-e2e-suite", "version": "0.0.1", "dependencies": { - "@faker-js/faker": "8.4.1", + "@faker-js/faker": "9.0.3", + "@types/promise-retry": "^1.1.6", "async-retry": "1.3.3", "mailhog": "4.16.0", "promise-retry": "^2.0.1" @@ -26,6 +27,7 @@ "got": "11.8.2", "json-schema-to-typescript": "12.0.0", "otplib": "12.0.1", + "phone-number-generator-js": "^1.2.12", "process": "0.11.10", "typescript": "4.7.4", "wait-on": "7.0.1", @@ -99,9 +101,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", - "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.3.tgz", + "integrity": "sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==", "funding": [ { "type": "opencollective", @@ -110,8 +112,8 @@ ], "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" + "node": ">=18.0.0", + "npm": ">=9.0.0" } }, "node_modules/@hapi/hoek": { @@ -329,6 +331,15 @@ "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", "dev": true }, + "node_modules/@types/promise-retry": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/promise-retry/-/promise-retry-1.1.6.tgz", + "integrity": "sha512-EC1+OMXV0PZb0pf+cmyxc43MEP2CDumZe4AfuxWboxxEixztIebknpJPZAX5XlodGF1OY+C1E/RAeNGzxf+bJA==", + "license": "MIT", + "dependencies": { + "@types/retry": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -341,8 +352,7 @@ "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", @@ -356,6 +366,13 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yamljs": { "version": "0.2.31", "resolved": "https://registry.npmjs.org/@types/yamljs/-/yamljs-0.2.31.tgz", @@ -540,9 +557,9 @@ "dev": true }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -780,6 +797,25 @@ "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", "dev": true }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, + "node_modules/class-validator/node_modules/libphonenumber-js": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.9.tgz", + "integrity": "sha512-Zs5wf5HaWzW2/inlupe2tstl0I/Tbqo7lH20ZLr6Is58u7Dz2n+gRFGNlj9/gWxFvNfp9+YyDsiegjNhdixB9A==", + "dev": true, + "license": "MIT" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -1158,14 +1194,16 @@ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, "hasInstallScript": true, + "license": "ISC", "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -1214,6 +1252,29 @@ "node": ">=0.8.0" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true, + "license": "ISC" + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -1966,6 +2027,13 @@ "node": "> 0.8" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.30", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.30.tgz", + "integrity": "sha512-PLGc+xfrQrkya/YK2/5X+bPpxRmyJBHM+xxz9krUdSgk4Vs2ZwxX5/Ow0lv3r9PDlDtNWb4u+it8MY5rZ0IyGw==", + "dev": true, + "license": "MIT" + }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", @@ -2365,6 +2433,18 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "node_modules/phone-number-generator-js": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/phone-number-generator-js/-/phone-number-generator-js-1.2.12.tgz", + "integrity": "sha512-AtJpQjHFlXqD2ZMZLUlzrNKNTwwyn9gFASeTgfcGqdWUUHsddThKkCbsJ7VyDyj7C2Xo0oce/XOARH8eElas6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "class-validator": "0.14.1", + "libphonenumber-js": "1.10.30", + "lodash": "4.17.21" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2446,6 +2526,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -2964,6 +3045,16 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -3060,10 +3151,11 @@ "dev": true }, "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -3172,9 +3264,9 @@ } }, "@faker-js/faker": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", - "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==" + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.3.tgz", + "integrity": "sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==" }, "@hapi/hoek": { "version": "9.3.0", @@ -3374,6 +3466,14 @@ "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", "dev": true }, + "@types/promise-retry": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/promise-retry/-/promise-retry-1.1.6.tgz", + "integrity": "sha512-EC1+OMXV0PZb0pf+cmyxc43MEP2CDumZe4AfuxWboxxEixztIebknpJPZAX5XlodGF1OY+C1E/RAeNGzxf+bJA==", + "requires": { + "@types/retry": "*" + } + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -3386,8 +3486,7 @@ "@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" }, "@types/sinonjs__fake-timers": { "version": "8.1.1", @@ -3401,6 +3500,12 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", + "dev": true + }, "@types/yamljs": { "version": "0.2.31", "resolved": "https://registry.npmjs.org/@types/yamljs/-/yamljs-0.2.31.tgz", @@ -3538,9 +3643,9 @@ "dev": true }, "axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "requires": { "follow-redirects": "^1.15.6", @@ -3718,6 +3823,25 @@ "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", "dev": true }, + "class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dev": true, + "requires": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + }, + "dependencies": { + "libphonenumber-js": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.9.tgz", + "integrity": "sha512-Zs5wf5HaWzW2/inlupe2tstl0I/Tbqo7lH20ZLr6Is58u7Dz2n+gRFGNlj9/gWxFvNfp9+YyDsiegjNhdixB9A==", + "dev": true + } + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4020,13 +4144,14 @@ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" }, "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, @@ -4069,6 +4194,26 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true + } + } + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -4624,6 +4769,12 @@ "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", "dev": true }, + "libphonenumber-js": { + "version": "1.10.30", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.30.tgz", + "integrity": "sha512-PLGc+xfrQrkya/YK2/5X+bPpxRmyJBHM+xxz9krUdSgk4Vs2ZwxX5/Ow0lv3r9PDlDtNWb4u+it8MY5rZ0IyGw==", + "dev": true + }, "listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", @@ -4924,6 +5075,17 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "phone-number-generator-js": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/phone-number-generator-js/-/phone-number-generator-js-1.2.12.tgz", + "integrity": "sha512-AtJpQjHFlXqD2ZMZLUlzrNKNTwwyn9gFASeTgfcGqdWUUHsddThKkCbsJ7VyDyj7C2Xo0oce/XOARH8eElas6A==", + "dev": true, + "requires": { + "class-validator": "0.14.1", + "libphonenumber-js": "1.10.30", + "lodash": "4.17.21" + } + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -5362,6 +5524,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -5436,9 +5604,9 @@ "dev": true }, "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "requires": {} }, diff --git a/test/e2e/package.json b/test/e2e/package.json index 05b04db6880a..a33ac330bf00 100644 --- a/test/e2e/package.json +++ b/test/e2e/package.json @@ -11,7 +11,8 @@ "wait-on": "wait-on" }, "dependencies": { - "@faker-js/faker": "8.4.1", + "@faker-js/faker": "9.0.3", + "@types/promise-retry": "^1.1.6", "async-retry": "1.3.3", "mailhog": "4.16.0", "promise-retry": "^2.0.1" @@ -29,6 +30,7 @@ "got": "11.8.2", "json-schema-to-typescript": "12.0.0", "otplib": "12.0.1", + "phone-number-generator-js": "^1.2.12", "process": "0.11.10", "typescript": "4.7.4", "wait-on": "7.0.1", diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index a3f81c73060d..2ace64395520 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -65,6 +65,12 @@ export default defineConfig({ reuseExistingServer: false, url: "http://localhost:8025/", }, + { + command: "go run test/e2e/mock/httptarget/main.go", + cwd: "../..", + reuseExistingServer: false, + url: "http://localhost:4471/health", + }, ], }) diff --git a/test/e2e/playwright/actions/identity.ts b/test/e2e/playwright/actions/identity.ts new file mode 100644 index 000000000000..f05bc2f68a3e --- /dev/null +++ b/test/e2e/playwright/actions/identity.ts @@ -0,0 +1,61 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { faker } from "@faker-js/faker" +import { APIRequestContext } from "@playwright/test" +import { CreateIdentityBody } from "@ory/kratos-client" +import { generatePhoneNumber, CountryNames } from "phone-number-generator-js" +import { expect } from "../fixtures" + +export async function createIdentity( + request: APIRequestContext, + data: Partial, +) { + const resp = await request.post("http://localhost:4434/admin/identities", { + data, + }) + expect(resp.status()).toBe(201) + return await resp.json() +} + +export async function createIdentityWithPhoneNumber( + request: APIRequestContext, +) { + const phone = generatePhoneNumber({ + countryName: CountryNames.Germany, + withoutCountryCode: false, + }) + return { + identity: await createIdentity(request, { + schema_id: "sms", + traits: { + phone, + }, + }), + phone, + } +} + +export async function createIdentityWithPassword(request: APIRequestContext) { + const email = faker.internet.email({ provider: "ory.sh" }) + const password = faker.internet.password() + return { + identity: await createIdentity(request, { + schema_id: "email", + traits: { + email, + website: faker.internet.url(), + }, + + credentials: { + password: { + config: { + password, + }, + }, + }, + }), + email, + password, + } +} diff --git a/test/e2e/playwright/actions/webhook.ts b/test/e2e/playwright/actions/webhook.ts new file mode 100644 index 000000000000..82b2511d5d90 --- /dev/null +++ b/test/e2e/playwright/actions/webhook.ts @@ -0,0 +1,57 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { request } from "@playwright/test" +import retry from "promise-retry" +import { retryOptions } from "../lib/config" + +export const WEBHOOK_TARGET = "http://127.0.0.1:4471" + +const baseUrl = WEBHOOK_TARGET + +/** + * Fetches a documented (hopefully) created by web hook + * + * @param key + */ +export async function fetchDocument(key: string) { + const r = await request.newContext() + + return retry(async (retry) => { + const res = await r.get(documentUrl(key)) + if (res.status() !== 200) { + const body = await res.text() + const message = `Expected response code 200 but received ${res.status()}: ${body}` + return retry(message) + } + return await res.json() + }, retryOptions) +} + +/** + * Fetches a documented (hopefully) created by web hook + * + * @param key + */ +export async function deleteDocument(key: string) { + const r = await request.newContext() + + return retry(async (retry) => { + const res = await r.delete(documentUrl(key)) + if (res.status() !== 204) { + const body = await res.text() + const message = `Expected response code 204 but received ${res.status()}: ${body}` + return retry(message) + } + return + }, retryOptions) +} + +/** + * Returns the URL for a specific document + * + * @param key + */ +export function documentUrl(key: string) { + return `${baseUrl}/documents/${key}` +} diff --git a/test/e2e/playwright/fixtures/index.ts b/test/e2e/playwright/fixtures/index.ts index b915dd4d937f..7f227ac7b5ee 100644 --- a/test/e2e/playwright/fixtures/index.ts +++ b/test/e2e/playwright/fixtures/index.ts @@ -1,14 +1,13 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { faker } from "@faker-js/faker" import { Identity } from "@ory/kratos-client" import { + APIRequestContext, CDPSession, - test as base, expect as baseExpect, - APIRequestContext, Page, + test as base, } from "@playwright/test" import { writeFile } from "fs/promises" import { merge } from "lodash" @@ -19,6 +18,7 @@ import { SessionWithResponse } from "../types" import { retryOptions } from "../lib/request" import promiseRetry from "promise-retry" import { Protocol } from "playwright-core/types/protocol" +import { createIdentityWithPassword } from "../actions/identity" // from https://stackoverflow.com/questions/61132262/typescript-deep-partial type DeepPartial = T extends object @@ -104,31 +104,15 @@ export const test = base.extend({ await pageCDPSession.send("WebAuthn.disable") }, identity: async ({ request }, use, i) => { - const email = faker.internet.email({ provider: "ory.sh" }) - const password = faker.internet.password() - const resp = await request.post("http://localhost:4434/admin/identities", { - data: { - schema_id: "email", - traits: { - email, - website: faker.internet.url(), - }, - - credentials: { - password: { - config: { - password, - }, - }, - }, - }, - }) - const oryIdentity = await resp.json() + const { + identity: oryIdentity, + password, + email, + } = await createIdentityWithPassword(request) i.attach("identity", { body: JSON.stringify(oryIdentity, null, 2), contentType: "application/json", }) - expect(resp.status()).toBe(201) await use({ oryIdentity, email, diff --git a/test/e2e/playwright/fixtures/schemas/sms.ts b/test/e2e/playwright/fixtures/schemas/sms.ts new file mode 100644 index 000000000000..0e5186633dfb --- /dev/null +++ b/test/e2e/playwright/fixtures/schemas/sms.ts @@ -0,0 +1,35 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +export default { + $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: { + phone: { + type: "string", + format: "tel", + title: "Your Phone Number", + minLength: 3, + "ory.sh/kratos": { + credentials: { + code: { + identifier: true, + via: "sms", + }, + }, + verification: { + via: "sms", + }, + }, + }, + }, + required: ["phone"], + additionalProperties: false, + }, + }, +} diff --git a/test/e2e/playwright/lib/config.ts b/test/e2e/playwright/lib/config.ts new file mode 100644 index 000000000000..4584d51f746e --- /dev/null +++ b/test/e2e/playwright/lib/config.ts @@ -0,0 +1,14 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import type { OperationOptions } from "retry" + +export type RetryOptions = OperationOptions + +export const retryOptions: RetryOptions = { + retries: 20, + factor: 1, + maxTimeout: 500, + minTimeout: 250, + randomize: false, +} diff --git a/test/e2e/playwright/models/elements/registration.ts b/test/e2e/playwright/models/elements/registration.ts new file mode 100644 index 000000000000..029903f14e49 --- /dev/null +++ b/test/e2e/playwright/models/elements/registration.ts @@ -0,0 +1,45 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { expect, Page } from "@playwright/test" +import { createInputLocator, InputLocator } from "../../selectors/input" +import { OryKratosConfiguration } from "../../../shared/config" + +export class RegistrationPage { + public identifier: InputLocator + + constructor(readonly page: Page, readonly config: OryKratosConfiguration) { + this.identifier = createInputLocator(page, "identifier") + } + + async open() { + await Promise.all([ + this.page.goto(this.config.selfservice.flows.registration.ui_url), + this.isReady(), + this.page.waitForURL((url) => + url + .toString() + .includes(this.config.selfservice.flows.registration.ui_url), + ), + ]) + await this.isReady() + } + + inputField(name: string) { + return this.page.locator(`input[name="${name}"]`) + } + + submitField(name: string) { + return this.page.locator(`[type="submit"][name="method"][value="${name}"]`) + } + + async isReady() { + await expect(this.inputField("csrf_token").nth(0)).toBeHidden() + } + + async triggerRegistrationWithCode(identifier: string) { + await this.inputField("traits.phone").fill(identifier) + await this.submitField("profile").click() + await this.submitField("code").click() + } +} diff --git a/test/e2e/playwright/tests/desktop/code/sms.spec.ts b/test/e2e/playwright/tests/desktop/code/sms.spec.ts new file mode 100644 index 000000000000..8d374c2ce1b7 --- /dev/null +++ b/test/e2e/playwright/tests/desktop/code/sms.spec.ts @@ -0,0 +1,116 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { test } from "../../../fixtures" +import { toConfig } from "../../../lib/helper" +import smsSchema from "../../../fixtures/schemas/sms" +import { LoginPage } from "../../../models/elements/login" +import { hasSession } from "../../../actions/session" +import { createIdentityWithPhoneNumber } from "../../../actions/identity" +import { + deleteDocument, + documentUrl, + fetchDocument, +} from "../../../actions/webhook" +import { RegistrationPage } from "../../../models/elements/registration" +import { CountryNames, generatePhoneNumber } from "phone-number-generator-js" + +const documentId = "doc-" + Math.random().toString(36).substring(7) + +test.describe("account enumeration protection off", () => { + test.use({ + configOverride: { + security: { + account_enumeration: { + enabled: false, + }, + }, + selfservice: { + flows: { + login: { + style: "unified", + }, + registration: { + after: { + code: { + hooks: [ + { + hook: "session", + }, + ], + }, + }, + }, + }, + methods: { + code: { + passwordless_enabled: true, + }, + password: { + enabled: false, + }, + }, + }, + courier: { + channels: [ + { + id: "sms", + type: "http", + request_config: { + body: "base64://ZnVuY3Rpb24oY3R4KSB7DQpjdHg6IGN0eCwNCn0=", + method: "PUT", + url: documentUrl(documentId), + }, + }, + ], + }, + identity: { + default_schema_id: "sms", + schemas: [ + { + id: "sms", + url: + "base64://" + + Buffer.from(JSON.stringify(smsSchema), "ascii").toString( + "base64", + ), + }, + ], + }, + }, + }) + + test.afterEach(async () => { + await deleteDocument(documentId) + }) + + test("login succeeds", async ({ page, config, kratosPublicURL }) => { + const identity = await createIdentityWithPhoneNumber(page.request) + + const login = new LoginPage(page, config) + await login.open() + await login.triggerLoginWithCode(identity.phone) + + const result = await fetchDocument(documentId) + await login.codeInput.input.fill(result.ctx.template_data.login_code) + await login.codeSubmit.getByText("Continue").click() + await hasSession(page.request, kratosPublicURL) + }) + + test("registration succeeds", async ({ page, config, kratosPublicURL }) => { + const phone = generatePhoneNumber({ + countryName: CountryNames.Germany, + withoutCountryCode: false, + }) + + const registration = new RegistrationPage(page, config) + await registration.open() + await registration.triggerRegistrationWithCode(phone) + + const result = await fetchDocument(documentId) + const code = result.ctx.template_data.registration_code + await registration.inputField("code").fill(code) + await registration.submitField("code").getByText("Continue").click() + await hasSession(page.request, kratosPublicURL) + }) +}) diff --git a/text/id.go b/text/id.go index 3d9028af8ed0..c6d26323f5e2 100644 --- a/text/id.go +++ b/text/id.go @@ -23,7 +23,7 @@ const ( InfoSelfServiceLoginContinueWebAuthn // 1010011 InfoSelfServiceLoginWebAuthnPasswordless // 1010012 InfoSelfServiceLoginContinue // 1010013 - InfoSelfServiceLoginEmailWithCodeSent // 1010014 + InfoSelfServiceLoginCodeSent // 1010014 InfoSelfServiceLoginCode // 1010015 InfoSelfServiceLoginLink // 1010016 InfoSelfServiceLoginAndLink // 1010017 diff --git a/text/message_login.go b/text/message_login.go index f5ebc54d9899..f60761f49e69 100644 --- a/text/message_login.go +++ b/text/message_login.go @@ -224,11 +224,11 @@ func NewInfoSelfServiceLoginContinue() *Message { } } -func NewLoginEmailWithCodeSent() *Message { +func NewLoginCodeSent() *Message { return &Message{ - ID: InfoSelfServiceLoginEmailWithCodeSent, + ID: InfoSelfServiceLoginCodeSent, Type: Info, - Text: "An email containing a code has been sent to the email address you provided. If you have not received an email, check the spelling of the address and retry the login.", + Text: "A code has been sent to the address you provided. If you have not received an message, check the spelling of the address and retry the login.", } } diff --git a/text/message_registration.go b/text/message_registration.go index 443d234cfda7..5249f21e3b8b 100644 --- a/text/message_registration.go +++ b/text/message_registration.go @@ -84,7 +84,7 @@ func NewRegistrationEmailWithCodeSent() *Message { return &Message{ ID: InfoSelfServiceRegistrationEmailWithCodeSent, Type: Info, - Text: "An email containing a code has been sent to the email address you provided. If you have not received an email, check the spelling of the address and retry the registration.", + Text: "A code has been sent to the address(es) you provided. If you have not received a message, check the spelling of the address and retry the registration.", } }