Skip to content

Commit

Permalink
fix: identity schema
Browse files Browse the repository at this point in the history
hperl committed Jan 17, 2024
1 parent 85d9ce5 commit 3d2a4cb
Showing 15 changed files with 77 additions and 25 deletions.
2 changes: 1 addition & 1 deletion contrib/quickstart/kratos/passkey/identity.schema.json
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
"identifier": true
},
"passkey": {
"identifier": true
"display_name": true
}
},
"verification": {
2 changes: 1 addition & 1 deletion embedx/identity_extension.schema.json
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@
"type": "object",
"additionalProperties": false,
"properties": {
"identifier": {
"display_name": {
"type": "boolean"
}
}
2 changes: 1 addition & 1 deletion schema/extension.go
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ type (
Identifier bool `json:"identifier"`
} `json:"webauthn"`
Passkey struct {
Identifier bool `json:"identifier"`
DisplayName bool `json:"display_name"`
} `json:"passkey"`
TOTP struct {
AccountName bool `json:"account_name"`
1 change: 1 addition & 0 deletions selfservice/flow/login/extension_identifier_label.go
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ func GetIdentifierLabelFromSchema(ctx context.Context, schemaURL string) (*text.
func (i *identifierLabelExtension) Run(_ jsonschema.CompilerContext, config schema.ExtensionConfig, rawSchema map[string]interface{}) error {
if config.Credentials.Password.Identifier ||
config.Credentials.WebAuthn.Identifier ||
config.Credentials.Passkey.DisplayName ||
config.Credentials.TOTP.AccountName ||
config.Credentials.Code.Identifier {
if title, ok := rawSchema["title"]; ok {
7 changes: 7 additions & 0 deletions selfservice/flow/login/extension_identifier_label_test.go
Original file line number Diff line number Diff line change
@@ -102,6 +102,13 @@ func TestGetIdentifierLabelFromSchema(t *testing.T) {
},
expected: text.NewInfoNodeLabelGenerated("Email"),
},
{
name: "email for passkey",
emailConfig: func(c *schema.ExtensionConfig) {
c.Credentials.Passkey.DisplayName = true
},
expected: text.NewInfoNodeLabelGenerated("Email"),
},
{
name: "email for all",
emailConfig: func(c *schema.ExtensionConfig) {
2 changes: 1 addition & 1 deletion selfservice/strategy/oidc/provider_config.go
Original file line number Diff line number Diff line change
@@ -71,7 +71,7 @@ type Configuration struct {

// SubjectSource is a flag which controls from which endpoint the subject identifier is taken by microsoft provider.
// Can be either `userinfo` or `me`.
// If the value is `uerinfo` then the subject identifier is taken from sub field of uderifo standard endpoint response.
// If the value is `userinfo` then the subject identifier is taken from sub field of userinfo standard endpoint response.
// If the value is `me` then the `id` field of https://graph.microsoft.com/v1.0/me response is taken as subject.
// The default is `userinfo`.
SubjectSource string `json:"subject_source"`
2 changes: 1 addition & 1 deletion selfservice/strategy/passkey/passkey_login.go
Original file line number Diff line number Diff line change
@@ -157,7 +157,7 @@ func (s *Strategy) populateLoginMethodForRefresh(r *http.Request, loginFlow *log
return nil

Check warning on line 157 in selfservice/strategy/passkey/passkey_login.go

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_login.go#L157

Added line #L157 was not covered by tests
}

passkeyIdentifier := s.PasskeyIdentifierFromIdentity(ctx, id)
passkeyIdentifier := s.PasskeyDisplayNameFromIdentity(ctx, id)

webAuthn, err := webauthn.New(s.d.Config().PasskeyConfig(ctx))
if err != nil {
16 changes: 11 additions & 5 deletions selfservice/strategy/passkey/passkey_login_test.go
Original file line number Diff line number Diff line change
@@ -212,12 +212,18 @@ func TestCompleteLogin(t *testing.T) {
assert.Empty(t, gjson.GetBytes(actualFlow.InternalContext, flow.PrefixInternalContextKey(identity.CredentialsTypePasskey, passkey.InternalContextKeySessionData)))
}

t.Run("type=browser", func(t *testing.T) {
run(t, false)
// We test here that login works even if the identity schema contains
// { webauthn: { identifier: true } } instead of
// { passkey: { display_name: true } }
t.Run("webauthn_identifier", func(t *testing.T) {
testhelpers.SetDefaultIdentitySchema(fix.conf, "file://./stub/login_webauthn.schema.json")
t.Run("type=browser", func(t *testing.T) { run(t, false) })
t.Run("type=spa", func(t *testing.T) { run(t, true) })
})

t.Run("type=spa", func(t *testing.T) {
run(t, true)
t.Run("passkey_display_name", func(t *testing.T) {
testhelpers.SetDefaultIdentitySchema(fix.conf, "file://./stub/login.schema.json")
t.Run("type=browser", func(t *testing.T) { run(t, false) })
t.Run("type=spa", func(t *testing.T) { run(t, true) })
})
})
})
6 changes: 3 additions & 3 deletions selfservice/strategy/passkey/passkey_registration.go
Original file line number Diff line number Diff line change
@@ -250,8 +250,8 @@ func (s *Strategy) populateRegistrationNodes(ctx context.Context, schemaURL *url
func (s *Strategy) addPassKeyNodes(r *http.Request, w http.ResponseWriter, regFlow *registration.Flow, params *updateRegistrationFlowWithPasskeyMethod) error {
ctx := r.Context()

identifier := s.PasskeyIdentifierFromTraits(ctx, identity.Traits(params.Traits))
if identifier == "" {
username := s.PasskeyDisplayNameFromTraits(ctx, identity.Traits(params.Traits))
if username == "" {
return s.handleRegistrationError(w, r, regFlow, params, schema.NewMissingIdentifierError())
}

@@ -271,7 +271,7 @@ func (s *Strategy) addPassKeyNodes(r *http.Request, w http.ResponseWriter, regFl
return errors.WithStack(err)

Check warning on line 271 in selfservice/strategy/passkey/passkey_registration.go

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L271

Added line #L271 was not covered by tests
}
user := &webauthnx.User{
Name: identifier,
Name: username,
ID: []byte(randx.MustString(64, randx.AlphaNum)),
Config: s.d.Config().PasskeyConfig(ctx),
}
23 changes: 15 additions & 8 deletions selfservice/strategy/passkey/passkey_schema_extension.go
Original file line number Diff line number Diff line change
@@ -12,39 +12,46 @@ import (
"github.com/ory/jsonschema/v3"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/schema"
"github.com/ory/x/stringsx"
)

type SchemaExtension struct {
Identifier string
WebauthnIdentifier string
PasskeyDisplayName string
sync.Mutex
}

func (e *SchemaExtension) Run(_ jsonschema.ValidationContext, s schema.ExtensionConfig, value any) error {
e.Lock()
defer e.Unlock()

if s.Credentials.Passkey.Identifier {
e.Identifier = strings.ToLower(fmt.Sprintf("%s", value))
if s.Credentials.WebAuthn.Identifier {
e.WebauthnIdentifier = strings.ToLower(fmt.Sprintf("%s", value))
}

if s.Credentials.Passkey.DisplayName {
e.PasskeyDisplayName = fmt.Sprintf("%s", value)
}

return nil
}

func (e *SchemaExtension) Finish() error { return nil }

// PasskeyIdentifierFromIdentity returns the passkey identifier from the
// PasskeyDisplayNameFromIdentity returns the passkey display name from the
// identity. It is usually the email address and used to name the passkey in the
// browser.
func (s *Strategy) PasskeyIdentifierFromIdentity(ctx context.Context, id *identity.Identity) string {
func (s *Strategy) PasskeyDisplayNameFromIdentity(ctx context.Context, id *identity.Identity) string {
e := new(SchemaExtension)
// We can ignore teh error here because proper validation happens once the identity is persisted.
_ = s.d.IdentityValidator().ValidateWithRunner(ctx, id, e)

return e.Identifier
return stringsx.Coalesce(e.PasskeyDisplayName, e.WebauthnIdentifier)
}

func (s *Strategy) PasskeyIdentifierFromTraits(ctx context.Context, traits identity.Traits) string {
func (s *Strategy) PasskeyDisplayNameFromTraits(ctx context.Context, traits identity.Traits) string {
id := identity.NewIdentity("")
id.Traits = traits

return s.PasskeyIdentifierFromIdentity(ctx, id)
return s.PasskeyDisplayNameFromIdentity(ctx, id)
}
2 changes: 1 addition & 1 deletion selfservice/strategy/passkey/passkey_settings.go
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity
return errors.WithStack(err)
}

identifier := s.PasskeyIdentifierFromIdentity(r.Context(), id)
identifier := s.PasskeyDisplayNameFromIdentity(r.Context(), id)
if identifier == "" {
f.UI.Messages.Add(text.NewErrorValidationIdentifierMissing())
return nil
2 changes: 1 addition & 1 deletion selfservice/strategy/passkey/stub/login.schema.json
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
"identifier": true
},
"passkey": {
"identifier": true
"display_name": true
}
}
}
31 changes: 31 additions & 0 deletions selfservice/strategy/passkey/stub/login_webauthn.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$id": "https://example.com/person.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"website": {
"type": "string"
},
"email": {
"type": "string",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
},
"webauthn": {
"identifier": true
}
}
}
}
},
"required": []
}
},
"additionalProperties": false
}
2 changes: 1 addition & 1 deletion selfservice/strategy/passkey/stub/registration.schema.json
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
"identifier": true
},
"passkey": {
"identifier": true
"display_name": true
},
"webauthn": {
"identifier": true
2 changes: 1 addition & 1 deletion selfservice/strategy/passkey/stub/settings.schema.json
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
"identifier": true
},
"passkey": {
"identifier": true
"display_name": true
}
}
}

0 comments on commit 3d2a4cb

Please sign in to comment.