From e25f614f00e4dbd0294494cf1d17895378e689fc Mon Sep 17 00:00:00 2001 From: hackerman <3372410+aeneasr@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:53:11 +0100 Subject: [PATCH] chore: code review (#3679) --- selfservice/strategy/passkey/passkey_login.go | 3 - .../strategy/passkey/passkey_registration.go | 296 +++++++++--------- .../strategy/passkey/passkey_settings.go | 3 - selfservice/strategy/passkey/schema.go | 15 + selfservice/strategy/webauthn/registration.go | 82 ++--- 5 files changed, 207 insertions(+), 192 deletions(-) create mode 100644 selfservice/strategy/passkey/schema.go diff --git a/selfservice/strategy/passkey/passkey_login.go b/selfservice/strategy/passkey/passkey_login.go index 91cb604dfd89..775720b8b283 100644 --- a/selfservice/strategy/passkey/passkey_login.go +++ b/selfservice/strategy/passkey/passkey_login.go @@ -29,9 +29,6 @@ import ( "github.com/ory/x/decoderx" ) -//go:embed .schema/login.schema.json -var loginSchema []byte - func (s *Strategy) RegisterLoginRoutes(r *x.RouterPublic) { webauthnx.RegisterWebauthnRoute(r) } diff --git a/selfservice/strategy/passkey/passkey_registration.go b/selfservice/strategy/passkey/passkey_registration.go index f56bae1acba7..9f02eae72061 100644 --- a/selfservice/strategy/passkey/passkey_registration.go +++ b/selfservice/strategy/passkey/passkey_registration.go @@ -31,132 +31,126 @@ import ( "github.com/ory/x/randx" ) -//go:embed .schema/registration.schema.json -var registrationSchema []byte - -func (s *Strategy) RegisterRegistrationRoutes(r *x.RouterPublic) { - webauthnx.RegisterWebauthnRoute(r) -} - -func (s *Strategy) PopulateRegistrationMethod(r *http.Request, regFlow *registration.Flow) error { - ctx := r.Context() - - if regFlow.Type != flow.TypeBrowser { - return nil - } +// Update Registration Flow with Passkey Method +// +// swagger:model updateRegistrationFlowWithPasskeyMethod +type updateRegistrationFlowWithPasskeyMethod struct { + // Register a WebAuthn Security Key + // + // It is expected that the JSON returned by the WebAuthn registration process + // is included here. + Register string `json:"passkey_register"` - defaultSchemaURL, err := s.d.Config().DefaultIdentityTraitsSchemaURL(ctx) - if err != nil { - return err - } - nodes, err := s.registrationNodes(ctx, defaultSchemaURL) - if err != nil { - return err - } + // CSRFToken is the anti-CSRF token + CSRFToken string `json:"csrf_token"` - for _, n := range nodes { - regFlow.UI.SetNode(n) - } + // The identity's traits + // + // required: true + Traits json.RawMessage `json:"traits"` - regFlow.UI.Nodes.Append(node.NewInputField( - "method", - "passkey", - node.PasskeyGroup, - node.InputAttributeTypeSubmit, - ).WithMetaLabel(text.NewInfoSelfServiceRegistrationRegisterPasskey())) + // Method + // + // Should be set to "passkey" when trying to add, update, or remove a Passkey. + // + // required: true + Method string `json:"method"` - regFlow.UI.SetCSRF(s.d.GenerateCSRFToken(r)) + // Flow is flow ID. + // + // swagger:ignore + Flow string `json:"flow"` - return nil + // Transient data to pass along to any webhooks + // + // required: false + TransientPayload json.RawMessage `json:"transient_payload,omitempty"` } -func (s *Strategy) registrationNodes(ctx context.Context, schemaURL *url.URL) (node.Nodes, error) { - runner, err := schema.NewExtensionRunner(ctx) - if err != nil { - return nil, err - } - c := jsonschema.NewCompiler() - runner.Register(c) - - nodes, err := container.NodesFromJSONSchema(ctx, node.DefaultGroup, schemaURL.String(), "", c) - if err != nil { - return nil, err - } - return nodes, nil +func (s *Strategy) RegisterRegistrationRoutes(r *x.RouterPublic) { + webauthnx.RegisterWebauthnRoute(r) } -// webauthnIdentifierNode returns the node that is used to identify the user in the WebAuthn flow. -func (s *Strategy) webauthnIdentifierNode(ctx context.Context, schemaURL *url.URL) (*node.Node, error) { - nodes, err := s.registrationNodes(ctx, schemaURL) - if err != nil { - return nil, err - } - for _, n := range nodes { - if attr, ok := n.Attributes.(*node.InputAttributes); ok { - if attr.DataWebauthnIdentifier { - return n, nil +func (s *Strategy) handleRegistrationError(_ http.ResponseWriter, r *http.Request, f *registration.Flow, p *updateRegistrationFlowWithPasskeyMethod, err error) error { + if f != nil { + if p != nil { + for _, n := range container.NewFromJSON("", node.DefaultGroup, p.Traits, "traits").Nodes { + // we only set the value and not the whole field because we want to keep types from the initial form generation + f.UI.Nodes.SetValueAttribute(n.ID(), n.Attributes.GetValue()) } } + + if f.Type == flow.TypeBrowser { + f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) + } } - return nil, schema.NewMissingIdentifierError() + return err } -func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *registration.Flow, ident *identity.Identity) (err error) { +func (s *Strategy) decode(r *http.Request) (*updateRegistrationFlowWithPasskeyMethod, error) { + var p updateRegistrationFlowWithPasskeyMethod + err := registration.DecodeBody(&p, r, s.hd, s.d.Config(), registrationSchema) + return &p, err +} + +func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *registration.Flow, i *identity.Identity) (err error) { ctx := r.Context() if regFlow.Type != flow.TypeBrowser { return flow.ErrStrategyNotResponsible } - params, err := s.decode(r) + p, err := s.decode(r) if err != nil { - return s.handleRegistrationError(w, r, regFlow, params, err) - } - if params.Register == "" && params.Method != "passkey" { - return flow.ErrStrategyNotResponsible + return s.handleRegistrationError(w, r, regFlow, p, err) } - regFlow.TransientPayload = params.TransientPayload + regFlow.TransientPayload = p.TransientPayload - if err := flow.EnsureCSRF(s.d, r, regFlow.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, params.CSRFToken); err != nil { - return s.handleRegistrationError(w, r, regFlow, params, err) + if err := flow.EnsureCSRF(s.d, r, regFlow.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, p.CSRFToken); err != nil { + return s.handleRegistrationError(w, r, regFlow, p, err) } - if len(params.Register) == 0 { - return s.createPasskey(r, w, regFlow, params) + if p.Register == "" && p.Method != "passkey" { + return flow.ErrStrategyNotResponsible } - if err := flow.MethodEnabledAndAllowed(ctx, regFlow.GetFlowName(), params.Method, params.Method, s.d); err != nil { - return s.handleRegistrationError(w, r, regFlow, params, err) + if len(p.Register) == 0 { + return s.addPassKeyNodes(r, w, regFlow, p) } - if len(params.Traits) == 0 { - params.Traits = json.RawMessage("{}") + p.Method = s.ID().String() + if err := flow.MethodEnabledAndAllowed(ctx, regFlow.GetFlowName(), p.Method, p.Method, s.d); err != nil { + return s.handleRegistrationError(w, r, regFlow, p, err) } - ident.Traits = identity.Traits(params.Traits) + + if len(p.Traits) == 0 { + p.Traits = json.RawMessage("{}") + } + i.Traits = identity.Traits(p.Traits) webAuthnSession := gjson.GetBytes(regFlow.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)) if !webAuthnSession.IsObject() { - return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack( + return s.handleRegistrationError(w, r, regFlow, p, errors.WithStack( herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object."))) } var webAuthnSess webauthn.SessionData if err := json.Unmarshal([]byte(webAuthnSession.Raw), &webAuthnSess); err != nil { - return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack( + return s.handleRegistrationError(w, r, regFlow, p, errors.WithStack( herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err))) } - webAuthnResponse, err := protocol.ParseCredentialCreationResponseBody(strings.NewReader(params.Register)) + webAuthnResponse, err := protocol.ParseCredentialCreationResponseBody(strings.NewReader(p.Register)) if err != nil { - return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack( + return s.handleRegistrationError(w, r, regFlow, p, errors.WithStack( herodot.ErrBadRequest.WithReasonf("Unable to parse WebAuthn response: %s", err))) } webAuthn, err := webauthn.New(s.d.Config().PasskeyConfig(ctx)) if err != nil { - return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack( + return s.handleRegistrationError(w, r, regFlow, p, errors.WithStack( herodot.ErrInternalServerError.WithReasonf("Unable to get webAuthn config.").WithDebug(err.Error()))) } @@ -165,40 +159,106 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *reg if devErr := new(protocol.Error); errors.As(err, &devErr) { s.d.Logger().WithError(err).WithField("error_devinfo", devErr.DevInfo).Error("Failed to create WebAuthn credential") } - return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to create WebAuthn credential: %s", err))) + return s.handleRegistrationError(w, r, regFlow, p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Unable to create WebAuthn credential: %s", err))) } credentialWebAuthn := identity.CredentialFromWebAuthn(credential, true) - credentialsConfig, err := json.Marshal(identity.CredentialsWebAuthnConfig{ + credentialWebAuthnConfig, err := json.Marshal(identity.CredentialsWebAuthnConfig{ Credentials: identity.CredentialsWebAuthn{*credentialWebAuthn}, UserHandle: webAuthnSess.UserID, }) if err != nil { - return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error()))) + return s.handleRegistrationError(w, r, regFlow, p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error()))) } - ident.UpsertCredentialsConfig(s.ID(), credentialsConfig, 1) - passkeyCred, _ := ident.GetCredentials(s.ID()) + i.UpsertCredentialsConfig(s.ID(), credentialWebAuthnConfig, 1) + passkeyCred, _ := i.GetCredentials(s.ID()) passkeyCred.Identifiers = []string{string(webAuthnSess.UserID)} - ident.SetCredentials(s.ID(), *passkeyCred) - if err := s.validateCredentials(ctx, ident); err != nil { - return s.handleRegistrationError(w, r, regFlow, params, err) + i.SetCredentials(s.ID(), *passkeyCred) + if err := s.validateCredentials(ctx, i); err != nil { + return s.handleRegistrationError(w, r, regFlow, p, err) } // Remove the WebAuthn URL from the internal context now that it is set! regFlow.InternalContext, err = sjson.DeleteBytes(regFlow.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)) if err != nil { - return s.handleRegistrationError(w, r, regFlow, params, err) + return s.handleRegistrationError(w, r, regFlow, p, err) } if err := s.d.RegistrationFlowPersister().UpdateRegistrationFlow(ctx, regFlow); err != nil { - return s.handleRegistrationError(w, r, regFlow, params, err) + return s.handleRegistrationError(w, r, regFlow, p, err) } return nil } -func (s *Strategy) createPasskey(r *http.Request, w http.ResponseWriter, regFlow *registration.Flow, params *updateRegistrationFlowWithPasskeyMethod) error { +func (s *Strategy) PopulateRegistrationMethod(r *http.Request, regFlow *registration.Flow) error { + ctx := r.Context() + + if regFlow.Type != flow.TypeBrowser { + return nil + } + + defaultSchemaURL, err := s.d.Config().DefaultIdentityTraitsSchemaURL(ctx) + if err != nil { + return err + } + nodes, err := s.populateRegistrationNodes(ctx, defaultSchemaURL) + if err != nil { + return err + } + + for _, n := range nodes { + regFlow.UI.SetNode(n) + } + + regFlow.UI.Nodes.Append(node.NewInputField( + "method", + "passkey", + node.PasskeyGroup, + node.InputAttributeTypeSubmit, + ).WithMetaLabel(text.NewInfoSelfServiceRegistrationRegisterPasskey())) + + regFlow.UI.SetCSRF(s.d.GenerateCSRFToken(r)) + + return nil +} + +func (s *Strategy) populateRegistrationNodes(ctx context.Context, schemaURL *url.URL) (node.Nodes, error) { + runner, err := schema.NewExtensionRunner(ctx) + if err != nil { + return nil, err + } + c := jsonschema.NewCompiler() + runner.Register(c) + + nodes, err := container.NodesFromJSONSchema(ctx, node.DefaultGroup, schemaURL.String(), "", c) + if err != nil { + return nil, err + } + return nodes, nil +} + +// webauthnIdentifierNode returns the node that is used to identify the user in the WebAuthn flow. +func (s *Strategy) webauthnIdentifierNode(ctx context.Context, schemaURL *url.URL) (*node.Node, error) { + nodes, err := s.populateRegistrationNodes(ctx, schemaURL) + if err != nil { + return nil, err + } + for _, n := range nodes { + if attr, ok := n.Attributes.(*node.InputAttributes); ok { + if attr.DataWebauthnIdentifier { + return n, nil + } + } + } + + return nil, schema.NewMissingIdentifierError() +} + +func (s *Strategy) addPassKeyNodes(r *http.Request, w http.ResponseWriter, regFlow *registration.Flow, params *updateRegistrationFlowWithPasskeyMethod) error { ctx := r.Context() defaultSchemaURL, err := s.d.Config().DefaultIdentityTraitsSchemaURL(ctx) @@ -284,66 +344,6 @@ func (s *Strategy) createPasskey(r *http.Request, w http.ResponseWriter, regFlow return flow.ErrCompletedByStrategy } -// Update Registration Flow with Passkey Method -// -// swagger:model updateRegistrationFlowWithPasskeyMethod -type updateRegistrationFlowWithPasskeyMethod struct { - // Register a WebAuthn Security Key - // - // It is expected that the JSON returned by the WebAuthn registration process - // is included here. - Register string `json:"passkey_register"` - - // CSRFToken is the anti-CSRF token - CSRFToken string `json:"csrf_token"` - - // The identity's traits - // - // required: true - Traits json.RawMessage `json:"traits"` - - // Method - // - // Should be set to "passkey" when trying to add, update, or remove a Passkey. - // - // required: true - Method string `json:"method"` - - // Flow is flow ID. - // - // swagger:ignore - Flow string `json:"flow"` - - // Transient data to pass along to any webhooks - // - // required: false - TransientPayload json.RawMessage `json:"transient_payload,omitempty"` -} - -func (s *Strategy) decode(r *http.Request) (*updateRegistrationFlowWithPasskeyMethod, error) { - var p updateRegistrationFlowWithPasskeyMethod - err := registration.DecodeBody(&p, r, s.hd, s.d.Config(), registrationSchema) - - return &p, err -} - -func (s *Strategy) handleRegistrationError(_ http.ResponseWriter, r *http.Request, f *registration.Flow, p *updateRegistrationFlowWithPasskeyMethod, err error) error { - if f != nil { - if p != nil { - for _, n := range container.NewFromJSON("", node.DefaultGroup, p.Traits, "traits").Nodes { - // we only set the value and not the whole field because we want to keep types from the initial form generation - f.UI.Nodes.SetValueAttribute(n.ID(), n.Attributes.GetValue()) - } - } - - if f.Type == flow.TypeBrowser { - f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - } - } - - return err -} - func (s *Strategy) validateCredentials(ctx context.Context, i *identity.Identity) error { if err := s.d.IdentityValidator().Validate(ctx, i); err != nil { return err diff --git a/selfservice/strategy/passkey/passkey_settings.go b/selfservice/strategy/passkey/passkey_settings.go index cabab1a795f7..cb40ebf83265 100644 --- a/selfservice/strategy/passkey/passkey_settings.go +++ b/selfservice/strategy/passkey/passkey_settings.go @@ -35,9 +35,6 @@ import ( "github.com/ory/x/sqlxx" ) -//go:embed .schema/settings.schema.json -var settingsSchema []byte - func (s *Strategy) RegisterSettingsRoutes(_ *x.RouterPublic) {} func (s *Strategy) SettingsStrategyID() string { return s.ID().String() } diff --git a/selfservice/strategy/passkey/schema.go b/selfservice/strategy/passkey/schema.go new file mode 100644 index 000000000000..8be1150729bc --- /dev/null +++ b/selfservice/strategy/passkey/schema.go @@ -0,0 +1,15 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package passkey + +import _ "embed" + +//go:embed .schema/registration.schema.json +var registrationSchema []byte + +//go:embed .schema/login.schema.json +var loginSchema []byte + +//go:embed .schema/settings.schema.json +var settingsSchema []byte diff --git a/selfservice/strategy/webauthn/registration.go b/selfservice/strategy/webauthn/registration.go index b8eb4ef4234b..8ad3ae06db6a 100644 --- a/selfservice/strategy/webauthn/registration.go +++ b/selfservice/strategy/webauthn/registration.go @@ -7,7 +7,6 @@ import ( "encoding/json" "net/http" "strings" - "time" "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" @@ -92,20 +91,22 @@ func (s *Strategy) decode(p *updateRegistrationFlowWithWebAuthnMethod, r *http.R return registration.DecodeBody(p, r, s.hd, s.d.Config(), registrationSchema) } -func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registration.Flow, i *identity.Identity) (err error) { - if f.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(r.Context()) { +func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *registration.Flow, i *identity.Identity) (err error) { + ctx := r.Context() + + if regFlow.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(r.Context()) { return flow.ErrStrategyNotResponsible } var p updateRegistrationFlowWithWebAuthnMethod if err := s.decode(&p, r); err != nil { - return s.handleRegistrationError(w, r, f, &p, err) + return s.handleRegistrationError(w, r, regFlow, &p, err) } - f.TransientPayload = p.TransientPayload + regFlow.TransientPayload = p.TransientPayload - if err := flow.EnsureCSRF(s.d, r, f.Type, s.d.Config().DisableAPIFlowEnforcement(r.Context()), s.d.GenerateCSRFToken, p.CSRFToken); err != nil { - return s.handleRegistrationError(w, r, f, &p, err) + if err := flow.EnsureCSRF(s.d, r, regFlow.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, p.CSRFToken); err != nil { + return s.handleRegistrationError(w, r, regFlow, &p, err) } if len(p.Register) == 0 { @@ -113,8 +114,8 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat } p.Method = s.SettingsStrategyID() - if err := flow.MethodEnabledAndAllowed(r.Context(), f.GetFlowName(), s.SettingsStrategyID(), p.Method, s.d); err != nil { - return s.handleRegistrationError(w, r, f, &p, err) + if err := flow.MethodEnabledAndAllowed(ctx, regFlow.GetFlowName(), s.SettingsStrategyID(), p.Method, s.d); err != nil { + return s.handleRegistrationError(w, r, regFlow, &p, err) } if len(p.Traits) == 0 { @@ -122,24 +123,28 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat } i.Traits = identity.Traits(p.Traits) - webAuthnSession := gjson.GetBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)) + webAuthnSession := gjson.GetBytes(regFlow.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)) if !webAuthnSession.IsObject() { - return s.handleRegistrationError(w, r, f, &p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object."))) + return s.handleRegistrationError(w, r, regFlow, &p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object."))) } var webAuthnSess webauthn.SessionData - if err := json.Unmarshal([]byte(gjson.GetBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)).Raw), &webAuthnSess); err != nil { - return s.handleRegistrationError(w, r, f, &p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err))) + if err := json.Unmarshal([]byte(webAuthnSession.Raw), &webAuthnSess); err != nil { + return s.handleRegistrationError(w, r, regFlow, &p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err))) } webAuthnResponse, err := protocol.ParseCredentialCreationResponseBody(strings.NewReader(p.Register)) if err != nil { - return s.handleRegistrationError(w, r, f, &p, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to parse WebAuthn response: %s", err))) + return s.handleRegistrationError(w, r, regFlow, &p, errors.WithStack( + herodot.ErrBadRequest.WithReasonf("Unable to parse WebAuthn response: %s", err))) } web, err := webauthn.New(s.d.Config().WebAuthnConfig(r.Context())) if err != nil { - return s.handleRegistrationError(w, r, f, &p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to get webAuthn config.").WithDebug(err.Error()))) + return s.handleRegistrationError(w, r, regFlow, &p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Unable to get webAuthn config.").WithDebug(err.Error()))) } credential, err := web.CreateCredential(webauthnx.NewUser(webAuthnSess.UserID, nil, web.Config), webAuthnSess, webAuthnResponse) @@ -147,42 +152,43 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat if devErr := new(protocol.Error); errors.As(err, &devErr) { s.d.Logger().WithError(err).WithField("error_devinfo", devErr.DevInfo).Error("Failed to create WebAuthn credential") } - return s.handleRegistrationError(w, r, f, &p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to create WebAuthn credential: %s", err))) + return s.handleRegistrationError(w, r, regFlow, &p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Unable to create WebAuthn credential: %s", err))) } - var cc identity.CredentialsWebAuthnConfig - wc := identity.CredentialFromWebAuthn(credential, true) - wc.AddedAt = time.Now().UTC().Round(time.Second) - wc.DisplayName = p.RegisterDisplayName - wc.IsPasswordless = s.d.Config().WebAuthnForPasswordless(r.Context()) - cc.UserHandle = webAuthnSess.UserID - - cc.Credentials = append(cc.Credentials, *wc) - co, err := json.Marshal(cc) + credentialWebAuthn := identity.CredentialFromWebAuthn(credential, true) + credentialWebAuthn.DisplayName = p.RegisterDisplayName + credentialWebAuthnConfig, err := json.Marshal(identity.CredentialsWebAuthnConfig{ + Credentials: identity.CredentialsWebAuthn{*credentialWebAuthn}, + UserHandle: webAuthnSess.UserID, + }) if err != nil { - return s.handleRegistrationError(w, r, f, &p, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error()))) + return s.handleRegistrationError(w, r, regFlow, &p, errors.WithStack( + herodot.ErrInternalServerError.WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error()))) } - i.UpsertCredentialsConfig(s.ID(), co, 1) - if err := s.validateCredentials(r.Context(), i); err != nil { - return s.handleRegistrationError(w, r, f, &p, err) + i.UpsertCredentialsConfig(s.ID(), credentialWebAuthnConfig, 1) + if err := s.validateCredentials(ctx, i); err != nil { + return s.handleRegistrationError(w, r, regFlow, &p, err) } // Remove the WebAuthn URL from the internal context now that it is set! - f.InternalContext, err = sjson.DeleteBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)) + regFlow.InternalContext, err = sjson.DeleteBytes(regFlow.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)) if err != nil { - return s.handleRegistrationError(w, r, f, &p, err) + return s.handleRegistrationError(w, r, regFlow, &p, err) } - if err := s.d.RegistrationFlowPersister().UpdateRegistrationFlow(r.Context(), f); err != nil { - return s.handleRegistrationError(w, r, f, &p, err) + if err := s.d.RegistrationFlowPersister().UpdateRegistrationFlow(ctx, regFlow); err != nil { + return s.handleRegistrationError(w, r, regFlow, &p, err) } return nil } func (s *Strategy) PopulateRegistrationMethod(r *http.Request, f *registration.Flow) error { - if f.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(r.Context()) { + ctx := r.Context() + + if f.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(ctx) { return nil } @@ -191,7 +197,7 @@ func (s *Strategy) PopulateRegistrationMethod(r *http.Request, f *registration.F return err } - nodes, err := container.NodesFromJSONSchema(r.Context(), node.DefaultGroup, ds.String(), "", nil) + nodes, err := container.NodesFromJSONSchema(ctx, node.DefaultGroup, ds.String(), "", nil) if err != nil { return err } @@ -200,13 +206,13 @@ func (s *Strategy) PopulateRegistrationMethod(r *http.Request, f *registration.F f.UI.SetNode(n) } - web, err := webauthn.New(s.d.Config().WebAuthnConfig(r.Context())) + web, err := webauthn.New(s.d.Config().WebAuthnConfig(ctx)) if err != nil { return errors.WithStack(err) } webauthID := x.NewUUID() - user := webauthnx.NewUser(webauthID[:], nil, s.d.Config().WebAuthnConfig(r.Context())) + user := webauthnx.NewUser(webauthID[:], nil, s.d.Config().WebAuthnConfig(ctx)) option, sessionData, err := web.BeginRegistration(user) if err != nil { return errors.WithStack(err) @@ -222,7 +228,7 @@ func (s *Strategy) PopulateRegistrationMethod(r *http.Request, f *registration.F return errors.WithStack(err) } - f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(r.Context()))) + f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx))) f.UI.Nodes.Upsert(webauthnx.NewWebAuthnConnectionName()) f.UI.Nodes.Upsert(webauthnx.NewWebAuthnConnectionInput()) f.UI.Nodes.Upsert(webauthnx.NewWebAuthnConnectionTrigger(string(injectWebAuthnOptions)).