snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:19:12 +05:00
commit c60cc7ca75
8092 changed files with 394160 additions and 0 deletions

View File

@@ -0,0 +1,750 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package settings_test
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"testing"
"time"
"github.com/ory/kratos/text"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/snapshotx"
"github.com/ory/x/assertx"
"github.com/ory/kratos/session"
"github.com/gofrs/uuid"
kratos "github.com/ory/kratos/pkg/httpclient"
"github.com/ory/kratos/corpx"
"github.com/ory/x/ioutilx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"github.com/ory/x/urlx"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/pkg"
"github.com/ory/kratos/pkg/testhelpers"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/selfservice/flow/settings"
"github.com/ory/kratos/x"
)
func init() {
corpx.RegisterFakes()
}
func TestHandler(t *testing.T) {
ctx := context.Background()
conf, reg := pkg.NewFastRegistryWithMocks(t)
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
conf.MustSet(ctx, config.ViperKeyIdentitySchemas, config.Schemas{
{ID: "default", URL: "file://./stub/identity.schema.json", SelfserviceSelectable: true},
{ID: "not-default", URL: "file://./stub/multi-email.schema.json"},
})
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypePassword.String(), true)
testhelpers.StrategyEnable(t, conf, settings.StrategyProfile, true)
publicTS, _ := testhelpers.NewKratosServer(t, reg)
_ = testhelpers.NewSettingsUITestServer(t, conf)
_ = testhelpers.NewLoginUIFlowEchoServer(t, reg)
_ = testhelpers.NewErrorTestServer(t, reg)
_ = testhelpers.NewSettingsUIFlowEchoServer(t, reg)
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, "1ns")
primaryIdentity := &identity.Identity{ID: x.NewUUID(), Traits: identity.Traits(`{}`)}
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), primaryIdentity))
primaryUser := testhelpers.NewHTTPClientWithIdentitySessionCookie(ctx, t, reg, primaryIdentity)
otherUser := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
newExpiredFlow := func() *settings.Flow {
f, err := settings.NewFlow(reg,
&http.Request{URL: urlx.ParseOrPanic(publicTS.URL + login.RouteInitBrowserFlow)},
primaryIdentity, flow.TypeBrowser)
require.NoError(t, err)
f.ExpiresAt = f.IssuedAt.Add(-time.Minute)
return f
}
assertion := func(t *testing.T, body []byte, isApi bool) {
if isApi {
assert.Equal(t, "api", gjson.GetBytes(body, "type").String())
} else {
assert.Equal(t, "browser", gjson.GetBytes(body, "type").String())
}
}
type options struct {
query url.Values
}
type initAuthOptions func(*options)
WithInitAuthQuery := func(q url.Values) initAuthOptions {
return func(o *options) {
o.query = q
}
}
initAuthenticatedFlow := func(t *testing.T, hc *http.Client, isAPI bool, isSPA bool, opts ...initAuthOptions) (*http.Response, []byte) {
op := new(options)
for _, o := range opts {
o(op)
}
route := settings.RouteInitBrowserFlow
if isAPI {
route = settings.RouteInitAPIFlow
}
reqURL, err := url.Parse(publicTS.URL + route)
require.NoError(t, err)
reqURL.RawQuery = op.query.Encode()
req := testhelpers.NewTestHTTPRequest(t, "GET", reqURL.String(), nil)
if isSPA || isAPI {
req.Header.Set("Accept", "application/json")
}
res, err := hc.Do(req)
require.NoError(t, err)
defer func() { _ = res.Body.Close() }()
if isAPI {
assert.Len(t, res.Header.Get("Set-Cookie"), 0)
}
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
return res, body
}
initFlow := func(t *testing.T, hc *http.Client, isAPI bool, opts ...initAuthOptions) (*http.Response, []byte) {
return initAuthenticatedFlow(t, hc, isAPI, false, opts...)
}
initSPAFlow := func(t *testing.T, hc *http.Client, opts ...initAuthOptions) (*http.Response, []byte) {
return initAuthenticatedFlow(t, hc, false, true, opts...)
}
aal2Identity := testhelpers.NewHTTPClientWithIdentitySessionCookie(ctx, t, reg, &identity.Identity{
State: identity.StateActive,
Traits: []byte(`{"email":"foo@bar"}`),
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {Type: identity.CredentialsTypePassword, Config: []byte(`{"hashed_password":"$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"}`), Identifiers: []string{"foo@bar"}},
identity.CredentialsTypeWebAuthn: {Type: identity.CredentialsTypeWebAuthn, Config: []byte(`{"credentials":[{"is_passwordless":false}]}`), Identifiers: []string{"foo@bar"}},
},
})
t.Run("endpoint=init", func(t *testing.T) {
t.Run("description=init a flow as API", func(t *testing.T) {
t.Run("description=without privileges", func(t *testing.T) {
res, body := initFlow(t, testhelpers.NewTestClient(t), true)
assert.Equalf(t, http.StatusUnauthorized, res.StatusCode, "%s", body)
assert.Equalf(t, text.ErrIDNoActiveSession, gjson.GetBytes(body, "error.id").String(), "%s", body)
})
t.Run("description=success", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
res, body := initFlow(t, user1, true)
assert.Contains(t, res.Request.URL.String(), settings.RouteInitAPIFlow)
assertion(t, body, true)
snapshotx.SnapshotT(t, json.RawMessage(gjson.GetBytes(body, "ui.nodes").Raw), snapshotx.ExceptPaths(
"0.attributes.value",
))
})
t.Run("description=can not init if identity has aal2 but session has aal1", func(t *testing.T) {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
res, body := initFlow(t, aal2Identity, true)
assert.Equalf(t, http.StatusForbidden, res.StatusCode, "%s", body)
assertx.EqualAsJSON(t,
session.NewErrAALNotSatisfied(publicTS.URL+"/self-service/login/browser?aal=aal2"),
json.RawMessage(body))
})
})
t.Run("description=init a flow as browser", func(t *testing.T) {
t.Run("case=unauthorized users are redirected to login preserving redirect_to param", func(t *testing.T) {
c := testhelpers.NewClientWithCookies(t)
// prevent the redirect
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
returnTo := "?return_to=validRedirect"
req, err := http.NewRequest("GET", publicTS.URL+settings.RouteInitBrowserFlow+returnTo, nil)
require.NoError(t, err)
res, err := c.Do(req)
require.NoError(t, err)
defer func() { _ = res.Body.Close() }()
// here we check that the redirect status is 303
require.Equal(t, http.StatusSeeOther, res.StatusCode)
location, err := res.Location()
require.NoError(t, err)
require.Equal(t, publicTS.URL+login.RouteInitBrowserFlow+returnTo, location.String())
})
t.Run("case=unauthorized users are redirected to login", func(t *testing.T) {
c := testhelpers.NewClientWithCookies(t)
// prevent the redirect
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
req, err := http.NewRequest("GET", publicTS.URL+settings.RouteInitBrowserFlow, nil)
require.NoError(t, err)
res, err := c.Do(req)
require.NoError(t, err)
defer func() { _ = res.Body.Close() }()
// here we check that the redirect status is 303
require.Equal(t, http.StatusSeeOther, res.StatusCode)
location, err := res.Location()
require.NoError(t, err)
require.Equal(t, publicTS.URL+login.RouteInitBrowserFlow, location.String())
})
t.Run("description=success", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
res, body := initFlow(t, user1, false)
assert.Contains(t, res.Request.URL.String(), reg.Config().SelfServiceFlowSettingsUI(ctx).String())
assertion(t, body, false)
})
t.Run("description=can not init if identity has aal2 but session has aal1", func(t *testing.T) {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
res, body := initFlow(t, aal2Identity, false)
assert.Contains(t, res.Request.URL.String(), reg.Config().SelfServiceFlowLoginUI(ctx).String())
assert.EqualValues(t, "Please complete the second authentication challenge.", gjson.GetBytes(body, "ui.messages.0.text").String(), "%s", body)
})
t.Run("description=settings return_to is persisted through aal2 flow", func(t *testing.T) {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
res, _ := initFlow(t, aal2Identity, false, WithInitAuthQuery(url.Values{"return_to": {"https://www.ory.com"}}))
assert.Contains(t, res.Request.URL.String(), reg.Config().SelfServiceFlowLoginUI(ctx).String())
lf, err := reg.LoginFlowPersister().GetLoginFlow(ctx, uuid.FromStringOrNil(res.Request.URL.Query().Get("flow")))
require.NoError(t, err)
reqURL, err := url.Parse(lf.RequestURL)
require.NoError(t, err)
// the return_to of login will be set to the full URL of the settings URL with its query parameters
settingsURL, err := url.Parse(publicTS.URL + settings.RouteInitBrowserFlow)
require.NoError(t, err)
q := settingsURL.Query()
q.Set("return_to", "https://www.ory.com")
settingsURL.RawQuery = q.Encode()
assert.Equal(t, settingsURL.String(), reqURL.Query().Get("return_to"))
})
t.Run("case=redirects with 303", func(t *testing.T) {
c := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
// prevent the redirect
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
req, err := http.NewRequest("GET", publicTS.URL+settings.RouteInitBrowserFlow, nil)
require.NoError(t, err)
res, err := c.Do(req)
require.NoError(t, err)
defer func() { _ = res.Body.Close() }()
// here we check that the redirect status is 303
require.Equal(t, http.StatusSeeOther, res.StatusCode)
location, err := res.Location()
require.NoError(t, err)
require.Contains(t, location.String(), conf.SelfServiceFlowSettingsUI(ctx).String())
})
})
t.Run("description=init a flow as SPA", func(t *testing.T) {
t.Run("description=without privileges", func(t *testing.T) {
res, body := initSPAFlow(t, testhelpers.NewTestClient(t))
assert.Equalf(t, http.StatusUnauthorized, res.StatusCode, "%s", body)
assert.Equalf(t, text.ErrIDNoActiveSession, gjson.GetBytes(body, "error.id").String(), "%s", body)
})
t.Run("description=success", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
res, body := initSPAFlow(t, user1)
assert.Contains(t, res.Request.URL.String(), settings.RouteInitBrowserFlow)
assertion(t, body, false)
})
t.Run("description=can not init if identity has aal2 but session has aal1", func(t *testing.T) {
email := testhelpers.RandomEmail()
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
user1 := testhelpers.NewHTTPClientWithIdentitySessionCookie(ctx, t, reg, &identity.Identity{
State: identity.StateActive,
Traits: []byte(`{"email":"` + email + `"}`),
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {Type: identity.CredentialsTypePassword, Config: []byte(`{"hashed_password":"$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"}`), Identifiers: []string{email}},
identity.CredentialsTypeWebAuthn: {Type: identity.CredentialsTypeWebAuthn, Config: []byte(`{"credentials":[{"is_passwordless":false}]}`), Identifiers: []string{email}},
},
})
res, body := initSPAFlow(t, user1)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
url := url.URL{
Scheme: conf.SelfPublicURL(ctx).Scheme,
Host: conf.SelfPublicURL(ctx).Host,
Path: login.RouteInitBrowserFlow,
}
q := url.Query()
q.Add("aal", "aal2")
url.RawQuery = q.Encode()
assertx.EqualAsJSON(t, session.NewErrAALNotSatisfied(url.String()), json.RawMessage(body))
})
t.Run("description=settings return_to should persist through mfa flows", func(t *testing.T) {
email := testhelpers.RandomEmail()
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
user1 := testhelpers.NewHTTPClientWithIdentitySessionCookie(ctx, t, reg, &identity.Identity{
State: identity.StateActive,
Traits: []byte(`{"email":"` + email + `"}`),
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {Type: identity.CredentialsTypePassword, Config: []byte(`{"hashed_password":"$argon2id$v=19$m=32,t=2,p=4$cm94YnRVOW5jZzFzcVE4bQ$MNzk5BtR2vUhrp6qQEjRNw"}`), Identifiers: []string{email}},
identity.CredentialsTypeWebAuthn: {Type: identity.CredentialsTypeWebAuthn, Config: []byte(`{"credentials":[{"is_passwordless":false}]}`), Identifiers: []string{email}},
},
})
res, body := initSPAFlow(t, user1, WithInitAuthQuery(url.Values{"return_to": {"https://www.ory.com/"}}))
assert.Equal(t, http.StatusForbidden, res.StatusCode)
returnToURL := gjson.GetBytes(body, "redirect_browser_to").String()
require.NotEmpty(t, returnToURL)
reqURL, err := url.Parse(gjson.GetBytes(body, "redirect_browser_to").String())
require.NoError(t, err)
settingsURL, err := url.Parse(publicTS.URL + settings.RouteInitBrowserFlow)
require.NoError(t, err)
q := settingsURL.Query()
q.Add("return_to", "https://www.ory.com/")
settingsURL.RawQuery = q.Encode()
assert.Equal(t, settingsURL.String(), reqURL.Query().Get("return_to"))
})
})
})
t.Run("case=multi-schema_endpoint=init", func(t *testing.T) {
t.Run("description=init a flow as API", func(t *testing.T) {
t.Run("description=success", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
})
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
// set the default schema to something else than the default
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "not-default")
res, body := initFlow(t, user1, true)
assert.Contains(t, res.Request.URL.String(), settings.RouteInitAPIFlow)
assertion(t, body, true)
snapshotx.SnapshotT(t, json.RawMessage(gjson.GetBytes(body, "ui.nodes").Raw), snapshotx.ExceptPaths(
"0.attributes.value",
))
})
})
t.Run("description=init a flow as browser", func(t *testing.T) {
t.Run("description=success", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
})
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
// set the default schema to something else than the default
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "not-default")
res, body := initFlow(t, user1, false)
assert.Contains(t, res.Request.URL.String(), reg.Config().SelfServiceFlowSettingsUI(ctx).String())
assertion(t, body, false)
snapshotx.SnapshotT(t, json.RawMessage(gjson.GetBytes(body, "ui.nodes").Raw), snapshotx.ExceptPaths(
"0.attributes.value",
))
})
})
t.Run("description=init a flow as SPA", func(t *testing.T) {
t.Run("description=success", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
})
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
// set the default schema to something else than the default
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "not-default")
res, body := initSPAFlow(t, user1)
assert.Contains(t, res.Request.URL.String(), settings.RouteInitBrowserFlow)
assertion(t, body, false)
snapshotx.SnapshotT(t, json.RawMessage(gjson.GetBytes(body, "ui.nodes").Raw), snapshotx.ExceptPaths(
"0.attributes.value",
))
})
})
})
t.Run("endpoint=fetch", func(t *testing.T) {
t.Run("description=fetching a non-existent flow should return a 404 error", func(t *testing.T) {
_, _, err := testhelpers.NewSDKCustomClient(publicTS, otherUser).FrontendAPI.GetSettingsFlow(context.Background()).Id("i-do-not-exist").Execute()
require.Error(t, err)
require.IsTypef(t, new(kratos.GenericOpenAPIError), err, "%T", err)
assert.Equal(t, int64(http.StatusNotFound), gjson.GetBytes(err.(*kratos.GenericOpenAPIError).Body(), "error.code").Int())
})
t.Run("description=fetching an expired flow returns 410", func(t *testing.T) {
pr := newExpiredFlow()
require.NoError(t, reg.SettingsFlowPersister().CreateSettingsFlow(context.Background(), pr))
_, _, err := testhelpers.NewSDKCustomClient(publicTS, primaryUser).FrontendAPI.GetSettingsFlow(context.Background()).Id(pr.ID.String()).Execute()
require.Error(t, err)
require.IsTypef(t, new(kratos.GenericOpenAPIError), err, "%T", err)
assert.Equal(t, int64(http.StatusGone), gjson.GetBytes(err.(*kratos.GenericOpenAPIError).Body(), "error.code").Int())
})
t.Run("case=expired with return_to", func(t *testing.T) {
returnTo := "https://www.ory.com"
conf.MustSet(ctx, config.ViperKeyURLsAllowedReturnToDomains, []string{returnTo})
client := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
body := testhelpers.EasyGetBody(t, client, publicTS.URL+settings.RouteInitBrowserFlow+"?return_to="+returnTo)
// Expire the flow
f, err := reg.SettingsFlowPersister().GetSettingsFlow(context.Background(), uuid.FromStringOrNil(gjson.GetBytes(body, "id").String()))
require.NoError(t, err)
f.ExpiresAt = time.Now().Add(-time.Second)
require.NoError(t, reg.SettingsFlowPersister().UpdateSettingsFlow(context.Background(), f))
// Retrieve the flow and verify that return_to is in the response
getURL := fmt.Sprintf("%s%s?id=%s&return_to=%s", publicTS.URL, settings.RouteGetFlow, f.ID, returnTo)
getBody := testhelpers.EasyGetBody(t, client, getURL)
assert.Equal(t, gjson.GetBytes(getBody, "error.details.return_to").String(), returnTo)
// submit the flow but it is expired
u := publicTS.URL + settings.RouteSubmitFlow + "?flow=" + f.ID.String()
res, err := client.PostForm(u, url.Values{"method": {"password"}, "csrf_token": {"csrf"}, "password": {"password"}})
require.NoError(t, err)
resBody, err := io.ReadAll(res.Body)
require.NoError(t, err)
require.NoError(t, res.Body.Close())
f, err = reg.SettingsFlowPersister().GetSettingsFlow(context.Background(), uuid.FromStringOrNil(gjson.GetBytes(resBody, "id").String()))
require.NoError(t, err)
assert.Equal(t, publicTS.URL+settings.RouteInitBrowserFlow+"?return_to="+returnTo, f.RequestURL)
})
t.Run("description=should fail to fetch request if identity changed", func(t *testing.T) {
t.Run("type=api", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
user2 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
res, err := user1.Get(publicTS.URL + settings.RouteInitAPIFlow)
require.NoError(t, err)
defer func() { _ = res.Body.Close() }()
assert.Len(t, res.Header.Get("Set-Cookie"), 0)
body := ioutilx.MustReadAll(res.Body)
id := gjson.GetBytes(body, "id")
require.NotEmpty(t, id)
res, err = user2.Get(publicTS.URL + settings.RouteGetFlow + "?id=" + id.String())
require.NoError(t, err)
defer func() { _ = res.Body.Close() }()
require.EqualValues(t, res.StatusCode, http.StatusForbidden)
body = ioutilx.MustReadAll(res.Body)
assert.Containsf(t, gjson.GetBytes(body, "error.reason").String(), "The request was made for another identity and has been blocked for security reasons", "%s", body)
})
t.Run("type=browser", func(t *testing.T) {
res, err := primaryUser.Get(publicTS.URL + settings.RouteInitBrowserFlow)
require.NoError(t, err)
rid := res.Request.URL.Query().Get("flow")
require.NotEmpty(t, rid)
_, _, err = testhelpers.NewSDKCustomClient(publicTS, otherUser).FrontendAPI.GetSettingsFlow(context.Background()).Id(rid).Execute()
require.Error(t, err)
require.IsTypef(t, new(kratos.GenericOpenAPIError), err, "%T", err)
assert.Equal(t, int64(http.StatusForbidden), gjson.GetBytes(err.(*kratos.GenericOpenAPIError).Body(), "error.code").Int(), "should return a 403 error because the identities from the cookies do not match")
})
})
t.Run("description=can not fetch if identity has aal2 but session has aal1", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
})
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, "aal1")
res, body := initFlow(t, aal2Identity, false)
require.Equal(t, http.StatusOK, res.StatusCode)
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
flowID := gjson.GetBytes(body, "id").String()
settingsURL := publicTS.URL + settings.RouteGetFlow + "?id=" + flowID
res, err := aal2Identity.Get(settingsURL)
require.NoError(t, err)
body = ioutilx.MustReadAll(res.Body)
require.NoError(t, res.Body.Close())
url := url.URL{
Scheme: conf.SelfPublicURL(ctx).Scheme,
Host: conf.SelfPublicURL(ctx).Host,
Path: login.RouteInitBrowserFlow,
}
returnTo := conf.SelfServiceFlowSettingsUI(context.Background())
rq := returnTo.Query()
// the flow ID should be perserved within the return_to query parameter of the login flow
rq.Set("flow", flowID)
returnTo.RawQuery = rq.Encode()
q := url.Query()
q.Set("aal", "aal2")
q.Set("return_to", returnTo.String())
url.RawQuery = q.Encode()
require.EqualValues(t, http.StatusForbidden, res.StatusCode)
assertx.EqualAsJSON(t, session.NewErrAALNotSatisfied(url.String()), json.RawMessage(body))
})
})
t.Run("endpoint=submit", func(t *testing.T) {
t.Run("description=can not submit if identity has aal2 but session has aal1", func(t *testing.T) {
t.Run("type=browser", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
})
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, "aal1")
res, body := initFlow(t, aal2Identity, false)
require.Equal(t, http.StatusOK, res.StatusCode)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
actual, res := testhelpers.SettingsMakeRequest(t, false, false, &f, aal2Identity, `{"method":"not-exists"}`)
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Equal(t, "Please complete the second authentication challenge.", gjson.Get(actual, "ui.messages.0.text").String(), actual)
})
t.Run("type=spa", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
})
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, "aal1")
res, body := initFlow(t, aal2Identity, false)
require.Equal(t, http.StatusOK, res.StatusCode)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
actual, res := testhelpers.SettingsMakeRequest(t, false, true, &f, aal2Identity, `{"method":"not-exists"}`)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
url := url.URL{
Scheme: conf.SelfPublicURL(ctx).Scheme,
Host: conf.SelfPublicURL(ctx).Host,
Path: login.RouteInitBrowserFlow,
}
q := url.Query()
q.Set("aal", "aal2")
q.Set("return_to", publicTS.URL+"/self-service/settings?flow="+f.GetId())
url.RawQuery = q.Encode()
assert.Equal(t, url.String(), gjson.Get(actual, "redirect_browser_to").String(), actual)
})
t.Run("type=api", func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
})
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, "aal1")
res, body := initFlow(t, aal2Identity, true)
require.Equal(t, http.StatusOK, res.StatusCode)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsRequiredAAL, config.HighestAvailableAAL)
actual, res := testhelpers.SettingsMakeRequest(t, true, false, &f, aal2Identity, `{"method":"not-exists"}`)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
url := url.URL{
Scheme: conf.SelfPublicURL(ctx).Scheme,
Host: conf.SelfPublicURL(ctx).Host,
Path: login.RouteInitBrowserFlow,
}
q := url.Query()
q.Set("aal", "aal2")
q.Set("return_to", publicTS.URL+"/self-service/settings?flow="+f.GetId())
url.RawQuery = q.Encode()
assert.Equal(t, url.String(), gjson.Get(actual, "redirect_browser_to").String(), actual)
})
})
t.Run("description=fail to submit form as another user", func(t *testing.T) {
t.Run("type=api", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
user2 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
_, body := initFlow(t, user1, true)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, true, false, &f, user2, `{"method":"not-exists"}`)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
assert.Equal(t, "The request was initiated by someone else and has been blocked for security reasons. Please go back and try again.", gjson.Get(actual, "error.reason").String(), actual)
})
t.Run("type=spa", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
user2 := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
_, body := initFlow(t, user1, true)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, true, &f, user2, `{"method":"not-exists"}`)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
assert.Equal(t, "The request was initiated by someone else and has been blocked for security reasons. Please go back and try again.", gjson.Get(actual, "error.reason").String(), actual)
})
t.Run("type=browser", func(t *testing.T) {
user1 := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
user2 := testhelpers.NewHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
_, body := initFlow(t, user1, true)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, false, &f, user2, `{"method":"not-exists"}`)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
assert.Equal(t, "The request was initiated by someone else and has been blocked for security reasons. Please go back and try again.", gjson.Get(actual, "error.reason").String(), actual)
})
})
t.Run("description=submit - kratos session cookie issued", func(t *testing.T) {
t.Run("type=spa", func(t *testing.T) {
_, body := initFlow(t, primaryUser, false)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, true, &f, primaryUser, fmt.Sprintf(`{"method":"profile", "numby": 15, "csrf_token": "%s"}`, nosurfx.FakeCSRFToken))
require.Equal(t, http.StatusOK, res.StatusCode)
require.Len(t, primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL+login.RouteGetFlow)), 1)
require.Contains(t, fmt.Sprintf("%v", primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL))), "ory_kratos_session")
assert.Equal(t, "Your changes have been saved!", gjson.Get(actual, "ui.messages.0.text").String(), actual)
})
t.Run("type=browser", func(t *testing.T) {
_, body := initFlow(t, primaryUser, false)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, false, &f, primaryUser, `method=profile&traits.numby=15&csrf_token=`+nosurfx.FakeCSRFToken)
assert.Equal(t, http.StatusOK, res.StatusCode)
require.Len(t, primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL+login.RouteGetFlow)), 1)
require.Contains(t, fmt.Sprintf("%v", primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL))), "ory_kratos_session")
assert.Equal(t, "Your changes have been saved!", gjson.Get(actual, "ui.messages.0.text").String(), actual)
})
})
})
t.Run("case=multi-schema_endpoint=submit", func(t *testing.T) {
t.Run("description=fail to submit form with invalid data", func(t *testing.T) {
for _, tc := range []struct {
name string
isAPI bool
isSPA bool
}{
{
name: "api",
isAPI: true,
}, {
name: "spa",
isSPA: true,
}, {
name: "browser",
},
} {
t.Run("type="+tc.name, func(t *testing.T) {
t.Cleanup(func() {
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
})
user1 := testhelpers.NewHTTPClientWithArbitrarySessionToken(ctx, t, reg)
// set the default schema to something else than the default
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "not-default")
_, body := initFlow(t, user1, true)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
_, res := testhelpers.SettingsMakeRequest(t, tc.isAPI, tc.isSPA, &f, user1, `{"should_long_string":"tooshort"}`)
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
})
}
})
t.Run("description=submit - kratos session cookie issued", func(t *testing.T) {
t.Run("type=spa", func(t *testing.T) {
_, body := initFlow(t, primaryUser, false)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, true, &f, primaryUser, fmt.Sprintf(`{"method":"profile", "numby": 15, "csrf_token": "%s"}`, nosurfx.FakeCSRFToken))
require.Equal(t, http.StatusOK, res.StatusCode)
require.Len(t, primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL+login.RouteGetFlow)), 1)
require.Contains(t, fmt.Sprintf("%v", primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL))), "ory_kratos_session")
assert.Equal(t, "Your changes have been saved!", gjson.Get(actual, "ui.messages.0.text").String(), actual)
})
t.Run("type=browser", func(t *testing.T) {
_, body := initFlow(t, primaryUser, false)
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, false, &f, primaryUser, `method=profile&traits.numby=15&csrf_token=`+nosurfx.FakeCSRFToken)
assert.Equal(t, http.StatusOK, res.StatusCode)
require.Len(t, primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL+login.RouteGetFlow)), 1)
require.Contains(t, fmt.Sprintf("%v", primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL))), "ory_kratos_session")
assert.Equal(t, "Your changes have been saved!", gjson.Get(actual, "ui.messages.0.text").String(), actual)
})
})
})
t.Run("case=relative redirect when self-service settings ui is a relative url", func(t *testing.T) {
reg.Config().MustSet(ctx, config.ViperKeySelfServiceSettingsURL, "/settings-ts")
user1 := testhelpers.NewNoRedirectHTTPClientWithArbitrarySessionCookie(ctx, t, reg)
res, _ := initFlow(t, user1, false)
assert.Regexp(
t,
"^/settings-ts.*$",
res.Header.Get("Location"),
)
})
}