Skip to content

Commit

Permalink
feat: support Safari
Browse files Browse the repository at this point in the history
  • Loading branch information
hperl committed Feb 19, 2024
1 parent b013bf3 commit 6a72123
Show file tree
Hide file tree
Showing 33 changed files with 243 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-fMylX5h+UK/Cb8ha7P4MBTcbtZ7NPXPV2ZVet49V5ZjJki1q2KJnk+HUohPzNdrndTQad4VRsMwOyPkWZ4GHVw==",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-fMylX5h+UK/Cb8ha7P4MBTcbtZ7NPXPV2ZVet49V5ZjJki1q2KJnk+HUohPzNdrndTQad4VRsMwOyPkWZ4GHVw==",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-fMylX5h+UK/Cb8ha7P4MBTcbtZ7NPXPV2ZVet49V5ZjJki1q2KJnk+HUohPzNdrndTQad4VRsMwOyPkWZ4GHVw==",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-fMylX5h+UK/Cb8ha7P4MBTcbtZ7NPXPV2ZVet49V5ZjJki1q2KJnk+HUohPzNdrndTQad4VRsMwOyPkWZ4GHVw==",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-fMylX5h+UK/Cb8ha7P4MBTcbtZ7NPXPV2ZVet49V5ZjJki1q2KJnk+HUohPzNdrndTQad4VRsMwOyPkWZ4GHVw==",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,40 @@
"meta": {},
"type": "input"
},
{
"attributes": {
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
},
"group": "webauthn",
"messages": [],
"meta": {},
"type": "script"
},
{
"attributes": {
"disabled": false,
"name": "method",
"name": "passkey_register",
"node_type": "input",
"type": "submit",
"value": "passkey"
"type": "hidden"
},
"group": "passkey",
"messages": [],
"meta": {},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "passkey_register_trigger",
"node_type": "input",
"onclick": "window.__oryPasskeyRegistration()",
"type": "button"
},
"group": "passkey",
"messages": [],
Expand All @@ -57,6 +84,18 @@
},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "passkey_create_data",
"node_type": "input",
"type": "hidden"
},
"group": "passkey",
"messages": [],
"meta": {},
"type": "input"
},
{
"attributes": {
"autocomplete": "new-password",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,40 @@
"meta": {},
"type": "input"
},
{
"attributes": {
"async": true,
"crossorigin": "anonymous",
"id": "webauthn_script",
"integrity": "sha512-SSVrbpK6KOwN4xsH+nSyinjp4BOw8dsCU5dgegdpYUk0k7idTnQTd2JGVd+EJZV/TkdRaSKFJHRetpt5vydIZA==",
"node_type": "script",
"referrerpolicy": "no-referrer",
"type": "text/javascript"
},
"group": "webauthn",
"messages": [],
"meta": {},
"type": "script"
},
{
"attributes": {
"disabled": false,
"name": "method",
"name": "passkey_register",
"node_type": "input",
"type": "submit",
"value": "passkey"
"type": "hidden"
},
"group": "passkey",
"messages": [],
"meta": {},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "passkey_register_trigger",
"node_type": "input",
"onclick": "window.__oryPasskeyRegistration()",
"type": "button"
},
"group": "passkey",
"messages": [],
Expand All @@ -57,6 +84,18 @@
},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "passkey_create_data",
"node_type": "input",
"type": "hidden"
},
"group": "passkey",
"messages": [],
"meta": {},
"type": "input"
},
{
"attributes": {
"autocomplete": "new-password",
Expand Down
117 changes: 47 additions & 70 deletions selfservice/strategy/passkey/passkey_registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,14 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *reg

regFlow.TransientPayload = params.TransientPayload

if params.Register == "" && params.Method != "passkey" {
if params.Register == "" {
return flow.ErrStrategyNotResponsible
}

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 len(params.Register) == 0 {
return s.addPassKeyNodes(r, w, regFlow, params)
}

params.Method = s.ID().String()
if err := flow.MethodEnabledAndAllowed(ctx, regFlow.GetFlowName(), params.Method, params.Method, s.d); err != nil {
return s.handleRegistrationError(w, r, regFlow, params, err)

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L121

Added line #L121 was not covered by tests
Expand Down Expand Up @@ -188,19 +184,18 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *reg
return s.handleRegistrationError(w, r, regFlow, params, err)

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L184

Added line #L184 was not covered by tests
}

// 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)
}

if err := s.d.RegistrationFlowPersister().UpdateRegistrationFlow(ctx, regFlow); err != nil {
return s.handleRegistrationError(w, r, regFlow, params, err)

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L188

Added line #L188 was not covered by tests
}

return nil
}

type passkeyCreateData struct {
CredentialOptions *protocol.CredentialCreation `json:"credentialOptions"`
DisplayNameFieldName string `json:"displayNameFieldName"`
}

func (s *Strategy) PopulateRegistrationMethod(r *http.Request, regFlow *registration.Flow) error {
ctx := r.Context()
if regFlow.Type != flow.TypeBrowser {
Expand All @@ -220,67 +215,31 @@ func (s *Strategy) PopulateRegistrationMethod(r *http.Request, regFlow *registra
regFlow.UI.SetNode(n)
}

regFlow.UI.Nodes.Append(node.NewInputField(
"method",
"passkey",
node.PasskeyGroup,
node.InputAttributeTypeSubmit,
).WithMetaLabel(text.NewInfoSelfServiceRegistrationRegisterPasskey()))
// Passkey nodes begin
createData := new(passkeyCreateData)

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)
fieldName, err := s.PasskeyDisplayNameFromSchema(ctx, defaultSchemaURL.String())
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) addPassKeyNodes(r *http.Request, w http.ResponseWriter, regFlow *registration.Flow, params *updateRegistrationFlowWithPasskeyMethod) error {
ctx := r.Context()

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

// Render default nodes as hidden fields, also create passkey
c, err := container.NewFromStruct("", node.DefaultGroup, params.Traits, "traits")
if err != nil {
return s.handleRegistrationError(w, r, regFlow, params, err)
}
for _, n := range c.Nodes {
regFlow.UI.SetValue(n.ID(), n)
return err

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L223

Added line #L223 was not covered by tests
}

regFlow.UI.SetCSRF(s.d.GenerateCSRFToken(r))
createData.DisplayNameFieldName = fieldName

webAuthn, err := webauthn.New(s.d.Config().PasskeyConfig(ctx))
if err != nil {
return errors.WithStack(err)

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L229

Added line #L229 was not covered by tests
}
user := &webauthnx.User{
Name: username,
Name: "",
ID: []byte(randx.MustString(64, randx.AlphaNum)),
Config: s.d.Config().PasskeyConfig(ctx),
}
option, sessionData, err := webAuthn.BeginRegistration(user)
if err != nil {
return errors.WithStack(err)

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L238

Added line #L238 was not covered by tests
}
createData.CredentialOptions = option

injectWebAuthnOptions, err := json.Marshal(option)
injectWebAuthnOptions, err := json.Marshal(createData)
if err != nil {
return errors.WithStack(err)

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L244

Added line #L244 was not covered by tests
}
Expand All @@ -301,33 +260,51 @@ func (s *Strategy) addPassKeyNodes(r *http.Request, w http.ResponseWriter, regFl
Group: node.PasskeyGroup,
Meta: &node.Meta{},
Attributes: &node.InputAttributes{
Name: node.PasskeyRegister,
Type: node.InputAttributeTypeHidden,
OnLoad: "window.__oryPasskeyRegistration()", // defined in webauthn.js
Name: node.PasskeyCreateData,
Type: node.InputAttributeTypeHidden,
FieldValue: string(injectWebAuthnOptions),
}})

regFlow.UI.Nodes.Upsert(&node.Node{
Type: node.Input,
Group: node.WebAuthnGroup,
Group: node.PasskeyGroup,
Meta: &node.Meta{},
Attributes: &node.InputAttributes{
Name: node.PasskeyCreateData,
Type: node.InputAttributeTypeHidden,
FieldValue: string(injectWebAuthnOptions),
Name: node.PasskeyRegister,
Type: node.InputAttributeTypeHidden,
}})

if err := s.d.RegistrationFlowPersister().UpdateRegistrationFlow(r.Context(), regFlow); err != nil {
return s.handleRegistrationError(w, r, regFlow, params, err)
regFlow.UI.Nodes.Append(&node.Node{
Type: node.Input,
Group: node.PasskeyGroup,
Meta: &node.Meta{Label: text.NewInfoSelfServiceRegistrationRegisterPasskey()},
Attributes: &node.InputAttributes{
Name: node.PasskeyRegisterTrigger,
Type: node.InputAttributeTypeButton,
OnClick: "window.__oryPasskeyRegistration()", // defined in webauthn.js
}})

// Passkey nodes end

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

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L297

Added line #L297 was not covered by tests
}
c := jsonschema.NewCompiler()
runner.Register(c)

redirectTo := regFlow.AppendTo(s.d.Config().SelfServiceFlowRegistrationUI(r.Context())).String()
if x.IsJSONRequest(r) {
s.d.Writer().WriteError(w, r, flow.NewBrowserLocationChangeRequiredError(redirectTo))
} else {
http.Redirect(w, r, redirectTo, http.StatusSeeOther)
nodes, err := container.NodesFromJSONSchema(ctx, node.DefaultGroup, schemaURL.String(), "", c)
if err != nil {
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/passkey/passkey_registration.go#L304

Added line #L304 was not covered by tests
}

return flow.ErrCompletedByStrategy
return nodes, nil
}

func (s *Strategy) validateCredentials(ctx context.Context, i *identity.Identity) error {
Expand Down
5 changes: 4 additions & 1 deletion selfservice/strategy/passkey/passkey_registration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ func TestRegistration(t *testing.T) {
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, fix.publicTS, flowIsSPA(flowType), false, false)
testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{
"2.attributes.value",
"3.attributes.src",
"3.attributes.nonce",
"6.attributes.value",
})
})
}
Expand Down Expand Up @@ -248,7 +251,7 @@ func TestRegistration(t *testing.T) {

assert.Contains(t, gjson.Get(actual, "ui.action").String(), fix.publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
registrationhelpers.CheckFormContent(t, []byte(actual), "csrf_token", "traits.email")
assert.Equal(t, text.NewErrorValidationIdentifierMissing().Text, gjson.Get(actual, "ui.messages.0.text").String(), "%s", actual)
assert.Equal(t, text.NewErrorValidationRegistrationNoStrategyFound().Text, gjson.Get(actual, "ui.messages.0.text").String(), "%s", actual)
})
}
})
Expand Down
Loading

0 comments on commit 6a72123

Please sign in to comment.