diff --git a/selfservice/strategy/code/strategy_recovery.go b/selfservice/strategy/code/strategy_recovery.go index 8c5f2c86d980..2a917f0dcecd 100644 --- a/selfservice/strategy/code/strategy_recovery.go +++ b/selfservice/strategy/code/strategy_recovery.go @@ -254,8 +254,12 @@ func (s *Strategy) recoveryUseCode(w http.ResponseWriter, r *http.Request, body return s.retryRecoveryFlow(w, r, f.Type, RetryWithError(err)) } - // No error - return nil + if f.Type == flow.TypeBrowser && !x.IsJSONRequest(r) { + http.Redirect(w, r, f.AppendTo(s.deps.Config().SelfServiceFlowRecoveryUI(r.Context())).String(), http.StatusSeeOther) + } else { + s.deps.Writer().Write(w, r, f) + } + return errors.WithStack(flow.ErrCompletedByStrategy) } else if err != nil { return s.retryRecoveryFlow(w, r, f.Type, RetryWithError(err)) } diff --git a/selfservice/strategy/code/strategy_recovery_test.go b/selfservice/strategy/code/strategy_recovery_test.go index 91afe5c3e149..b481a37c8726 100644 --- a/selfservice/strategy/code/strategy_recovery_test.go +++ b/selfservice/strategy/code/strategy_recovery_test.go @@ -1597,7 +1597,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { t.Run("type="+testCase.ClientType.String(), func(t *testing.T) { recoveryEmail := testhelpers.RandomEmail() createIdentityToRecover(t, reg, recoveryEmail) - conf.MustSet(ctx, config.ViperKeySelfServiceRecoveryRequestLifespan, time.Millisecond*10) + conf.MustSet(ctx, config.ViperKeySelfServiceRecoveryRequestLifespan, time.Millisecond*100) t.Cleanup(func() { conf.MustSet(ctx, config.ViperKeySelfServiceRecoveryRequestLifespan, time.Minute) }) @@ -1611,7 +1611,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { fallthrough case RecoveryClientTypeSPA: rs = testhelpers.GetRecoveryFlow(t, c, public) - time.Sleep(time.Millisecond * 11) + time.Sleep(time.Millisecond * 110) res, err = c.PostForm(rs.Ui.Action, url.Values{"email": {recoveryEmail}, "method": {"code"}}) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, res.StatusCode) @@ -1619,7 +1619,7 @@ func TestRecovery_WithContinueWith(t *testing.T) { assert.Contains(t, res.Request.URL.String(), conf.SelfServiceFlowRecoveryUI(ctx).String()) case RecoveryClientTypeAPI: rs = testhelpers.InitializeRecoveryFlowViaAPI(t, c, public) - time.Sleep(time.Millisecond * 11) + time.Sleep(time.Millisecond * 110) form := testhelpers.EncodeFormAsJSON(t, true, url.Values{"email": {recoveryEmail}, "method": {"code"}}) res, err = c.Post(rs.Ui.Action, "application/json", bytes.NewBufferString(form)) require.NoError(t, err) diff --git a/selfservice/strategy/code/strategy_verification.go b/selfservice/strategy/code/strategy_verification.go index 28e21456323e..e6969fda738d 100644 --- a/selfservice/strategy/code/strategy_verification.go +++ b/selfservice/strategy/code/strategy_verification.go @@ -236,8 +236,12 @@ func (s *Strategy) verificationUseCode(w http.ResponseWriter, r *http.Request, c return s.retryVerificationFlowWithError(w, r, f.Type, err) } - // No error - return nil + if x.IsBrowserRequest(r) { + http.Redirect(w, r, f.AppendTo(s.deps.Config().SelfServiceFlowVerificationUI(r.Context())).String(), http.StatusSeeOther) + } else { + s.deps.Writer().Write(w, r, f) + } + return errors.WithStack(flow.ErrCompletedByStrategy) } else if err != nil { return s.retryVerificationFlowWithError(w, r, f.Type, err) } diff --git a/selfservice/strategy/code/strategy_verification_test.go b/selfservice/strategy/code/strategy_verification_test.go index cdbfedc6069d..0f6a9fe15a10 100644 --- a/selfservice/strategy/code/strategy_verification_test.go +++ b/selfservice/strategy/code/strategy_verification_test.go @@ -16,6 +16,8 @@ import ( "testing" "time" + "github.com/gofrs/uuid" + "github.com/ory/x/urlx" "github.com/ory/kratos/selfservice/strategy/code" @@ -377,6 +379,11 @@ func TestVerification(t *testing.T) { f, err := verification.NewFlow(conf, time.Hour, x.FakeCSRFToken, httptest.NewRequest("GET", requestURL, nil), code.NewStrategy(reg), fType) require.NoError(t, err) f.State = flow.StateEmailSent + u, err := url.Parse(f.RequestURL) + require.NoError(t, err) + f.OAuth2LoginChallenge = sqlxx.NullString(u.Query().Get("login_challenge")) + f.IdentityID = uuid.NullUUID{UUID: x.NewUUID(), Valid: true} + f.SessionID = uuid.NullUUID{UUID: x.NewUUID(), Valid: true} require.NoError(t, reg.VerificationFlowPersister().CreateVerificationFlow(context.Background(), f)) email := identity.NewVerifiableEmailAddress(verificationEmail, identityToVerify.ID) identityToVerify.VerifiableAddresses = append(identityToVerify.VerifiableAddresses, *email) @@ -634,4 +641,25 @@ func TestVerification(t *testing.T) { }) } }) + + t.Run("case=doesn't continue with OAuth2 flow if code is invalid", func(t *testing.T) { + returnToURL := public.URL + "/after-verification" + conf.MustSet(ctx, config.ViperKeyURLsAllowedReturnToDomains, []string{returnToURL}) + + client := testhelpers.NewClientWithCookies(t) + flow, _, _ := newValidFlow(t, flow.TypeBrowser, public.URL+verification.RouteInitBrowserFlow+"?"+url.Values{"return_to": {returnToURL}, "login_challenge": {"any_valid_challenge"}}.Encode()) + + body := fmt.Sprintf( + `{"csrf_token":"%s","code":"%s"}`, flow.CSRFToken, "2475", + ) + + res, err := client.Post(public.URL+verification.RouteSubmitFlow+"?"+url.Values{"flow": {flow.ID.String()}}.Encode(), "application/json", bytes.NewBuffer([]byte(body))) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + responseBody := gjson.ParseBytes(ioutilx.MustReadAll(res.Body)) + + assert.Equal(t, responseBody.Get("state").String(), "sent_email", "%v", responseBody) + assert.Len(t, responseBody.Get("ui.messages").Array(), 1, "%v", responseBody) + assert.Equal(t, "The verification code is invalid or has already been used. Please try again.", responseBody.Get("ui.messages.0.text").String(), "%v", responseBody) + }) }