mirror of
https://github.com/discourse/discourse.git
synced 2024-11-29 12:05:31 +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.
189 lines
5.5 KiB
Ruby
189 lines
5.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe "Changing email", type: :system do
|
|
fab!(:password) { "mysupersecurepassword" }
|
|
fab!(:user) { Fabricate(:user, active: true, password: password) }
|
|
let(:new_email) { "newemail@example.com" }
|
|
let(:user_preferences_security_page) { PageObjects::Pages::UserPreferencesSecurity.new }
|
|
|
|
before { Jobs.run_immediately! }
|
|
|
|
def generate_confirm_link
|
|
visit "/my/preferences/account"
|
|
|
|
email_dropdown = PageObjects::Components::SelectKit.new(".email-dropdown")
|
|
expect(email_dropdown.visible?).to eq(true)
|
|
email_dropdown.select_row_by_value("updateEmail")
|
|
|
|
find("#change-email").fill_in with: "newemail@example.com"
|
|
|
|
find(".save-button button").click
|
|
|
|
wait_for(timeout: Capybara.default_max_wait_time) { ActionMailer::Base.deliveries.count === 1 }
|
|
|
|
if user.admin?
|
|
get_link_from_email(:old)
|
|
else
|
|
get_link_from_email(:new)
|
|
end
|
|
end
|
|
|
|
def get_link_from_email(type)
|
|
mail = ActionMailer::Base.deliveries.last
|
|
expect(mail.to).to contain_exactly(type == :new ? new_email : user.email)
|
|
|
|
mail.body.to_s[%r{/u/confirm-#{type}-email/\S+}, 0]
|
|
end
|
|
|
|
it "allows regular user to change their email" do
|
|
sign_in user
|
|
|
|
visit generate_confirm_link
|
|
|
|
find(".confirm-new-email .btn-primary").click
|
|
|
|
expect(page).to have_css(".dialog-body", text: I18n.t("js.user.change_email.confirm_success"))
|
|
find(".dialog-footer .btn-primary").click
|
|
|
|
expect(user.reload.email).to eq(new_email)
|
|
end
|
|
|
|
it "works when user has totp 2fa" do
|
|
second_factor = Fabricate(:user_second_factor_totp, user: user)
|
|
sign_in user
|
|
|
|
visit generate_confirm_link
|
|
|
|
find(".confirm-new-email .btn-primary").click
|
|
|
|
find(".second-factor-token-input").fill_in with: second_factor.totp_object.now
|
|
|
|
find("button[type=submit]").click
|
|
|
|
try_until_success { expect(current_url).to match("/u/#{user.username}/preferences/account") }
|
|
|
|
expect(user.reload.email).to eq(new_email)
|
|
end
|
|
|
|
it "works when user has webauthn 2fa" do
|
|
# enforced 2FA flow needs a user created > 5 minutes ago
|
|
user.created_at = 6.minutes.ago
|
|
user.save!
|
|
|
|
sign_in user
|
|
|
|
DiscourseWebauthn.stubs(:origin).returns(current_host + ":" + Capybara.server_port.to_s)
|
|
options =
|
|
::Selenium::WebDriver::VirtualAuthenticatorOptions.new(
|
|
user_verification: true,
|
|
user_verified: true,
|
|
resident_key: true,
|
|
)
|
|
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")
|
|
|
|
visit generate_confirm_link
|
|
|
|
find(".confirm-new-email .btn-primary").click
|
|
|
|
find("#security-key-authenticate-button").click
|
|
|
|
try_until_success { expect(current_url).to match("/u/#{user.username}/preferences/account") }
|
|
|
|
expect(user.reload.email).to eq(new_email)
|
|
ensure
|
|
authenticator.remove!
|
|
end
|
|
|
|
it "does not require login to verify" do
|
|
second_factor = Fabricate(:user_second_factor_totp, user: user)
|
|
sign_in user
|
|
|
|
confirm_link = generate_confirm_link
|
|
|
|
Capybara.reset_sessions! # log out
|
|
|
|
visit confirm_link
|
|
|
|
find(".confirm-new-email .btn-primary").click
|
|
|
|
find(".second-factor-token-input").fill_in with: second_factor.totp_object.now
|
|
|
|
find("button[type=submit]").click
|
|
|
|
try_until_success { expect(current_url).to match("/latest") }
|
|
|
|
expect(user.reload.email).to eq(new_email)
|
|
end
|
|
|
|
it "makes admins verify old email" do
|
|
user.update!(admin: true)
|
|
sign_in user
|
|
|
|
confirm_old_link = generate_confirm_link
|
|
|
|
# Confirm old email
|
|
visit confirm_old_link
|
|
find(".confirm-old-email .btn-primary").click
|
|
expect(page).to have_css(
|
|
".dialog-body",
|
|
text: I18n.t("js.user.change_email.authorizing_old.confirm_success"),
|
|
)
|
|
find(".dialog-footer .btn-primary").click
|
|
|
|
# Confirm new email
|
|
wait_for(timeout: Capybara.default_max_wait_time) { ActionMailer::Base.deliveries.count === 2 }
|
|
confirm_new_link = get_link_from_email(:new)
|
|
|
|
visit confirm_new_link
|
|
|
|
find(".confirm-new-email .btn-primary").click
|
|
|
|
expect(page).to have_css(".dialog-body", text: I18n.t("js.user.change_email.confirm_success"))
|
|
find(".dialog-footer .btn-primary").click
|
|
|
|
expect(user.reload.email).to eq(new_email)
|
|
end
|
|
|
|
it "allows admin to verify old email while logged out" do
|
|
user.update!(admin: true)
|
|
sign_in user
|
|
|
|
confirm_old_link = generate_confirm_link
|
|
|
|
Capybara.reset_sessions! # log out
|
|
|
|
# Confirm old email
|
|
visit confirm_old_link
|
|
find(".confirm-old-email .btn-primary").click
|
|
expect(page).to have_css(
|
|
".dialog-body",
|
|
text: I18n.t("js.user.change_email.authorizing_old.confirm_success"),
|
|
)
|
|
find(".dialog-footer .btn-primary").click
|
|
|
|
# Confirm new email
|
|
wait_for(timeout: Capybara.default_max_wait_time) { ActionMailer::Base.deliveries.count === 2 }
|
|
confirm_new_link = get_link_from_email(:new)
|
|
|
|
visit confirm_new_link
|
|
|
|
find(".confirm-new-email .btn-primary").click
|
|
|
|
expect(page).to have_css(".dialog-body", text: I18n.t("js.user.change_email.confirm_success"))
|
|
find(".dialog-footer .btn-primary").click
|
|
|
|
expect(user.reload.email).to eq(new_email)
|
|
end
|
|
end
|