diff --git a/app/assets/images/mfa-options/auth_app.svg b/app/assets/images/mfa-options/auth_app.svg
index cb21fa47fb4..0c4275dd14c 100644
--- a/app/assets/images/mfa-options/auth_app.svg
+++ b/app/assets/images/mfa-options/auth_app.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/images/mfa-options/backup_code.svg b/app/assets/images/mfa-options/backup_code.svg
index 6d566a9bdce..9d589417f78 100644
--- a/app/assets/images/mfa-options/backup_code.svg
+++ b/app/assets/images/mfa-options/backup_code.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/images/mfa-options/phone.svg b/app/assets/images/mfa-options/phone.svg
index 3ad7f6051df..8d5b481d254 100644
--- a/app/assets/images/mfa-options/phone.svg
+++ b/app/assets/images/mfa-options/phone.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/images/mfa-options/piv_cac.svg b/app/assets/images/mfa-options/piv_cac.svg
index f1f7abcc009..f9eeb11ee89 100644
--- a/app/assets/images/mfa-options/piv_cac.svg
+++ b/app/assets/images/mfa-options/piv_cac.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/images/mfa-options/security-key-icon.svg b/app/assets/images/mfa-options/security-key-icon.svg
new file mode 100644
index 00000000000..f821f937858
--- /dev/null
+++ b/app/assets/images/mfa-options/security-key-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/images/mfa-options/webauthn_platform.svg b/app/assets/images/mfa-options/webauthn_platform.svg
index c63ccaa3603..18016c59cdd 100644
--- a/app/assets/images/mfa-options/webauthn_platform.svg
+++ b/app/assets/images/mfa-options/webauthn_platform.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/models/auth_app_configuration.rb b/app/models/auth_app_configuration.rb
index 27f99724d12..d096ac034d0 100644
--- a/app/models/auth_app_configuration.rb
+++ b/app/models/auth_app_configuration.rb
@@ -13,7 +13,7 @@ def mfa_enabled?
def selection_presenters
if mfa_enabled?
- [TwoFactorAuthentication::AuthAppSelectionPresenter.new(self)]
+ [TwoFactorAuthentication::AuthAppSelectionPresenter.new(configuration: self)]
else
[]
end
diff --git a/app/models/backup_code_configuration.rb b/app/models/backup_code_configuration.rb
index eae10993af0..05138a035e5 100644
--- a/app/models/backup_code_configuration.rb
+++ b/app/models/backup_code_configuration.rb
@@ -18,7 +18,7 @@ def mfa_enabled?
end
def selection_presenters
- [TwoFactorAuthentication::BackupCodeSelectionPresenter.new(self)]
+ [TwoFactorAuthentication::BackupCodeSelectionPresenter.new(configuration: self)]
end
def friendly_name
diff --git a/app/models/personal_key_configuration.rb b/app/models/personal_key_configuration.rb
index 836eaf6f2c0..76ee680c85b 100644
--- a/app/models/personal_key_configuration.rb
+++ b/app/models/personal_key_configuration.rb
@@ -13,7 +13,7 @@ def mfa_enabled?
def selection_presenters
if mfa_enabled?
- [TwoFactorAuthentication::PersonalKeySelectionPresenter.new(self)]
+ [TwoFactorAuthentication::PersonalKeySelectionPresenter.new(configuration: self)]
else
[]
end
diff --git a/app/models/phone_configuration.rb b/app/models/phone_configuration.rb
index 1d07ae92f8b..7cd2ad3b1e6 100644
--- a/app/models/phone_configuration.rb
+++ b/app/models/phone_configuration.rb
@@ -25,11 +25,11 @@ def selection_presenters
capabilities = PhoneNumberCapabilities.new(phone, phone_confirmed: !!confirmed_at?)
if capabilities.supports_sms?
- options << TwoFactorAuthentication::SmsSelectionPresenter.new(self)
+ options << TwoFactorAuthentication::SmsSelectionPresenter.new(configuration: self)
end
if capabilities.supports_voice?
- options << TwoFactorAuthentication::VoiceSelectionPresenter.new(self)
+ options << TwoFactorAuthentication::VoiceSelectionPresenter.new(configuration: self)
end
options
diff --git a/app/models/piv_cac_configuration.rb b/app/models/piv_cac_configuration.rb
index 609098ae856..b492dff6df5 100644
--- a/app/models/piv_cac_configuration.rb
+++ b/app/models/piv_cac_configuration.rb
@@ -9,7 +9,7 @@ def mfa_enabled?
def selection_presenters
if mfa_enabled?
- [TwoFactorAuthentication::PivCacSelectionPresenter.new(self)]
+ [TwoFactorAuthentication::PivCacSelectionPresenter.new(configuration: self)]
else
[]
end
diff --git a/app/models/webauthn_configuration.rb b/app/models/webauthn_configuration.rb
index df1eeac8745..8c866349110 100644
--- a/app/models/webauthn_configuration.rb
+++ b/app/models/webauthn_configuration.rb
@@ -18,9 +18,9 @@ def mfa_enabled?
def selection_presenters
if platform_authenticator?
- [TwoFactorAuthentication::WebauthnPlatformSelectionPresenter.new(self)]
+ [TwoFactorAuthentication::WebauthnPlatformSelectionPresenter.new(configuration: self)]
else
- [TwoFactorAuthentication::WebauthnSelectionPresenter.new(self)]
+ [TwoFactorAuthentication::WebauthnSelectionPresenter.new(configuration: self)]
end
end
diff --git a/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb b/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb
index 780d6bcdc94..1fb20b1f617 100644
--- a/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb
@@ -8,5 +8,13 @@ def method
def security_level
I18n.t('two_factor_authentication.two_factor_choice_options.secure_label')
end
+
+ def disabled?
+ user&.auth_app_configurations&.any?
+ end
+
+ def mfa_configuration_count
+ user.auth_app_configurations.count
+ end
end
end
diff --git a/app/presenters/two_factor_authentication/backup_code_selection_presenter.rb b/app/presenters/two_factor_authentication/backup_code_selection_presenter.rb
index a9653240ffd..27872f54394 100644
--- a/app/presenters/two_factor_authentication/backup_code_selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/backup_code_selection_presenter.rb
@@ -8,5 +8,21 @@ def method
def security_level
I18n.t('two_factor_authentication.two_factor_choice_options.least_secure_label')
end
+
+ def disabled?
+ user&.backup_code_configurations&.any?
+ end
+
+ def mfa_configuration_description
+ return '' if !disabled?
+ t(
+ 'two_factor_authentication.two_factor_choice_options.unused_backup_code',
+ count: mfa_configuration_count,
+ )
+ end
+
+ def mfa_configuration_count
+ user.backup_code_configurations.unused.count
+ end
end
end
diff --git a/app/presenters/two_factor_authentication/phone_selection_presenter.rb b/app/presenters/two_factor_authentication/phone_selection_presenter.rb
index dc5f67c9292..d3931ff1e6e 100644
--- a/app/presenters/two_factor_authentication/phone_selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/phone_selection_presenter.rb
@@ -18,12 +18,16 @@ def info
t('two_factor_authentication.two_factor_choice_options.phone_info')
end
+ def mfa_configuration_count
+ user.phone_configurations.count
+ end
+
def security_level
t('two_factor_authentication.two_factor_choice_options.less_secure_label')
end
def disabled?
- VendorStatus.new.all_phone_vendor_outage?
+ VendorStatus.new.all_phone_vendor_outage? || user&.phone_configurations&.any?
end
end
end
diff --git a/app/presenters/two_factor_authentication/piv_cac_selection_presenter.rb b/app/presenters/two_factor_authentication/piv_cac_selection_presenter.rb
index ee27b99becf..2628a45ed5f 100644
--- a/app/presenters/two_factor_authentication/piv_cac_selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/piv_cac_selection_presenter.rb
@@ -7,5 +7,13 @@ def method
def security_level
I18n.t('two_factor_authentication.two_factor_choice_options.more_secure_label')
end
+
+ def disabled?
+ user&.piv_cac_configurations&.any?
+ end
+
+ def mfa_configuration_count
+ user.piv_cac_configurations.count
+ end
end
end
diff --git a/app/presenters/two_factor_authentication/selection_presenter.rb b/app/presenters/two_factor_authentication/selection_presenter.rb
index e03675b1445..87d74dac06d 100644
--- a/app/presenters/two_factor_authentication/selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/selection_presenter.rb
@@ -2,10 +2,11 @@ module TwoFactorAuthentication
class SelectionPresenter
include ActionView::Helpers::TranslationHelper
- attr_reader :configuration
+ attr_reader :configuration, :user
- def initialize(configuration = nil)
+ def initialize(configuration: nil, user: nil)
@configuration = configuration
+ @user = user
end
def type
@@ -28,6 +29,16 @@ def info
end
end
+ def mfa_configuration_count; end
+
+ def mfa_configuration_description
+ return '' if !disabled?
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: mfa_configuration_count,
+ )
+ end
+
def security_level; end
def html_class
diff --git a/app/presenters/two_factor_authentication/webauthn_platform_selection_presenter.rb b/app/presenters/two_factor_authentication/webauthn_platform_selection_presenter.rb
index ce90baa0ac4..4d80d2de017 100644
--- a/app/presenters/two_factor_authentication/webauthn_platform_selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/webauthn_platform_selection_presenter.rb
@@ -8,8 +8,16 @@ def html_class
'display-none'
end
+ def disabled?
+ user&.webauthn_configurations&.where(platform_authenticator: true)&.any?
+ end
+
def security_level
I18n.t('two_factor_authentication.two_factor_choice_options.more_secure_label')
end
+
+ def mfa_configuration_count
+ user.webauthn_configurations.where(platform_authenticator: true).count
+ end
end
end
diff --git a/app/presenters/two_factor_authentication/webauthn_selection_presenter.rb b/app/presenters/two_factor_authentication/webauthn_selection_presenter.rb
index 9ad09a8c4c8..44bf1b3ec31 100644
--- a/app/presenters/two_factor_authentication/webauthn_selection_presenter.rb
+++ b/app/presenters/two_factor_authentication/webauthn_selection_presenter.rb
@@ -12,5 +12,13 @@ def html_class
def security_level
I18n.t('two_factor_authentication.two_factor_choice_options.more_secure_label')
end
+
+ def disabled?
+ user&.webauthn_configurations&.where(platform_authenticator: [false, nil])&.any?
+ end
+
+ def mfa_configuration_count
+ user.webauthn_configurations.where(platform_authenticator: [false, nil]).count
+ end
end
end
diff --git a/app/presenters/two_factor_options_presenter.rb b/app/presenters/two_factor_options_presenter.rb
index 4ef32a6ea09..086dead8e6b 100644
--- a/app/presenters/two_factor_options_presenter.rb
+++ b/app/presenters/two_factor_options_presenter.rb
@@ -1,6 +1,8 @@
class TwoFactorOptionsPresenter
include ActionView::Helpers::TranslationHelper
+ attr_reader :user
+
def initialize(user_agent:, user: nil, aal3_required: false, piv_cac_required: false)
@user_agent = user_agent
@user = user
@@ -50,32 +52,32 @@ def show_security_level?
def piv_cac_option
return [] unless current_device_is_desktop?
- [TwoFactorAuthentication::PivCacSelectionPresenter.new]
+ [TwoFactorAuthentication::PivCacSelectionPresenter.new(user: user)]
end
def webauthn_option
return [] if piv_cac_required?
- [TwoFactorAuthentication::WebauthnSelectionPresenter.new]
+ [TwoFactorAuthentication::WebauthnSelectionPresenter.new(user: user)]
end
def webauthn_platform_option
return [] if piv_cac_required? || !IdentityConfig.store.platform_authentication_enabled
- [TwoFactorAuthentication::WebauthnPlatformSelectionPresenter.new]
+ [TwoFactorAuthentication::WebauthnPlatformSelectionPresenter.new(user: user)]
end
def phone_options
return [] if piv_cac_required? || aal3_only? || IdentityConfig.store.hide_phone_mfa_signup
- [TwoFactorAuthentication::PhoneSelectionPresenter.new]
+ [TwoFactorAuthentication::PhoneSelectionPresenter.new(user: user)]
end
def totp_option
return [] if piv_cac_required? || aal3_only?
- [TwoFactorAuthentication::AuthAppSelectionPresenter.new]
+ [TwoFactorAuthentication::AuthAppSelectionPresenter.new(user: user)]
end
def backup_code_option
return [] if piv_cac_required? || aal3_only?
- [TwoFactorAuthentication::BackupCodeSelectionPresenter.new]
+ [TwoFactorAuthentication::BackupCodeSelectionPresenter.new(user: user)]
end
def current_device_is_desktop?
@@ -91,6 +93,6 @@ def aal3_only?
end
def mfa_policy
- @mfa_policy ||= MfaPolicy.new(@user)
+ @mfa_policy ||= MfaPolicy.new(user)
end
end
diff --git a/app/views/partials/multi_factor_authentication/_mfa_selection.html.erb b/app/views/partials/multi_factor_authentication/_mfa_selection.html.erb
index 1e4909f7556..146ea7adf16 100644
--- a/app/views/partials/multi_factor_authentication/_mfa_selection.html.erb
+++ b/app/views/partials/multi_factor_authentication/_mfa_selection.html.erb
@@ -4,6 +4,7 @@
option.type,
option.type == 'phone' && flash[:phone_error].present?,
disabled: option.disabled?,
+ checked: option.disabled?,
class: 'usa-checkbox__input usa-checkbox__input--tile',
id: "two_factor_options_form_selection_#{option.type}",
) %>
@@ -16,7 +17,7 @@
].select(&:present?).join(' '),
) do %>
<%= image_tag(asset_url("mfa-options/#{option.type}.svg"), alt: "#{option.label} icon", class: 'usa-checkbox__image') %>
-
<%= option.label %>
+
<%= option.label %> <%= option.mfa_configuration_description %>
<%= option.info %>
diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml
index d57527b7d9f..76e699c1598 100644
--- a/config/locales/two_factor_authentication/en.yml
+++ b/config/locales/two_factor_authentication/en.yml
@@ -129,6 +129,9 @@ en:
backup_code_info: A list of 10 codes you can print or save to your device. When
you use the last code, we will generate a new list. Keep in mind backup
codes are easy to lose.
+ configurations_added:
+ one: '(%{count} added)'
+ other: '(%{count} added)'
least_secure_label: Least secure
less_secure_label: Less secure
more_secure_label: More Secure
@@ -143,6 +146,9 @@ en:
secure_label: Secure
sms: Text message / SMS
sms_info: Get your security code via text message / SMS.
+ unused_backup_code:
+ one: '(%{count} unused code)'
+ other: '(%{count} unused codes)'
voice: Phone call
voice_info: Get your security code via phone call.
webauthn: Security key
diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml
index 8568b851fe2..052f7d7704c 100644
--- a/config/locales/two_factor_authentication/es.yml
+++ b/config/locales/two_factor_authentication/es.yml
@@ -140,6 +140,9 @@ es:
dispositivo. Generaremos una nueva lista cuando haya usado el último
código. Tenga presente que los códigos de seguridad pueden perderse con
facilidad.
+ configurations_added:
+ one: '(%{count} añadido)'
+ other: '(%{count} añadido)'
least_secure_label: Lo menos seguro
less_secure_label: Menos seguro
more_secure_label: Más seguro
@@ -157,6 +160,9 @@ es:
secure_label: Seguro
sms: Mensaje de texto / SMS
sms_info: Obtenga su código de seguridad a través de mensajes de texto / SMS.
+ unused_backup_code:
+ one: '(%{count} código sin usar)'
+ other: '(%{count} códigos sin usar)'
voice: Llamada telefónica
voice_info: Obtenga su código de seguridad a través de una llamada telefónica.
webauthn: Clave de seguridad
diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml
index 0509626e2a5..983798ecfd6 100644
--- a/config/locales/two_factor_authentication/fr.yml
+++ b/config/locales/two_factor_authentication/fr.yml
@@ -144,6 +144,9 @@ fr:
sur votre appareil. Lorsque vous utilisez le dernier code, nous
générerons une nouvelle liste. Gardez à l’esprit que les codes de
sauvegarde sont faciles à perdre.
+ configurations_added:
+ one: '(%{count} ajouté)'
+ other: '(%{count} ajoutés)'
least_secure_label: Le moins sécurisé
less_secure_label: Moins sécurisé
more_secure_label: Plus sécurisé
@@ -160,6 +163,9 @@ fr:
secure_label: Sécurisé
sms: SMS
sms_info: Obtenez votre code de sécurité par SMS.
+ unused_backup_code:
+ one: '(%{count} code non utilisé)'
+ other: '(%{count} codes non utilisés)'
voice: Appel téléphonique
voice_info: Obtenez votre code de sécurité par appel téléphonique.
webauthn: Clef de sécurité
diff --git a/package.json b/package.json
index 181a207c77f..aeb41b24a66 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"fast-glob": "^3.2.7",
"focus-trap": "^6.7.1",
"foundation-emails": "^2.3.1",
- "identity-style-guide": "^6.4.1",
+ "identity-style-guide": "^6.4.2",
"intl-tel-input": "^17.0.8",
"react": "^17.0.2",
"react-dom": "^17.0.2",
diff --git a/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb
index 43c92e4786f..699245541a1 100644
--- a/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb
@@ -1,12 +1,33 @@
require 'rails_helper'
describe TwoFactorAuthentication::AuthAppSelectionPresenter do
- let(:subject) { described_class.new(configuration) }
let(:configuration) {}
+ let(:user_without_mfa) { create(:user) }
+ let(:user_with_mfa) { create(:user, :with_authentication_app) }
+ let(:presenter_without_mfa) do
+ described_class.new(configuration: configuration, user: user_without_mfa)
+ end
+ let(:presenter_with_mfa) do
+ described_class.new(configuration: configuration, user: user_with_mfa)
+ end
describe '#type' do
it 'returns auth_app' do
- expect(subject.type).to eq 'auth_app'
+ expect(presenter_without_mfa.type).to eq 'auth_app'
+ end
+ end
+
+ describe '#mfa_configruation' do
+ it 'return an empty string when user has not configured this authenticator' do
+ expect(presenter_without_mfa.mfa_configuration_description).to eq('')
+ end
+ it 'return an # added when user has configured this authenticator' do
+ expect(presenter_with_mfa.mfa_configuration_description).to eq(
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: 1,
+ ),
+ )
end
end
end
diff --git a/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb
index f5204d63262..b892020ca88 100644
--- a/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
describe TwoFactorAuthentication::PersonalKeySelectionPresenter do
- let(:subject) { described_class.new(configuration) }
+ let(:subject) { described_class.new(configuration: configuration) }
let(:configuration) {}
describe '#type' do
diff --git a/spec/presenters/two_factor_authentication/phone_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/phone_selection_presenter_spec.rb
index a673c34bac2..57fd3f0ed84 100644
--- a/spec/presenters/two_factor_authentication/phone_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/phone_selection_presenter_spec.rb
@@ -1,7 +1,10 @@
require 'rails_helper'
RSpec.describe TwoFactorAuthentication::PhoneSelectionPresenter do
- let(:presenter) { described_class.new(phone) }
+ let(:user_without_mfa) { create(:user) }
+ let(:user_with_mfa) { create(:user, :with_phone) }
+ let(:presenter_with_mfa) { described_class.new(configuration: phone, user: user_with_mfa) }
+ let(:presenter_without_mfa) { described_class.new(configuration: phone, user: user_without_mfa) }
describe '#info' do
context 'when a user does not have a phone configuration (first time)' do
@@ -11,12 +14,12 @@
end
it 'includes a note about choosing voice or sms' do
- expect(presenter.info).
+ expect(presenter_without_mfa.info).
to include(t('two_factor_authentication.two_factor_choice_options.phone_info'))
end
it 'does not include a masked number' do
- expect(presenter.info).to_not include('***')
+ expect(presenter_without_mfa.info).to_not include('***')
end
context 'when VOIP numbers are blocked' do
@@ -30,14 +33,33 @@
describe '#disabled?' do
let(:phone) { build(:phone_configuration, phone: '+1 888 867-5309') }
- it { expect(presenter.disabled?).to eq(false) }
+ it { expect(presenter_without_mfa.disabled?).to eq(false) }
context 'all phone vendor outage' do
before do
allow_any_instance_of(VendorStatus).to receive(:all_phone_vendor_outage?).and_return(true)
end
- it { expect(presenter.disabled?).to eq(true) }
+ it { expect(presenter_without_mfa.disabled?).to eq(true) }
+ end
+ end
+
+ describe '#mfa_configruation' do
+ let(:phone) { nil }
+ before do
+ allow(IdentityConfig.store).to receive(:select_multiple_mfa_options).and_return(true)
+ end
+ it 'returns an empty string when user has not configured this authenticator' do
+ expect(presenter_without_mfa.mfa_configuration_description).to eq('')
+ end
+ it 'returns an # added when user has configured this authenticator' do
+ puts user_with_mfa.phone_configurations.first
+ expect(presenter_with_mfa.mfa_configuration_description).to eq(
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: 1,
+ ),
+ )
end
end
end
diff --git a/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb
index 5cd077d058b..35f3bb3c645 100644
--- a/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb
@@ -1,12 +1,33 @@
require 'rails_helper'
describe TwoFactorAuthentication::PivCacSelectionPresenter do
- let(:subject) { described_class.new(configuration) }
+ let(:user_without_mfa) { create(:user) }
+ let(:user_with_mfa) { create(:user, :with_piv_or_cac) }
let(:configuration) {}
+ let(:presenter_without_mfa) {
+ described_class.new(configuration: configuration, user: user_without_mfa)
+ }
+ let(:presenter_with_mfa) {
+ described_class.new(configuration: configuration, user: user_with_mfa)
+ }
describe '#type' do
it 'returns piv_cac' do
- expect(subject.type).to eq 'piv_cac'
+ expect(presenter_without_mfa.type).to eq 'piv_cac'
+ end
+ end
+
+ describe '#mfa_configruation' do
+ it 'returns an empty string when user has not configured this authenticator' do
+ expect(presenter_without_mfa.mfa_configuration_description).to eq('')
+ end
+ it 'returns an # added when user has configured this authenticator' do
+ expect(presenter_with_mfa.mfa_configuration_description).to eq(
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: 1,
+ ),
+ )
end
end
end
diff --git a/spec/presenters/two_factor_authentication/selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/selection_presenter_spec.rb
index 32c6894b263..078f9c30485 100644
--- a/spec/presenters/two_factor_authentication/selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/selection_presenter_spec.rb
@@ -22,7 +22,7 @@ def method
context 'with configuration' do
it 'raises with missing translation' do
- expect { PlaceholderPresenter.new(1).label }.to raise_error(RuntimeError)
+ expect { PlaceholderPresenter.new(configuration: 1).label }.to raise_error(RuntimeError)
end
end
end
@@ -36,7 +36,7 @@ def method
context 'with configuration' do
it 'raises with missing translation' do
- expect { PlaceholderPresenter.new(1).info }.to raise_error(RuntimeError)
+ expect { PlaceholderPresenter.new(configuration: 1).info }.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb
index e88accba671..d29b9cb0706 100644
--- a/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
describe TwoFactorAuthentication::SmsSelectionPresenter do
- let(:subject) { described_class.new(phone) }
+ let(:subject) { described_class.new(configuration: phone) }
describe '#type' do
context 'when a user has only one phone configuration' do
diff --git a/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb
index da9b47cbcd4..27808a115a9 100644
--- a/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
describe TwoFactorAuthentication::VoiceSelectionPresenter do
- let(:subject) { described_class.new(phone) }
+ let(:subject) { described_class.new(configuration: phone) }
describe '#type' do
context 'when a user has only one phone configuration' do
diff --git a/spec/presenters/two_factor_authentication/webauthn_platform_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/webauthn_platform_selection_presenter_spec.rb
index 31618970e1f..21855f0ba34 100644
--- a/spec/presenters/two_factor_authentication/webauthn_platform_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/webauthn_platform_selection_presenter_spec.rb
@@ -1,18 +1,41 @@
require 'rails_helper'
describe TwoFactorAuthentication::WebauthnPlatformSelectionPresenter do
- let(:subject) { described_class.new(configuration) }
+ let(:user_without_mfa) { create(:user) }
+ let(:user_with_mfa) { create(:user) }
let(:configuration) {}
+ let(:presenter_without_mfa) do
+ described_class.new(configuration: configuration, user: user_without_mfa)
+ end
+ let(:presenter_with_mfa) do
+ described_class.new(configuration: configuration, user: user_with_mfa)
+ end
describe '#type' do
it 'returns webauthn_platform' do
- expect(subject.type).to eq 'webauthn_platform'
+ expect(presenter_without_mfa.type).to eq 'webauthn_platform'
end
end
describe '#html_class' do
it 'returns display-none' do
- expect(subject.html_class).to eq 'display-none'
+ expect(presenter_without_mfa.html_class).to eq 'display-none'
+ end
+ end
+
+ describe '#mfa_configruation' do
+ it 'returns an empty string when user has not configured this authenticator' do
+ expect(presenter_without_mfa.mfa_configuration_description).to eq('')
+ end
+
+ it 'returns an # added when user has configured this authenticator' do
+ create(:webauthn_configuration, platform_authenticator: true, user: user_with_mfa)
+ expect(presenter_with_mfa.mfa_configuration_description).to eq(
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: 1,
+ ),
+ )
end
end
end
diff --git a/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb
index 2935d677298..715aa778c87 100644
--- a/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb
@@ -1,18 +1,41 @@
require 'rails_helper'
describe TwoFactorAuthentication::WebauthnSelectionPresenter do
- let(:subject) { described_class.new(configuration) }
+ let(:user_without_mfa) { create(:user) }
+ let(:user_with_mfa) { create(:user) }
let(:configuration) {}
+ let(:presenter_without_mfa) do
+ described_class.new(configuration: configuration, user: user_without_mfa)
+ end
+ let(:presenter_with_mfa) do
+ described_class.new(configuration: configuration, user: user_with_mfa)
+ end
describe '#type' do
it 'returns webauthn' do
- expect(subject.type).to eq 'webauthn'
+ expect(presenter_without_mfa.type).to eq 'webauthn'
end
end
describe '#html_class' do
it 'returns display-none' do
- expect(subject.html_class).to eq 'display-none'
+ expect(presenter_without_mfa.html_class).to eq 'display-none'
+ end
+ end
+
+ describe '#mfa_configruation' do
+ it 'returns an empty string when user has not configured this authenticator' do
+ expect(presenter_without_mfa.mfa_configuration_description).to eq('')
+ end
+
+ it 'returns an # added when user has configured this authenticator' do
+ create(:webauthn_configuration, platform_authenticator: false, user: user_with_mfa)
+ expect(presenter_with_mfa.mfa_configuration_description).to eq(
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: 1,
+ ),
+ )
end
end
end
diff --git a/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb b/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb
index ea6d2e3f6b8..71b6fc2737b 100644
--- a/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb
+++ b/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb
@@ -6,26 +6,62 @@
let(:lookup_context) { ActionView::LookupContext.new(ActionController::Base.view_paths) }
let(:view_context) { ActionView::Base.new(lookup_context, {}, controller) }
- let(:form_object) { User.new }
- let(:presenter) { TwoFactorOptionsPresenter.new(user_agent: nil) }
+ let(:user) { create(:user) }
+ let(:form_object) { user }
+ let(:presenter) { TwoFactorOptionsPresenter.new(user_agent: nil, user: user) }
let(:form_builder) do
SimpleForm::FormBuilder.new(form_object.model_name.param_key, form_object, view_context, {})
end
- subject(:rendered) do
- render partial: 'mfa_selection', locals: {
- form: form_builder,
- option: presenter.options[4],
- }
- end
-
- it 'renders an lg-validated-field tag' do
- expect(rendered).to have_css('.mfa-selection')
- end
-
context 'before selecting options' do
+ subject(:rendered) do
+ render partial: 'mfa_selection', locals: {
+ form: form_builder,
+ option: presenter.options[4],
+ }
+ end
it 'does not display any errors' do
expect(rendered).to_not have_css('.checkbox__invalid')
end
+
+ it 'renders a field with mfa-selection class' do
+ expect(rendered).to have_css('.mfa-selection')
+ end
+ end
+
+ context 'user already setup an mfa configuration and is returning to create a second' do
+ let(:user) { create(:user, :with_authentication_app) }
+ let(:form_object) { user }
+ let(:presenter) { TwoFactorOptionsPresenter.new(user_agent: nil, user: user) }
+ let(:form_builder) do
+ SimpleForm::FormBuilder.new(form_object.model_name.param_key, form_object, view_context, {})
+ end
+ subject(:rendered) do
+ render partial: 'mfa_selection', locals: {
+ form: form_builder,
+ option: presenter.options[3],
+ }
+ end
+
+ it 'shows a disabled checkbox for the configuration already created' do
+ expect(rendered).to have_field('two_factor_options_form[selection][]', disabled: true)
+ end
+
+ it 'shows a checked checkbox for the configuration already created' do
+ expect(rendered).to have_field(
+ 'two_factor_options_form[selection][]',
+ disabled: true,
+ checked: true,
+ )
+ end
+
+ it 'the checkbox for the configuration created communicates it is already created' do
+ expect(rendered).to have_content(
+ t(
+ 'two_factor_authentication.two_factor_choice_options.configurations_added',
+ count: 1,
+ ),
+ )
+ end
end
end
diff --git a/yarn.lock b/yarn.lock
index d17005dfe14..5a23acd6a5b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2261,7 +2261,12 @@ chrome-trace-event@^1.0.2:
dependencies:
tslib "^1.9.0"
-classlist-polyfill@^1.0.3, classlist-polyfill@^1.2.0:
+classlist-polyfill@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.0.3.tgz#7cd5a9207c8d6932f592fdeaa6b45352ed71690d"
+ integrity sha512-bDLDUsSg5LYFWsc2hphtG6ulyaCFSupdEBU3wxNECKWHnyPVvY8EB9Wbt9DzWkstWclFZhDaZK/VnEK/DmqE/Q==
+
+classlist-polyfill@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e"
integrity sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4=
@@ -2815,7 +2820,7 @@ domhandler@^4.2.0, domhandler@^4.3.0:
dependencies:
domelementtype "^2.2.0"
-domready@^1.0.8:
+domready@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/domready/-/domready-1.0.8.tgz#91f252e597b65af77e745ae24dd0185d5e26d58c"
integrity sha1-kfJS5Ze2Wvd+dFriTdAYXV4m1Yw=
@@ -3786,13 +3791,13 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
-identity-style-guide@^6.4.1:
- version "6.4.1"
- resolved "https://registry.yarnpkg.com/identity-style-guide/-/identity-style-guide-6.4.1.tgz#d485e55ad188af86645a91f4dec33b0b9f1dcd10"
- integrity sha512-uSN+xpjf7QdeE09Yeym+F8TfEzmUxfkBRFltfKHq3VE4efg+lDPRWHpvIPfxDA2Z8coQ5m8giq7RJe1FDJRNfQ==
+identity-style-guide@^6.4.2:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/identity-style-guide/-/identity-style-guide-6.4.2.tgz#3efcc311132de24fbe37cada262d3f84c23e72fe"
+ integrity sha512-NUIFXpoKjVI+Pout3MPx9F0rZ1/dbiDomvecv/VOvZaebD0by2iWtJ6JGm00I5Mhg74G5ePCGjnJeVkD15L7AQ==
dependencies:
- domready "^1.0.8"
- uswds "^2.13.1"
+ domready "1.0.8"
+ uswds "^2.13.3"
ignore@^4.0.6:
version "4.0.6"
@@ -4745,7 +4750,7 @@ nwsapi@^2.2.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
-object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@4.1.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -5526,7 +5531,7 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
-receptor@^1.0.0:
+receptor@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/receptor/-/receptor-1.0.0.tgz#bf54477e0387e44bebf3855120bbda5adea08f8b"
integrity sha1-v1RHfgOH5Evr84VRILvaWt6gj4s=
@@ -5644,7 +5649,7 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-resolve-id-refs@^0.1.0:
+resolve-id-refs@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/resolve-id-refs/-/resolve-id-refs-0.1.0.tgz#3126624b887489da8fc0ae889632f8413ac6c3ec"
integrity sha1-MSZiS4h0idqPwK6IljL4QTrGw+w=
@@ -6500,16 +6505,16 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
-uswds@^2.13.1:
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/uswds/-/uswds-2.13.1.tgz#4a1d1cb88debacfb854edef4d946be87ee306e7b"
- integrity sha512-tl4bSB+0ejZw90gwqsbO5XA+1b9nnGdbvVhY4ARGKoTIJ7M8/vpUBoz+Tm327ATZI2LUWmoced5V986muq1Iew==
+uswds@^2.13.3:
+ version "2.13.3"
+ resolved "https://registry.yarnpkg.com/uswds/-/uswds-2.13.3.tgz#f2a0623b496941ff30ad3a0ea1610407d35a6b14"
+ integrity sha512-qCblljeaRvS3+PrSxoHqQwmMnp746+Y1YZA34BkTzJknvo2bhhdzGE21yJaInumzIqV3glLD13TFdRwrwikMMQ==
dependencies:
- classlist-polyfill "^1.0.3"
- domready "^1.0.8"
- object-assign "^4.1.1"
- receptor "^1.0.0"
- resolve-id-refs "^0.1.0"
+ classlist-polyfill "1.0.3"
+ domready "1.0.8"
+ object-assign "4.1.1"
+ receptor "1.0.0"
+ resolve-id-refs "0.1.0"
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"