mirror of
https://github.com/discourse/discourse.git
synced 2025-06-21 22:35:39 +08:00

This commit improves said method to ensure that user is redirected to the right page before returning. ### Reviewer notes Example of test flakiness: https://github.com/discourse/discourse/actions/runs/14081653020/job/39435797236 ``` Failure/Error: raise capybara_timeout_error CapybaraTimeoutExtension::CapybaraTimedOut: This spec passed, but capybara waited for the full wait duration (10s) at least once. This will slow down the test suite. Beware of negating the result of selenium's RSpec matchers. [Screenshot Image]: /__w/discourse/discourse/tmp/capybara/failures_r_spec_example_groups_user_resetting_password_when_desktop_when_user_has_multi_factor_authentication_configured_when_user_has_security_key_and_backup_codes_configured_should_allow_a_user_to_reset_pass_261.png ~~~~~~~ JS LOGS ~~~~~~~ ~~~~~ END JS LOGS ~~~~~ Shared Example Group: "forgot password scenarios" called from ./spec/system/forgot_password_spec.rb:213 ./spec/rails_helper.rb:426:in `block (3 levels) in <top (required)>' ./spec/rails_helper.rb:619:in `block (3 levels) in <top (required)>' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/benchmark-0.4.0/lib/benchmark.rb:304:in `measure' ./spec/rails_helper.rb:619:in `block (2 levels) in <top (required)>' ./spec/rails_helper.rb:580:in `block (3 levels) in <top (required)>' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:185:in `block in timeout' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:192:in `timeout' ./spec/rails_helper.rb:570:in `block (2 levels) in <top (required)>' ./spec/rails_helper.rb:527:in `block (2 levels) in <top (required)>' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>' ```
220 lines
6.9 KiB
Ruby
220 lines
6.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rotp"
|
|
|
|
shared_examples "forgot password scenarios" do
|
|
let(:user_preferences_security_page) { PageObjects::Pages::UserPreferencesSecurity.new }
|
|
fab!(:user) { Fabricate(:user, username: "john", password: "supersecurepassword") }
|
|
fab!(:password_reset_token) do
|
|
Fabricate(
|
|
:email_token,
|
|
user:,
|
|
scope: EmailToken.scopes[:password_reset],
|
|
email: user.email,
|
|
).token
|
|
end
|
|
let(:user_menu) { PageObjects::Components::UserMenu.new }
|
|
let(:user_reset_password_page) { PageObjects::Pages::UserResetPassword.new }
|
|
|
|
def visit_reset_password_link
|
|
visit("/u/password-reset/#{password_reset_token}")
|
|
end
|
|
|
|
def create_user_security_key(user)
|
|
# testing the 2FA flow requires a user that was created > 5 minutes ago
|
|
user.update!(created_at: 6.minutes.ago)
|
|
|
|
sign_in(user)
|
|
|
|
user_preferences_security_page.visit(user)
|
|
user_preferences_security_page.visit_second_factor(user, "supersecurepassword")
|
|
|
|
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
|
|
end
|
|
|
|
context "when user does not have any multi-factor authentication configured" do
|
|
it "should allow a user to reset their password" do
|
|
visit_reset_password_link
|
|
|
|
user_reset_password_page.fill_in_new_password("newsuperpassword").submit_new_password
|
|
|
|
expect(user_reset_password_page).to have_logged_in_user
|
|
end
|
|
end
|
|
|
|
context "when user has multi-factor authentication configured" do
|
|
context "when user only has TOTP configured" do
|
|
fab!(:user_second_factor_totp) { Fabricate(:user_second_factor_totp, user:) }
|
|
|
|
it "should allow a user to reset password with TOTP" do
|
|
visit_reset_password_link
|
|
|
|
expect(user_reset_password_page).to have_no_toggle_button_to_second_factor_form
|
|
|
|
user_reset_password_page
|
|
.fill_in_totp(ROTP::TOTP.new(user_second_factor_totp.data).now)
|
|
.submit_totp
|
|
.fill_in_new_password("newsuperpassword")
|
|
.submit_new_password
|
|
|
|
expect(user_reset_password_page).to have_logged_in_user
|
|
end
|
|
end
|
|
|
|
context "when user only has security key configured" do
|
|
before do
|
|
@authenticator =
|
|
page.driver.browser.add_virtual_authenticator(
|
|
Selenium::WebDriver::VirtualAuthenticatorOptions.new,
|
|
)
|
|
|
|
create_user_security_key(user)
|
|
end
|
|
|
|
after { @authenticator.remove! }
|
|
|
|
it "should allow a user to reset password with a security key" do
|
|
visit_reset_password_link
|
|
|
|
expect(user_reset_password_page).to have_no_toggle_button_to_second_factor_form
|
|
|
|
user_reset_password_page.submit_security_key
|
|
|
|
user_reset_password_page.fill_in_new_password("newsuperpassword").submit_new_password
|
|
|
|
expect(user_reset_password_page).to have_logged_in_user
|
|
end
|
|
end
|
|
|
|
context "when user has TOTP and backup codes configured" do
|
|
fab!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user:) }
|
|
fab!(:user_second_factor_totp) { Fabricate(:user_second_factor_totp, user:) }
|
|
|
|
it "should allow a user to reset password with backup code" do
|
|
visit_reset_password_link
|
|
|
|
user_reset_password_page
|
|
.use_backup_codes
|
|
.fill_in_backup_code("iAmValidBackupCode")
|
|
.submit_backup_code
|
|
.fill_in_new_password("newsuperpassword")
|
|
.submit_new_password
|
|
|
|
expect(user_reset_password_page).to have_logged_in_user
|
|
end
|
|
end
|
|
|
|
context "when user has security key and backup codes configured" do
|
|
fab!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user:) }
|
|
|
|
before do
|
|
@authenticator =
|
|
page.driver.browser.add_virtual_authenticator(
|
|
Selenium::WebDriver::VirtualAuthenticatorOptions.new,
|
|
)
|
|
|
|
create_user_security_key(user)
|
|
end
|
|
|
|
after { @authenticator.remove! }
|
|
|
|
it "should allow a user to reset password with backup code instead of security key" do
|
|
visit_reset_password_link
|
|
|
|
user_reset_password_page.try_another_way
|
|
|
|
expect(user_reset_password_page).to have_no_toggle_button_in_second_factor_form
|
|
|
|
user_reset_password_page
|
|
.fill_in_backup_code("iAmValidBackupCode")
|
|
.submit_backup_code
|
|
.fill_in_new_password("newsuperpassword")
|
|
.submit_new_password
|
|
|
|
expect(user_reset_password_page).to have_logged_in_user
|
|
end
|
|
end
|
|
|
|
context "when user has TOTP, security key and backup codes configured" do
|
|
fab!(:user_second_factor_totp) { Fabricate(:user_second_factor_totp, user:) }
|
|
fab!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user:) }
|
|
|
|
before do
|
|
@authenticator =
|
|
page.driver.browser.add_virtual_authenticator(
|
|
Selenium::WebDriver::VirtualAuthenticatorOptions.new,
|
|
)
|
|
|
|
create_user_security_key(user)
|
|
end
|
|
|
|
after { @authenticator.remove! }
|
|
|
|
it "should allow a user to toggle from security key to TOTP and between TOTP and backup codes" do
|
|
visit_reset_password_link
|
|
|
|
user_reset_password_page.try_another_way
|
|
|
|
expect(user_reset_password_page).to have_totp_description
|
|
|
|
user_reset_password_page.use_backup_codes
|
|
|
|
expect(user_reset_password_page).to have_backup_codes_description
|
|
|
|
user_reset_password_page.use_totp
|
|
|
|
expect(user_reset_password_page).to have_totp_description
|
|
end
|
|
end
|
|
|
|
context "when user has TOTP and security key configured but no backup codes" do
|
|
fab!(:user_second_factor_totp) { Fabricate(:user_second_factor_totp, user:) }
|
|
|
|
before do
|
|
@authenticator =
|
|
page.driver.browser.add_virtual_authenticator(
|
|
Selenium::WebDriver::VirtualAuthenticatorOptions.new,
|
|
)
|
|
|
|
create_user_security_key(user)
|
|
end
|
|
|
|
after { @authenticator.remove! }
|
|
|
|
it "should allow a user to reset password with TOTP instead of security key" do
|
|
visit_reset_password_link
|
|
|
|
user_reset_password_page.try_another_way
|
|
|
|
expect(user_reset_password_page).to have_no_toggle_button_in_second_factor_form
|
|
|
|
user_reset_password_page
|
|
.fill_in_totp(ROTP::TOTP.new(user_second_factor_totp.data).now)
|
|
.submit_totp
|
|
.fill_in_new_password("newsuperpassword")
|
|
.submit_new_password
|
|
|
|
expect(user_reset_password_page).to have_logged_in_user
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "User resetting password", type: :system do
|
|
describe "when desktop" do
|
|
include_examples "forgot password scenarios"
|
|
end
|
|
|
|
describe "when mobile", mobile: true do
|
|
include_examples "forgot password scenarios"
|
|
end
|
|
end
|