Skip to content

Commit

Permalink
test: recovery strategy with mfa account
Browse files Browse the repository at this point in the history
  • Loading branch information
Benehiko committed May 3, 2023
1 parent a647cfe commit c42963f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 37 deletions.
76 changes: 65 additions & 11 deletions selfservice/strategy/code/strategy_recovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ import (
"github.com/ory/kratos/internal"
"github.com/ory/kratos/internal/testhelpers"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/selfservice/flow/recovery"
"github.com/ory/kratos/selfservice/flow/settings"
"github.com/ory/kratos/selfservice/strategy/code"
"github.com/ory/kratos/selfservice/strategy/totp"
"github.com/ory/kratos/text"
"github.com/ory/kratos/x"
)
Expand Down Expand Up @@ -314,7 +317,8 @@ func createIdentityToRecover(t *testing.T, reg *driver.RegistryDefault, email st
func TestRecovery(t *testing.T) {
ctx := context.Background()
conf, reg := internal.NewFastRegistryWithMocks(t)
conf.MustSet(ctx, config.ViperKeySelfServiceStrategyConfig+"."+string(recovery.RecoveryStrategyLink+".enabled"), false)
testhelpers.StrategyEnable(t, conf, string(recovery.RecoveryStrategyCode), true)
testhelpers.StrategyEnable(t, conf, string(recovery.RecoveryStrategyLink), true)
initViper(t, ctx, conf)

_ = testhelpers.NewRecoveryUIFlowEchoServer(t, reg)
Expand Down Expand Up @@ -468,21 +472,22 @@ func TestRecovery(t *testing.T) {
returnTo := public.URL + "/return-to"
conf.Set(ctx, config.ViperKeyURLsAllowedReturnToDomains, []string{returnTo})
for _, tc := range []struct {
desc string
returnTo string
f func(t *testing.T, client *http.Client) *kratos.RecoveryFlow
desc string
returnTo string
f func(t *testing.T, client *http.Client, identity *identity.Identity) *kratos.RecoveryFlow
expectedAAL string
}{
{
desc: "should use return_to from recovery flow",
returnTo: returnTo,
f: func(t *testing.T, client *http.Client) *kratos.RecoveryFlow {
f: func(t *testing.T, client *http.Client, identity *identity.Identity) *kratos.RecoveryFlow {
return testhelpers.InitializeRecoveryFlowViaBrowser(t, client, false, public, url.Values{"return_to": []string{returnTo}})
},
},
{
desc: "should use return_to from config",
returnTo: returnTo,
f: func(t *testing.T, client *http.Client) *kratos.RecoveryFlow {
f: func(t *testing.T, client *http.Client, identity *identity.Identity) *kratos.RecoveryFlow {
conf.Set(ctx, config.ViperKeySelfServiceRecoveryBrowserDefaultReturnTo, returnTo)
t.Cleanup(func() {
conf.Set(ctx, config.ViperKeySelfServiceRecoveryBrowserDefaultReturnTo, "")
Expand All @@ -493,19 +498,47 @@ func TestRecovery(t *testing.T) {
{
desc: "no return to",
returnTo: "",
f: func(t *testing.T, client *http.Client) *kratos.RecoveryFlow {
f: func(t *testing.T, client *http.Client, identity *identity.Identity) *kratos.RecoveryFlow {
return testhelpers.InitializeRecoveryFlowViaBrowser(t, client, false, public, nil)
},
},
{
desc: "should use return_to with an account that has 2fa enabled",
returnTo: returnTo,
f: func(t *testing.T, client *http.Client, id *identity.Identity) *kratos.RecoveryFlow {
conf.Set(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
conf.Set(ctx, config.ViperKeySessionWhoAmIAAL, "aal2")
testhelpers.StrategyEnable(t, conf, settings.StrategyProfile, true)
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypeTOTP.String(), true)
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypePassword.String(), true)

key, err := totp.NewKey(context.Background(), "foo", reg)
require.NoError(t, err)

assert.True(t, id.IsActive())

id.SetCredentials(identity.CredentialsTypeTOTP, identity.Credentials{
Type: identity.CredentialsTypeTOTP,
Identifiers: []string{id.ID.String()},
Config: sqlxx.JSONRawMessage(`{"totp_url":"` + string(key.URL()) + `"}`),
})

id.State = identity.StateActive

require.NoError(t, reg.PrivilegedIdentityPool().UpdateIdentity(context.Background(), id))

return testhelpers.InitializeRecoveryFlowViaBrowser(t, client, true, public, url.Values{"return_to": []string{returnTo}})
},
expectedAAL: "aal2",
},
} {
t.Run(fmt.Sprintf("%s", tc.desc), func(t *testing.T) {
client := testhelpers.NewClientWithCookies(t)
email := testhelpers.RandomEmail()
createIdentityToRecover(t, reg, email)
i := createIdentityToRecover(t, reg, email)

client.Transport = testhelpers.NewTransportWithLogger(http.DefaultTransport, t).RoundTripper

f := tc.f(t, client)
f := tc.f(t, client, i)

formPayload := testhelpers.SDKFormFieldsToURLValues(f.Ui.Nodes)
formPayload.Set("email", email)
Expand All @@ -517,6 +550,23 @@ func TestRecovery(t *testing.T) {

body = checkRecovery(t, client, RecoveryFlowTypeBrowser, email, body)

if tc.expectedAAL == "aal2" {
res, err := client.Get(public.URL + session.RouteWhoami)
require.NoError(t, err)
body = string(x.MustReadAll(res.Body))
require.NoError(t, res.Body.Close())
assert.Equal(t, "code_recovery", gjson.Get(body, "authentication_methods.0.method").String(), "%s", body)
assert.Equal(t, "aal2", gjson.Get(body, "authenticator_assurance_level").String(), "%s", body)

_, _, err = reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypeTOTP, i.ID.String())
require.NoError(t, err)

require.Equal(t, "", string(body))
loc, err := res.Location()
require.NoError(t, err)
assert.Equal(t, login.RouteInitBrowserFlow+"?aal=aal2&return_to="+tc.returnTo, loc.String())
}

assert.Equal(t, text.NewRecoverySuccessful(time.Now().Add(time.Hour)).Text,
gjson.Get(body, "ui.messages.0.text").String())

Expand All @@ -532,7 +582,11 @@ func TestRecovery(t *testing.T) {
body = string(x.MustReadAll(res.Body))
require.NoError(t, res.Body.Close())
assert.Equal(t, "code_recovery", gjson.Get(body, "authentication_methods.0.method").String(), "%s", body)
assert.Equal(t, "aal1", gjson.Get(body, "authenticator_assurance_level").String(), "%s", body)
expectedAAL := "aal1"
if tc.expectedAAL != "" {
expectedAAL = tc.expectedAAL
}
assert.Equal(t, expectedAAL, gjson.Get(body, "authenticator_assurance_level").String(), "%s", body)
})
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { routes as react } from "../../../../helpers/react"

context("Recovery with `return_to`", () => {
;[
{
recovery: react.recovery,
settings: react.settings,
base: react.base,
app: "react" as "react",
profile: "spa",
},
//{
// recovery: react.recovery,
// settings: react.settings,
// base: react.base,
// app: "react" as "react",
// profile: "spa",
//},
{
recovery: express.recovery,
settings: express.settings,
Expand Down Expand Up @@ -58,34 +58,37 @@ context("Recovery with `return_to`", () => {
cy.recoveryEmailWithCode({ expect: { email: identity.email } })
cy.get("button[value='code']").click()

}

it("should return to the `return_to` url after successful account recovery and settings update", () => {
cy.visit(recovery + "?return_to=https://www.ory.sh/")
doRecovery()

cy.get('[data-testid="ui/message/1060001"]', { timeout: 30000 }).should(
"contain.text",
"You successfully recovered your account. ",
)

cy.getSession()
cy.location("pathname").should("eq", "/settings")
}

it("should return to the `return_to` url after successful account recovery and settings update", () => {
cy.visit(recovery + "?return_to=https://www.ory.sh/")
doRecovery()

const newPassword = gen.password()
cy.get(appPrefix(app) + 'input[name="password"]')
.clear()
.type(newPassword)
cy.get('button[value="password"]').click()

cy.location("hostname").should("eq", "ory.sh")
cy.location("hostname").should("eq", "www.ory.sh")
})

it("should return to the `return_to` url even with mfa enabled after successful account recovery and settings update", () => {
cy.requireStrictAal()

cy.visit(settings)
cy.get('input[name="identifier"]').type(email)
cy.get('input[name="password"]').type(password)
cy.get('input[name="identifier"]').type(identity.email)
cy.get('input[name="password"]').type(identity.password)
cy.get('button[type="submit"]').click()
cy.visit(settings)

// enable mfa for this account
let secret
Expand All @@ -107,21 +110,19 @@ context("Recovery with `return_to`", () => {
cy.visit(recovery + "?return_to=https://www.ory.sh/")
doRecovery()

const newPassword = gen.password()
cy.get(appPrefix(app) + 'input[name="password"]')
.clear()
.type(newPassword)
cy.get('button[value="password"]').click()


cy.shouldShow2FAScreen()
cy.get('input[name="totp_code"]').then(($e) => {
cy.wrap($e).type(authenticator.generate(secret))
})
cy.get('*[name="method"][value="totp"]').click()
cy.location("pathname").should((loc) => {
expect(loc.hostname).to.eq("ory.sh")
})

cy.location("hostname").should("eq", "www.ory.sh")

const newPassword = gen.password()
cy.get(appPrefix(app) + 'input[name="password"]')
.clear()
.type(newPassword)
cy.get('button[value="password"]').click()
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ run() {
nc -zv localhost 4433 && exit 1

ls -la .
for profile in email mobile oidc recovery verification mfa spa network passwordless webhooks oidc-provider oidc-provider-mfa; do
for profile in email mobile oidc recovery recovery-mfa verification mfa spa network passwordless webhooks oidc-provider oidc-provider-mfa; do
yq ea '. as $item ireduce ({}; . * $item )' test/e2e/profiles/kratos.base.yml "test/e2e/profiles/${profile}/.kratos.yml" > test/e2e/kratos.${profile}.yml
cat "test/e2e/kratos.${profile}.yml" | envsubst | sponge "test/e2e/kratos.${profile}.yml"
done
Expand Down

0 comments on commit c42963f

Please sign in to comment.