Skip to content

Commit 048d315

Browse files
jonas-jonasory-bot
authored andcommitted
fix: fetch login challenge after code submissions
GitOrigin-RevId: 4ff0402e96d4b8242bbfcbae518f96c77e1fdf09
1 parent 25d35cc commit 048d315

5 files changed

Lines changed: 166 additions & 4 deletions

File tree

selfservice/strategy/code/strategy.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ import (
1010
"sort"
1111
"strings"
1212

13-
"github.com/pkg/errors"
13+
"github.com/ory/kratos/hydra"
14+
"github.com/ory/kratos/x/nosurfx"
15+
1416
"github.com/samber/lo"
17+
18+
"github.com/pkg/errors"
1519
"github.com/tidwall/gjson"
1620

1721
"github.com/ory/herodot"
@@ -33,7 +37,6 @@ import (
3337
"github.com/ory/kratos/ui/container"
3438
"github.com/ory/kratos/ui/node"
3539
"github.com/ory/kratos/x"
36-
"github.com/ory/kratos/x/nosurfx"
3740
"github.com/ory/x/httpx"
3841
"github.com/ory/x/logrusx"
3942
"github.com/ory/x/otelx"
@@ -108,6 +111,8 @@ type (
108111
sessiontokenexchange.PersistenceProvider
109112

110113
continuity.ManagementProvider
114+
115+
hydra.Provider
111116
}
112117

113118
Strategy struct{ deps dependencies }

selfservice/strategy/code/strategy_login.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,14 @@ func (s *Strategy) loginSendCode(ctx context.Context, w http.ResponseWriter, r *
513513
return err
514514
}
515515

516+
if f.OAuth2LoginChallenge != "" {
517+
hlr, err := s.deps.Hydra().GetLoginRequest(ctx, string(f.OAuth2LoginChallenge))
518+
if err != nil {
519+
return errors.WithStack(err)
520+
}
521+
f.HydraLoginRequest = hlr
522+
}
523+
516524
if x.IsJSONRequest(r) {
517525
if successResponse {
518526
s.deps.Writer().WriteCode(w, r, http.StatusOK, f)

selfservice/strategy/code/strategy_login_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,26 @@ import (
1919
"github.com/stretchr/testify/require"
2020
"github.com/tidwall/gjson"
2121

22+
"github.com/ory/herodot"
2223
"github.com/ory/x/configx"
2324

2425
"github.com/ory/kratos/courier"
2526
"github.com/ory/kratos/driver"
2627
"github.com/ory/kratos/driver/config"
28+
"github.com/ory/kratos/hydra"
2729
"github.com/ory/kratos/identity"
2830
"github.com/ory/kratos/internal"
2931
oryClient "github.com/ory/kratos/internal/httpclient"
3032
"github.com/ory/kratos/internal/testhelpers"
3133
"github.com/ory/kratos/selfservice/flow"
3234
"github.com/ory/kratos/selfservice/flow/login"
35+
"github.com/ory/kratos/selfservice/strategy/code"
3336
"github.com/ory/kratos/selfservice/strategy/idfirst"
3437
"github.com/ory/kratos/selfservice/strategy/totp"
3538
"github.com/ory/kratos/session"
3639
"github.com/ory/kratos/text"
3740
"github.com/ory/kratos/x"
41+
"github.com/ory/kratos/x/nosurfx"
3842
"github.com/ory/x/contextx"
3943
"github.com/ory/x/ioutilx"
4044
"github.com/ory/x/snapshotx"
@@ -1495,3 +1499,68 @@ func TestFormHydration(t *testing.T) {
14951499
toSnapshot(t, f)
14961500
})
14971501
}
1502+
1503+
func TestCodeLoginWithLoginChallenge(t *testing.T) {
1504+
t.Parallel()
1505+
1506+
_, reg := internal.NewFastRegistryWithMocks(t,
1507+
configx.WithValue(config.ViperKeySelfServiceStrategyConfig+"."+string(identity.CredentialsTypeCodeAuth), map[string]interface{}{
1508+
"enabled": true,
1509+
"passwordless_enabled": true,
1510+
}),
1511+
configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://./stub/code.identity.schema.json")),
1512+
)
1513+
reg.SetHydra(hydra.NewFake())
1514+
reg.WithCSRFTokenGenerator(nosurfx.FakeCSRFTokenGenerator)
1515+
s := code.NewStrategy(reg)
1516+
1517+
loginChallenge := hydra.FakeValidLoginChallenge
1518+
1519+
newFlow := func(ctx context.Context, t *testing.T) (*http.Request, *login.Flow) {
1520+
t.Helper()
1521+
r := httptest.NewRequest("GET", "/self-service/login/browser", nil)
1522+
r = r.WithContext(ctx)
1523+
f, err := login.NewFlow(reg.Config(), time.Minute, nosurfx.FakeCSRFToken, r, flow.TypeBrowser)
1524+
require.NoError(t, err)
1525+
require.NoError(t, reg.LoginFlowPersister().CreateLoginFlow(ctx, f))
1526+
return r, f
1527+
}
1528+
1529+
t.Run("case=fetches login challenge on code input state", func(t *testing.T) {
1530+
_, f := newFlow(t.Context(), t)
1531+
f.OAuth2LoginChallenge = sqlxx.NullString(loginChallenge)
1532+
i := createIdentity(t.Context(), t, reg, false, false)
1533+
email := gjson.Get(i.Traits.String(), "email").String()
1534+
1535+
body := gjson.Parse(`{
1536+
"method": "code",
1537+
"identifier": "` + email + `",
1538+
"csrf_token": "` + f.CSRFToken + `"
1539+
}`)
1540+
r := httptest.NewRequest("POST", "/self-service/login/browser", strings.NewReader(body.Raw))
1541+
r.Header.Add("Content-Type", "application/json")
1542+
1543+
_, err := s.Login(httptest.NewRecorder(), r, f, nil)
1544+
require.ErrorIs(t, err, flow.ErrCompletedByStrategy)
1545+
require.NotNil(t, f.HydraLoginRequest)
1546+
})
1547+
1548+
t.Run("case=returns error if login challenge is invalid", func(t *testing.T) {
1549+
_, f := newFlow(t.Context(), t)
1550+
f.OAuth2LoginChallenge = sqlxx.NullString(hydra.FakeInvalidLoginChallenge)
1551+
i := createIdentity(t.Context(), t, reg, false, false)
1552+
email := gjson.Get(i.Traits.String(), "email").String()
1553+
1554+
body := gjson.Parse(`{
1555+
"method": "code",
1556+
"identifier": "` + email + `",
1557+
"csrf_token": "` + f.CSRFToken + `"
1558+
}`)
1559+
r := httptest.NewRequest("POST", "/self-service/login/browser", strings.NewReader(body.Raw))
1560+
r.Header.Add("Content-Type", "application/json")
1561+
1562+
_, err := s.Login(httptest.NewRecorder(), r, f, nil)
1563+
require.ErrorIs(t, err, herodot.ErrBadRequest)
1564+
require.Nil(t, f.HydraLoginRequest)
1565+
})
1566+
}

selfservice/strategy/code/strategy_registration.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ import (
2626
"github.com/ory/x/urlx"
2727
)
2828

29-
var _ registration.Strategy = new(Strategy)
30-
var _ registration.FormHydrator = new(Strategy)
29+
var (
30+
_ registration.Strategy = new(Strategy)
31+
_ registration.FormHydrator = new(Strategy)
32+
)
3133

3234
// Update Registration Flow with Code Method
3335
//
@@ -255,6 +257,14 @@ func (s *Strategy) registrationSendEmail(ctx context.Context, w http.ResponseWri
255257
return errors.WithStack(err)
256258
}
257259

260+
if f.OAuth2LoginChallenge != "" {
261+
hlr, err := s.deps.Hydra().GetLoginRequest(ctx, string(f.OAuth2LoginChallenge))
262+
if err != nil {
263+
return errors.WithStack(err)
264+
}
265+
f.HydraLoginRequest = hlr
266+
}
267+
258268
if x.IsJSONRequest(r) {
259269
s.deps.Writer().WriteCode(w, r, http.StatusBadRequest, f)
260270
} else {

selfservice/strategy/code/strategy_registration_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ import (
2222
"github.com/stretchr/testify/require"
2323
"github.com/tidwall/gjson"
2424

25+
"github.com/ory/herodot"
2526
"github.com/ory/x/configx"
27+
"github.com/ory/x/sqlxx"
2628

2729
"github.com/ory/kratos/driver"
2830
"github.com/ory/kratos/driver/config"
31+
"github.com/ory/kratos/hydra"
2932
"github.com/ory/kratos/identity"
3033
"github.com/ory/kratos/internal"
3134
oryClient "github.com/ory/kratos/internal/httpclient"
@@ -34,6 +37,7 @@ import (
3437
"github.com/ory/kratos/selfservice/flow/registration"
3538
"github.com/ory/kratos/selfservice/strategy/code"
3639
"github.com/ory/kratos/ui/node"
40+
"github.com/ory/kratos/x/nosurfx"
3741
"github.com/ory/pop/v6"
3842
"github.com/ory/x/assertx"
3943
"github.com/ory/x/snapshotx"
@@ -819,3 +823,69 @@ func TestPopulateRegistrationMethod(t *testing.T) {
819823
})
820824
})
821825
}
826+
827+
func TestCodeRegistrationWithLoginChallenge(t *testing.T) {
828+
t.Parallel()
829+
830+
_, reg := internal.NewFastRegistryWithMocks(t,
831+
configx.WithValue(config.ViperKeySelfServiceStrategyConfig+"."+string(identity.CredentialsTypeCodeAuth), map[string]interface{}{
832+
"enabled": true,
833+
"passwordless_enabled": true,
834+
}),
835+
configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://./stub/code.identity.schema.json")),
836+
)
837+
reg.SetHydra(hydra.NewFake())
838+
reg.WithCSRFTokenGenerator(nosurfx.FakeCSRFTokenGenerator)
839+
s := code.NewStrategy(reg)
840+
841+
loginChallenge := hydra.FakeValidLoginChallenge
842+
843+
newFlow := func(ctx context.Context, t *testing.T) (*http.Request, *registration.Flow) {
844+
t.Helper()
845+
r := httptest.NewRequest("GET", "/self-service/registration/browser", nil)
846+
r = r.WithContext(ctx)
847+
f, err := registration.NewFlow(reg.Config(), time.Minute, nosurfx.FakeCSRFToken, r, flow.TypeBrowser)
848+
require.NoError(t, err)
849+
require.NoError(t, reg.RegistrationFlowPersister().CreateRegistrationFlow(ctx, f))
850+
return r, f
851+
}
852+
853+
t.Run("case=fetches login challenge on code input state", func(t *testing.T) {
854+
_, f := newFlow(t.Context(), t)
855+
f.OAuth2LoginChallenge = sqlxx.NullString(loginChallenge)
856+
i := identity.NewIdentity(f.IdentitySchema.ID(t.Context(), reg.Config()))
857+
email := testhelpers.RandomEmail()
858+
859+
body := gjson.Parse(`{
860+
"method": "code",
861+
"traits.email": "` + email + `",
862+
"csrf_token": "` + f.CSRFToken + `"
863+
}`)
864+
r := httptest.NewRequest("POST", "/self-service/registration/browser", strings.NewReader(body.Raw))
865+
r.Header.Add("Content-Type", "application/json")
866+
867+
err := s.Register(httptest.NewRecorder(), r, f, i)
868+
require.ErrorIs(t, err, flow.ErrCompletedByStrategy)
869+
require.NotNil(t, f.HydraLoginRequest)
870+
})
871+
872+
t.Run("case=returns error if login challenge is invalid", func(t *testing.T) {
873+
_, f := newFlow(t.Context(), t)
874+
f.OAuth2LoginChallenge = sqlxx.NullString(hydra.FakeInvalidLoginChallenge)
875+
i := identity.NewIdentity(f.IdentitySchema.ID(t.Context(), reg.Config()))
876+
877+
email := testhelpers.RandomEmail()
878+
879+
body := gjson.Parse(`{
880+
"method": "code",
881+
"traits.email": "` + email + `",
882+
"csrf_token": "` + f.CSRFToken + `"
883+
}`)
884+
r := httptest.NewRequest("POST", "/self-service/registration/browser", strings.NewReader(body.Raw))
885+
r.Header.Add("Content-Type", "application/json")
886+
887+
err := s.Register(httptest.NewRecorder(), r, f, i)
888+
require.ErrorIs(t, err, herodot.ErrBadRequest)
889+
require.Nil(t, f.HydraLoginRequest)
890+
})
891+
}

0 commit comments

Comments
 (0)