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
986 lines
43 KiB
Go
986 lines
43 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package password_test
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tidwall/gjson"
|
|
|
|
"github.com/ory/kratos/driver"
|
|
"github.com/ory/kratos/driver/config"
|
|
"github.com/ory/kratos/identity"
|
|
"github.com/ory/kratos/pkg"
|
|
kratos "github.com/ory/kratos/pkg/httpclient"
|
|
"github.com/ory/kratos/pkg/registrationhelpers"
|
|
"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/registration"
|
|
"github.com/ory/kratos/text"
|
|
"github.com/ory/kratos/ui/container"
|
|
"github.com/ory/kratos/ui/node"
|
|
"github.com/ory/kratos/x"
|
|
"github.com/ory/kratos/x/nosurfx"
|
|
"github.com/ory/x/assertx"
|
|
"github.com/ory/x/configx"
|
|
"github.com/ory/x/snapshotx"
|
|
"github.com/ory/x/sqlcon"
|
|
)
|
|
|
|
var flows = []string{"spa", "api", "browser"}
|
|
|
|
func newRegistrationRegistry(t *testing.T, cfgOpts ...configx.OptionModifier) *driver.RegistryDefault {
|
|
_, reg := pkg.NewFastRegistryWithMocks(t, append([]configx.OptionModifier{
|
|
configx.WithValues(map[string]any{
|
|
config.ViperKeySelfServiceStrategyConfig + "." + string(identity.CredentialsTypePassword) + ".enabled": true,
|
|
config.ViperKeySelfServiceRegistrationLoginHints: true,
|
|
config.ViperKeySelfServiceRegistrationEnableLegacyOneStep: true,
|
|
}),
|
|
}, cfgOpts...)...)
|
|
|
|
return reg
|
|
}
|
|
|
|
func TestRegistration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("case=registration", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
reg := newRegistrationRegistry(t, configx.WithValues(map[string]any{
|
|
config.ViperKeySelfServiceRegistrationEnableLegacyOneStep: true,
|
|
config.ViperKeyDefaultIdentitySchemaID: "default",
|
|
config.ViperKeyIdentitySchemas: config.Schemas{
|
|
{ID: "default", URL: "file://stub/registration.schema.json", SelfserviceSelectable: true},
|
|
{ID: "other", URL: "file://stub/registration.schema.json", SelfserviceSelectable: true},
|
|
},
|
|
}))
|
|
conf := reg.Config()
|
|
|
|
publicTS, _ := testhelpers.NewKratosServer(t, reg)
|
|
_ = testhelpers.NewErrorTestServer(t, reg)
|
|
_ = testhelpers.NewRegistrationUIFlowEchoServer(t, reg)
|
|
redirTS := testhelpers.NewRedirSessionEchoTS(t, reg)
|
|
redirNoSessionTS := testhelpers.NewRedirNoSessionTS(t, reg)
|
|
|
|
// set the "return to" server, which will assert the session state
|
|
// (redirTS: enforce that a session exists, redirNoSessionTS: enforce that no session exists)
|
|
useReturnToFromTS := func(ts *httptest.Server) {
|
|
conf.MustSet(t.Context(), config.ViperKeySelfServiceBrowserDefaultReturnTo, ts.URL+"/default-return-to")
|
|
conf.MustSet(t.Context(), config.ViperKeySelfServiceRegistrationAfter+"."+config.DefaultBrowserReturnURL, ts.URL+"/registration-return-ts")
|
|
}
|
|
|
|
useReturnToFromTS(redirTS)
|
|
|
|
apiClient := testhelpers.NewDebugClient(t)
|
|
|
|
t.Run("AssertCommonErrorCases", func(t *testing.T) {
|
|
registrationhelpers.AssertCommonErrorCases(t, flows)
|
|
})
|
|
|
|
t.Run("AssertRegistrationRespectsValidation", func(t *testing.T) {
|
|
reg := newRegistrationRegistry(t)
|
|
registrationhelpers.AssertRegistrationRespectsValidation(t, reg, flows, func(v url.Values) {
|
|
v.Del("traits.foobar")
|
|
})
|
|
})
|
|
|
|
t.Run("AssertCSRFFailures", func(t *testing.T) {
|
|
reg := newRegistrationRegistry(t)
|
|
registrationhelpers.AssertCSRFFailures(t, reg, flows, func(v url.Values) {
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("method", identity.CredentialsTypePassword.String())
|
|
})
|
|
})
|
|
|
|
t.Run("AssertSchemaDoesNotExist", func(t *testing.T) {
|
|
reg := newRegistrationRegistry(t)
|
|
registrationhelpers.AssertSchemaDoesNotExist(t, reg, flows, func(v url.Values) {
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("method", identity.CredentialsTypePassword.String())
|
|
})
|
|
})
|
|
|
|
expectRegistrationBody := func(t *testing.T, browserRedirTS *httptest.Server, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string {
|
|
if isAPI {
|
|
return testhelpers.SubmitRegistrationForm(t, isAPI, hc, publicTS, values,
|
|
isSPA, http.StatusOK,
|
|
publicTS.URL+registration.RouteSubmitFlow)
|
|
}
|
|
|
|
if hc == nil {
|
|
hc = testhelpers.NewClientWithCookies(t)
|
|
}
|
|
|
|
expectReturnTo := browserRedirTS.URL + "/registration-return-ts"
|
|
if isSPA {
|
|
expectReturnTo = publicTS.URL
|
|
}
|
|
|
|
return testhelpers.SubmitRegistrationForm(t, isAPI, hc, publicTS, values,
|
|
isSPA, http.StatusOK, expectReturnTo)
|
|
}
|
|
|
|
expectSuccessfulRegistration := func(t *testing.T, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string {
|
|
useReturnToFromTS(redirTS)
|
|
return expectRegistrationBody(t, redirTS, isAPI, isSPA, hc, values)
|
|
}
|
|
|
|
expectNoRegistration := func(t *testing.T, isAPI, isSPA bool, hc *http.Client, values func(url.Values)) string {
|
|
useReturnToFromTS(redirNoSessionTS)
|
|
t.Cleanup(func() {
|
|
useReturnToFromTS(redirTS)
|
|
})
|
|
return expectRegistrationBody(t, redirNoSessionTS, isAPI, isSPA, hc, values)
|
|
}
|
|
|
|
t.Run("case=should reject invalid transient payload", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}})
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
})
|
|
|
|
for _, f := range flows {
|
|
t.Run("type="+f, func(t *testing.T) {
|
|
username := x.NewUUID().String()
|
|
body := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, f, func(v url.Values) {
|
|
v.Set("traits.username", username)
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
v.Set("transient_payload", "42")
|
|
})
|
|
assert.Equal(t, "bar", gjson.Get(body, "ui.nodes.#(attributes.name==traits.foobar).attributes.value").String(), "%s", body)
|
|
assert.Equal(t, username, gjson.Get(body, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", body)
|
|
assert.Equal(t, int64(4000026), gjson.Get(body, "ui.nodes.#(attributes.name==transient_payload).messages.0.id").Int(), "%s", body)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("case=should accept valid transient payload", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}})
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
})
|
|
|
|
setValues := func(username string, v url.Values) {
|
|
v.Set("traits.username", username)
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
v.Set("transient_payload.stuff", "42")
|
|
}
|
|
|
|
t.Run("type=api", func(t *testing.T) {
|
|
username := x.NewUUID().String()
|
|
body := expectSuccessfulRegistration(t, true, false, nil, func(v url.Values) {
|
|
setValues(username, v)
|
|
})
|
|
assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.NotEmpty(t, gjson.Get(body, "session_token").String(), "%s", body)
|
|
assert.NotEmpty(t, gjson.Get(body, "session.id").String(), "%s", body)
|
|
})
|
|
|
|
t.Run("type=spa", func(t *testing.T) {
|
|
username := x.NewUUID().String()
|
|
body := expectSuccessfulRegistration(t, false, true, nil, func(v url.Values) {
|
|
setValues(username, v)
|
|
})
|
|
assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "session_token").String(), "%s", body)
|
|
assert.NotEmpty(t, gjson.Get(body, "session.id").String(), "%s", body)
|
|
})
|
|
|
|
t.Run("type=browser", func(t *testing.T) {
|
|
username := x.NewUUID().String()
|
|
body := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) {
|
|
setValues(username, v)
|
|
})
|
|
assert.Equal(t, username, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
})
|
|
})
|
|
|
|
t.Run("case=should pass and set up a session", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}})
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
})
|
|
|
|
t.Run("type=api", func(t *testing.T) {
|
|
body := expectSuccessfulRegistration(t, true, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-api")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-8-api`, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.NotEmpty(t, gjson.Get(body, "session_token").String(), "%s", body)
|
|
assert.NotEmpty(t, gjson.Get(body, "session.id").String(), "%s", body)
|
|
assert.NotContains(t, gjson.Get(body, "continue_with").Raw, string(flow.ContinueWithActionRedirectBrowserToString), "%s", body)
|
|
})
|
|
|
|
t.Run("type=spa", func(t *testing.T) {
|
|
body := expectSuccessfulRegistration(t, false, true, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-spa")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-8-spa`, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "session_token").String(), "%s", body)
|
|
assert.NotEmpty(t, gjson.Get(body, "session.id").String(), "%s", body)
|
|
assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(body, "continue_with.0.action").String(), "%s", body)
|
|
})
|
|
|
|
t.Run("type=browser", func(t *testing.T) {
|
|
body := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-browser")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-8-browser`, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "continue_with").Array(), "%s", body)
|
|
})
|
|
})
|
|
|
|
t.Run("case=should not set up a session if hook is not configured", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
|
|
t.Run("type=api", func(t *testing.T) {
|
|
body := expectNoRegistration(t, true, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-api-nosession")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-8-api-nosession`, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "session_token").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "session.id").String(), "%s", body)
|
|
})
|
|
|
|
t.Run("type=spa", func(t *testing.T) {
|
|
expectNoRegistration(t, false, true, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-spa-nosession")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
})
|
|
|
|
t.Run("type=browser", func(t *testing.T) {
|
|
expectNoRegistration(t, false, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-browser-nosession")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("case=should fail to register the same user again", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}})
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
})
|
|
|
|
applyTransform := func(values, transform func(v url.Values)) func(v url.Values) {
|
|
return func(v url.Values) {
|
|
values(v)
|
|
transform(v)
|
|
}
|
|
}
|
|
|
|
// test duplicate registration on all client types, where the values can be transformed before
|
|
// they are sent the second time
|
|
testWithTransform := func(t *testing.T, suffix string, transform func(v url.Values)) {
|
|
t.Run("type=api", func(t *testing.T) {
|
|
values := func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-api-duplicate-"+suffix)
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
}
|
|
|
|
_ = expectSuccessfulRegistration(t, true, false, apiClient, values)
|
|
body := testhelpers.SubmitRegistrationForm(t, true, apiClient, publicTS,
|
|
applyTransform(values, transform), false, http.StatusBadRequest,
|
|
publicTS.URL+registration.RouteSubmitFlow)
|
|
assert.Contains(t, gjson.Get(body, "ui.messages.0.text").String(), "You tried signing in with registration-identifier-8-api-duplicate-"+suffix+" which is already in use by another account. You can sign in using your password.", "%s", body)
|
|
})
|
|
|
|
t.Run("type=spa", func(t *testing.T) {
|
|
values := func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-spa-duplicate-"+suffix)
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
}
|
|
|
|
_ = expectSuccessfulRegistration(t, false, true, nil, values)
|
|
body := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, "spa", applyTransform(values, transform))
|
|
assert.Contains(t, gjson.Get(body, "ui.messages.0.text").String(), "You tried signing in with registration-identifier-8-spa-duplicate-"+suffix+" which is already in use by another account. You can sign in using your password.", "%s", body)
|
|
})
|
|
|
|
t.Run("type=browser", func(t *testing.T) {
|
|
values := func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-8-browser-duplicate-"+suffix)
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
}
|
|
|
|
_ = expectSuccessfulRegistration(t, false, false, nil, values)
|
|
body := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, "browser", applyTransform(values, transform))
|
|
assert.Contains(t, gjson.Get(body, "ui.messages.0.text").String(), "You tried signing in with registration-identifier-8-browser-duplicate-"+suffix+" which is already in use by another account. You can sign in using your password.", "%s", body)
|
|
})
|
|
}
|
|
|
|
t.Run("case=identical input", func(t *testing.T) {
|
|
testWithTransform(t, "identical", func(v url.Values) {
|
|
// base case
|
|
})
|
|
})
|
|
|
|
t.Run("case=different capitalization", func(t *testing.T) {
|
|
testWithTransform(t, "caps", func(v url.Values) {
|
|
v.Set("traits.username", strings.ToUpper(v.Get("traits.username")))
|
|
})
|
|
})
|
|
|
|
t.Run("case=leading whitespace", func(t *testing.T) {
|
|
testWithTransform(t, "leading", func(v url.Values) {
|
|
v.Set("traits.username", " "+v.Get("traits.username"))
|
|
})
|
|
})
|
|
|
|
t.Run("case=trailing whitespace", func(t *testing.T) {
|
|
testWithTransform(t, "trailing", func(v url.Values) {
|
|
v.Set("traits.username", v.Get("traits.username")+" ")
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("case=should return correct error ids from validation failures", func(t *testing.T) {
|
|
test := func(t *testing.T, constraint string, setValues func(url.Values), expectedId text.ID, expectedMesage string) {
|
|
template := `{
|
|
"$id": "https://example.com/person.schema.json",
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"title": "Person",
|
|
"type": "object",
|
|
"properties": {
|
|
"traits": {
|
|
"type": "object",
|
|
"properties": {
|
|
"foobar": {
|
|
%s
|
|
},
|
|
"username": {
|
|
"type": "string",
|
|
"ory.sh/kratos": {
|
|
"credentials": {
|
|
"password": {
|
|
"identifier": true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"required": [
|
|
"foobar",
|
|
"username"
|
|
]
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}`
|
|
|
|
testhelpers.SetDefaultIdentitySchemaFromRaw(conf, []byte(fmt.Sprintf(template, constraint)))
|
|
|
|
browserClient := testhelpers.NewClientWithCookies(t)
|
|
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, browserClient, publicTS, false, false, false)
|
|
c := f.Ui
|
|
|
|
values := testhelpers.SDKFormFieldsToURLValues(c.Nodes)
|
|
setValues(values)
|
|
values.Set("traits.username", "registration-identifier-9")
|
|
values.Set("password", x.NewUUID().String())
|
|
actual, _ := testhelpers.RegistrationMakeRequest(t, false, false, f, browserClient, values.Encode())
|
|
|
|
assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual)
|
|
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
|
registrationhelpers.CheckFormContent(t, []byte(actual), "password", "csrf_token", "traits.username")
|
|
assert.EqualValues(t, "registration-identifier-9", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", actual)
|
|
assert.Empty(t, gjson.Get(actual, "ui.nodes.messages").Array())
|
|
assert.Len(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).messages").Array(), 0)
|
|
assert.Equal(t, int64(expectedId), gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0.id").Int())
|
|
assert.Equal(t, expectedMesage, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0.text").String())
|
|
assert.Equal(t, "error", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0.type").String())
|
|
}
|
|
|
|
const key = "traits.foobar"
|
|
t.Run("case=string violating minLength", func(t *testing.T) {
|
|
test(t, `"type": "string", "minLength": 5`, func(v url.Values) { v.Set(key, "bar") }, text.ErrorValidationMinLength, "length must be >= 5, but got 3")
|
|
})
|
|
|
|
t.Run("case=string violating maxLength", func(t *testing.T) {
|
|
test(t, `"type": "string", "maxLength": 5`, func(v url.Values) { v.Set(key, "qwerty") }, text.ErrorValidationMaxLength, "length must be <= 5, but got 6")
|
|
})
|
|
|
|
t.Run("case=string violating pattern", func(t *testing.T) {
|
|
test(t, `"type": "string", "pattern": "^[a-z]*$"`, func(v url.Values) { v.Set(key, "FUBAR") }, text.ErrorValidationInvalidFormat, "does not match pattern \"^[a-z]*$\"")
|
|
})
|
|
|
|
t.Run("case=number violating minimum", func(t *testing.T) {
|
|
test(t, `"type": "number", "minimum": 5`, func(v url.Values) { v.Set(key, "3") }, text.ErrorValidationMinimum, "must be >= 5 but found 3")
|
|
})
|
|
|
|
t.Run("case=number violating exclusiveMinimum", func(t *testing.T) {
|
|
test(t, `"type": "number", "exclusiveMinimum": 5`, func(v url.Values) { v.Set(key, "5") }, text.ErrorValidationExclusiveMinimum, "must be > 5 but found 5")
|
|
})
|
|
|
|
t.Run("case=number violating maximum", func(t *testing.T) {
|
|
test(t, `"type": "number", "maximum": 5`, func(v url.Values) { v.Set(key, "6") }, text.ErrorValidationMaximum, "must be <= 5 but found 6")
|
|
})
|
|
|
|
t.Run("case=number violating exclusiveMaximum", func(t *testing.T) {
|
|
test(t, `"type": "number", "exclusiveMaximum": 5`, func(v url.Values) { v.Set(key, "5") }, text.ErrorValidationExclusiveMaximum, "must be < 5 but found 5")
|
|
})
|
|
|
|
t.Run("case=number violating multipleOf", func(t *testing.T) {
|
|
test(t, `"type": "number", "multipleOf": 3`, func(v url.Values) { v.Set(key, "7") }, text.ErrorValidationMultipleOf, "7 not multipleOf 3")
|
|
})
|
|
|
|
t.Run("case=array violating maxItems", func(t *testing.T) {
|
|
test(t, `"type": "array", "items": { "type": "string" }, "maxItems": 3`, func(v url.Values) { v.Add(key, "a"); v.Add(key, "b"); v.Add(key, "c"); v.Add(key, "d") }, text.ErrorValidationMaxItems, "maximum 3 items allowed, but found 4 items")
|
|
})
|
|
|
|
t.Run("case=array violating minItems", func(t *testing.T) {
|
|
test(t, `"type": "array", "items": { "type": "string" }, "minItems": 3`, func(v url.Values) { v.Add(key, "a"); v.Add(key, "b") }, text.ErrorValidationMinItems, "minimum 3 items allowed, but found 2 items")
|
|
})
|
|
|
|
t.Run("case=array violating uniqueItems", func(t *testing.T) {
|
|
test(t, `"type": "array", "items": { "type": "string" }, "uniqueItems": true`, func(v url.Values) { v.Add(key, "abc"); v.Add(key, "XYZ"); v.Add(key, "abc") }, text.ErrorValidationUniqueItems, "items at index 0 and 2 are equal")
|
|
})
|
|
|
|
t.Run("case=wrong type", func(t *testing.T) {
|
|
test(t, `"type": "number"`, func(v url.Values) { v.Set(key, "blabla") }, text.ErrorValidationWrongType, "expected number, but got string")
|
|
})
|
|
})
|
|
|
|
t.Run("case=should return an error because not passing validation and reset previous errors and values", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
|
|
|
check := func(t *testing.T, actual string) {
|
|
assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual)
|
|
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
|
registrationhelpers.CheckFormContent(t, []byte(actual), "password", "csrf_token", "traits.username")
|
|
}
|
|
|
|
checkFirst := func(t *testing.T, actual string) {
|
|
check(t, actual)
|
|
assert.Contains(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).messages.0").String(), `Property username is missing`, "%s", actual)
|
|
}
|
|
|
|
checkSecond := func(t *testing.T, actual string) {
|
|
check(t, actual)
|
|
assert.EqualValues(t, "registration-identifier-9", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", actual)
|
|
assert.Empty(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).messages").Array())
|
|
assert.Empty(t, gjson.Get(actual, "ui.nodes.messages").Array())
|
|
assert.Contains(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0").String(), `Property foobar is missing`, "%s", actual)
|
|
}
|
|
|
|
valuesFirst := func(v url.Values) url.Values {
|
|
v.Del("traits.username")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
return v
|
|
}
|
|
|
|
valuesSecond := func(v url.Values) url.Values {
|
|
v.Set("traits.username", "registration-identifier-9")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Del("traits.foobar")
|
|
return v
|
|
}
|
|
|
|
t.Run("type=api", func(t *testing.T) {
|
|
f := testhelpers.InitializeRegistrationFlowViaAPI(t, apiClient, publicTS)
|
|
c := f.Ui
|
|
|
|
actual, _ := testhelpers.RegistrationMakeRequest(t, true, false, f, apiClient, testhelpers.EncodeFormAsJSON(t, true, valuesFirst(testhelpers.SDKFormFieldsToURLValues(c.Nodes))))
|
|
checkFirst(t, actual)
|
|
actual, _ = testhelpers.RegistrationMakeRequest(t, true, false, f, apiClient, testhelpers.EncodeFormAsJSON(t, true, valuesSecond(testhelpers.SDKFormFieldsToURLValues(c.Nodes))))
|
|
checkSecond(t, actual)
|
|
})
|
|
|
|
t.Run("type=spa", func(t *testing.T) {
|
|
browserClient := testhelpers.NewClientWithCookies(t)
|
|
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, browserClient, publicTS, true, false, false)
|
|
c := f.Ui
|
|
|
|
actual, _ := testhelpers.RegistrationMakeRequest(t, false, true, f, browserClient, testhelpers.EncodeFormAsJSON(t, false, valuesFirst(testhelpers.SDKFormFieldsToURLValues(c.Nodes))))
|
|
checkFirst(t, actual)
|
|
actual, _ = testhelpers.RegistrationMakeRequest(t, false, true, f, browserClient, testhelpers.EncodeFormAsJSON(t, false, valuesSecond(testhelpers.SDKFormFieldsToURLValues(c.Nodes))))
|
|
checkSecond(t, actual)
|
|
})
|
|
|
|
t.Run("type=browser", func(t *testing.T) {
|
|
browserClient := testhelpers.NewClientWithCookies(t)
|
|
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, browserClient, publicTS, false, false, false)
|
|
c := f.Ui
|
|
|
|
actual, _ := testhelpers.RegistrationMakeRequest(t, false, false, f, browserClient, valuesFirst(testhelpers.SDKFormFieldsToURLValues(c.Nodes)).Encode())
|
|
checkFirst(t, actual)
|
|
actual, _ = testhelpers.RegistrationMakeRequest(t, false, false, f, browserClient, valuesSecond(testhelpers.SDKFormFieldsToURLValues(c.Nodes)).Encode())
|
|
checkSecond(t, actual)
|
|
})
|
|
})
|
|
|
|
t.Run("case=should work even if password is just numbers", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}})
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
})
|
|
|
|
t.Run("type=api", func(t *testing.T) {
|
|
actual := expectSuccessfulRegistration(t, true, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-10-api")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-10-api`, gjson.Get(actual, "identity.traits.username").String(), "%s", actual)
|
|
})
|
|
|
|
t.Run("type=spa", func(t *testing.T) {
|
|
actual := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-10-spa")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-10-spa`, gjson.Get(actual, "identity.traits.username").String(), "%s", actual)
|
|
})
|
|
|
|
t.Run("type=browser", func(t *testing.T) {
|
|
actual := expectSuccessfulRegistration(t, false, false, nil, func(v url.Values) {
|
|
v.Set("traits.username", "registration-identifier-10-browser")
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.foobar", "bar")
|
|
})
|
|
assert.Equal(t, `registration-identifier-10-browser`, gjson.Get(actual, "identity.traits.username").String(), "%s", actual)
|
|
})
|
|
})
|
|
|
|
t.Run("case=should fail if no identifier was set in the schema", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://stub/missing-identifier.schema.json")
|
|
|
|
for _, f := range []string{"spa", "api", "browser"} {
|
|
t.Run("type="+f, func(t *testing.T) {
|
|
actual := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, f, func(v url.Values) {
|
|
v.Set("traits.email", testhelpers.RandomEmail())
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("method", "password")
|
|
})
|
|
assert.Equal(t, text.NewErrorValidationIdentifierMissing().Text, gjson.Get(actual, "ui.messages.0.text").String(), "%s", actual)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("case=should work with regular JSON", func(t *testing.T) {
|
|
testhelpers.SetDefaultIdentitySchema(conf, "file://stub/registration.schema.json")
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), []config.SelfServiceHook{{Name: "session"}})
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
})
|
|
|
|
hc := testhelpers.NewClientWithCookies(t)
|
|
hc.Transport = testhelpers.NewTransportWithLogger(hc.Transport, t)
|
|
payload := testhelpers.InitializeRegistrationFlowViaBrowser(t, hc, publicTS, false, false, false)
|
|
values := testhelpers.SDKFormFieldsToURLValues(payload.Ui.Nodes)
|
|
time.Sleep(time.Millisecond) // add a bit of delay to allow `1ns` to time out.
|
|
|
|
username := x.NewUUID()
|
|
actual, res := testhelpers.RegistrationMakeRequest(t, true, false, payload, hc, fmt.Sprintf(`{
|
|
"method": "password",
|
|
"csrf_token": "%s",
|
|
"password": "%s",
|
|
"traits": {
|
|
"foobar": "bar",
|
|
"username": "%s"
|
|
}
|
|
}`, values.Get("csrf_token"), x.NewUUID(), username))
|
|
assert.EqualValues(t, http.StatusOK, res.StatusCode, assertx.PrettifyJSONPayload(t, actual))
|
|
assert.Equal(t, username.String(), gjson.Get(actual, "identity.traits.username").String(), "%s", actual)
|
|
})
|
|
|
|
t.Run("case=should choose the correct identity schema", func(t *testing.T) {
|
|
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "advanced-user")
|
|
conf.MustSet(t.Context(), config.ViperKeyIdentitySchemas, config.Schemas{
|
|
{ID: "does-not-exist", URL: "file://./stub/not-exists.schema.json"},
|
|
{ID: "advanced-user", URL: "file://./stub/registration.secondary.schema.json"},
|
|
})
|
|
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
|
|
|
username := "registration-custom-schema"
|
|
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) {
|
|
body := expectNoRegistration(t, tc.isAPI, tc.isSPA, nil, func(v url.Values) {
|
|
v.Set("traits.username", username+"-"+tc.name)
|
|
v.Set("password", x.NewUUID().String())
|
|
v.Set("traits.baz", "bar")
|
|
})
|
|
|
|
if tc.isAPI || tc.isSPA { // body is empty for browser flow
|
|
assert.Equal(t, username+"-"+tc.name, gjson.Get(body, "identity.traits.username").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "session_token").String(), "%s", body)
|
|
assert.Empty(t, gjson.Get(body, "session.id").String(), "%s", body)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("case=multi-schema select", func(t *testing.T) {
|
|
conf.MustSet(t.Context(), config.ViperKeyIdentitySchemas, config.Schemas{
|
|
{ID: "username", URL: "file://stub/sort.schema.json", SelfserviceSelectable: true},
|
|
{ID: "email", URL: "file://stub/email.schema.json", SelfserviceSelectable: true},
|
|
{ID: "phone", URL: "file://stub/phone.schema.json"},
|
|
{ID: "not-allowed", URL: "file://stub/login.schema.json"},
|
|
})
|
|
// We set default to phone and select email in InitializeRegistrationFlowViaBrowser
|
|
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "phone")
|
|
|
|
conf.MustSet(t.Context(), config.ViperKeySelfServiceRegistrationAfter+"."+config.DefaultBrowserReturnURL, "https://www.ory.com")
|
|
|
|
browserClient := testhelpers.NewClientWithCookies(t)
|
|
|
|
expected := container.Container{
|
|
Method: "POST",
|
|
Nodes: node.Nodes{
|
|
node.NewCSRFNode(nosurfx.FakeCSRFToken),
|
|
node.NewInputField("traits.email", nil, node.DefaultGroup, node.InputAttributeTypeEmail, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) {
|
|
a.Autocomplete = node.InputAttributeAutocompleteEmail
|
|
})).WithMetaLabel(text.NewInfoNodeLabelGenerated("E-Mail", "traits.email")),
|
|
node.NewInputField("password", nil, node.PasswordGroup, node.InputAttributeTypePassword, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) {
|
|
a.Autocomplete = node.InputAttributeAutocompleteNewPassword
|
|
})).WithMetaLabel(text.NewInfoNodeInputPassword()),
|
|
node.NewInputField("method", "password", node.PasswordGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoRegistration()),
|
|
},
|
|
}
|
|
|
|
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+" registration success", func(t *testing.T) {
|
|
var f *kratos.RegistrationFlow
|
|
var hc *http.Client
|
|
var payload string
|
|
if tc.isAPI {
|
|
f = testhelpers.InitializeRegistrationFlowViaAPICtx(t.Context(), t, apiClient, publicTS, testhelpers.InitFlowWithIdentitySchema("email"))
|
|
hc = apiClient
|
|
} else {
|
|
f = testhelpers.InitializeRegistrationFlowViaBrowserCtx(t.Context(), t, browserClient, publicTS, tc.isSPA, false, false, testhelpers.InitFlowWithIdentitySchema("email"))
|
|
hc = browserClient
|
|
}
|
|
|
|
expected.Action = conf.SelfPublicURL(t.Context()).String() + registration.RouteSubmitFlow + "?flow=" + f.Id
|
|
assertx.EqualAsJSON(t, expected, f.Ui)
|
|
|
|
values := testhelpers.SDKFormFieldsToURLValues(f.Ui.Nodes)
|
|
values.Set("traits.email", testhelpers.RandomEmail())
|
|
values.Set("password", x.NewUUID().String())
|
|
|
|
var expectedURL string
|
|
if tc.isAPI {
|
|
payload = testhelpers.EncodeFormAsJSON(t, true, values)
|
|
} else {
|
|
payload = values.Encode()
|
|
}
|
|
if tc.name == "browser" {
|
|
expectedURL = redirTS.URL
|
|
} else {
|
|
expectedURL = conf.SelfPublicURL(t.Context()).String() + login.RouteSubmitFlow
|
|
}
|
|
|
|
actual, resp := testhelpers.RegistrationMakeRequest(t, tc.isAPI, tc.isSPA, f, hc, payload)
|
|
require.EqualValues(t, http.StatusOK, resp.StatusCode)
|
|
|
|
if tc.isAPI {
|
|
assert.NotEmpty(t, gjson.Get(actual, "identity.id").String(), "%s", actual)
|
|
assert.Equal(t, gjson.Get(actual, "identity.traits.email").String(), values.Get("traits.email"), "%s", actual)
|
|
assert.Equal(t, gjson.Get(actual, "identity.schema_id").String(), "email", "%s", actual)
|
|
}
|
|
|
|
identity, _, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(t.Context(), identity.CredentialsTypePassword, values.Get("traits.email"))
|
|
require.NoError(t, err, sqlcon.ErrNoRows())
|
|
|
|
assert.NotEmpty(t, identity.ID)
|
|
assert.Equalf(t, values.Get("traits.email"), gjson.GetBytes(identity.Traits, "email").String(), "%s", identity.Traits)
|
|
assert.Equal(t, "email", identity.SchemaID)
|
|
|
|
// login
|
|
loginValues := func(v url.Values) {
|
|
v.Set("identifier", values.Get("traits.email"))
|
|
v.Set("password", values.Get("password"))
|
|
}
|
|
|
|
body := testhelpers.SubmitLoginForm(t, tc.isAPI, nil, publicTS, loginValues,
|
|
tc.isSPA, false, http.StatusOK, expectedURL, testhelpers.InitFlowWithIdentitySchema("email"))
|
|
|
|
if tc.name == "browser" {
|
|
assert.Equalf(t, identity.ID.String(), gjson.Get(body, "identity.id").String(), "%s", body)
|
|
} else {
|
|
assert.Equalf(t, identity.ID.String(), gjson.Get(body, "session.identity.id").String(), "%s", body)
|
|
}
|
|
})
|
|
|
|
t.Run("type="+tc.name+" registration fail due to invalid form data", func(t *testing.T) {
|
|
var f *kratos.RegistrationFlow
|
|
var hc *http.Client
|
|
var payload string
|
|
if tc.isAPI {
|
|
f = testhelpers.InitializeRegistrationFlowViaAPICtx(t.Context(), t, apiClient, publicTS, testhelpers.InitFlowWithIdentitySchema("email"))
|
|
hc = apiClient
|
|
} else {
|
|
f = testhelpers.InitializeRegistrationFlowViaBrowserCtx(t.Context(), t, browserClient, publicTS, tc.isSPA, false, false, testhelpers.InitFlowWithIdentitySchema("email"))
|
|
hc = browserClient
|
|
}
|
|
|
|
expected.Action = conf.SelfPublicURL(t.Context()).String() + registration.RouteSubmitFlow + "?flow=" + f.Id
|
|
assertx.EqualAsJSON(t, expected, f.Ui)
|
|
|
|
values := testhelpers.SDKFormFieldsToURLValues(f.Ui.Nodes)
|
|
values.Set("traits.email", "invalidemail")
|
|
values.Set("password", x.NewUUID().String())
|
|
|
|
if tc.isAPI {
|
|
payload = testhelpers.EncodeFormAsJSON(t, true, values)
|
|
} else {
|
|
payload = values.Encode()
|
|
}
|
|
|
|
actual, resp := testhelpers.RegistrationMakeRequest(t, tc.isAPI, tc.isSPA, f, hc, payload)
|
|
if tc.name == "browser" {
|
|
require.EqualValues(t, http.StatusOK, resp.StatusCode)
|
|
} else {
|
|
require.EqualValues(t, http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
|
|
assert.EqualValuesf(t, 4000040, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.email).messages.0.id").Int(), "%s", actual)
|
|
assert.Equalf(t, "Enter a valid email address", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.email).messages.0.text").String(), "%s", actual)
|
|
})
|
|
}
|
|
|
|
t.Run("type=browser schema=default", func(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "phone")
|
|
})
|
|
// We set default to email
|
|
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "email")
|
|
|
|
f := testhelpers.InitializeRegistrationFlowViaBrowserCtx(t.Context(), t, browserClient, publicTS, false, false, false)
|
|
|
|
expected.Action = conf.SelfPublicURL(t.Context()).String() + registration.RouteSubmitFlow + "?flow=" + f.Id
|
|
assertx.EqualAsJSON(t, expected, f.Ui)
|
|
})
|
|
|
|
t.Run("type=api schema=default", func(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "phone")
|
|
})
|
|
// We set default to email
|
|
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "email")
|
|
|
|
f := testhelpers.InitializeRegistrationFlowViaAPICtx(t.Context(), t, apiClient, publicTS)
|
|
|
|
expected.Action = conf.SelfPublicURL(t.Context()).String() + registration.RouteSubmitFlow + "?flow=" + f.Id
|
|
assertx.EqualAsJSON(t, expected, f.Ui)
|
|
|
|
values := testhelpers.SDKFormFieldsToURLValues(f.Ui.Nodes)
|
|
values.Set("traits.email", testhelpers.RandomEmail())
|
|
values.Set("password", x.NewUUID().String())
|
|
|
|
actual, _ := testhelpers.RegistrationMakeRequest(t, true, false, f, apiClient, testhelpers.EncodeFormAsJSON(t, true, values))
|
|
|
|
assert.NotEmptyf(t, gjson.Get(actual, "identity.id").String(), "%s", actual)
|
|
assert.Containsf(t, gjson.Get(actual, "identity.traits.email").String(), values.Get("email"), "%s", actual)
|
|
assert.Containsf(t, gjson.Get(actual, "identity.schema_id").String(), "email", "%s", actual)
|
|
})
|
|
|
|
t.Run("type=browser schema=does-not-exist", func(t *testing.T) {
|
|
testhelpers.InitializeRegistrationFlowViaBrowserCtx(t.Context(), t, browserClient, publicTS, false, false, true, testhelpers.InitFlowWithIdentitySchema("does-not-exist"))
|
|
|
|
testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, false, false, true, testhelpers.InitFlowWithIdentitySchema("does-not-exist"))
|
|
})
|
|
|
|
t.Run("type=api schema=does-not-exist", func(t *testing.T) {
|
|
testhelpers.InitializeRegistrationFlowViaAPIExpectError(t, apiClient, publicTS, testhelpers.InitFlowWithIdentitySchema("does-not-exist"))
|
|
|
|
testhelpers.InitializeLoginFlowViaAPIExpectError(t.Context(), t, apiClient, publicTS, false, testhelpers.InitFlowWithIdentitySchema("does-not-exist"))
|
|
})
|
|
|
|
t.Run("type=browser schema=not-allowed", func(t *testing.T) {
|
|
testhelpers.InitializeRegistrationFlowViaBrowserCtx(t.Context(), t, browserClient, publicTS, false, false, true, testhelpers.InitFlowWithIdentitySchema("not-allowed"))
|
|
|
|
testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, false, false, true, testhelpers.InitFlowWithIdentitySchema("not-allowed"))
|
|
})
|
|
|
|
t.Run("type=api schema=not-allowed", func(t *testing.T) {
|
|
testhelpers.InitializeRegistrationFlowViaAPIExpectError(t, apiClient, publicTS, testhelpers.InitFlowWithIdentitySchema("not-allowed"))
|
|
|
|
testhelpers.InitializeLoginFlowViaAPIExpectError(t.Context(), t, apiClient, publicTS, false, testhelpers.InitFlowWithIdentitySchema("not-allowed"))
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("method=PopulateSignUpMethod", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conf, reg := pkg.NewFastRegistryWithMocks(t,
|
|
configx.WithValues(map[string]any{
|
|
config.ViperKeyPublicBaseURL: "https://foo/",
|
|
config.ViperKeySelfServiceRegistrationEnableLegacyOneStep: true,
|
|
config.ViperKeySelfServiceStrategyConfig + "." + string(identity.CredentialsTypePassword) + ".enabled": true,
|
|
}),
|
|
configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://stub/sort.schema.json")),
|
|
)
|
|
|
|
publicTS, _ := testhelpers.NewKratosServer(t, reg)
|
|
_ = testhelpers.NewRegistrationUIFlowEchoServer(t, reg)
|
|
|
|
browserClient := testhelpers.NewClientWithCookies(t)
|
|
f := testhelpers.InitializeRegistrationFlowViaBrowserCtx(t.Context(), t, browserClient, publicTS, false, false, false)
|
|
|
|
assertx.EqualAsJSON(t, container.Container{
|
|
Action: conf.SelfPublicURL(t.Context()).String() + registration.RouteSubmitFlow + "?flow=" + f.Id,
|
|
Method: "POST",
|
|
Nodes: node.Nodes{
|
|
node.NewCSRFNode(nosurfx.FakeCSRFToken),
|
|
node.NewInputField("traits.username", nil, node.DefaultGroup, node.InputAttributeTypeText),
|
|
node.NewInputField("password", nil, node.PasswordGroup, node.InputAttributeTypePassword, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) {
|
|
a.Autocomplete = node.InputAttributeAutocompleteNewPassword
|
|
})).WithMetaLabel(text.NewInfoNodeInputPassword()),
|
|
node.NewInputField("traits.bar", nil, node.DefaultGroup, node.InputAttributeTypeText),
|
|
node.NewInputField("method", "password", node.PasswordGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoRegistration()),
|
|
},
|
|
}, f.Ui)
|
|
})
|
|
}
|
|
|
|
func TestPopulateRegistrationMethod(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, reg := pkg.NewFastRegistryWithMocks(t,
|
|
configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://stub/identity.schema.json")),
|
|
)
|
|
|
|
s, err := reg.AllRegistrationStrategies().Strategy(identity.CredentialsTypePassword)
|
|
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")
|
|
snapshotx.SnapshotT(t, f, snapshotx.ExceptNestedKeys("nonce", "src"))
|
|
}
|
|
|
|
newFlow := func(ctx context.Context, t *testing.T) (*http.Request, *registration.Flow) {
|
|
r := httptest.NewRequest("GET", "/self-service/registration/browser", 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
|
|
}
|
|
|
|
t.Run("method=PopulateRegistrationMethod", func(t *testing.T) {
|
|
r, f := newFlow(t.Context(), t)
|
|
require.NoError(t, fh.PopulateRegistrationMethod(r, f))
|
|
toSnapshot(t, f.UI.Nodes)
|
|
})
|
|
|
|
t.Run("method=PopulateRegistrationMethodProfile", func(t *testing.T) {
|
|
r, f := newFlow(t.Context(), t)
|
|
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
|
|
toSnapshot(t, f.UI.Nodes)
|
|
})
|
|
|
|
t.Run("method=PopulateRegistrationMethodCredentials", func(t *testing.T) {
|
|
r, f := newFlow(t.Context(), t)
|
|
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
|
|
toSnapshot(t, f.UI.Nodes)
|
|
})
|
|
|
|
t.Run("method=idempotency", func(t *testing.T) {
|
|
r, f := newFlow(t.Context(), 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])
|
|
})
|
|
})
|
|
}
|