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
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:
115
selfservice/strategy/passkey/passkey_schema_extension.go
Normal file
115
selfservice/strategy/passkey/passkey_schema_extension.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package passkey
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/schema"
|
||||
"github.com/ory/x/jsonschemax"
|
||||
)
|
||||
|
||||
// errNoDisplayNameTrait is returned by PasskeyDisplayNameFromSchema when the
|
||||
// identity schema flags no trait as a passkey display name or WebAuthn
|
||||
// identifier and has no untitled trait to fall back to.
|
||||
var errNoDisplayNameTrait = errors.New("no identifier found")
|
||||
|
||||
type SchemaExtension struct {
|
||||
WebauthnIdentifier string
|
||||
PasskeyDisplayName string
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (e *SchemaExtension) Run(_ jsonschema.ValidationContext, s schema.ExtensionConfig, value any) error {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
// When a schema flags multiple traits, the validator visits them in an
|
||||
// unspecified order. Keep the first non-empty value so an empty flagged
|
||||
// trait never clobbers a populated one. This mirrors the client, which
|
||||
// names the passkey after the first non-empty candidate field.
|
||||
if s.Credentials.WebAuthn.Identifier && e.WebauthnIdentifier == "" {
|
||||
e.WebauthnIdentifier = strings.ToLower(fmt.Sprintf("%s", value))
|
||||
}
|
||||
|
||||
if s.Credentials.Passkey.DisplayName && e.PasskeyDisplayName == "" {
|
||||
e.PasskeyDisplayName = fmt.Sprintf("%s", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SchemaExtension) Finish() error { return nil }
|
||||
|
||||
// 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) 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 cmp.Or(e.PasskeyDisplayName, e.WebauthnIdentifier)
|
||||
}
|
||||
|
||||
func (s *Strategy) PasskeyDisplayNameFromTraits(ctx context.Context, traits identity.Traits) string {
|
||||
id := identity.NewIdentity("")
|
||||
id.Traits = traits
|
||||
|
||||
return s.PasskeyDisplayNameFromIdentity(ctx, id)
|
||||
}
|
||||
|
||||
// PasskeyDisplayNameFromSchema returns every trait path (e.g. ["traits.email", "traits.phone"])
|
||||
// whose schema flags `passkey.display_name: true` or `webauthn.identifier: true`. The slice
|
||||
// is sorted alphabetically so server and client agree on precedence. When no
|
||||
// trait is flagged, it preserves the legacy behavior: fall back to the first
|
||||
// untitled trait, and return an error only when there is none.
|
||||
func (s *Strategy) PasskeyDisplayNameFromSchema(ctx context.Context, schemaURL string) ([]string, error) {
|
||||
runner, err := schema.NewExtensionRunner(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := schema.NewCompilerWithURL(ctx, schemaURL, s.d.Config().SecurityDisallowRefInIdentitySchemas(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.ExtractAnnotations = true
|
||||
runner.Register(c)
|
||||
|
||||
paths, err := jsonschemax.ListPaths(ctx, schemaURL, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []string
|
||||
for _, p := range paths {
|
||||
ext, ok := p.CustomProperties[schema.ExtensionName].(*schema.ExtensionConfig)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ext.Credentials.WebAuthn.Identifier || ext.Credentials.Passkey.DisplayName {
|
||||
result = append(result, p.Name)
|
||||
}
|
||||
}
|
||||
if len(result) > 0 {
|
||||
slices.Sort(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
if strings.HasPrefix(p.Name, "traits.") && p.Title == "" {
|
||||
return []string{p.Name}, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.WithStack(errNoDisplayNameTrait)
|
||||
}
|
||||
Reference in New Issue
Block a user