test: update specs to not require OTP for enabling WebAuthn as 2FA

Co-authored-by: Santiago Rodriguez <santiago.rodriguez@cedarcode.com>
This commit is contained in:
Nicolas Temciuc 2025-08-15 14:38:08 -03:00 committed by Nicolas Temciuc
parent 4df50b9c7e
commit c5a075b6c2
4 changed files with 100 additions and 159 deletions

View File

@ -349,9 +349,9 @@ RSpec.describe Auth::SessionsController do
end end
end end
context 'with WebAuthn and OTP enabled as second factor' do context 'with WebAuthn enabled as second factor' do
let!(:user) do let!(:user) do
Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret) Fabricate(:user, email: 'x@y.com', password: 'abcdefgh')
end end
let!(:webauthn_credential) do let!(:webauthn_credential) do

View File

@ -20,29 +20,10 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
sign_in user, scope: :user sign_in user, scope: :user
end end
context 'when user has otp enabled' do it 'returns http success' do
before do get :new
user.update(otp_required_for_login: true)
end
it 'returns http success' do expect(response).to have_http_status(200)
get :new
expect(response).to have_http_status(200)
end
end
context 'when user does not have otp enabled' do
before do
user.update(otp_required_for_login: false)
end
it 'requires otp enabled first' do
get :new
expect(response).to redirect_to settings_two_factor_authentication_methods_path
expect(flash[:error]).to be_present
end
end end
end end
end end
@ -53,40 +34,21 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
sign_in user, scope: :user sign_in user, scope: :user
end end
context 'when user has otp enabled' do context 'when user has webauthn enabled' do
before do before do
user.update(otp_required_for_login: true) user.update(webauthn_id: WebAuthn.generate_user_id)
add_webauthn_credential(user)
end end
context 'when user has webauthn enabled' do it 'returns http success' do
before do get :index
user.update(webauthn_id: WebAuthn.generate_user_id)
add_webauthn_credential(user)
end
it 'returns http success' do expect(response).to have_http_status(200)
get :index
expect(response).to have_http_status(200)
end
end
context 'when user does not has webauthn enabled' do
it 'redirects to 2FA methods list page' do
get :index
expect(response).to redirect_to settings_two_factor_authentication_methods_path
expect(flash[:error]).to be_present
end
end end
end end
context 'when user does not have otp enabled' do context 'when user does not has webauthn enabled' do
before do it 'redirects to 2FA methods list page' do
user.update(otp_required_for_login: false)
end
it 'requires otp enabled first' do
get :index get :index
expect(response).to redirect_to settings_two_factor_authentication_methods_path expect(response).to redirect_to settings_two_factor_authentication_methods_path
@ -110,50 +72,53 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
sign_in user, scope: :user sign_in user, scope: :user
end end
context 'when user has otp enabled' do context 'when user has webauthn enabled' do
before do before do
user.update(otp_required_for_login: true) user.update(webauthn_id: WebAuthn.generate_user_id)
add_webauthn_credential(user)
end end
context 'when user has webauthn enabled' do it 'returns http success' do
before do get :options
user.update(webauthn_id: WebAuthn.generate_user_id)
add_webauthn_credential(user)
end
it 'includes existing credentials in list of excluded credentials', :aggregate_failures do expect(response).to have_http_status(200)
expect { get :options }.to_not change(user, :webauthn_id)
expect(response).to have_http_status(200)
expect(controller.session[:webauthn_challenge]).to be_present
excluded_credentials_ids = response.parsed_body['excludeCredentials'].pluck('id')
expect(excluded_credentials_ids).to match_array(user.webauthn_credentials.pluck(:external_id))
end
end end
context 'when user does not have webauthn enabled' do it 'stores the challenge on the session' do
it 'stores the challenge on the session and sets user webauthn_id', :aggregate_failures do get :options
get :options
expect(response).to have_http_status(200) expect(controller.session[:webauthn_challenge]).to be_present
expect(controller.session[:webauthn_challenge]).to be_present end
expect(user.reload.webauthn_id).to be_present
end it 'does not change webauthn_id' do
expect { get :options }.to_not change(user, :webauthn_id)
end
it 'includes existing credentials in list of excluded credentials' do
get :options
excluded_credentials_ids = response.parsed_body['excludeCredentials'].pluck('id')
expect(excluded_credentials_ids).to match_array(user.webauthn_credentials.pluck(:external_id))
end end
end end
context 'when user has not enabled otp' do context 'when user does not have webauthn enabled' do
before do it 'returns http success' do
user.update(otp_required_for_login: false)
end
it 'requires otp enabled first' do
get :options get :options
expect(response).to redirect_to settings_two_factor_authentication_methods_path expect(response).to have_http_status(200)
expect(flash[:error]).to be_present end
it 'stores the challenge on the session' do
get :options
expect(controller.session[:webauthn_challenge]).to be_present
end
it 'sets user webauthn_id' do
get :options
expect(user.reload.webauthn_id).to be_present
end end
end end
end end
@ -183,29 +148,40 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
sign_in user, scope: :user sign_in user, scope: :user
end end
context 'when user has enabled otp' do context 'when user has enabled webauthn' do
before do before do
user.update(otp_required_for_login: true) user.update(webauthn_id: WebAuthn.generate_user_id)
add_webauthn_credential(user)
end end
context 'when user has enabled webauthn' do context 'when creation succeeds' do
before do it 'returns http success' do
user.update(webauthn_id: WebAuthn.generate_user_id) controller.session[:webauthn_challenge] = challenge
add_webauthn_credential(user)
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
expect(response).to have_http_status(200)
end end
it 'adds a new credential to user credentials and does not change webauthn_id when creation succeeds', :aggregate_failures do it 'adds a new credential to user credentials' do
controller.session[:webauthn_challenge] = challenge controller.session[:webauthn_challenge] = challenge
expect do expect do
post :create, params: { credential: new_webauthn_credential, nickname: nickname } post :create, params: { credential: new_webauthn_credential, nickname: nickname }
end.to change { user.webauthn_credentials.count }.by(1) end.to change { user.webauthn_credentials.count }.by(1)
.and not_change(user, :webauthn_id)
expect(response).to have_http_status(200)
end end
it 'fails when the nickname is already used' do it 'does not change webauthn_id' do
controller.session[:webauthn_challenge] = challenge
expect do
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
end.to_not change(user, :webauthn_id)
end
end
context 'when the nickname is already used' do
it 'fails' do
controller.session[:webauthn_challenge] = challenge controller.session[:webauthn_challenge] = challenge
post :create, params: { credential: new_webauthn_credential, nickname: 'USB Key' } post :create, params: { credential: new_webauthn_credential, nickname: 'USB Key' }
@ -213,14 +189,19 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
expect(flash[:error]).to be_present expect(flash[:error]).to be_present
end end
end
it 'fails when the credential already exists' do context 'when the credential already exists' do
before do
user2 = Fabricate(:user)
public_key_credential = WebAuthn::Credential.from_create(new_webauthn_credential) public_key_credential = WebAuthn::Credential.from_create(new_webauthn_credential)
Fabricate(:webauthn_credential, Fabricate(:webauthn_credential,
user_id: Fabricate(:user).id, user_id: user2.id,
external_id: public_key_credential.id, external_id: public_key_credential.id,
public_key: public_key_credential.public_key) public_key: public_key_credential.public_key)
end
it 'fails' do
controller.session[:webauthn_challenge] = challenge controller.session[:webauthn_challenge] = challenge
post :create, params: { credential: new_webauthn_credential, nickname: nickname } post :create, params: { credential: new_webauthn_credential, nickname: nickname }
@ -230,29 +211,18 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
end end
end end
context 'when user have not enabled webauthn and creation succeeds' do context 'when user have not enabled webauthn' do
it 'creates a webauthn credential' do context 'when creation succeeds' do
controller.session[:webauthn_challenge] = challenge it 'creates a webauthn credential' do
controller.session[:webauthn_challenge] = challenge
expect do expect do
post :create, params: { credential: new_webauthn_credential, nickname: nickname } post :create, params: { credential: new_webauthn_credential, nickname: nickname }
end.to change { user.webauthn_credentials.count }.by(1) end.to change { user.webauthn_credentials.count }.by(1)
end
end end
end end
end end
context 'when user has not enabled otp' do
before do
user.update(otp_required_for_login: false)
end
it 'requires otp enabled first' do
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
expect(response).to redirect_to settings_two_factor_authentication_methods_path
expect(flash[:error]).to be_present
end
end
end end
context 'when not signed in' do context 'when not signed in' do
@ -270,39 +240,30 @@ RSpec.describe Settings::TwoFactorAuthentication::WebauthnCredentialsController
sign_in user, scope: :user sign_in user, scope: :user
end end
context 'when user has otp enabled' do context 'when user has webauthn enabled' do
before do before do
user.update(otp_required_for_login: true) user.update(webauthn_id: WebAuthn.generate_user_id)
add_webauthn_credential(user)
end end
context 'when user has webauthn enabled' do context 'when deletion succeeds' do
before do it 'redirects to 2FA methods list and shows flash success' do
user.update(webauthn_id: WebAuthn.generate_user_id) delete :destroy, params: { id: user.webauthn_credentials.take.id }
add_webauthn_credential(user)
end
it 'redirects to 2FA methods list and shows flash success and deletes the credential when deletion succeeds', :aggregate_failures do
expect do
delete :destroy, params: { id: user.webauthn_credentials.take.id }
end.to change { user.webauthn_credentials.count }.by(-1)
expect(response).to redirect_to settings_two_factor_authentication_methods_path expect(response).to redirect_to settings_two_factor_authentication_methods_path
expect(flash[:success]).to be_present expect(flash[:success]).to be_present
end end
end
context 'when user does not have webauthn enabled' do it 'deletes the credential' do
it 'redirects to 2FA methods list and shows flash error' do expect do
delete :destroy, params: { id: '1' } delete :destroy, params: { id: user.webauthn_credentials.take.id }
end.to change { user.webauthn_credentials.count }.by(-1)
expect(response).to redirect_to settings_two_factor_authentication_methods_path
expect(flash[:error]).to be_present
end end
end end
end end
context 'when user does not have otp enabled' do context 'when user does not have webauthn enabled' do
it 'requires otp enabled first' do it 'redirects to 2FA methods list and shows flash error' do
delete :destroy, params: { id: '1' } delete :destroy, params: { id: '1' }
expect(response).to redirect_to settings_two_factor_authentication_methods_path expect(response).to redirect_to settings_two_factor_authentication_methods_path

