Files
upstream-ory-kratos/selfservice/strategy/profile/registration_test.go
upstream-mirror c60cc7ca75
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
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

356 lines
14 KiB
Go

// Copyright © 2025 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package profile_test
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"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/registration"
"github.com/ory/kratos/selfservice/strategy/oidc"
"github.com/ory/kratos/ui/node"
"github.com/ory/x/assertx"
"github.com/ory/x/contextx"
"github.com/ory/x/snapshotx"
)
func TestTwoStepRegistration(t *testing.T) {
ctx := context.Background()
conf, reg := pkg.NewFastRegistryWithMocks(t)
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypePassword.String(), true)
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypeOIDC.String(), true)
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypePasskey.String(), true)
conf.MustSet(ctx, config.ViperKeySelfServiceStrategyConfig+"."+string(identity.CredentialsTypeOIDC)+".config", &oidc.ConfigurationCollection{Providers: []oidc.Configuration{
{
ID: "google",
Provider: "google",
ClientID: "1234",
ClientSecret: "1234",
},
}})
conf.MustSet(ctx, config.ViperKeyWebAuthnPasswordless, true)
conf.MustSet(ctx, config.ViperKeyPasskeyRPID, "localhost")
conf.MustSet(ctx, config.ViperKeyPasskeyRPDisplayName, "localhost")
conf.MustSet(ctx, config.ViperKeyWebAuthnRPID, "localhost")
conf.MustSet(ctx, config.ViperKeyWebAuthnRPDisplayName, "localhost")
conf.MustSet(ctx, fmt.Sprintf("%s.%s.passwordless_enabled", config.ViperKeySelfServiceStrategyConfig, identity.CredentialsTypeCodeAuth), true)
conf.MustSet(ctx, config.ViperKeySelfServiceRegistrationFlowStyle, "profile_first")
_ = testhelpers.NewErrorTestServer(t, reg)
publicTS, _ := testhelpers.NewKratosServer(t, reg)
_ = testhelpers.NewRedirSessionEchoTS(t, reg)
ui := testhelpers.NewRegistrationUIFlowEchoServer(t, reg)
t.Run("initial form is populated with identity traits", func(t *testing.T) {
t.Run("type=browser", func(t *testing.T) {
client := testhelpers.NewClientWithCookies(t)
t.Run("empty_flow", func(t *testing.T) {
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, false, false, false)
snapshotx.SnapshotT(t, f.Ui.Nodes, snapshotx.ExceptPaths(
"1.attributes.value",
"8.attributes.nonce",
"8.attributes.src",
"10.attributes.value",
))
})
t.Run("case=multi-schema-empty_flow", func(t *testing.T) {
t.Cleanup(func() {
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
})
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
conf.MustSet(ctx, config.ViperKeyIdentitySchemas, config.Schemas{
{ID: "default", URL: "file://./stub/identity-doesnotexist.schema.json"},
{ID: "not-default", URL: "file://./stub/identity.schema.json", SelfserviceSelectable: true},
})
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, false, false, false, testhelpers.InitFlowWithIdentitySchema("not-default"))
snapshotx.SnapshotT(t, f.Ui.Nodes, snapshotx.ExceptPaths(
"1.attributes.value",
"8.attributes.nonce",
"8.attributes.src",
"10.attributes.value",
))
})
t.Run("case=multi-schema-invalid-form-data", func(t *testing.T) {
t.Cleanup(func() {
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
})
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
conf.MustSet(ctx, config.ViperKeyIdentitySchemas, config.Schemas{
{ID: "default", URL: "file://./stub/identity-doesnotexist.schema.json"},
{ID: "not-default", URL: "file://./stub/identity.schema.json", SelfserviceSelectable: true},
})
body := testhelpers.SubmitRegistrationForm(t, false, client, publicTS, func(v url.Values) {
v.Set("traits.email", "invalidemail")
v.Set("traits.booly", "true")
v.Set("traits.numby", "1")
v.Set("traits.stringy", "string")
v.Set("traits.should_big_number", "1000000")
v.Set("traits.should_long_string", "1111111111111111111111111111111111111111111111111111111111")
v.Set("method", "profile")
}, false, http.StatusOK, ui.URL, testhelpers.InitFlowWithIdentitySchema("not-default"))
fmt.Println(body)
require.Equal(t, int64(4000040), gjson.Get(body, "ui.nodes.#(attributes.name==traits.email).messages.0.id").Int(), "%s", body)
require.Equal(t, "Enter a valid email address", gjson.Get(body, "ui.nodes.#(attributes.name==traits.email).messages.0.text").String(), "%s", body)
})
t.Run("select_credentials", func(t *testing.T) {
res := testhelpers.SubmitRegistrationForm(t, false, client, publicTS, func(v url.Values) {
v.Set("traits.email", "browser-1@example.org")
v.Set("traits.booly", "true")
v.Set("traits.numby", "1")
v.Set("traits.stringy", "string")
v.Set("traits.should_big_number", "1000000")
v.Set("traits.should_long_string", "1111111111111111111111111111111111111111111111111111111111")
v.Set("method", "profile")
}, false, http.StatusOK, ui.URL)
snapshotx.SnapshotT(t, json.RawMessage(gjson.Get(res, "ui.nodes").Raw), snapshotx.ExceptPaths(
"1.attributes.value",
"8.attributes.nonce",
"8.attributes.src",
"11.attributes.value",
))
})
t.Run("return_to_profile", func(t *testing.T) {
res := testhelpers.SubmitRegistrationForm(t, false, client, publicTS, func(v url.Values) {
v.Set("traits.email", "browser-1-1@example.org")
v.Set("traits.booly", "true")
v.Set("traits.numby", "1")
v.Set("traits.stringy", "string")
v.Set("traits.should_big_number", "1000000")
v.Set("traits.should_long_string", "1111111111111111111111111111111111111111111111111111111111")
v.Set("screen", "previous")
}, false, http.StatusOK, ui.URL)
snapshotx.SnapshotT(t, json.RawMessage(gjson.Get(res, "ui.nodes").Raw), snapshotx.ExceptPaths(
"1.attributes.value",
"8.attributes.nonce",
"8.attributes.src",
"10.attributes.value",
))
})
t.Run("select_credentials_again", func(t *testing.T) {
res := testhelpers.SubmitRegistrationForm(t, false, client, publicTS, func(v url.Values) {
v.Set("traits.email", "browser-1-1@example.org")
v.Set("method", "profile")
}, false, http.StatusOK, ui.URL)
snapshotx.SnapshotT(t, json.RawMessage(gjson.Get(res, "ui.nodes").Raw), snapshotx.ExceptPaths(
"1.attributes.value",
"8.attributes.nonce",
"8.attributes.src",
"11.attributes.value",
))
})
})
})
}
func TestOneStepRegistration(t *testing.T) {
ctx := context.Background()
conf, reg := pkg.NewFastRegistryWithMocks(t)
testhelpers.StrategyEnable(t, conf, identity.CredentialsTypePassword.String(), true)
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
conf.MustSet(ctx, config.ViperKeySelfServiceBrowserDefaultReturnTo, "https://www.ory.com/")
conf.MustSet(ctx, config.ViperKeySelfServiceRegistrationFlowStyle, "unified")
_ = testhelpers.NewErrorTestServer(t, reg)
publicTS, _ := testhelpers.NewKratosServer(t, reg)
_ = testhelpers.NewRedirSessionEchoTS(t, reg)
ui := testhelpers.NewRegistrationUIFlowEchoServer(t, reg)
t.Run("initial form is populated with identity traits", func(t *testing.T) {
t.Run("type=browser", func(t *testing.T) {
client := testhelpers.NewClientWithCookies(t)
t.Run("empty_flow", func(t *testing.T) {
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, false, false, false)
snapshotx.SnapshotT(t, f.Ui.Nodes, snapshotx.ExceptPaths(
"0.attributes.value",
))
})
t.Run("case=multi-schema-empty_flow", func(t *testing.T) {
t.Cleanup(func() {
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
})
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
conf.MustSet(ctx, config.ViperKeyIdentitySchemas, config.Schemas{
{ID: "default", URL: "file://./stub/identity-doesnotexist.schema.json"},
{ID: "not-default", URL: "file://./stub/identity.schema.json", SelfserviceSelectable: true},
})
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, false, false, false, testhelpers.InitFlowWithIdentitySchema("not-default"))
snapshotx.SnapshotT(t, f.Ui.Nodes, snapshotx.ExceptPaths(
"0.attributes.value",
))
})
t.Run("case=multi-schema-invalid-form-data", func(t *testing.T) {
t.Cleanup(func() {
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
})
conf.MustSet(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
conf.MustSet(ctx, config.ViperKeyIdentitySchemas, config.Schemas{
{ID: "default", URL: "file://./stub/identity-doesnotexist.schema.json"},
{ID: "not-default", URL: "file://./stub/identity.schema.json", SelfserviceSelectable: true},
})
body := testhelpers.SubmitRegistrationForm(t, false, client, publicTS, func(v url.Values) {
v.Set("traits.email", "invalidemail")
v.Set("traits.booly", "true")
v.Set("traits.numby", "1")
v.Set("traits.stringy", "string")
v.Set("traits.should_big_number", "1000000")
v.Set("traits.should_long_string", "1111111111111111111111111111111111111111111111111111111111")
v.Set("password", "password")
v.Set("method", "password")
}, false, http.StatusOK, ui.URL, testhelpers.InitFlowWithIdentitySchema("not-default"))
fmt.Println(body)
require.Equal(t, int64(4000040), gjson.Get(body, "ui.nodes.#(attributes.name==traits.email).messages.0.id").Int(), "%s", body)
require.Equal(t, "Enter a valid email address", gjson.Get(body, "ui.nodes.#(attributes.name==traits.email).messages.0.text").String(), "%s", body)
})
})
})
}
func TestPopulateRegistrationMethod(t *testing.T) {
ctx := context.Background()
conf, reg := pkg.NewFastRegistryWithMocks(t)
ctx = testhelpers.WithDefaultIdentitySchema(ctx, "file://stub/identity.schema.json")
s, err := reg.AllRegistrationStrategies().Strategy(identity.CredentialsTypeProfile)
require.NoError(t, err)
fh, ok := s.(registration.FormHydrator)
require.True(t, ok)
toSnapshot := func(t *testing.T, f node.Nodes) {
t.Helper()
// The CSRF token has a unique value that messes with the snapshot - ignore it.
f.ResetNodes("csrf_token")
f.ResetNodes("passkey_challenge")
snapshotx.SnapshotT(t, f, snapshotx.ExceptNestedKeys("nonce", "src"))
}
newFlowInternal := func(ctx context.Context, t *testing.T, identitySchema string) (*http.Request, *registration.Flow) {
query := ""
if identitySchema != "" {
query = "?identity_schema=" + identitySchema
}
r := httptest.NewRequest("GET", "/self-service/registration/browser"+query, nil)
r = r.WithContext(ctx)
t.Helper()
f, err := registration.NewFlow(reg, r, flow.TypeBrowser)
f.UI.Nodes = make(node.Nodes, 0)
require.NoError(t, err)
return r, f
}
newFlow := func(ctx context.Context, t *testing.T) (*http.Request, *registration.Flow) {
return newFlowInternal(ctx, t, "")
}
newFlowWithIdentitySchema := func(ctx context.Context, t *testing.T, identitySchema string) (*http.Request, *registration.Flow) {
return newFlowInternal(ctx, t, identitySchema)
}
t.Run("method=PopulateRegistrationMethod", func(t *testing.T) {
r, f := newFlow(ctx, t)
require.NoError(t, fh.PopulateRegistrationMethod(r, f))
toSnapshot(t, f.UI.Nodes)
})
t.Run("method=PopulateRegistrationMethodProfile", func(t *testing.T) {
r, f := newFlow(ctx, t)
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
toSnapshot(t, f.UI.Nodes)
})
t.Run("method=PopulateRegistrationMethodCredentials", func(t *testing.T) {
r, f := newFlow(ctx, t)
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
toSnapshot(t, f.UI.Nodes)
})
t.Run("method=idempotency", func(t *testing.T) {
r, f := newFlow(ctx, t)
var snapshots []node.Nodes
t.Run("case=1", func(t *testing.T) {
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
snapshots = append(snapshots, f.UI.Nodes)
toSnapshot(t, f.UI.Nodes)
})
t.Run("case=2", func(t *testing.T) {
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
snapshots = append(snapshots, f.UI.Nodes)
toSnapshot(t, f.UI.Nodes)
})
t.Run("case=3", func(t *testing.T) {
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
snapshots = append(snapshots, f.UI.Nodes)
toSnapshot(t, f.UI.Nodes)
})
t.Run("case=4", func(t *testing.T) {
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
snapshots = append(snapshots, f.UI.Nodes)
toSnapshot(t, f.UI.Nodes)
})
t.Run("case=evaluate", func(t *testing.T) {
assertx.EqualAsJSON(t, snapshots[0], snapshots[2])
assertx.EqualAsJSON(t, snapshots[1], snapshots[3])
})
})
t.Run("case=multi-schema-method=PopulateRegistrationMethodProfile", func(t *testing.T) {
t.Cleanup(func() {
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/identity.schema.json")
})
multiSchema := contextx.WithConfigValue(ctx, config.ViperKeyDefaultIdentitySchemaID, "default")
multiSchema = contextx.WithConfigValue(multiSchema, config.ViperKeyIdentitySchemas, config.Schemas{
{ID: "default", URL: "file://./stub/identity-doesnotexist.schema.json"},
{ID: "not-default", URL: "file://./stub/identity.schema.json", SelfserviceSelectable: true},
})
r, f := newFlowWithIdentitySchema(multiSchema, t, "not-default")
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
toSnapshot(t, f.UI.Nodes)
})
}