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"