View File

@ -13,23 +13,4 @@ RSpec.describe 'Settings TwoFactorAuthenticationMethods' do
end end
end end
end end
context 'when signed in' do
let(:user) { Fabricate(:user) }
before { sign_in user }
describe 'GET to /settings/two_factor_authentication_methods' do
describe 'when user has not enabled otp' do
before { user.update(otp_required_for_login: false) }
it 'redirects to enable otp' do
get settings_two_factor_authentication_methods_path
expect(response)
.to redirect_to(settings_otp_authentication_path)
end
end
end
end
end end

View File

@ -26,15 +26,14 @@ RSpec.describe 'Admin Users TwoFactorAuthentications' do
end end
end end
context 'when user has OTP and WebAuthn enabled' do context 'when user has WebAuthn enabled' do
before { user.update(otp_required_for_login: true, webauthn_id: WebAuthn.generate_user_id) } before { user.update(webauthn_id: WebAuthn.generate_user_id) }
it 'disables OTP and webauthn and redirects to admin account page' do it 'disables OTP and webauthn and redirects to admin account page' do
visit admin_account_path(user.account.id) visit admin_account_path(user.account.id)
expect { disable_two_factor } expect { disable_two_factor }
.to change { user.reload.otp_enabled? }.to(false) .to change { user.reload.webauthn_enabled? }.to(false)
.and(change { user.reload.webauthn_enabled? }.to(false))
expect(page) expect(page)
.to have_title(user.account.pretty_acct) .to have_title(user.account.pretty_acct)
end end