diff --git a/app/assets/javascripts/discourse/app/components/modal/login.hbs b/app/assets/javascripts/discourse/app/components/modal/login.hbs index acb9696aea8..83e42cc2b89 100644 --- a/app/assets/javascripts/discourse/app/components/modal/login.hbs +++ b/app/assets/javascripts/discourse/app/components/modal/login.hbs @@ -15,7 +15,10 @@ @createAccount={{this.createAccount}} /> {{#if this.showLoginButtons}} - + {{/if}} {{/if}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js index 85ce8213ad3..d1b07457627 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js @@ -1,5 +1,6 @@ import { click, fillIn, tab, visit } from "@ember/test-helpers"; import { test } from "qunit"; +import sinon from "sinon"; import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import I18n from "discourse-i18n"; @@ -82,3 +83,37 @@ acceptance("Modal - Login - With Passkeys disabled", function (needs) { assert.dom("#login-account-name").hasAttribute("autocomplete", "username"); }); }); + +acceptance("Modal - Login - Passkeys on mobile", function (needs) { + needs.mobileView(); + needs.settings({ + experimental_passkeys: true, + }); + + needs.pretender((server, helper) => { + server.get(`/session/passkey/challenge.json`, () => + helper.response({ + challenge: "some-challenge", + }) + ); + }); + + test("Includes passkeys button and conditional UI", async function (assert) { + await visit("/"); + await click("header .login-button"); + + sinon.stub(navigator.credentials, "get").callsFake(function () { + return Promise.reject(new Error("credentials.get got called")); + }); + + assert + .dom("#login-account-name") + .hasAttribute("autocomplete", "username webauthn"); + + await click(".passkey-login-button"); + + // clicking the button triggers credentials.get + // but we can't really test that in frontend so an error is returned + assert.dom(".dialog-body").exists(); + }); +}); diff --git a/spec/system/user_page/user_preferences_security_spec.rb b/spec/system/user_page/user_preferences_security_spec.rb index ccd7a10e9c8..2177ccd8664 100644 --- a/spec/system/user_page/user_preferences_security_spec.rb +++ b/spec/system/user_page/user_preferences_security_spec.rb @@ -15,7 +15,7 @@ describe "User preferences for Security", type: :system do DiscourseWebauthn.stubs(:origin).returns(current_host + ":" + Capybara.server_port.to_s) end - describe "Security keys" do + shared_examples "security keys" do it "adds a 2FA security key and logs in with it" do options = ::Selenium::WebDriver::VirtualAuthenticatorOptions.new authenticator = page.driver.browser.add_virtual_authenticator(options) @@ -48,7 +48,7 @@ describe "User preferences for Security", type: :system do end end - describe "Passkeys" do + shared_examples "passkeys" do before { SiteSetting.experimental_passkeys = true } it "adds a passkey and logs in with it" do @@ -88,8 +88,9 @@ describe "User preferences for Security", type: :system do user_menu.sign_out # login with the key we just created + # this triggers the conditional UI for passkeys + # which uses the virtual authenticator find(".d-header .login-button").click - find(".passkey-login-button").click expect(page).to have_css(".header-dropdown-toggle.current-user") @@ -97,4 +98,14 @@ describe "User preferences for Security", type: :system do authenticator.remove! end end + + context "when desktop" do + include_examples "security keys" + include_examples "passkeys" + end + + context "when mobile", mobile: true do + include_examples "security keys" + include_examples "passkeys" + end end