mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 09:33:39 +08:00
974b3a2a6f
When making sensitive changes to an account (adding 2FA or passkeys), we require users to confirm their password. This is to prevent an attacker from adding 2FA to an account they have access to. However, on newly created accounts, we should not require this, it's an extra step and it doesn't provide extra security (since the account was just created). This commit makes it so that we don't require session confirmation for accounts created less than 5 minutes ago.
150 lines
4.9 KiB
Ruby
150 lines
4.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe "User preferences | Security", type: :system do
|
|
fab!(:password) { "kungfukenny" }
|
|
fab!(:email) { "email@user.com" }
|
|
fab!(:user) { Fabricate(:user, email: email, password: password) }
|
|
let(:user_preferences_security_page) { PageObjects::Pages::UserPreferencesSecurity.new }
|
|
let(:user_menu) { PageObjects::Components::UserMenu.new }
|
|
|
|
before do
|
|
user.activate
|
|
# testing the enforced 2FA flow requires a user that was created > 5 minutes ago
|
|
user.created_at = 6.minutes.ago
|
|
user.save!
|
|
sign_in(user)
|
|
|
|
# system specs run on their own host + port
|
|
DiscourseWebauthn.stubs(:origin).returns(current_host + ":" + Capybara.server_port.to_s)
|
|
end
|
|
|
|
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)
|
|
|
|
user_preferences_security_page.visit(user)
|
|
user_preferences_security_page.visit_second_factor(password)
|
|
|
|
find(".security-key .new-security-key").click
|
|
expect(user_preferences_security_page).to have_css("input#security-key-name")
|
|
|
|
find(".d-modal__body input#security-key-name").fill_in(with: "First Key")
|
|
find(".add-security-key").click
|
|
|
|
expect(user_preferences_security_page).to have_css(".security-key .second-factor-item")
|
|
|
|
user_menu.sign_out
|
|
|
|
# login flow
|
|
find(".d-header .login-button").click
|
|
find("input#login-account-name").fill_in(with: user.username)
|
|
find("input#login-account-password").fill_in(with: password)
|
|
|
|
find(".d-modal__footer .btn-primary").click
|
|
find("#security-key .btn-primary").click
|
|
|
|
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
|
|
|
# clear authenticator (otherwise it will interfere with other tests)
|
|
authenticator.remove!
|
|
end
|
|
end
|
|
|
|
shared_examples "passkeys" do
|
|
before { SiteSetting.enable_passkeys = true }
|
|
|
|
it "adds a passkey and logs in with it" do
|
|
options =
|
|
::Selenium::WebDriver::VirtualAuthenticatorOptions.new(
|
|
user_verification: true,
|
|
user_verified: true,
|
|
resident_key: true,
|
|
)
|
|
authenticator = page.driver.browser.add_virtual_authenticator(options)
|
|
|
|
page.driver.browser.manage.add_cookie(
|
|
domain: Discourse.current_hostname,
|
|
name: "destination_url",
|
|
value: "/new",
|
|
path: "/",
|
|
)
|
|
|
|
user_preferences_security_page.visit(user)
|
|
|
|
find(".pref-passkeys__add .btn").click
|
|
expect(user_preferences_security_page).to have_css("input#password")
|
|
|
|
find(".dialog-body input#password").fill_in(with: password)
|
|
find(".confirm-session .btn-primary").click
|
|
|
|
expect(user_preferences_security_page).to have_css(".rename-passkey__form")
|
|
|
|
find(".dialog-close").click
|
|
|
|
expect(user_preferences_security_page).to have_css(".pref-passkeys__rows .row")
|
|
|
|
select_kit = PageObjects::Components::SelectKit.new(".passkey-options-dropdown")
|
|
select_kit.expand
|
|
select_kit.select_row_by_name("Delete")
|
|
|
|
# confirm deletion screen shown without requiring session confirmation
|
|
# since this was already done when adding the passkey
|
|
expect(user_preferences_security_page).to have_css(".dialog-footer .btn-danger")
|
|
|
|
# close the dialog (don't delete the key, we need it to login in the next step)
|
|
find(".dialog-close").click
|
|
|
|
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
|
|
|
|
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
|
|
|
# ensures that we are redirected to the destination_url cookie
|
|
expect(page.driver.current_url).to include("/new")
|
|
|
|
# clear authenticator (otherwise it will interfere with other tests)
|
|
authenticator.remove!
|
|
end
|
|
end
|
|
|
|
shared_examples "enforced second factor" do
|
|
it "allows user to add 2FA" do
|
|
SiteSetting.enforce_second_factor = "all"
|
|
|
|
visit("/")
|
|
|
|
expect(page).to have_selector(
|
|
".alert-error",
|
|
text: "You are required to enable two-factor authentication before accessing this site.",
|
|
)
|
|
|
|
expect(page).to have_css(".user-preferences .totp")
|
|
expect(page).to have_css(".user-preferences .security-key")
|
|
|
|
find(".user-preferences .totp .btn.new-totp").click
|
|
|
|
find(".dialog-body input#password").fill_in(with: password)
|
|
find(".confirm-session .btn-primary").click
|
|
|
|
expect(page).to have_css(".qr-code")
|
|
end
|
|
end
|
|
|
|
context "when desktop" do
|
|
include_examples "security keys"
|
|
include_examples "passkeys"
|
|
include_examples "enforced second factor"
|
|
end
|
|
|
|
context "when mobile", mobile: true do
|
|
include_examples "security keys"
|
|
include_examples "passkeys"
|
|
include_examples "enforced second factor"
|
|
end
|
|
end
|