diff --git a/app/controllers/concerns/mfa_setup_concern.rb b/app/controllers/concerns/mfa_setup_concern.rb
index d22df51a6c0..12b3f345465 100644
--- a/app/controllers/concerns/mfa_setup_concern.rb
+++ b/app/controllers/concerns/mfa_setup_concern.rb
@@ -1,8 +1,26 @@
module MfaSetupConcern
extend ActiveSupport::Concern
- def user_next_authentication_setup_path!(final_path = nil)
- case user_session[:selected_mfa_options]&.shift
+ def user_next_authentication_setup_path(next_setup_choice)
+ if user_session.dig(:selected_mfa_options, determine_next_mfa_selection).present? &&
+ IdentityConfig.store.select_multiple_mfa_options
+ auth_method_confirmation_url(next_setup_choice: next_setup_choice)
+ else
+ user_session.delete(:selected_mfa_options)
+ nil
+ end
+ end
+
+ def determine_next_mfa_selection
+ return unless user_session[:selected_mfa_options]
+ current_session = user_session[:next_mfa_selection_choice]
+ current_index = user_session[:selected_mfa_options].find_index(current_session) || 0
+ current_index + 1
+ end
+
+ def confirmation_path(next_mfa_selection_choice)
+ user_session[:next_mfa_selection_choice] = next_mfa_selection_choice
+ case next_mfa_selection_choice
when 'voice', 'sms', 'phone'
phone_setup_url
when 'auth_app'
@@ -15,8 +33,6 @@ def user_next_authentication_setup_path!(final_path = nil)
webauthn_setup_url(platform: true)
when 'backup_code'
backup_code_setup_url
- else
- final_path
end
end
diff --git a/app/controllers/mfa_confirmation_controller.rb b/app/controllers/mfa_confirmation_controller.rb
index c53ae468bb0..e27ff2316e6 100644
--- a/app/controllers/mfa_confirmation_controller.rb
+++ b/app/controllers/mfa_confirmation_controller.rb
@@ -1,5 +1,20 @@
class MfaConfirmationController < ApplicationController
- before_action :confirm_two_factor_authenticated
+ include MfaSetupConcern
+ before_action :confirm_two_factor_authenticated, except: [:show]
+
+ def show
+ @presenter = MfaConfirmationShowPresenter.new(
+ current_user: current_user,
+ next_path: next_path,
+ final_path: after_mfa_setup_path,
+ )
+ end
+
+ def skip
+ user_session.delete(:selected_mfa_options)
+ user_session.delete(:next_mfa_selection_choice)
+ redirect_to after_mfa_setup_path
+ end
def new
session[:password_attempts] ||= 0
@@ -19,6 +34,15 @@ def password
params.require(:user)[:password]
end
+ def next_mfa_selection_choice
+ params[:next_setup_choice] ||
+ user_session[:next_mfa_selection_choice]
+ end
+
+ def next_path
+ confirmation_path(next_mfa_selection_choice)
+ end
+
def handle_valid_password
if current_user.auth_app_configurations.any?
redirect_to login_two_factor_authenticator_url(reauthn: true)
diff --git a/app/controllers/two_factor_authentication/otp_verification_controller.rb b/app/controllers/two_factor_authentication/otp_verification_controller.rb
index 6c170fc3d3e..e304a39c3a2 100644
--- a/app/controllers/two_factor_authentication/otp_verification_controller.rb
+++ b/app/controllers/two_factor_authentication/otp_verification_controller.rb
@@ -19,7 +19,13 @@ def create
if result.success?
next_url = nil
if UserSessionContext.confirmation_context?(context)
- next_url = user_next_authentication_setup_path!
+ next_mfa_setup_for_user = user_session.dig(
+ :selected_mfa_options,
+ determine_next_mfa_selection,
+ )
+ next_url = user_next_authentication_setup_path(
+ next_mfa_setup_for_user,
+ )
end
handle_valid_otp(next_url)
else
diff --git a/app/controllers/users/backup_code_setup_controller.rb b/app/controllers/users/backup_code_setup_controller.rb
index d844d6d1157..2d226d26d52 100644
--- a/app/controllers/users/backup_code_setup_controller.rb
+++ b/app/controllers/users/backup_code_setup_controller.rb
@@ -25,7 +25,12 @@ def edit; end
def continue
flash[:success] = t('notices.backup_codes_configured')
- redirect_to user_next_authentication_setup_path!(after_mfa_setup_path)
+ next_mfa_setup_for_user = user_session.dig(
+ :selected_mfa_options,
+ determine_next_mfa_selection,
+ )
+ redirect_to user_next_authentication_setup_path(next_mfa_setup_for_user) ||
+ after_mfa_setup_path
end
def download
diff --git a/app/controllers/users/piv_cac_authentication_setup_controller.rb b/app/controllers/users/piv_cac_authentication_setup_controller.rb
index ee21c20ff7d..f8a8560d6d1 100644
--- a/app/controllers/users/piv_cac_authentication_setup_controller.rb
+++ b/app/controllers/users/piv_cac_authentication_setup_controller.rb
@@ -104,7 +104,12 @@ def process_valid_submission
Funnel::Registration::AddMfa.call(current_user.id, 'piv_cac')
session[:needs_to_setup_piv_cac_after_sign_in] = false
final_path = after_sign_in_path_for(current_user)
- redirect_to user_next_authentication_setup_path!(final_path)
+ next_mfa_setup_for_user = user_session.dig(
+ :selected_mfa_options,
+ determine_next_mfa_selection,
+ )
+ redirect_to user_next_authentication_setup_path(next_mfa_setup_for_user) ||
+ final_path
end
def piv_cac_enabled?
diff --git a/app/controllers/users/totp_setup_controller.rb b/app/controllers/users/totp_setup_controller.rb
index 1790e3de945..d49cdcc9897 100644
--- a/app/controllers/users/totp_setup_controller.rb
+++ b/app/controllers/users/totp_setup_controller.rb
@@ -79,7 +79,12 @@ def process_valid_code
handle_remember_device
flash[:success] = t('notices.totp_configured')
user_session.delete(:new_totp_secret)
- redirect_to user_next_authentication_setup_path!(after_mfa_setup_path)
+ next_mfa_setup_for_user = user_session.dig(
+ :selected_mfa_options,
+ determine_next_mfa_selection,
+ )
+ redirect_to user_next_authentication_setup_path(next_mfa_setup_for_user) ||
+ after_mfa_setup_path
end
def handle_remember_device
diff --git a/app/controllers/users/two_factor_authentication_setup_controller.rb b/app/controllers/users/two_factor_authentication_setup_controller.rb
index c1067d4ad30..9543960b3cd 100644
--- a/app/controllers/users/two_factor_authentication_setup_controller.rb
+++ b/app/controllers/users/two_factor_authentication_setup_controller.rb
@@ -44,7 +44,7 @@ def two_factor_options_presenter
def process_valid_form
user_session[:selected_mfa_options] = @two_factor_options_form.selection
- redirect_to user_next_authentication_setup_path!(user_session[:selected_mfa_options].first)
+ redirect_to confirmation_path(user_session[:selected_mfa_options].first)
end
def handle_empty_selection
diff --git a/app/controllers/users/webauthn_setup_controller.rb b/app/controllers/users/webauthn_setup_controller.rb
index 1fd64e6a13c..6f4a6e985d3 100644
--- a/app/controllers/users/webauthn_setup_controller.rb
+++ b/app/controllers/users/webauthn_setup_controller.rb
@@ -139,7 +139,12 @@ def process_valid_webauthn(form)
end
user_session[:auth_method] = 'webauthn'
- redirect_to user_next_authentication_setup_path!(after_mfa_setup_path)
+ next_mfa_setup_for_user = user_session.dig(
+ :selected_mfa_options,
+ determine_next_mfa_selection,
+ )
+ redirect_to user_next_authentication_setup_path(next_mfa_setup_for_user) ||
+ after_mfa_setup_path
end
def handle_remember_device
diff --git a/app/presenters/mfa_confirmation_show_presenter.rb b/app/presenters/mfa_confirmation_show_presenter.rb
new file mode 100644
index 00000000000..b574ad63f29
--- /dev/null
+++ b/app/presenters/mfa_confirmation_show_presenter.rb
@@ -0,0 +1,34 @@
+class MfaConfirmationShowPresenter
+ include ActionView::Helpers::TranslationHelper
+ attr_reader :mfa_context, :final_path, :next_path
+ def initialize(current_user:, next_path:, final_path:)
+ @mfa_context = MfaContext.new(current_user)
+ @final_path = final_path
+ @next_path = next_path
+ end
+
+ def title
+ if enabled_method_count > 1
+ t(
+ 'titles.mfa_setup.multiple_authentication_methods_setup',
+ method_count: method_count_text,
+ )
+ else
+ t('titles.mfa_setup.first_authentication_method')
+ end
+ end
+
+ def info
+ t('mfa.account_info', count: enabled_method_count)
+ end
+
+ private
+
+ def enabled_method_count
+ mfa_context.enabled_mfa_methods_count
+ end
+
+ def method_count_text
+ t('mfa.current_method_count')[enabled_method_count - 1]
+ end
+end
diff --git a/app/views/mfa_confirmation/index.html.erb b/app/views/mfa_confirmation/index.html.erb
deleted file mode 100644
index d46e018e7f7..00000000000
--- a/app/views/mfa_confirmation/index.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-<%# All content on this page is currently set up for MFA user testing.
- Once ready to ship to prod, translate all text content in Gengo %>
-
-<% title t('titles.mfa_setup.first_authentication_method') %>
-
-<%= render AlertComponent.new(type: :success, class: 'margin-bottom-4') do %>
- <%= t('mfa.method_confirmation.face_id') %>
-<% end %>
-
-<%= image_tag asset_url('user-signup-ial1.svg'), width: 107, height: 119, alt: '', class: 'margin-bottom-4' %>
-
-<%= render PageHeadingComponent.new.with_content(t('headings.mfa_setup.first_authentication_method')) %>
-
-
<%= t('mfa.cta') %>
-
-<%= button_to(
- account_reset_pending_cancel_path,
- class: 'usa-button usa-button--wide usa-button--big margin-bottom-3',
- ) { t('mfa.add') } %>
-
-<%= link_to t('mfa.skip'), root_url %>
\ No newline at end of file
diff --git a/app/views/mfa_confirmation/show.html.erb b/app/views/mfa_confirmation/show.html.erb
new file mode 100644
index 00000000000..6eb1477d6bd
--- /dev/null
+++ b/app/views/mfa_confirmation/show.html.erb
@@ -0,0 +1,22 @@
+<% title @presenter.title %>
+
+<%= image_tag asset_url('user-signup-ial1.svg'), width: 107, height: 119, alt: '', class: 'margin-bottom-4' %>
+
+<%= render PageHeadingComponent.new.with_content(@presenter.title) %>
+
+<%= @presenter.info %>
+
+
+ <%= link_to(
+ @presenter.next_path,
+ class: 'usa-button usa-button--wide usa-button--big margin-bottom-3',
+ ) { t('mfa.add') } %>
+
+
+<%= button_to(
+ auth_method_confirmation_skip_path,
+ method: :post,
+ class: 'usa-button usa-button--unstyled',
+ ) do %>
+ <%= t('mfa.skip') %>
+<% end %>
\ No newline at end of file
diff --git a/config/locales/headings/en.yml b/config/locales/headings/en.yml
index 99f2d903461..1746afd5dc8 100644
--- a/config/locales/headings/en.yml
+++ b/config/locales/headings/en.yml
@@ -29,8 +29,6 @@ en:
edit_info:
password: Change your password
phone: Manage your phone settings
- mfa_setup:
- first_authentication_method: You’ve added your first authentication method!
passwords:
change: Change your password
confirm: Confirm your current password to continue
diff --git a/config/locales/headings/es.yml b/config/locales/headings/es.yml
index fc06eeabe1e..6315c930147 100644
--- a/config/locales/headings/es.yml
+++ b/config/locales/headings/es.yml
@@ -29,8 +29,6 @@ es:
edit_info:
password: Cambie su contraseña
phone: Administrar la configuración de su teléfono
- mfa_setup:
- first_authentication_method: ¡Has agregado tu primer método de autenticación!
passwords:
change: Cambie su contraseña
confirm: Confirme la contraseña actual para continuar
diff --git a/config/locales/headings/fr.yml b/config/locales/headings/fr.yml
index 536cec98c0a..c00f6e3cfb8 100644
--- a/config/locales/headings/fr.yml
+++ b/config/locales/headings/fr.yml
@@ -29,8 +29,6 @@ fr:
edit_info:
password: Changez votre mot de passe
phone: Administrer les paramètres de votre téléphone
- mfa_setup:
- first_authentication_method: Vous avez ajouté votre première méthode d’authentification!
passwords:
change: Changez votre mot de passe
confirm: Confirmez votre mot de passe actuel pour continuer
diff --git a/config/locales/mfa/en.yml b/config/locales/mfa/en.yml
index 16ac718e808..3fdb5378c28 100644
--- a/config/locales/mfa/en.yml
+++ b/config/locales/mfa/en.yml
@@ -1,13 +1,23 @@
---
en:
mfa:
+ account_info:
+ one: Adding another authentication method prevents you from getting locked out
+ of your account if you lose one of your methods.
+ other: Congratulations! You are doing very well to keep your account secure. Add
+ new devices to stay up to date.
add: Add another method
- cta: Adding another authentication method prevents you from getting locked out
- of your account if you lose one of your methods.
+ current_method_count:
+ - first
+ - second
+ - third
+ - fourth
+ - fifth
+ - sixth
+ - seventh
+ - eighth
info: We recommend you select at least (2) two different methods so you have a
backup if you lose one of your chosen authentication devices.
- method_confirmation:
- face_id: Face ID has been added to your account
second_method_warning:
link: Add a second authentication method.
text: You will have to delete your account and start over if you lose your only
diff --git a/config/locales/mfa/es.yml b/config/locales/mfa/es.yml
index 448026c1e8a..b07e4a02585 100644
--- a/config/locales/mfa/es.yml
+++ b/config/locales/mfa/es.yml
@@ -1,14 +1,24 @@
---
es:
mfa:
+ account_info:
+ one: Agregar otro método de autenticación evita que se le bloquee el acceso a su
+ cuenta si pierde uno de sus métodos.
+ other: ¡Felicitaciones! Está haciendo muy bien en mantener su cuenta segura.
+ Añada nuevos dispositivos para mantenerse al día.
add: Agregar otro método
- cta: Agregar otro método de autenticación evita que se le bloquee el acceso a su
- cuenta si pierde uno de sus métodos.
+ current_method_count:
+ - primero
+ - segundo
+ - tercero
+ - cuarto
+ - quinto
+ - sexto
+ - séptimo
+ - octavo
info: Le recomendamos que seleccione al menos (2) dos métodos diferentes para
tener una copia de seguridad si pierde uno de los dispositivos de
autenticación elegidos.
- method_confirmation:
- face_id: Se ha agregado Face ID a su cuenta
second_method_warning:
link: Agregue un segundo método de autenticación.
text: Deberá eliminar su cuenta y comenzar de nuevo si pierde su único método de
diff --git a/config/locales/mfa/fr.yml b/config/locales/mfa/fr.yml
index 19f604c2033..b26f1b14b94 100644
--- a/config/locales/mfa/fr.yml
+++ b/config/locales/mfa/fr.yml
@@ -1,14 +1,24 @@
---
fr:
mfa:
+ account_info:
+ one: L’ajout d’une autre méthode d’authentification vous empêche d’être bloqué
+ sur votre compte si vous perdez l’une de vos méthodes.
+ other: Félicitations! Vous faites très bien d’assurer la sécurité de votre
+ compte. Ajoutez de nouveaux appareils pour rester à jour.
add: Agregar otro método
- cta: L’ajout d’une autre méthode d’authentification vous empêche d’être bloqué
- sur votre compte si vous perdez l’une de vos méthodes.
+ current_method_count:
+ - premiere
+ - deuxième
+ - troisième
+ - quatrième
+ - cinquième
+ - sixième
+ - septième
+ - huitième
info: Nous vous recommandons de sélectionner au moins (2) deux méthodes
différentes afin d’avoir une sauvegarde si vous perdez l’un de vos
dispositifs d’authentification choisis.
- method_confirmation:
- face_id: Face ID a été ajouté à votre compte
second_method_warning:
link: Ajoutez une deuxième méthode d’authentification.
text: Vous devrez supprimer votre compte et recommencer si vous perdez votre
diff --git a/config/locales/titles/en.yml b/config/locales/titles/en.yml
index f4aa00f3803..e952eac35be 100644
--- a/config/locales/titles/en.yml
+++ b/config/locales/titles/en.yml
@@ -42,6 +42,7 @@ en:
review: Re-enter your password
mfa_setup:
first_authentication_method: You’ve added your first authentication method!
+ multiple_authentication_methods_setup: You’ve added a %{method_count} authentication method!
no_auth_option: No sign-in method found
openid_connect:
authorization: OpenID Connect Authorization
diff --git a/config/locales/titles/es.yml b/config/locales/titles/es.yml
index b43247e20ca..37a73435e53 100644
--- a/config/locales/titles/es.yml
+++ b/config/locales/titles/es.yml
@@ -42,6 +42,7 @@ es:
review: Vuelve a ingresar tu contraseña
mfa_setup:
first_authentication_method: ¡Has agregado tu primer método de autenticación!
+ multiple_authentication_methods_setup: ¡Se ha agregado un %{method_count} código de autenticación!
no_auth_option: No se encontró mensaje de inicio de sesión
openid_connect:
authorization: Autorización de OpenID Connect
diff --git a/config/locales/titles/fr.yml b/config/locales/titles/fr.yml
index dd4c6d205fc..768bba9c639 100644
--- a/config/locales/titles/fr.yml
+++ b/config/locales/titles/fr.yml
@@ -42,6 +42,7 @@ fr:
review: Saisissez à nouveau votre mot de passe
mfa_setup:
first_authentication_method: Vous avez ajouté votre première méthode d’authentification!
+ multiple_authentication_methods_setup: Vous avez ajouté une %{method_count} méthode d’authentification!
no_auth_option: Aucun message de connexion trouvé
openid_connect:
authorization: Autorisation OpenID Connect
diff --git a/config/routes.rb b/config/routes.rb
index 44feb832dca..47f926bc0bb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -140,9 +140,8 @@
end
end
- if IdentityConfig.store.select_multiple_mfa_options
- get '/auth_method_confirmation' => 'mfa_confirmation#show'
- end
+ get '/auth_method_confirmation' => 'mfa_confirmation#show'
+ post '/auth_method_confirmation/skip' => 'mfa_confirmation#skip'
# Non-devise-controller routes. Alphabetically sorted.
get '/.well-known/openid-configuration' => 'openid_connect/configuration#index',
diff --git a/spec/controllers/mfa_confirmation_controller_spec.rb b/spec/controllers/mfa_confirmation_controller_spec.rb
index c064ae8acc1..22453bfc17d 100644
--- a/spec/controllers/mfa_confirmation_controller_spec.rb
+++ b/spec/controllers/mfa_confirmation_controller_spec.rb
@@ -1,6 +1,16 @@
require 'rails_helper'
describe MfaConfirmationController do
+ describe '#show' do
+ it 'presents the mfa confirmation page.' do
+ stub_sign_in
+
+ get :show, params: { final_path: account_url }
+
+ expect(response.status).to eq 200
+ end
+ end
+
describe '#new' do
it 'presents the password confirmation form' do
stub_sign_in
diff --git a/spec/controllers/users/backup_code_setup_controller_spec.rb b/spec/controllers/users/backup_code_setup_controller_spec.rb
index 0c0b15daf30..1c80a71329a 100644
--- a/spec/controllers/users/backup_code_setup_controller_spec.rb
+++ b/spec/controllers/users/backup_code_setup_controller_spec.rb
@@ -35,16 +35,20 @@
end
context 'when user selects multiple mfas on account creation' do
- it 'redirects to phone setup page' do
+ before do
+ allow(IdentityConfig.store).to receive(:select_multiple_mfa_options).and_return true
+ end
+
+ it 'redirects to MFA confirmation page' do
user = build(:user, :signed_up)
stub_sign_in(user)
codes = BackupCodeGenerator.new(user).create
controller.user_session[:backup_codes] = codes
- controller.user_session[:selected_mfa_options] = ['voice']
+ controller.user_session[:selected_mfa_options] = ['backup_code', 'voice']
post :continue
- expect(response).to redirect_to(phone_setup_url)
+ expect(response).to redirect_to(auth_method_confirmation_url(next_setup_choice: 'voice'))
end
end
diff --git a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb
index 55d89d59d44..2d45e41621d 100644
--- a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb
+++ b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb
@@ -120,11 +120,16 @@
context 'with additional MFAs leftover' do
before do
- subject.user_session[:selected_mfa_options] = ['voice']
+ subject.user_session[:selected_mfa_options] = ['piv_cac', 'voice']
+ allow(IdentityConfig.store).to receive(:select_multiple_mfa_options).and_return true
end
- it 'redirects to phone setup page' do
+ it 'redirects to Mfa Confirmation page' do
get :new, params: { token: good_token }
- expect(response).to redirect_to(phone_setup_url)
+ expect(response).to redirect_to(
+ auth_method_confirmation_url(
+ next_setup_choice: 'voice',
+ ),
+ )
end
it 'sets the piv/cac session information' do
diff --git a/spec/controllers/users/totp_setup_controller_spec.rb b/spec/controllers/users/totp_setup_controller_spec.rb
index fe3764b300c..5437d138d56 100644
--- a/spec/controllers/users/totp_setup_controller_spec.rb
+++ b/spec/controllers/users/totp_setup_controller_spec.rb
@@ -235,6 +235,7 @@
allow(@analytics).to receive(:track_event)
subject.user_session[:new_totp_secret] = secret
subject.user_session[:selected_mfa_options] = selected_mfa_options
+ allow(IdentityConfig.store).to receive(:select_multiple_mfa_options).and_return true
patch :confirm, params: { name: name, code: generate_totp_code(secret) }
end
@@ -257,9 +258,14 @@
end
context 'when user has multiple MFA methods left in user session' do
- let(:selected_mfa_options) { ['voice'] }
- it 'redirects to phone_setup_path with a success message and still logs analytics' do
- expect(response).to redirect_to(phone_setup_path)
+ let(:selected_mfa_options) { ['auth_app', 'voice'] }
+
+ it 'redirects to mfa confirmation path with a success message and still logs analytics' do
+ expect(response).to redirect_to(
+ auth_method_confirmation_url(
+ next_setup_choice: 'voice',
+ ),
+ )
result = {
success: true,
diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb
index 75fe4ef17dc..c43dd0b8a5c 100644
--- a/spec/controllers/users/webauthn_setup_controller_spec.rb
+++ b/spec/controllers/users/webauthn_setup_controller_spec.rb
@@ -154,13 +154,14 @@
context 'with multiple MFA methods chosen on account creation' do
before do
- controller.user_session[:selected_mfa_options] = ['voice']
+ controller.user_session[:selected_mfa_options] = ['webauthn_platform', 'voice']
+ allow(IdentityConfig.store).to receive(:select_multiple_mfa_options).and_return true
end
it 'should direct user to phone page' do
patch :confirm, params: params
- expect(response).to redirect_to(phone_setup_url)
+ expect(response).to redirect_to(auth_method_confirmation_url(next_setup_choice: 'voice'))
end
end
diff --git a/spec/features/multi_factor_authentication/mfa_cta_spec.rb b/spec/features/multi_factor_authentication/mfa_cta_spec.rb
index d214ab970f9..317bd6a32d2 100644
--- a/spec/features/multi_factor_authentication/mfa_cta_spec.rb
+++ b/spec/features/multi_factor_authentication/mfa_cta_spec.rb
@@ -42,6 +42,11 @@
expect(page).to have_current_path(phone_setup_path)
set_up_mfa_with_valid_phone
+
+ expect(page).to have_current_path(
+ auth_method_confirmation_path(next_setup_choice: 'backup_code'),
+ )
+ click_link t('mfa.add')
expect(page).to have_current_path(backup_code_setup_path)
set_up_mfa_with_backup_codes
expect(page).to have_current_path(sign_up_completed_path)
diff --git a/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb b/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb
new file mode 100644
index 00000000000..921d5f03681
--- /dev/null
+++ b/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+feature 'Multi Two Factor Authentication' do
+ before do
+ allow(IdentityConfig.store).to receive(:select_multiple_mfa_options).and_return(true)
+ end
+
+ describe 'When the user has not set up 2FA' do
+ scenario 'user can set up 2 MFA methods properly' do
+ sign_in_before_2fa
+
+ expect(current_path).to eq two_factor_options_path
+
+ click_2fa_option('phone')
+ click_2fa_option('backup_code')
+
+ click_continue
+
+ expect(page).
+ to have_content t('titles.phone_setup')
+
+ expect(current_path).to eq phone_setup_path
+
+ fill_in 'new_phone_form_phone', with: '703-555-1212'
+ click_send_security_code
+
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+
+ expect(page).to have_current_path(
+ auth_method_confirmation_path(next_setup_choice: 'backup_code'),
+ )
+
+ click_link t('mfa.add')
+
+ expect(current_path).to eq backup_code_setup_path
+
+ click_continue
+
+ expect(page).to have_link(t('forms.backup_code.download'))
+
+ click_continue
+
+ expect(page).to have_content(t('notices.backup_codes_configured'))
+ expect(current_path).to eq account_path
+ end
+
+ scenario 'user can select 2 MFA methods and complete 1 and skip one' do
+ sign_in_before_2fa
+
+ expect(current_path).to eq two_factor_options_path
+
+ click_2fa_option('phone')
+ click_2fa_option('backup_code')
+
+ click_continue
+
+ expect(page).
+ to have_content t('titles.phone_setup')
+
+ expect(current_path).to eq phone_setup_path
+
+ fill_in 'new_phone_form_phone', with: '703-555-1212'
+ click_send_security_code
+
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+
+ expect(page).to have_current_path(
+ auth_method_confirmation_path(next_setup_choice: 'backup_code'),
+ )
+
+ click_button t('mfa.skip')
+
+ expect(current_path).to eq account_path
+ end
+ end
+
+ def click_2fa_option(option)
+ find("label[for='two_factor_options_form_selection_#{option}']").click
+ end
+end
diff --git a/spec/views/mfa_confirmation/index.html.erb_spec.rb b/spec/views/mfa_confirmation/index.html.erb_spec.rb
deleted file mode 100644
index 701fdbbc128..00000000000
--- a/spec/views/mfa_confirmation/index.html.erb_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'rails_helper'
-
-describe 'mfa_confirmation/index.html.erb' do
- it 'has a localized title' do
- expect(view).to receive(:title).with(t('titles.mfa_setup.first_authentication_method'))
-
- render
- end
-
- it 'has a localized header' do
- render
-
- expect(rendered).to have_content(t('headings.mfa_setup.first_authentication_method'))
- end
-
- it 'provides a call to action to add another MFA method' do
- render
-
- expect(rendered).to have_selector('p', text: t('mfa.cta'))
- end
-
- it 'has a button with the next step' do
- render
-
- expect(rendered).to have_selector('button', text: t('mfa.add'))
- end
-end
diff --git a/spec/views/mfa_confirmation/show.html.erb_spec.rb b/spec/views/mfa_confirmation/show.html.erb_spec.rb
new file mode 100644
index 00000000000..7deccdb1827
--- /dev/null
+++ b/spec/views/mfa_confirmation/show.html.erb_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe 'mfa_confirmation/show.html.erb' do
+ let(:user) { create(:user, :signed_up, :with_personal_key) }
+ let(:decorated_user) { user.decorate }
+
+ before do
+ allow(view).to receive(:current_user).and_return(user)
+ assign(
+ :presenter,
+ MfaConfirmationShowPresenter.new(
+ current_user: user,
+ next_path: phone_setup_url,
+ final_path: account_url,
+ ),
+ )
+ end
+
+ it 'has a localized title' do
+ expect(view).to receive(:title).with(t('titles.mfa_setup.first_authentication_method'))
+
+ render
+ end
+
+ it 'has a localized header' do
+ render
+
+ expect(rendered).to have_content(t('titles.mfa_setup.first_authentication_method'))
+ end
+
+ it 'provides a call to action to add another MFA method' do
+ render
+
+ expect(rendered).to have_selector(
+ 'p',
+ text: t('mfa.account_info', count: 1),
+ )
+ end
+
+ it 'has a button with the ability to skip step' do
+ render
+
+ expect(rendered).to have_selector('button', text: t('mfa.skip'))
+ end
+end