Add ability to require 2FA for specific roles (including Everybody) (#37701)

This commit is contained in:
Claire
2026-02-11 15:34:09 +01:00
committed by GitHub
parent 3e1127d27b
commit dfe44bcaef
23 changed files with 184 additions and 15 deletions

View File

@@ -13,16 +13,19 @@ RSpec.describe 'Log in' do
before do
as_a_registered_user
visit new_user_session_path
end
it 'A valid email and password user is able to log in' do
visit new_user_session_path
fill_in_auth_details(email, password)
expect(subject).to have_css('div.app-holder')
end
it 'A invalid email and password user is not able to log in' do
visit new_user_session_path
fill_in_auth_details('invalid_email', 'invalid_password')
expect(subject).to have_css('.flash-message', text: /#{failure_message_invalid}/i)
@@ -32,12 +35,53 @@ RSpec.describe 'Log in' do
let(:confirmed_at) { nil }
it 'A unconfirmed user is able to log in' do
visit new_user_session_path
fill_in_auth_details(email, password)
expect(subject).to have_css('.title', text: I18n.t('auth.setup.title'))
end
end
context 'when the user role requires 2FA' do
before do
bob.role.update!(require_2fa: true)
end
context 'when the user has not configured 2FA' do
it 'they are redirected to 2FA setup' do
visit new_user_session_path
fill_in_auth_details(email, password)
expect(subject).to have_no_css('div.app-holder')
expect(subject).to have_title(I18n.t('settings.two_factor_authentication'))
end
end
context 'when the user has configured 2FA' do
before do
bob.update!(otp_required_for_login: true, otp_secret: User.generate_otp_secret)
end
it 'they are able to log in' do
visit new_user_session_path
fill_in_auth_details(email, password)
fill_in_otp_details(bob.current_otp)
expect(subject).to have_css('div.app-holder')
end
end
end
private
def fill_in_otp_details(value)
fill_in 'user_otp_attempt', with: value
click_on I18n.t('auth.login')
end
def failure_message_invalid
keys = User.authentication_keys.map { |key| User.human_attribute_name(key) }
I18n.t('devise.failure.invalid', authentication_keys: keys.join('support.array.words_connector'))

View File

@@ -121,6 +121,46 @@ RSpec.describe 'Using OAuth from an external app' do
end
end
end
context 'when the user has yet to enable TOTP' do
let(:new_otp_secret) { ROTP::Base32.random(User.otp_secret_length) }
before do
allow(User).to receive(:generate_otp_secret).and_return(new_otp_secret)
user.role.update!(require_2fa: true)
end
it 'when accepting the authorization request' do
subject
# It presents the user with the 2FA setup page
expect(page).to have_content(I18n.t('two_factor_authentication.role_requirement', domain: local_domain_uri.host))
click_on I18n.t('otp_authentication.setup')
# Fill in challenge form
fill_in 'form_challenge_current_password', with: user.password
click_on I18n.t('challenge.confirm')
# It presents the user with the TOTP confirmation screen
expect(page).to have_title(I18n.t('settings.two_factor_authentication'))
fill_in 'form_two_factor_confirmation_otp_attempt', with: ROTP::TOTP.new(new_otp_secret).at(Time.now.utc)
click_on I18n.t('otp_authentication.enable')
# It presents the user with recovery codes
click_on I18n.t('two_factor_authentication.resume_app_authorization')
# It presents the user with an authorization page
expect(page).to have_content(oauth_authorize_text)
# It grants the app access to the account
expect { click_on oauth_authorize_text }
.to change { user_has_grant_with_client_app? }.to(true)
# Upon authorizing, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
end
end
end