diff --git a/.rubocop.yml b/.rubocop.yml index c04b6c3133b..d5ac161138f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -74,7 +74,6 @@ IdentityIdp/ImageSizeLinter: - app/views/shared/_nav_branded.html.erb - app/views/sign_up/completions/show.html.erb - app/views/users/two_factor_authentication_setup/index.html.erb - - app/views/users/webauthn_setup/new.html.erb IdentityIdp/RedirectBackLinter: Enabled: true diff --git a/app/assets/images/mfa-options/security_key.svg b/app/assets/images/mfa-options/security_key.svg new file mode 100644 index 00000000000..25e8722f75d --- /dev/null +++ b/app/assets/images/mfa-options/security_key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/mfa-options/security_key_mobile.svg b/app/assets/images/mfa-options/security_key_mobile.svg new file mode 100644 index 00000000000..7433b38df38 --- /dev/null +++ b/app/assets/images/mfa-options/security_key_mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/security-key.svg b/app/assets/images/security-key.svg deleted file mode 100644 index 9d305f7f6d0..00000000000 --- a/app/assets/images/security-key.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/controllers/users/webauthn_setup_controller.rb b/app/controllers/users/webauthn_setup_controller.rb index 31ccc66063b..4f633ead285 100644 --- a/app/controllers/users/webauthn_setup_controller.rb +++ b/app/controllers/users/webauthn_setup_controller.rb @@ -15,6 +15,7 @@ class WebauthnSetupController < ApplicationController before_action :validate_existing_platform_authenticator helper_method :in_multi_mfa_selection_flow? + helper_method :mobile? def new form = WebauthnVisitForm.new( diff --git a/app/presenters/webauthn_setup_presenter.rb b/app/presenters/webauthn_setup_presenter.rb index 7fbcc3810d0..eba26a0ffc9 100644 --- a/app/presenters/webauthn_setup_presenter.rb +++ b/app/presenters/webauthn_setup_presenter.rb @@ -4,6 +4,7 @@ class WebauthnSetupPresenter < SetupPresenter include Rails.application.routes.url_helpers include ActionView::Helpers::UrlHelper include ActionView::Helpers::TranslationHelper + include LinkHelper attr_reader :url_options @@ -26,11 +27,18 @@ def initialize( @url_options = url_options end - def image_path - if @platform_authenticator - 'platform-authenticator.svg' - else - 'security-key.svg' + def learn_more_html + if !@platform_authenticator + new_tab_link_to( + t('forms.webauthn_setup.learn_more'), + help_center_redirect_path( + category: 'get-started', + article: 'authentication-options', + article_anchor: 'security-key', + flow: :two_factor_authentication, + step: :security_key_setup, + ), + ) end end @@ -71,7 +79,7 @@ def intro_html ), ) else - t('forms.webauthn_setup.intro_html') + t('forms.webauthn_setup.intro', app_name: APP_NAME) end end @@ -87,7 +95,7 @@ def button_text if @platform_authenticator t('forms.webauthn_platform_setup.continue') else - t('forms.webauthn_setup.continue') + t('forms.webauthn_setup.set_up') end end end diff --git a/app/views/users/webauthn_setup/new.html.erb b/app/views/users/webauthn_setup/new.html.erb index f0eee7961fc..90cb3d3c4f7 100644 --- a/app/views/users/webauthn_setup/new.html.erb +++ b/app/views/users/webauthn_setup/new.html.erb @@ -1,6 +1,8 @@ <% self.title = @presenter.page_title %> -<%= image_tag asset_url(@presenter.image_path), alt: '', width: '90', class: 'margin-left-1 margin-bottom-2', role: 'img' %> +<% if @platform_authenticator %> + <%= image_tag asset_url('platform-authenticator.svg'), alt: '', width: 84, height: 95, class: 'margin-left-1 margin-bottom-2', role: 'img' %> +<% end %> <%= render PageHeadingComponent.new.with_content(@presenter.heading) %> @@ -10,7 +12,10 @@ <% end %> <% end %> -<%= @presenter.intro_html %> + + <%= @presenter.intro_html %> + +<%= @presenter.learn_more_html unless @platform_authenticator %> <%= simple_form_for( '', @@ -33,20 +38,38 @@ <%= hidden_field_tag :platform_authenticator, @platform_authenticator, id: 'platform_authenticator' %> <% if !@platform_authenticator %> - - <%= render ValidatedFieldComponent.new( - form: f, - name: :name, - required: true, - label: @presenter.nickname_label, - hint: @presenter.device_nickname_hint, - input_html: { - id: 'nickname', - class: 'font-family-mono', - size: 16, - maxlength: 20, - }, - ) %> +
<% end %>Add a security key that meets the FIDO standard as your - authentication method. You can add as many security keys as you want. To - get started, first give your security key a nickname.
' + intro: Use your physical security key to add an additional layer of protection + to your %{app_name} account to prevent unauthorized access. + learn_more: Learn more about security keys nickname: Security key nickname saving: Saving your credentials … + set_up: Set up security key + step_1: Give it a nickname + step_1a: If you add more than one security key, you’ll know which one is which. + step_2: Insert a security key into your device + step_2_image_alt: A security key being inserted into the right side of a laptop + step_2_image_mobile_alt: A security key being inserted into the bottom of a smart phone + step_3: Set up your security key + step_3a: Click “set up security key” below and follow your browser’s instructions. diff --git a/config/locales/forms/es.yml b/config/locales/forms/es.yml index 95c932fb8f6..5530b4377f4 100644 --- a/config/locales/forms/es.yml +++ b/config/locales/forms/es.yml @@ -133,10 +133,18 @@ es: nickname_hint: Si agrega más dispositivos para desbloquear con la cara o con la huella digital, podrá distinguirlos. webauthn_setup: - continue: Continuar - intro_html: 'Añada una clave de seguridad que cumpla el estándar FIDO como - método de autenticación. Puede añadir tantas claves de seguridad como - desee. Para empezar, primero asigne un apodo a su clave de - seguridad.
' + intro: Utilice su clave de seguridad física para añadir un nivel adicional de + protección a su cuenta de %{app_name} y evitar accesos no autorizados. + learn_more: Obtenga información sobre claves de seguridad nickname: Apodo clave de seguridad saving: Guardando sus credenciales … + set_up: Configure su clave de seguridad + step_1: Darle un apodo + step_1a: Si añade más de una llave de seguridad, sabrá cuál es cuál. + step_2: Inserte una clave de seguridad en su dispositivo + step_2_image_alt: Una llave de seguridad insertada en el lado derecho de una + computadora portátil + step_2_image_mobile_alt: Una llave de seguridad insertada en la parte inferior de un celular + step_3: Configure su clave de seguridad + step_3a: Haga clic en “configurar clave de seguridad” (set up security key) más + abajo y siga las instrucciones de su navegador. diff --git a/config/locales/forms/fr.yml b/config/locales/forms/fr.yml index 08a3c3d74ce..d701464683e 100644 --- a/config/locales/forms/fr.yml +++ b/config/locales/forms/fr.yml @@ -138,10 +138,20 @@ fr: déverrouillage facial ou pour le déverrouillage tactile, vous saurez les reconnaître. webauthn_setup: - continue: Continuer - intro_html: 'Ajoutez une clé de sécurité conforme à la norme FIDO comme - méthode d’authentification. Vous pouvez ajouter autant de clés de - sécurité que vous le souhaitez. Pour commencer, donnez d’abord un surnom - à votre clé de sécurité.
' + intro: Utilisez votre clé de sécurité physique pour ajouter une couche de + protection supplémentaire à votre compte %{app_name} pour empêcher tout + accès non autorisé. + learn_more: En savoir plus sur les clés de sécurité nickname: Pseudo clé de sécurité saving: Enregistrement de vos informations d’identification … + set_up: Configurer votre clé de sécurité + step_1: Donnez-lui un surnom + step_1a: Si vous ajoutez plus d’une clé de sécurité, vous saurez reconnaître + chacune d’entre elles. + step_2: Insérer votre clé de sécurité + step_2_image_alt: Insertion d’une clé de sécurité dans le côté droit d’un + ordinateur portable + step_2_image_mobile_alt: Insertion d’une clé de sécurité dans le bas d’un téléphone intelligent + step_3: Configurer une clé de sécurité + step_3a: Cliquez sur « Configurer votre clé de sécurité » ci-dessous et suivez + les instructions de votre navigateur. diff --git a/config/locales/headings/en.yml b/config/locales/headings/en.yml index f88741dd91d..92bb4db275b 100644 --- a/config/locales/headings/en.yml +++ b/config/locales/headings/en.yml @@ -73,4 +73,4 @@ en: webauthn_platform_setup: new: Add face or touch unlock webauthn_setup: - new: Add your security key + new: Insert your security key diff --git a/config/locales/headings/es.yml b/config/locales/headings/es.yml index 4b98a646142..e951e3ab7af 100644 --- a/config/locales/headings/es.yml +++ b/config/locales/headings/es.yml @@ -73,4 +73,4 @@ es: webauthn_platform_setup: new: Desbloqueo facial o táctil webauthn_setup: - new: Añade tu clave de seguridad + new: Inserte su clave de seguridad diff --git a/config/locales/headings/fr.yml b/config/locales/headings/fr.yml index 7353464d25e..d59540f0ce9 100644 --- a/config/locales/headings/fr.yml +++ b/config/locales/headings/fr.yml @@ -76,4 +76,4 @@ fr: webauthn_platform_setup: new: Déverrouillage facial ou tactile webauthn_setup: - new: Ajoutez votre clé de sécurité + new: Insérer votre clé de sécurité diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb index f230f738759..f557fa8f315 100644 --- a/spec/controllers/users/webauthn_setup_controller_spec.rb +++ b/spec/controllers/users/webauthn_setup_controller_spec.rb @@ -2,6 +2,7 @@ RSpec.describe Users::WebauthnSetupController, allowed_extra_analytics: [:*] do include WebAuthnHelper + include UserAgentHelper describe 'before_actions' do it 'includes appropriate before_actions' do @@ -56,9 +57,20 @@ ) expect(@irs_attempts_api_tracker).not_to receive(:track_event) + expect(controller.send(:mobile?)).to be false get :new end + + context 'with a mobile device' do + it 'sets mobile to true' do + request.headers['User-Agent'] = mobile_user_agent + + get :new + expect(controller.send(:mobile?)).to be true + end + end + context 'when adding webauthn platform to existing user MFA methods' do it 'should set need_to_set_up_additional_mfa to false' do get :new, params: { platform: true } @@ -144,7 +156,18 @@ controller.user_session[:webauthn_challenge] = webauthn_challenge end - describe 'webauthn platform #new' do + describe '#new' do + context 'with a mobile device' do + let(:mfa_selections) { ['webauthn'] } + + it 'sets mobile to true' do + request.headers['User-Agent'] = mobile_user_agent + + get :new + expect(controller.send(:mobile?)).to be true + end + end + context 'when in account creation flow and selected multiple mfa' do let(:mfa_selections) { ['webauthn_platform', 'voice'] } before do @@ -169,6 +192,7 @@ get :new, params: { platform: true } additional_mfa_check = assigns(:need_to_set_up_additional_mfa) expect(additional_mfa_check).to be_truthy + expect(controller.send(:mobile?)).to be false end end @@ -249,6 +273,7 @@ context 'with a single MFA method chosen on account creation' do let(:mfa_selections) { ['webauthn_platform'] } + it 'should direct user to second mfa suggestion page' do patch :confirm, params: params diff --git a/spec/presenters/webauthn_setup_presenter_spec.rb b/spec/presenters/webauthn_setup_presenter_spec.rb index 8f1b36cd43e..6056c20cc9a 100644 --- a/spec/presenters/webauthn_setup_presenter_spec.rb +++ b/spec/presenters/webauthn_setup_presenter_spec.rb @@ -3,6 +3,7 @@ RSpec.describe WebauthnSetupPresenter do include Rails.application.routes.url_helpers include ActionView::Helpers::UrlHelper + include LinkHelper let(:user) { build(:user) } let(:user_fully_authenticated) { false } @@ -20,12 +21,6 @@ ) end - describe '#image_path' do - subject { presenter.image_path } - - it { is_expected.to eq('security-key.svg') } - end - describe '#page_title' do subject { presenter.page_title } @@ -41,7 +36,26 @@ describe '#intro_html' do subject { presenter.intro_html } - it { is_expected.to eq(t('forms.webauthn_setup.intro_html')) } + it { is_expected.to eq(t('forms.webauthn_setup.intro', app_name: APP_NAME)) } + end + + describe '#learn_more_html' do + subject { presenter.learn_more_html } + + it { + is_expected.to eq( + new_tab_link_to( + t('forms.webauthn_setup.learn_more'), + help_center_redirect_path( + category: 'get-started', + article: 'authentication-options', + article_anchor: 'security-key', + flow: :two_factor_authentication, + step: :security_key_setup, + ), + ), + ) + } end describe '#nickname_label' do @@ -59,18 +73,12 @@ describe '#button_text' do subject { presenter.button_text } - it { is_expected.to eq(t('forms.webauthn_setup.continue')) } + it { is_expected.to eq(t('forms.webauthn_setup.set_up')) } end context 'with platform_authenticator' do let(:platform_authenticator) { true } - describe '#image_path' do - subject { presenter.image_path } - - it { is_expected.to eq('platform-authenticator.svg') } - end - describe '#page_title' do subject { presenter.page_title } @@ -104,6 +112,12 @@ end end + describe '#learn_more_html' do + subject { presenter.learn_more_html } + + it { is_expected.to eq(nil) } + end + describe '#nickname_label' do subject { presenter.nickname_label } diff --git a/spec/support/features/webauthn_helper.rb b/spec/support/features/webauthn_helper.rb index 2800a436c60..45278d60a9f 100644 --- a/spec/support/features/webauthn_helper.rb +++ b/spec/support/features/webauthn_helper.rb @@ -2,6 +2,12 @@ module WebAuthnHelper include JavascriptDriverHelper include ActionView::Helpers::UrlHelper + def click_setup + if page.has_button?(t('forms.webauthn_setup.set_up')) + click_button t('forms.webauthn_setup.set_up') + end + end + def mock_webauthn_setup_challenge allow(WebAuthn::Credential).to receive(:options_for_create).and_return( instance_double( @@ -25,7 +31,7 @@ def fill_in_nickname_and_click_continue(nickname: 'mykey') end def mock_submit_without_pressing_button_on_hardware_key_on_setup - click_continue + click_setup end def mock_press_button_on_hardware_key_on_setup @@ -40,7 +46,7 @@ def mock_press_button_on_hardware_key_on_setup if javascript_enabled? page.evaluate_script('document.querySelector("form").submit()') else - click_continue + click_continue || click_setup end end diff --git a/spec/views/users/webauthn_setup/new.html.erb_spec.rb b/spec/views/users/webauthn_setup/new.html.erb_spec.rb index caeee4df434..d8477ef4f7d 100644 --- a/spec/views/users/webauthn_setup/new.html.erb_spec.rb +++ b/spec/views/users/webauthn_setup/new.html.erb_spec.rb @@ -21,6 +21,7 @@ allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:user_session).and_return(user_session) allow(view).to receive(:in_multi_mfa_selection_flow?).and_return(false) + allow(view).to receive(:mobile?).and_return(false) assign(:platform_authenticator, platform_authenticator) assign(:user_session, user_session) assign(:presenter, presenter) @@ -50,7 +51,7 @@ it 'does not displays info alert' do render - expect(rendered).to_not have_content(I18n.t('forms.webauthn_platform_setup.info_text')) + expect(rendered).to_not have_content(t('forms.webauthn_platform_setup.info_text')) end end @@ -62,7 +63,7 @@ it 'displays info alert' do render - expect(rendered).to have_content(I18n.t('forms.webauthn_platform_setup.info_text')) + expect(rendered).to have_content(t('forms.webauthn_platform_setup.info_text')) end end @@ -74,18 +75,85 @@ it 'does not displays info alert' do render - expect(rendered).to_not have_content(I18n.t('forms.webauthn_platform_setup.info_text')) + expect(rendered).to_not have_content(t('forms.webauthn_platform_setup.info_text')) end end end context 'non-platform webauthn' do let(:platform_authenticator) { false } + it 'displays the form' do render + expect(rendered).to have_content( - t('two_factor_authentication.two_factor_choice_options.webauthn'), + t('two_factor_authentication.two_factor_choice_options.webauthn').downcase, + ) + end + + it 'links to help screen' do + render + + expect(rendered).to have_link( + t('forms.webauthn_setup.learn_more'), + href: help_center_redirect_path( + category: 'get-started', + article: 'authentication-options', + article_anchor: 'security-key', + flow: :two_factor_authentication, + step: :security_key_setup, + ), ) end + + it 'displays the step 1 heading' do + render + + expect(rendered).to have_css('h2', text: t('forms.webauthn_setup.step_1')) + end + + it 'displays the step 2 heading' do + render + + expect(rendered).to have_css('h2', text: t('forms.webauthn_setup.step_2')) + end + + it 'displays the step 3 heading' do + render + + expect(rendered).to have_css('h2', text: t('forms.webauthn_setup.step_3')) + end + + it 'displays the nickname input field' do + render + + expect(rendered).to have_selector("input#nickname[type='text']") + end + + it 'displays form submission button' do + render + + expect(rendered).to have_button(t('forms.webauthn_setup.set_up')) + end + + describe 'security key image' do + it 'displays the security key image' do + render + + expect(rendered).to match(/src=".*security_key-.*\.svg"/) + end + + context 'when on a mobile device' do + before do + allow(view).to receive(:mobile?).and_return(true) + end + + it 'displays the mobile security key image' do + render + + expect(rendered).to match(/src=".*security_key_mobile-.*\.svg"/) + end + end + end end end