From 93a1ae4fe98487a0bca00d2afdc5e7b07c0e1c46 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Fri, 11 Mar 2022 12:10:24 +0100 Subject: [PATCH] fix(webauthn): resolve missing identifier bug --- ...oad_is_set_when_identity_has_webauthn.json | 12 +++++++++ ...ebauthn_login_is_invalid-type=browser.json | 26 +++++++++---------- ...if_webauthn_login_is_invalid-type=spa.json | 26 +++++++++---------- selfservice/strategy/webauthn/login.go | 4 +++ selfservice/strategy/webauthn/login_test.go | 12 +++++---- .../integration/profiles/mfa/webauthn.spec.ts | 19 ++++++++++++++ 6 files changed, 68 insertions(+), 31 deletions(-) diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json index 628ef257bd2d..97bce7903c50 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=mfa-case=webauthn_payload_is_set_when_identity_has_webauthn.json @@ -1,4 +1,16 @@ [ + { + "attributes": { + "disabled": false, + "name": "identifier", + "node_type": "input", + "type": "hidden" + }, + "group": "default", + "messages": [], + "meta": {}, + "type": "input" + }, { "attributes": { "disabled": false, diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json index 1d719d715254..4ec85e234b85 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=browser.json @@ -3,6 +3,19 @@ "ui": { "method": "POST", "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, { "type": "input", "group": "default", @@ -58,19 +71,6 @@ }, "messages": [], "meta": {} - }, - { - "type": "input", - "group": "default", - "attributes": { - "name": "identifier", - "type": "hidden", - "required": true, - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": {} } ], "messages": [ diff --git a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json index 1d719d715254..4ec85e234b85 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestCompleteLogin-flow=passwordless-case=should_fail_if_webauthn_login_is_invalid-type=spa.json @@ -3,6 +3,19 @@ "ui": { "method": "POST", "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "hidden", + "required": true, + "disabled": false, + "node_type": "input" + }, + "messages": [], + "meta": {} + }, { "type": "input", "group": "default", @@ -58,19 +71,6 @@ }, "messages": [], "meta": {} - }, - { - "type": "input", - "group": "default", - "attributes": { - "name": "identifier", - "type": "hidden", - "required": true, - "disabled": false, - "node_type": "input" - }, - "messages": [], - "meta": {} } ], "messages": [ diff --git a/selfservice/strategy/webauthn/login.go b/selfservice/strategy/webauthn/login.go index 15474bdb26fc..a61a51fff55c 100644 --- a/selfservice/strategy/webauthn/login.go +++ b/selfservice/strategy/webauthn/login.go @@ -142,6 +142,10 @@ func (s *Strategy) populateLoginMethod(r *http.Request, sr *login.Flow, i *ident return errors.WithStack(err) } + if len(cred.Identifiers) > 0 { + sr.UI.SetNode(node.NewInputField("identifier", cred.Identifiers[0], node.DefaultGroup, node.InputAttributeTypeHidden)) + } + sr.UI.SetCSRF(s.d.GenerateCSRFToken(r)) sr.UI.Nodes.Upsert(NewWebAuthnScript(urlx.AppendPaths(s.d.Config(r.Context()).SelfPublicURL(), webAuthnRoute).String(), jsOnLoad)) sr.UI.SetNode(NewWebAuthnLoginTrigger(string(injectWebAuthnOptions)). diff --git a/selfservice/strategy/webauthn/login_test.go b/selfservice/strategy/webauthn/login_test.go index 78fa02cded2c..87ecba6efc98 100644 --- a/selfservice/strategy/webauthn/login_test.go +++ b/selfservice/strategy/webauthn/login_test.go @@ -458,14 +458,16 @@ func TestCompleteLogin(t *testing.T) { apiClient := testhelpers.NewHTTPClientWithIdentitySessionToken(t, reg, id) f := testhelpers.InitializeLoginFlowViaBrowser(t, apiClient, publicTS, false, true, testhelpers.InitFlowWithAAL(identity.AuthenticatorAssuranceLevel2)) + assert.Equal(t, gjson.GetBytes(id.Traits, "subject").String(), f.Ui.Nodes[0].Attributes.UiNodeInputAttributes.Value) testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{ "0.attributes.value", - "1.attributes.onclick", - "1.attributes.onload", - "3.attributes.src", - "3.attributes.nonce", + "1.attributes.value", + "2.attributes.onclick", + "2.attributes.onload", + "4.attributes.src", + "4.attributes.nonce", }) - ensureReplacement(t, "1", f.Ui, "allowCredentials") + ensureReplacement(t, "2", f.Ui, "allowCredentials") }) t.Run("case=webauthn payload is not set when identity has no webauthn", func(t *testing.T) { diff --git a/test/e2e/cypress/integration/profiles/mfa/webauthn.spec.ts b/test/e2e/cypress/integration/profiles/mfa/webauthn.spec.ts index 7e6cd14c0a4f..e12b177ed7ad 100644 --- a/test/e2e/cypress/integration/profiles/mfa/webauthn.spec.ts +++ b/test/e2e/cypress/integration/profiles/mfa/webauthn.spec.ts @@ -215,6 +215,25 @@ context('2FA WebAuthn', () => { cy.expectSettingsSaved() cy.get('*[name="webauthn_remove"]').should('exist') + // Login without refresh + cy.login({ email, password }) + cy.visit(login + '?aal=aal2') + cy.location().should((loc) => { + expect(loc.href).to.include('/login') + }) + + cy.get('*[name="webauthn_login_trigger"]').should('have.length', 1) + cy.clickWebAuthButton('login') + cy.location().should((loc) => { + expect(loc.href).to.not.include('/login') + }) + + cy.getSession({ + expectAal: 'aal2', + expectMethods: ['password', 'webauthn'] + }) + + // Login with refresh cy.visit(login + '?aal=aal2&refresh=true') cy.location().should((loc) => { expect(loc.href).to.include('/login')