From c891fccb8e3edb237ddbdf7d5286ff1127151f38 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:54:33 -0400 Subject: [PATCH 01/17] Use CSV.parse_line for config CSV parse (#10358) changelog: Internal, Configuration, Improve CSV parsing for configuration values Co-authored-by: Zach Margolis --- lib/identity_config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 294d334427d..aaf25e09e66 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -30,7 +30,7 @@ class << self end, symbol: proc { |value| value.to_sym }, comma_separated_string_list: proc do |value| - value.parse_csv.to_a + CSV.parse_line(value).to_a end, integer: proc do |value| Integer(value) From d880c8f6ae06220a7525859dfb398d4a9a1cdc25 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Wed, 3 Apr 2024 15:33:20 -0500 Subject: [PATCH 02/17] Remove Doc Auth AB Test Fallbacks (#10356) changelog: Internal, AB Tests, Remove Doc Auth Fallbacks --- app/services/doc_auth_router.rb | 11 ++----- spec/services/doc_auth_router_spec.rb | 45 --------------------------- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/app/services/doc_auth_router.rb b/app/services/doc_auth_router.rb index 29e615654ef..38331817ffd 100644 --- a/app/services/doc_auth_router.rb +++ b/app/services/doc_auth_router.rb @@ -199,18 +199,11 @@ def self.client(vendor_discriminator: nil, warn_notifier: nil, analytics: nil) def self.doc_auth_vendor(discriminator: nil, analytics: nil) case AbTests::DOC_AUTH_VENDOR.bucket(discriminator) when :alternate_vendor - vendor = IdentityConfig.store.doc_auth_vendor_randomize_alternate_vendor + IdentityConfig.store.doc_auth_vendor_randomize_alternate_vendor else analytics&.idv_doc_auth_randomizer_defaulted if discriminator.blank? - vendor = IdentityConfig.store.doc_auth_vendor + IdentityConfig.store.doc_auth_vendor end - - # if vendor is not set to mock and selfie enabled use lexisnexis - if FeatureManagement.idv_allow_selfie_check? && - vendor != Idp::Constants::Vendors::MOCK - vendor = Idp::Constants::Vendors::LEXIS_NEXIS - end - vendor end end diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb index bd366c23e49..79938a61654 100644 --- a/spec/services/doc_auth_router_spec.rb +++ b/spec/services/doc_auth_router_spec.rb @@ -72,32 +72,6 @@ def reload_ab_test_initializer! expect(result).to eq(doc_auth_vendor) end - - context 'when selfie is enabled' do - before do - expect(FeatureManagement).to receive(:idv_allow_selfie_check?).at_least(:once). - and_return(true) - end - context 'when vendor is not set to mock' do - it 'chose lexisnexis' do - result = DocAuthRouter.doc_auth_vendor( - discriminator: discriminator, - analytics: analytics, - ) - expect(result).to eq(Idp::Constants::Vendors::LEXIS_NEXIS) - end - end - context 'when vendor is set to mock' do - let(:doc_auth_vendor) { Idp::Constants::Vendors::MOCK } - it 'stays with the mock' do - result = DocAuthRouter.doc_auth_vendor( - discriminator: discriminator, - analytics: analytics, - ) - expect(result).to eq(Idp::Constants::Vendors::MOCK) - end - end - end end context 'with a discriminator that hashes inside the test group' do @@ -112,25 +86,6 @@ def reload_ab_test_initializer! to eq(doc_auth_vendor_randomize_alternate_vendor) end - context 'with selfie enabled' do - before do - expect(FeatureManagement).to receive(:idv_allow_selfie_check?).at_least(:once). - and_return(true) - end - it 'is the lexisnexis vendor' do - expect(DocAuthRouter.doc_auth_vendor(discriminator: discriminator)). - to eq(Idp::Constants::Vendors::LEXIS_NEXIS) - end - - context 'when alternate is set to mock' do - let(:doc_auth_vendor_randomize_alternate_vendor) { Idp::Constants::Vendors::MOCK } - it 'stays with the mock vendor' do - expect(DocAuthRouter.doc_auth_vendor(discriminator: discriminator)). - to eq(Idp::Constants::Vendors::MOCK) - end - end - end - context 'with randomize false' do let(:doc_auth_vendor_randomize) { false } From 0b2842df148da9096482a5aa336f4686045d1988 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 4 Apr 2024 08:51:17 -0400 Subject: [PATCH 03/17] LG-12294: Send aggregated email notification for new device sign-in (#10314) * LG-12294: Send aggregated email notification for new device sign-in changelog: Upcoming Features, Sign In, Send single aggregated email notification for new device sign-in * Localize to geocoded timezone if available * Move event grouping logic to mailer class See: https://github.com/18F/identity-idp/pull/10314/files#r1540041239 Co-authored-by: Zach Margolis * Memoize user object for user mailer preview * Use EasternTimePresenter for timestamp * Linkify authentication methods * Update "potentially located in" language https://gsa-tts.slack.com/archives/C05R6BLVAQG/p1711549883747499?thread_ts=1711476471.048729&cid=C05R6BLVAQG * Add English string for sign_in_unsuccessful_2fa event * Track event sign_in_unsuccessful_2fa * Incorporate "failed X times" text * Revert to uswds-core Original change was trying to resolve Sass error, fixed by reinstalling dependencies. Importing uswds-core ensures other overrides defined in stylesheets/_uswds-core.scss are applied * Split separate mailers for sign in before/after 2fa The two content are more distinct than initially expected See: https://github.com/18F/identity-idp/pull/10314/files#r1548593213 * Annotate used dynamic strings * Add translations * Interpolate app_name * Normalize YAML * Sync preview name with mailer name * Update French translation for failed times --------- Co-authored-by: Zach Margolis --- app/assets/stylesheets/email.css.scss | 36 ++++++++++- .../two_factor_authenticatable_methods.rb | 8 +++ .../backup_code_verification_controller.rb | 1 + .../webauthn_verification_controller.rb | 1 + .../users/piv_cac_login_controller.rb | 1 + app/mailers/user_mailer.rb | 31 +++++++++ app/models/event.rb | 1 + app/services/marketing_site.rb | 1 + .../new_device_sign_in_after_2fa.html.erb | 26 ++++++++ .../new_device_sign_in_before_2fa.html.erb | 19 ++++++ .../_new_device_sign_in_attempts.html.erb | 26 ++++++++ config/locales/event_types/en.yml | 1 + config/locales/event_types/es.yml | 1 + config/locales/event_types/fr.yml | 1 + config/locales/user_mailer/en.yml | 26 ++++++++ config/locales/user_mailer/es.yml | 29 +++++++++ config/locales/user_mailer/fr.yml | 29 +++++++++ ...ackup_code_verification_controller_spec.rb | 6 ++ .../otp_verification_controller_spec.rb | 20 ++++-- ...rsonal_key_verification_controller_spec.rb | 6 ++ .../piv_cac_verification_controller_spec.rb | 28 ++++++-- .../totp_verification_controller_spec.rb | 23 +++++-- .../webauthn_verification_controller_spec.rb | 1 + .../users/piv_cac_login_controller_spec.rb | 11 +++- spec/mailers/previews/user_mailer_preview.rb | 64 ++++++++++++++++++- 25 files changed, 377 insertions(+), 20 deletions(-) create mode 100644 app/views/user_mailer/new_device_sign_in_after_2fa.html.erb create mode 100644 app/views/user_mailer/new_device_sign_in_before_2fa.html.erb create mode 100644 app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb diff --git a/app/assets/stylesheets/email.css.scss b/app/assets/stylesheets/email.css.scss index 2015a1661d0..38d0dfc7a89 100644 --- a/app/assets/stylesheets/email.css.scss +++ b/app/assets/stylesheets/email.css.scss @@ -1,4 +1,7 @@ -@use 'uswds-core' as *; +@use 'uswds-core' as * with ( + $theme-table-border-color: 'base-lighter', + $theme-table-header-background-color: 'base-lightest' +); @use 'variables/app' as *; @use 'variables/email' as *; @@ -198,6 +201,10 @@ h6 { @include u-font('sans', 'md'); } +.font-family-mono { + font-family: monospace; +} + .margin-bottom-0 { @include u-margin-bottom(0); } @@ -269,3 +276,30 @@ h6 { @extend %usa-list-item; } } + +.usa-table { + @include usa-table; + + border-collapse: separate; + border-spacing: 0; + + tbody td { + border-top: 0; + } + + thead th:first-child { + border-top-left-radius: units(0.5); + } + + thead th:last-child { + border-top-right-radius: units(0.5); + } + + tbody tr:last-child td:first-child { + border-bottom-left-radius: units(0.5); + } + + tbody tr:last-child td:last-child { + border-bottom-right-radius: units(0.5); + } +} diff --git a/app/controllers/concerns/two_factor_authenticatable_methods.rb b/app/controllers/concerns/two_factor_authenticatable_methods.rb index c0b778b283a..a7e37560ef0 100644 --- a/app/controllers/concerns/two_factor_authenticatable_methods.rb +++ b/app/controllers/concerns/two_factor_authenticatable_methods.rb @@ -100,6 +100,10 @@ def handle_remember_device_preference(remember_device_preference) # You can pass in any "type" with a corresponding I18n key in # two_factor_authentication.invalid_#{type} def handle_invalid_otp(type:, context: nil) + if context == UserSessionContext::AUTHENTICATION_CONTEXT + handle_invalid_verification_for_authentication_context + end + update_invalid_user flash.now[:error] = invalid_otp_error(type) @@ -150,6 +154,10 @@ def update_invalid_user current_user.increment_second_factor_attempts_count! end + def handle_invalid_verification_for_authentication_context + create_user_event(:sign_in_unsuccessful_2fa) + end + def handle_valid_verification_for_confirmation_context(auth_method:) mark_user_session_authenticated(auth_method:, authentication_type: :valid_2fa_confirmation) reset_second_factor_attempts_count diff --git a/app/controllers/two_factor_authentication/backup_code_verification_controller.rb b/app/controllers/two_factor_authentication/backup_code_verification_controller.rb index 19ad610ec59..4e8c05c55e3 100644 --- a/app/controllers/two_factor_authentication/backup_code_verification_controller.rb +++ b/app/controllers/two_factor_authentication/backup_code_verification_controller.rb @@ -52,6 +52,7 @@ def presenter_for_two_factor_authentication_method end def handle_invalid_backup_code + handle_invalid_verification_for_authentication_context update_invalid_user flash.now[:error] = t('two_factor_authentication.invalid_backup_code') diff --git a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb index 9ef3e53ed32..57072bccfc6 100644 --- a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb +++ b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb @@ -70,6 +70,7 @@ def handle_valid_webauthn end def handle_invalid_webauthn(result) + handle_invalid_verification_for_authentication_context flash[:error] = result.first_error_message if platform_authenticator? diff --git a/app/controllers/users/piv_cac_login_controller.rb b/app/controllers/users/piv_cac_login_controller.rb index 7daf4dc762d..13fb27a9ad0 100644 --- a/app/controllers/users/piv_cac_login_controller.rb +++ b/app/controllers/users/piv_cac_login_controller.rb @@ -91,6 +91,7 @@ def next_step end def process_invalid_submission + handle_invalid_verification_for_authentication_context session[:needs_to_setup_piv_cac_after_sign_in] = true if piv_cac_login_form.valid_token? process_token_with_error diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 2d93d530235..d51013d7531 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -138,6 +138,37 @@ def new_device_sign_in(date:, location:, device_name:, disavowal_token:) end end + # @param [Array] events Array of sign-in Event records (event types "sign_in_before_2fa", + # "sign_in_after_2fa", "sign_in_unsuccessful_2fa") + # @param [String] disavowal_token Token to generate URL for disavowing event + def new_device_sign_in_after_2fa(events:, disavowal_token:) + with_user_locale(user) do + @events = events + @disavowal_token = disavowal_token + + mail( + to: email_address.email, + subject: t('user_mailer.new_device_sign_in_after_2fa.subject', app_name: APP_NAME), + ) + end + end + + # @param [Array] events Array of sign-in Event records (event types "sign_in_before_2fa", + # "sign_in_after_2fa", "sign_in_unsuccessful_2fa") + # @param [String] disavowal_token Token to generate URL for disavowing event + def new_device_sign_in_before_2fa(events:, disavowal_token:) + with_user_locale(user) do + @events = events + @disavowal_token = disavowal_token + @failed_times = events.count { |event| event.event_type == 'sign_in_unsuccessful_2fa' } + + mail( + to: email_address.email, + subject: t('user_mailer.new_device_sign_in_before_2fa.subject', app_name: APP_NAME), + ) + end + end + def personal_key_regenerated with_user_locale(user) do mail(to: email_address.email, subject: t('user_mailer.personal_key_regenerated.subject')) diff --git a/app/models/event.rb b/app/models/event.rb index 13c07d6bdd4..596bf9d74ca 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -27,6 +27,7 @@ class Event < ApplicationRecord email_deleted: 20, phone_added: 21, password_invalidated: 22, + sign_in_unsuccessful_2fa: 23, } validates :event_type, presence: true diff --git a/app/services/marketing_site.rb b/app/services/marketing_site.rb index 42b9c7be9bf..b3eb7655839 100644 --- a/app/services/marketing_site.rb +++ b/app/services/marketing_site.rb @@ -7,6 +7,7 @@ class UnknownArticleException < StandardError; end HELP_CENTER_ARTICLES = %w[ get-started/authentication-options + manage-your-account/add-or-change-your-authentication-method manage-your-account/personal-key trouble-signing-in/face-or-touch-unlock verify-your-identity/accepted-identification-documents diff --git a/app/views/user_mailer/new_device_sign_in_after_2fa.html.erb b/app/views/user_mailer/new_device_sign_in_after_2fa.html.erb new file mode 100644 index 00000000000..a1d9f81298a --- /dev/null +++ b/app/views/user_mailer/new_device_sign_in_after_2fa.html.erb @@ -0,0 +1,26 @@ +

+ <%= t('user_mailer.new_device_sign_in_after_2fa.info_p1', app_name: APP_NAME) %> +

+ +

+ <%= t('user_mailer.new_device_sign_in_after_2fa.info_p2') %> +

+ +

+ <%= t( + 'user_mailer.new_device_sign_in_after_2fa.info_p3_html', + reset_password_link_html: link_to( + t('user_mailer.new_device_sign_in_after_2fa.reset_password'), + event_disavowal_url(disavowal_token: @disavowal_token), + ), + authentication_methods_link_html: link_to( + t('user_mailer.new_device_sign_in_after_2fa.authentication_methods'), + MarketingSite.help_center_article_url( + category: 'manage-your-account', + article: 'add-or-change-your-authentication-method', + ), + ), + ) %> +

+ +<%= render 'user_mailer/shared/new_device_sign_in_attempts' %> diff --git a/app/views/user_mailer/new_device_sign_in_before_2fa.html.erb b/app/views/user_mailer/new_device_sign_in_before_2fa.html.erb new file mode 100644 index 00000000000..e7f59bcd4f4 --- /dev/null +++ b/app/views/user_mailer/new_device_sign_in_before_2fa.html.erb @@ -0,0 +1,19 @@ +

+ <%= t('user_mailer.new_device_sign_in_before_2fa.info_p1_html', count: @failed_times, app_name: APP_NAME) %> +

+ +

+ <%= t('user_mailer.new_device_sign_in_before_2fa.info_p2') %> +

+ +

+ <%= t( + 'user_mailer.new_device_sign_in_before_2fa.info_p3_html', + reset_password_link_html: link_to( + t('user_mailer.new_device_sign_in_before_2fa.reset_password'), + event_disavowal_url(disavowal_token: @disavowal_token), + ), + ) %> +

+ +<%= render 'user_mailer/shared/new_device_sign_in_attempts' %> diff --git a/app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb b/app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb new file mode 100644 index 00000000000..09a5fb83791 --- /dev/null +++ b/app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb @@ -0,0 +1,26 @@ +<% @events.group_by { |event| IpGeocoder.new(event.device.last_ip).location }.each do |location, events| %> +
+ + + + + + + + <% events.each do |event| %> + + + + <% end %> + +
+ <%= t('user_mailer.new_device_sign_in_attempts.new_sign_in_from', location:) %> +
+ <%# i18n-tasks-use t('user_mailer.new_device_sign_in_attempts.events.sign_in_after_2fa') %> + <%# i18n-tasks-use t('user_mailer.new_device_sign_in_attempts.events.sign_in_before_2fa') %> + <%# i18n-tasks-use t('user_mailer.new_device_sign_in_attempts.events.sign_in_unsuccessful_2fa') %> + <%= t(event.event_type, scope: [:user_mailer, :new_device_sign_in_attempts, :events]) %>
+ <%= EasternTimePresenter.new(event.created_at) %> +
+
+<% end %> diff --git a/config/locales/event_types/en.yml b/config/locales/event_types/en.yml index d56e1d17811..cfa6adea444 100644 --- a/config/locales/event_types/en.yml +++ b/config/locales/event_types/en.yml @@ -24,5 +24,6 @@ en: piv_cac_enabled: PIV/CAC card associated sign_in_after_2fa: Signed in with second factor sign_in_before_2fa: Signed in with password + sign_in_unsuccessful_2fa: Failed to authenticate webauthn_key_added: Hardware security key added webauthn_key_removed: Hardware security key removed diff --git a/config/locales/event_types/es.yml b/config/locales/event_types/es.yml index 628e54848fa..727f2581fd8 100644 --- a/config/locales/event_types/es.yml +++ b/config/locales/event_types/es.yml @@ -24,5 +24,6 @@ es: piv_cac_enabled: Tarjeta PIV/CAC asociada sign_in_after_2fa: Inicia sesión con segundo factor sign_in_before_2fa: Inicia sesión con contraseña + sign_in_unsuccessful_2fa: Error al autenticar webauthn_key_added: Clave de seguridad de hardware añadido webauthn_key_removed: Clave de seguridad de hardware eliminada diff --git a/config/locales/event_types/fr.yml b/config/locales/event_types/fr.yml index 3eca7777284..568c692b19b 100644 --- a/config/locales/event_types/fr.yml +++ b/config/locales/event_types/fr.yml @@ -24,5 +24,6 @@ fr: piv_cac_enabled: Carte PIV/CAC associée sign_in_after_2fa: Signé avec deuxième facteur sign_in_before_2fa: Connecté avec mot de passe + sign_in_unsuccessful_2fa: Échec de l’authentification webauthn_key_added: Clé de sécurité ajoutée webauthn_key_removed: Clé de sécurité retirée diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index d2cf1b14368..0803ee4201d 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -219,6 +219,32 @@ en: %{contact_link_html}. info: 'Your %{app_name} account was just used to sign in on a new device.' subject: New sign-in with your %{app_name} account + new_device_sign_in_after_2fa: + authentication_methods: authentication methods + info_p1: Your %{app_name} email and password were used to sign-in and + authenticate on a new device. + info_p2: If you recognize this activity, you don’t need to do anything. + info_p3_html: If this wasn’t you, %{reset_password_link_html} and change your + %{authentication_methods_link_html} immediately. + reset_password: reset your password + subject: New sign-in and authentication with your %{app_name} account + new_device_sign_in_attempts: + events: + sign_in_after_2fa: Authenticated + sign_in_before_2fa: Signed in with password + sign_in_unsuccessful_2fa: Failed to authenticate + new_sign_in_from: New sign-in potentially located in %{location} + new_device_sign_in_before_2fa: + info_p1_html: + one: Your %{app_name} email and password were used to sign in from a new device + but failed to authenticate. + other: Your %{app_name} email and password were used to sign in from a new + device but failed to authenticate %{count} times + info_p2: If you recognize this activity, you don’t need to do anything. + info_p3_html: Two-factor authentication protects your account from unauthorized + access. If this wasn’t you, %{reset_password_link_html} immediately. + reset_password: reset your password + subject: New sign-in with your %{app_name} account password_changed: disavowal_link: reset your password help_html: If you did not make this change, %{disavowal_link_html}. For more diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index b7c372edafb..bed4f08e957 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -232,6 +232,35 @@ es: visite el %{app_name_html} %{help_link_html} o el %{contact_link_html}. info: 'Su cuenta %{app_name} acaba de iniciar sesión en un nuevo dispositivo.' subject: Nuevo initio de sesion con su %{app_name} cuenta + new_device_sign_in_after_2fa: + authentication_methods: métodos de autenticación + info_p1: Su correo electrónico y su contraseña de %{app_name} se usaron para + iniciar sesión y para la autenticación desde un nuevo dispositivo. + info_p2: Si reconoce esta actividad, no tiene que hacer nada. + info_p3_html: Si no fue usted, %{reset_password_link_html} y cambie sus + %{authentication_methods_link_html} inmediatamente. + reset_password: restablezca la contraseña + subject: Nuevo inicio de sesión y autenticación con su cuenta de %{app_name} + new_device_sign_in_attempts: + events: + sign_in_after_2fa: Autenticado + sign_in_before_2fa: Inicia sesión con contraseña + sign_in_unsuccessful_2fa: Error al autenticar + new_sign_in_from: Nuevo inicio de sesión potencialmente ubicado en %{location} + new_device_sign_in_before_2fa: + info_p1_html: + one: Su correo electrónico y su contraseña de %{app_name} se usaron para + ingresar desde un nuevo dispositivo, pero la autenticación dio + error. + other: Su correo electrónico y su contraseña de %{app_name} se usaron para + ingresar desde un nuevo dispositivo, pero error al autenticar + %{count} veces + info_p2: Si reconoce esta actividad, no tiene que hacer nada. + info_p3_html: La autenticación de dos factores protege su cuenta de accesos no + autorizados. Si no fue usted, %{reset_password_link_html} + inmediatamente. + reset_password: restablezca la contraseña + subject: Nuevo inicio de sesión con su cuenta de %{app_name} password_changed: disavowal_link: restablecer su contraseña help_html: Si no realizó este cambio, %{disavowal_link_html}. Para más ayuda, diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index b52f7806e2d..6eefb89ad04 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -238,6 +238,35 @@ fr: %{app_name_html} ou %{contact_link_html}. info: 'Votre compte %{app_name} a été connecté sur un nouvel appareil.' subject: Nouvelle connexion avec votre compte %{app_name} + new_device_sign_in_after_2fa: + authentication_methods: méthodes d’authentification + info_p1: Votre adresse e-mail et votre mot de passe %{app_name} ont été utilisés + pour se connecter et s’authentifier sur un nouvel appareil. + info_p2: Si vous reconnaissez cette activité, vous n’avez rien à faire. + info_p3_html: Si ce n’est pas vous, %{reset_password_link_html} et modifiez + immédiatement vos %{authentication_methods_link_html}. + reset_password: réinitialisez votre mot de passe + subject: Nouvelle connexion et authentification avec votre compte %{app_name} + new_device_sign_in_attempts: + events: + sign_in_after_2fa: Signé avec deuxième facteur + sign_in_before_2fa: Connecté avec mot de passe + sign_in_unsuccessful_2fa: Échec de l’authentification + new_sign_in_from: Nouvelle connexion potentiellement située à %{location} + new_device_sign_in_before_2fa: + info_p1_html: + one: Votre adresse électronique et votre mot de passe %{app_name} ont été + utilisés pour vous connecter à partir d’un nouvel appareil, mais + l’authentification a échoué. + other: Votre adresse électronique et votre mot de passe %{app_name} ont été + utilisés pour vous connecter à partir d’un nouvel appareil, mais + l’authentification a échoué %{count} reprises. + info_p2: Si vous reconnaissez cette activité, vous n’avez rien à faire. + info_p3_html: L’authentification à deux facteurs protège votre compte contre + tout accès non autorisé. Si ce n’est pas vous, + %{reset_password_link_html}. + reset_password: réinitialisez immédiatement votre mot de passe + subject: Nouvelle connexion avec votre compte %{app_name} password_changed: disavowal_link: réinitialiser votre mot de passe help_html: Si vous n’avez pas effectué ce changement, %{disavowal_link_html}. diff --git a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb index 8df4dc200b2..5d353308aff 100644 --- a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb @@ -213,6 +213,12 @@ post :create, params: payload end + + it 'records unsuccessful 2fa event' do + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) + + post :create, params: payload + end end end end diff --git a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb index 380284b7f4a..56c9d199f4e 100644 --- a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb @@ -122,8 +122,10 @@ end describe '#create' do - let(:parsed_phone) { Phonelib.parse(subject.current_user.default_phone_configuration.phone) } + let(:parsed_phone) { Phonelib.parse(controller.current_user.default_phone_configuration.phone) } context 'when the user enters an invalid OTP during authentication context' do + subject(:response) { post :create, params: { code: '12345', otp_delivery_preference: 'sms' } } + before do sign_in_before_2fa controller.user_session[:mfa_selections] = ['sms'] @@ -155,13 +157,11 @@ expect(@irs_attempts_api_tracker).to receive(:mfa_login_phone_otp_submitted). with({ reauthentication: false, success: false }) - - post :create, params: - { code: '12345', - otp_delivery_preference: 'sms' } end it 'increments second_factor_attempts_count' do + response + expect(controller.current_user.reload.second_factor_attempts_count).to eq 1 end @@ -170,13 +170,23 @@ end it 'displays flash error message' do + response + expect(flash[:error]).to eq t('two_factor_authentication.invalid_otp') end it 'does not set auth_method and requires 2FA' do + response + expect(controller.user_session[:auth_events]).to eq nil expect(controller.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq true end + + it 'records unsuccessful 2fa event' do + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) + + response + end end context 'when the user enters an invalid OTP during reauthentication context' do diff --git a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb index 9ec58b054c0..a10da783e3f 100644 --- a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb @@ -233,6 +233,12 @@ post :create, params: payload end + + it 'records unsuccessful 2fa event' do + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) + + post :create, params: payload + end end it 'does not generate a new personal key if the user enters an invalid key' do diff --git a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb index a57eba0bd01..9612e4f5a08 100644 --- a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb @@ -19,7 +19,7 @@ before(:each) do session_info = { piv_cac_nonce: nonce } - allow(subject).to receive(:user_session).and_return(session_info) + allow(controller).to receive(:user_session).and_return(session_info) allow(PivCacService).to receive(:decode_token).with('good-token').and_return( 'uuid' => user.piv_cac_configurations.first.x509_dn_uuid, 'subject' => x509_subject, @@ -163,14 +163,16 @@ end context 'when the user presents an invalid piv/cac' do + subject(:response) { get :show, params: { token: 'bad-token' } } + before do stub_sign_in_before_2fa(user) - - get :show, params: { token: 'bad-token' } end it 'increments second_factor_attempts_count' do - expect(subject.current_user.reload.second_factor_attempts_count).to eq 1 + response + + expect(controller.current_user.reload.second_factor_attempts_count).to eq 1 end it 'redirects to the piv/cac entry screen' do @@ -178,16 +180,28 @@ end it 'displays flash error message' do + response + expect(flash[:error]).to eq t('two_factor_authentication.invalid_piv_cac') end it 'resets the piv/cac session information' do - expect(subject.user_session[:decrypted_x509]).to be_nil + response + + expect(controller.user_session[:decrypted_x509]).to be_nil end it 'does not set auth_method and requires 2FA' do - expect(subject.user_session[:auth_events]).to eq nil - expect(subject.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq true + response + + expect(controller.user_session[:auth_events]).to eq nil + expect(controller.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq true + end + + it 'records unsuccessful 2fa event' do + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) + + response end end diff --git a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb index bfe42ca4446..ea71d27e16e 100644 --- a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb @@ -111,16 +111,19 @@ end context 'when the user enters an invalid TOTP' do + subject(:response) { post :create, params: { code: 'abc' } } + before do sign_in_before_2fa - user = subject.current_user + user = controller.current_user @secret = user.generate_totp_secret Db::AuthAppConfiguration.create(user, @secret, nil, 'foo') - post :create, params: { code: 'abc' } end it 'increments second_factor_attempts_count' do - expect(subject.current_user.reload.second_factor_attempts_count).to eq 1 + response + + expect(controller.current_user.reload.second_factor_attempts_count).to eq 1 end it 're-renders the TOTP entry screen' do @@ -128,12 +131,22 @@ end it 'displays flash error message' do + response + expect(flash[:error]).to eq t('two_factor_authentication.invalid_otp') end it 'does not set auth_method and still requires 2FA' do - expect(subject.user_session[:auth_events]).to eq nil - expect(subject.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq true + response + + expect(controller.user_session[:auth_events]).to eq nil + expect(controller.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq true + end + + it 'records unsuccessful 2fa event' do + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) + + response end end diff --git a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb index 668b48b6eb2..4e0940a508d 100644 --- a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb @@ -266,6 +266,7 @@ new_device: nil } expect(@analytics).to receive(:track_mfa_submit_event). with(result) + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) patch :confirm, params: params end diff --git a/spec/controllers/users/piv_cac_login_controller_spec.rb b/spec/controllers/users/piv_cac_login_controller_spec.rb index 4167e3b92a0..9ed2668a552 100644 --- a/spec/controllers/users/piv_cac_login_controller_spec.rb +++ b/spec/controllers/users/piv_cac_login_controller_spec.rb @@ -22,8 +22,11 @@ let(:token) { 'TEST:abcdefg' } context 'an invalid token' do - before { get :new, params: { token: token } } + subject(:response) { get :new, params: { token: token } } + it 'tracks the login attempt' do + response + expect(@analytics).to have_logged_event( :piv_cac_login, errors: {}, @@ -35,6 +38,12 @@ it 'redirects to the error url' do expect(response).to redirect_to(login_piv_cac_error_url(error: 'token.bad')) end + + it 'records unsuccessful 2fa event' do + expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) + + response + end end context 'with a valid token' do diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index c39f6e1b2b7..7c1ad4a4a2b 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -64,6 +64,56 @@ def new_device_sign_in ) end + def new_device_sign_in_after_2fa + UserMailer.with(user: user, email_address: email_address_record).new_device_sign_in_after_2fa( + events: [ + unsaveable( + Event.new( + event_type: :sign_in_before_2fa, + created_at: Time.zone.now - 2.minutes, + user:, + device: user.devices.first, + ), + ), + unsaveable( + Event.new( + event_type: :sign_in_after_2fa, + created_at: Time.zone.now, + user:, + device: user.devices.first, + ), + ), + ], + disavowal_token: SecureRandom.hex, + ) + end + + def new_device_sign_in_before_2fa + UserMailer.with(user: user, email_address: email_address_record).new_device_sign_in_before_2fa( + events: [ + unsaveable( + Event.new( + event_type: :sign_in_before_2fa, + created_at: Time.zone.now - 2.minutes, + user:, + device: user.devices.first, + ), + ), + *Array.new((params['failed_times'] || 1).to_i) do + unsaveable( + Event.new( + event_type: :sign_in_unsuccessful_2fa, + created_at: Time.zone.now, + user:, + device: user.devices.first, + ), + ) + end, + ], + disavowal_token: SecureRandom.hex, + ) + end + def personal_key_regenerated UserMailer.with(user: user, email_address: email_address_record).personal_key_regenerated end @@ -222,7 +272,19 @@ def account_reinstated private def user - unsaveable(User.new(email_addresses: [email_address_record])) + @user ||= unsaveable( + User.new( + email_addresses: [email_address_record], + devices: [ + unsaveable( + Device.new( + user_agent: Faker::Internet.user_agent, + last_ip: Faker::Internet.ip_v4_address, + ), + ), + ], + ), + ) end def user_with_pending_gpo_letter From 8d02c7f418c039624cb871b0078d22f6bc3741e7 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 4 Apr 2024 08:44:36 -0500 Subject: [PATCH 04/17] Consolidate identity verification accessibility tests to improve test speed (#10359) changelog: Internal, Testing, Consolidate identity verification accessibility tests to improve test speed --- spec/features/accessibility/idv_pages_spec.rb | 42 +------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb index 79e006a21d0..85087f1fe33 100644 --- a/spec/features/accessibility/idv_pages_spec.rb +++ b/spec/features/accessibility/idv_pages_spec.rb @@ -9,14 +9,6 @@ create(:service_provider, :active, :in_person_proofing_enabled) end - scenario 'home page' do - sign_in_and_2fa_user - - visit idv_path - - expect_page_to_have_no_accessibility_violations(page) - end - scenario 'how to verify page' do allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) allow(IdentityConfig.store).to receive(:in_person_proofing_opt_in_enabled).and_return(true) @@ -32,47 +24,15 @@ expect_page_to_have_no_accessibility_violations(page) end - scenario 'cancel idv' do + scenario 'doc auth steps accessibility' do sign_in_and_2fa_user visit idv_cancel_path - expect(current_path).to eq idv_cancel_path expect_page_to_have_no_accessibility_violations(page) - end - - scenario 'phone info' do - sign_in_and_2fa_user - visit idv_path - complete_all_doc_auth_steps - - expect(current_path).to eq idv_phone_path - expect_page_to_have_no_accessibility_violations(page) - end - - scenario 'review page' do - sign_in_and_2fa_user - visit idv_path - complete_all_doc_auth_steps_before_password_step - - expect(page).to have_current_path(idv_enter_password_path) - expect_page_to_have_no_accessibility_violations(page) - end - scenario 'personal key / confirmation page' do - sign_in_and_2fa_user visit idv_path - complete_all_doc_auth_steps_before_password_step - fill_in t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD - click_continue - - expect(current_path).to eq idv_personal_key_path expect_page_to_have_no_accessibility_violations(page) - end - - scenario 'doc auth steps accessibility' do - sign_in_and_2fa_user - visit idv_path complete_all_doc_auth_steps_before_password_step(expect_accessible: true) fill_in t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD click_continue From 80e2800568fee6e23298a61d3841b27caa1375d6 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 4 Apr 2024 08:59:07 -0500 Subject: [PATCH 05/17] Enable Rubocop Rule to Freeze Constants (#10340) * Enable MutableConstants rubocop rule changelog: Internal, Performance, Freeze constants --------- Co-authored-by: Zach Margolis --- .rubocop.yml | 4 ++ Gemfile | 2 +- Gemfile.lock | 14 ++-- app/components/alert_icon_component.rb | 2 +- .../one_time_code_input_component.rb | 2 +- app/components/spinner_button_component.rb | 2 +- .../analytics_events_controller.rb | 2 +- .../concerns/two_factor_authenticatable.rb | 2 +- app/controllers/idv/please_call_controller.rb | 2 +- app/controllers/idv/unavailable_controller.rb | 2 +- app/forms/idv/doc_pii_form.rb | 3 +- app/forms/openid_connect_token_form.rb | 2 +- app/jobs/get_usps_proofing_results_job.rb | 8 +-- app/models/gpo_confirmation.rb | 2 +- app/models/service_provider_identity.rb | 2 +- app/presenters/account_show_presenter.rb | 2 +- .../in_person/ready_to_verify_presenter.rb | 2 +- .../verification_results_email_presenter.rb | 2 +- app/services/browser_cache.rb | 2 + app/services/doc_auth/error_generator.rb | 9 +-- app/services/doc_auth/errors.rb | 2 +- app/services/doc_auth/lexis_nexis/config.rb | 2 +- app/services/doc_auth/mock/config.rb | 2 +- app/services/doc_auth/response.rb | 2 +- app/services/doc_auth/selfie_concern.rb | 4 +- .../document_capture_session_result.rb | 2 +- .../encryption/contextless_kms_client.rb | 2 +- .../encryption/encryptors/pii_encryptor.rb | 2 +- app/services/encryption/kms_client.rb | 2 +- app/services/encryption/password_verifier.rb | 2 +- .../encryption/regional_ciphertext_pair.rb | 2 +- .../encryption/uak_password_verifier.rb | 2 +- .../openid_connect_attribute_scoper.rb | 14 ++-- app/services/personal_key_formatter.rb | 5 +- app/services/phone_number_capabilities.rb | 4 +- app/services/pii/attributes.rb | 2 +- app/services/proofing/aamva/proofer.rb | 2 +- app/services/proofing/lexis_nexis/config.rb | 2 +- .../proofing/lexis_nexis/ddp/proofer.rb | 2 +- .../lexis_nexis/ddp/response_redacter.rb | 2 +- app/services/proofing/mock/ddp_mock_client.rb | 2 +- app/services/recaptcha_validator.rb | 4 +- app/services/request_password_reset.rb | 2 +- app/services/service_provider_updater.rb | 2 +- .../usps_in_person_proofing/proofer.rb | 2 +- app/services/vot/component_value.rb | 2 +- app/services/vot/legacy_component_values.rb | 26 ++++---- app/services/vot/parser.rb | 2 +- .../vot/supported_component_values.rb | 14 ++-- app/validators/form_password_validator.rb | 2 +- config/initializers/01_redis.rb | 4 +- config/initializers/ab_tests.rb | 6 +- config/initializers/rack_attack.rb | 6 +- config/initializers/rack_timeout.rb | 4 +- lib/ab_test_bucket.rb | 5 +- lib/analytics_events_documenter.rb | 2 +- lib/deploy/activate.rb | 2 +- lib/identity_config.rb | 17 ++--- lib/idp/constants.rb | 2 +- lib/linters/analytics_event_name_linter.rb | 2 +- lib/linters/errors_add_linter.rb | 2 +- lib/linters/image_size_linter.rb | 2 +- lib/linters/redirect_back_linter.rb | 2 +- lib/pwned_password_downloader.rb | 2 +- lib/saml_idp_constants.rb | 2 +- lib/session_encryptor.rb | 4 +- lib/telephony/pinpoint/sms_sender.rb | 2 + lib/telephony/pinpoint/voice_sender.rb | 2 + scripts/changelog_check.rb | 2 +- spec/config/initializers/ab_tests_spec.rb | 47 -------------- .../options_controller_spec.rb | 4 +- spec/features/account_connected_apps_spec.rb | 4 +- spec/features/idv/analytics_spec.rb | 2 - spec/i18n_spec.rb | 2 +- .../lib/telephony/pinpoint/sms_sender_spec.rb | 64 ++++++------------- .../telephony/pinpoint/voice_sender_spec.rb | 28 ++++---- spec/requests/redis_down_spec.rb | 2 +- spec/services/doc_auth_router_spec.rb | 40 +----------- spec/spec_helper.rb | 2 +- spec/support/fake_analytics.rb | 4 +- spec/support/features/doc_auth_helper.rb | 10 +-- spec/support/features/in_person_helper.rb | 27 ++++---- spec/support/saml_auth_helper.rb | 2 + 83 files changed, 200 insertions(+), 293 deletions(-) delete mode 100644 spec/config/initializers/ab_tests_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index c04b6c3133b..96686cf9fc9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1218,6 +1218,10 @@ Style/MultilineMemoization: Style/MultilineWhenThen: Enabled: true +Style/MutableConstant: + Enabled: true + EnforcedStyle: strict + Style/NegatedWhile: Enabled: true diff --git a/Gemfile b/Gemfile index c89b14189e1..ee8fd4bdcbb 100644 --- a/Gemfile +++ b/Gemfile @@ -115,7 +115,7 @@ group :development, :test do gem 'psych' gem 'rspec', '~> 3.12.0' gem 'rspec-rails', '~> 6.0' - gem 'rubocop', '~> 1.59.0', require: false + gem 'rubocop', '~> 1.62.0', require: false gem 'rubocop-performance', '~> 1.20.2', require: false gem 'rubocop-rails', '>= 2.5.2', require: false gem 'rubocop-rspec', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d0258b2095f..4b07a48e50b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -456,7 +456,7 @@ GEM openssl (> 2.0, < 3.1) orm_adapter (0.5.0) parallel (1.24.0) - parser (3.3.0.0) + parser (3.3.0.5) ast (~> 2.4.1) racc pg (1.5.4) @@ -611,19 +611,19 @@ GEM rspec-support (3.12.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.59.0) + rubocop (1.62.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.4) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.30.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) + rubocop-ast (1.31.2) + parser (>= 3.3.0.4) rubocop-capybara (2.19.0) rubocop (~> 1.41) rubocop-factory_bot (2.24.0) @@ -842,7 +842,7 @@ DEPENDENCIES rspec-rails (~> 6.0) rspec-retry rspec_junit_formatter - rubocop (~> 1.59.0) + rubocop (~> 1.62.0) rubocop-performance (~> 1.20.2) rubocop-rails (>= 2.5.2) rubocop-rspec diff --git a/app/components/alert_icon_component.rb b/app/components/alert_icon_component.rb index 74b146c6c83..c85c53db461 100644 --- a/app/components/alert_icon_component.rb +++ b/app/components/alert_icon_component.rb @@ -8,7 +8,7 @@ class AlertIconComponent < BaseComponent personal_key: 'status/personal-key.svg', info_question: 'status/info-question.svg', delete: 'status/delete.svg', - } + }.freeze DEFAULT_WIDTH = 88 DEFAULT_HEIGHT = 88 diff --git a/app/components/one_time_code_input_component.rb b/app/components/one_time_code_input_component.rb index a0bbbc2c57f..a3247b49c19 100644 --- a/app/components/one_time_code_input_component.rb +++ b/app/components/one_time_code_input_component.rb @@ -16,7 +16,7 @@ class OneTimeCodeInputComponent < BaseComponent alias_method :autofocus?, :autofocus # @see https://tc39.es/ecma262/#prod-SyntaxCharacter - JS_REGEXP_SYNTAX_CHARACTER = Regexp.union(%w[^ $ \ . * + ? ( ) [ ] { } |]) + JS_REGEXP_SYNTAX_CHARACTER = Regexp.union(%w[^ $ \ . * + ? ( ) [ ] { } |]).freeze # @param [FormBuilder] form Form builder instance. # @param [Symbol] name Field name. Defaults to `:code`. diff --git a/app/components/spinner_button_component.rb b/app/components/spinner_button_component.rb index 64beda14bb8..b536f5c92f6 100644 --- a/app/components/spinner_button_component.rb +++ b/app/components/spinner_button_component.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class SpinnerButtonComponent < BaseComponent - DEFAULT_LONG_WAIT_DURATION = 15.seconds + DEFAULT_LONG_WAIT_DURATION = 15.seconds.freeze attr_reader :action_message, :button_options, diff --git a/app/controllers/analytics_events_controller.rb b/app/controllers/analytics_events_controller.rb index fb24dd24e01..55a2070d69d 100644 --- a/app/controllers/analytics_events_controller.rb +++ b/app/controllers/analytics_events_controller.rb @@ -6,7 +6,7 @@ class AnalyticsEventsController < ApplicationController prepend_before_action :skip_session_expiration skip_before_action :disable_caching - JSON_FILE = Rails.public_path.join('api', '_analytics-events.json') + JSON_FILE = Rails.public_path.join('api', '_analytics-events.json').freeze def index if File.exist?(JSON_FILE) diff --git a/app/controllers/concerns/two_factor_authenticatable.rb b/app/controllers/concerns/two_factor_authenticatable.rb index 90b46dbe4e0..31ae84c059c 100644 --- a/app/controllers/concerns/two_factor_authenticatable.rb +++ b/app/controllers/concerns/two_factor_authenticatable.rb @@ -9,7 +9,7 @@ module TwoFactorAuthenticatable DIRECT_OTP_LENGTH = 6 PROOFING_DIRECT_OTP_LENGTH = 6 ALLOWED_OTP_DRIFT_SECONDS = 30 - DIRECT_OTP_VALID_FOR_MINUTES = IdentityConfig.store.otp_valid_for + DIRECT_OTP_VALID_FOR_MINUTES = IdentityConfig.store.otp_valid_for.freeze DIRECT_OTP_VALID_FOR_SECONDS = DIRECT_OTP_VALID_FOR_MINUTES * 60 REMEMBER_2FA_COOKIE = 'remember_tfa' diff --git a/app/controllers/idv/please_call_controller.rb b/app/controllers/idv/please_call_controller.rb index 5b3eb66be19..00c027b2cb1 100644 --- a/app/controllers/idv/please_call_controller.rb +++ b/app/controllers/idv/please_call_controller.rb @@ -9,7 +9,7 @@ class PleaseCallController < ApplicationController before_action :handle_fraud_rejection before_action :confirm_fraud_pending - FRAUD_REVIEW_CONTACT_WITHIN_DAYS = 14.days + FRAUD_REVIEW_CONTACT_WITHIN_DAYS = 14.days.freeze def show analytics.idv_please_call_visited diff --git a/app/controllers/idv/unavailable_controller.rb b/app/controllers/idv/unavailable_controller.rb index a3fba15759b..2c546fdbd7c 100644 --- a/app/controllers/idv/unavailable_controller.rb +++ b/app/controllers/idv/unavailable_controller.rb @@ -2,7 +2,7 @@ module Idv class UnavailableController < ApplicationController - ALLOWED_FROM_LOCATIONS = [SignUp::RegistrationsController::CREATE_ACCOUNT] + ALLOWED_FROM_LOCATIONS = [SignUp::RegistrationsController::CREATE_ACCOUNT].freeze before_action :redirect_if_idv_available_and_from_create_account diff --git a/app/forms/idv/doc_pii_form.rb b/app/forms/idv/doc_pii_form.rb index b37946160a2..6b62481c092 100644 --- a/app/forms/idv/doc_pii_form.rb +++ b/app/forms/idv/doc_pii_form.rb @@ -78,7 +78,8 @@ def self.present_error(existing_errors) private - PII_ERROR_KEYS = %i[name dob address1 state zipcode jurisdiction state_id_number dob_min_age] + PII_ERROR_KEYS = %i[name dob address1 state zipcode jurisdiction state_id_number + dob_min_age].freeze attr_reader :pii_from_doc diff --git a/app/forms/openid_connect_token_form.rb b/app/forms/openid_connect_token_form.rb index d9af0890798..404887ba55e 100644 --- a/app/forms/openid_connect_token_form.rb +++ b/app/forms/openid_connect_token_form.rb @@ -5,7 +5,7 @@ class OpenidConnectTokenForm include ActionView::Helpers::TranslationHelper include Rails.application.routes.url_helpers - ISSUED_AT_LEEWAY_SECONDS = 10.seconds.to_i + ISSUED_AT_LEEWAY_SECONDS = 10.seconds.to_i.freeze ATTRS = %i[ client_assertion diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index d7222a3856a..d0322525ad8 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -11,10 +11,10 @@ class GetUspsProofingResultsJob < ApplicationJob SUPPORTED_ID_TYPES = [ "State driver's license", "State non-driver's identification card", - ] + ].freeze SUPPORTED_SECONDARY_ID_TYPES = [ 'Visual Inspection of Name and Address on Primary ID Match', - ] + ].freeze queue_as :long_running @@ -57,8 +57,8 @@ def perform(_now) attr_accessor :enrollment_outcomes DEFAULT_EMAIL_DELAY_IN_HOURS = 1 - REQUEST_DELAY_IN_SECONDS = IdentityConfig.store. - get_usps_proofing_results_job_request_delay_milliseconds / MILLISECONDS_PER_SECOND + REQUEST_DELAY_IN_SECONDS = (IdentityConfig.store. + get_usps_proofing_results_job_request_delay_milliseconds / MILLISECONDS_PER_SECOND).freeze def proofer @proofer ||= UspsInPersonProofing::EnrollmentHelper.usps_proofer diff --git a/app/models/gpo_confirmation.rb b/app/models/gpo_confirmation.rb index 04a99d6e903..6d69cbca090 100644 --- a/app/models/gpo_confirmation.rb +++ b/app/models/gpo_confirmation.rb @@ -3,7 +3,7 @@ class GpoConfirmation < ApplicationRecord self.table_name = 'usps_confirmations' - ENTRY_ATTRIBUTES = %i[otp address1 city state zipcode] + ENTRY_ATTRIBUTES = %i[otp address1 city state zipcode].freeze ENTRY_ATTRIBUTES.each do |attr| define_method(:"entry_#{attr}") do entry[attr] diff --git a/app/models/service_provider_identity.rb b/app/models/service_provider_identity.rb index 84dcc6d148a..2c1bb0272b4 100644 --- a/app/models/service_provider_identity.rb +++ b/app/models/service_provider_identity.rb @@ -18,7 +18,7 @@ class ServiceProviderIdentity < ApplicationRecord scope :not_deleted, -> { where(deleted_at: nil) } - CONSENT_EXPIRATION = 1.year + CONSENT_EXPIRATION = 1.year.freeze def deactivate update!(session_uuid: nil) diff --git a/app/presenters/account_show_presenter.rb b/app/presenters/account_show_presenter.rb index 0b944d8f4f4..109bc1286c5 100644 --- a/app/presenters/account_show_presenter.rb +++ b/app/presenters/account_show_presenter.rb @@ -89,7 +89,7 @@ def totp_content :dob, :phone, keyword_init: true, - ) + ).freeze def obfuscated_pii_accessor PiiAccessor.new( diff --git a/app/presenters/idv/in_person/ready_to_verify_presenter.rb b/app/presenters/idv/in_person/ready_to_verify_presenter.rb index c84b0eb2e57..36efbcb2b49 100644 --- a/app/presenters/idv/in_person/ready_to_verify_presenter.rb +++ b/app/presenters/idv/in_person/ready_to_verify_presenter.rb @@ -4,7 +4,7 @@ module Idv module InPerson class ReadyToVerifyPresenter # WILLFIX: With LG-6881, confirm timezone or use deadline from enrollment response. - USPS_SERVER_TIMEZONE = ActiveSupport::TimeZone['America/New_York'] + USPS_SERVER_TIMEZONE = ActiveSupport::TimeZone['America/New_York'].dup.freeze attr_reader :barcode_image_url diff --git a/app/presenters/idv/in_person/verification_results_email_presenter.rb b/app/presenters/idv/in_person/verification_results_email_presenter.rb index 2a11a73351a..3fae3d98a69 100644 --- a/app/presenters/idv/in_person/verification_results_email_presenter.rb +++ b/app/presenters/idv/in_person/verification_results_email_presenter.rb @@ -8,7 +8,7 @@ class VerificationResultsEmailPresenter attr_reader :enrollment, :url_options # update to user's time zone when out of pilot - USPS_SERVER_TIMEZONE = ActiveSupport::TimeZone['America/New_York'] + USPS_SERVER_TIMEZONE = ActiveSupport::TimeZone['America/New_York'].dup.freeze def initialize(enrollment:, url_options:) @enrollment = enrollment diff --git a/app/services/browser_cache.rb b/app/services/browser_cache.rb index 802a7ba40f3..71bdf379430 100644 --- a/app/services/browser_cache.rb +++ b/app/services/browser_cache.rb @@ -2,7 +2,9 @@ class BrowserCache @cache = LruRedux::Cache.new(1_000) + # rubocop:disable Style/MutableConstant DEFAULT_BROWSER = Browser.new(nil) + # rubocop:enable Style/MutableConstant USER_AGENT_SIZE = Browser.user_agent_size_limit - 1 # Detects browser attributes from User-Agent, truncated to 2047 bytes due diff --git a/app/services/doc_auth/error_generator.rb b/app/services/doc_auth/error_generator.rb index 74f56ec6e05..89dacadb736 100644 --- a/app/services/doc_auth/error_generator.rb +++ b/app/services/doc_auth/error_generator.rb @@ -11,7 +11,7 @@ def handle(response_info) class IdTypeErrorHandler < ErrorHandler SUPPORTED_ID_CLASSNAME = ['Identification Card', 'Drivers License'].freeze ACCEPTED_ISSUER_TYPES = [DocAuth::LexisNexis::IssuerTypes::STATE_OR_PROVINCE.name, - DocAuth::LexisNexis::IssuerTypes::UNKNOWN.name] + DocAuth::LexisNexis::IssuerTypes::UNKNOWN.name].freeze def handle(response_info) get_id_type_errors(response_info[:classification_info]) end @@ -114,7 +114,7 @@ def is_generic_selfie_error?(error) error == Errors::SELFIE_FAILURE end - SELFIE_GENERAL_FAILURE_ERROR = + def selfie_general_failure_error { general: [Errors::SELFIE_FAILURE], front: [Errors::MULTIPLE_FRONT_ID_FAILURES], @@ -122,6 +122,7 @@ def is_generic_selfie_error?(error) selfie: [Errors::SELFIE_FAILURE], hints: false, } + end private @@ -252,7 +253,7 @@ class ErrorGenerator GENERAL = :general ACCEPTED_ISSUER_TYPES = [DocAuth::LexisNexis::IssuerTypes::STATE_OR_PROVINCE.name, - DocAuth::LexisNexis::IssuerTypes::UNKNOWN.name] + DocAuth::LexisNexis::IssuerTypes::UNKNOWN.name].freeze ERROR_KEYS = [ ID, @@ -317,7 +318,7 @@ def generate_doc_auth_errors(response_info) # if selfie itself is ok, but we have selfie related error if selfie_error_handler.is_generic_selfie_error?(selfie_error) - return SelfieErrorHandler::SELFIE_GENERAL_FAILURE_ERROR + return selfie_error_handler.selfie_general_failure_error end # other vendor response detail error diff --git a/app/services/doc_auth/errors.rb b/app/services/doc_auth/errors.rb index bf645210e15..262b0ed3590 100644 --- a/app/services/doc_auth/errors.rb +++ b/app/services/doc_auth/errors.rb @@ -123,7 +123,7 @@ module Errors SELFIE_FAILURE => { long_msg: SELFIE_FAILURE, field_msg: SELFIE_FAILURE, hints: false }, SELFIE_NOT_LIVE => { long_msg: SELFIE_NOT_LIVE, field_msg: SELFIE_FAILURE, hints: false }, SELFIE_POOR_QUALITY => { long_msg: SELFIE_POOR_QUALITY, field_msg: SELFIE_FAILURE, hints: false }, - } + }.transform_values(&:freeze).freeze # rubocop:enable Layout/LineLength end end diff --git a/app/services/doc_auth/lexis_nexis/config.rb b/app/services/doc_auth/lexis_nexis/config.rb index 52a5ee050be..f689212eb9e 100644 --- a/app/services/doc_auth/lexis_nexis/config.rb +++ b/app/services/doc_auth/lexis_nexis/config.rb @@ -39,6 +39,6 @@ def validate! raise 'config missing base_url' if !base_url raise 'config missing locale' if !locale end - end + end.freeze end end diff --git a/app/services/doc_auth/mock/config.rb b/app/services/doc_auth/mock/config.rb index 1976cae432d..6f50f497bba 100644 --- a/app/services/doc_auth/mock/config.rb +++ b/app/services/doc_auth/mock/config.rb @@ -13,6 +13,6 @@ module Mock :sharpness_threshold, :glare_threshold, ], - ) + ).freeze end end diff --git a/app/services/doc_auth/response.rb b/app/services/doc_auth/response.rb index 64e60231114..cee56818514 100644 --- a/app/services/doc_auth/response.rb +++ b/app/services/doc_auth/response.rb @@ -10,7 +10,7 @@ class Response ID_TYPE_SLUGS = { 'Identification Card' => 'state_id_card', 'Drivers License' => 'drivers_license', - } + }.freeze def initialize( success:, diff --git a/app/services/doc_auth/selfie_concern.rb b/app/services/doc_auth/selfie_concern.rb index 9cbc24fbada..0c518e0118c 100644 --- a/app/services/doc_auth/selfie_concern.rb +++ b/app/services/doc_auth/selfie_concern.rb @@ -33,13 +33,13 @@ def selfie_check_performed? private - SELFIE_PERFORMED_STATUSES = %i[success fail] + SELFIE_PERFORMED_STATUSES = %i[success fail].freeze ERROR_TEXTS = { success: 'Successful. Liveness: Live', not_live: 'Liveness: NotLive', poor_quality: 'Liveness: PoorQuality', - } + }.freeze # @param [Object] portrait_match_results trueid portait match info def get_portrait_error(portrait_match_results) diff --git a/app/services/document_capture_session_result.rb b/app/services/document_capture_session_result.rb index 4c50a0ce2d4..31ddc9b5e0b 100644 --- a/app/services/document_capture_session_result.rb +++ b/app/services/document_capture_session_result.rb @@ -45,4 +45,4 @@ def selfie_status return self[member_name]&.include?(fingerprint) end end -end +end.freeze diff --git a/app/services/encryption/contextless_kms_client.rb b/app/services/encryption/contextless_kms_client.rb index 23494bfef0d..3500b968fcb 100644 --- a/app/services/encryption/contextless_kms_client.rb +++ b/app/services/encryption/contextless_kms_client.rb @@ -12,7 +12,7 @@ class ContextlessKmsClient instance_profile_credentials_timeout: 1, # defaults to 1 second instance_profile_credentials_retries: 5, # defaults to 0 retries ) - end + end.freeze KEY_TYPE = { KMS: 'KMSx', diff --git a/app/services/encryption/encryptors/pii_encryptor.rb b/app/services/encryption/encryptors/pii_encryptor.rb index 0a74c3e9903..c577f04b9b0 100644 --- a/app/services/encryption/encryptors/pii_encryptor.rb +++ b/app/services/encryption/encryptors/pii_encryptor.rb @@ -33,7 +33,7 @@ def self.extract_encrypted_data(parsed_json) ) decode(encoded_encrypted_data) end - end + end.freeze def initialize(password) @password = password diff --git a/app/services/encryption/kms_client.rb b/app/services/encryption/kms_client.rb index 7d059229373..308606a7d1c 100644 --- a/app/services/encryption/kms_client.rb +++ b/app/services/encryption/kms_client.rb @@ -22,7 +22,7 @@ class KmsClient instance_profile_credentials_retries: 5, # defaults to 0 retries region: IdentityConfig.store.aws_region, # The region in which the client is being instantiated ) - end + end.freeze # rubocop:enable Layout/LineLength attr_reader :kms_key_id diff --git a/app/services/encryption/password_verifier.rb b/app/services/encryption/password_verifier.rb index d8bd2ec082f..95f0a52d6e9 100644 --- a/app/services/encryption/password_verifier.rb +++ b/app/services/encryption/password_verifier.rb @@ -29,7 +29,7 @@ def to_s def uak_password_digest? encryption_key.present? end - end + end.freeze def initialize @aes_cipher = AesCipher.new diff --git a/app/services/encryption/regional_ciphertext_pair.rb b/app/services/encryption/regional_ciphertext_pair.rb index a3d3a6ef370..09db686c95b 100644 --- a/app/services/encryption/regional_ciphertext_pair.rb +++ b/app/services/encryption/regional_ciphertext_pair.rb @@ -10,4 +10,4 @@ def to_ary def multi_or_single_region_ciphertext multi_region_ciphertext.presence || single_region_ciphertext end -end +end.freeze diff --git a/app/services/encryption/uak_password_verifier.rb b/app/services/encryption/uak_password_verifier.rb index fad98691fd0..c5c91cf0e80 100644 --- a/app/services/encryption/uak_password_verifier.rb +++ b/app/services/encryption/uak_password_verifier.rb @@ -28,7 +28,7 @@ def to_s password_cost: password_cost, }.to_json end - end + end.freeze def self.digest(password) salt = SecureRandom.hex(32) diff --git a/app/services/openid_connect_attribute_scoper.rb b/app/services/openid_connect_attribute_scoper.rb index 81a869b230d..0886ab7230c 100644 --- a/app/services/openid_connect_attribute_scoper.rb +++ b/app/services/openid_connect_attribute_scoper.rb @@ -6,7 +6,7 @@ class OpenidConnectAttributeScoper x509:subject x509:issuer x509:presented - ] + ].freeze IAL2_SCOPES = %w[ address @@ -15,21 +15,21 @@ class OpenidConnectAttributeScoper profile:name profile:birthdate social_security_number - ] + ].freeze - VALID_SCOPES = %w[ + VALID_SCOPES = (%w[ email all_emails openid profile:verified_at - ] + X509_SCOPES + IAL2_SCOPES + ] + X509_SCOPES + IAL2_SCOPES).freeze - VALID_IAL1_SCOPES = %w[ + VALID_IAL1_SCOPES = (%w[ email all_emails openid profile:verified_at - ] + X509_SCOPES + ] + X509_SCOPES).freeze ATTRIBUTE_SCOPES_MAP = { email: %w[email], @@ -58,7 +58,7 @@ class OpenidConnectAttributeScoper end end.with_indifferent_access.freeze - CLAIMS = ATTRIBUTE_SCOPES_MAP.keys + CLAIMS = ATTRIBUTE_SCOPES_MAP.keys.freeze attr_reader :scopes diff --git a/app/services/personal_key_formatter.rb b/app/services/personal_key_formatter.rb index 8b80d962067..64d92a760b6 100644 --- a/app/services/personal_key_formatter.rb +++ b/app/services/personal_key_formatter.rb @@ -2,11 +2,12 @@ class PersonalKeyFormatter CHAR_COUNT = RandomPhrase::WORD_LENGTH - WORD_COUNT = IdentityConfig.store.recovery_code_length + WORD_COUNT = IdentityConfig.store.recovery_code_length.freeze VALID_CHAR = '[a-zA-Z0-9]' VALID_WORD = "#{VALID_CHAR}{#{CHAR_COUNT}}".freeze REGEXP_STRING = "(?:#{VALID_WORD}([\\s\\-])?){#{WORD_COUNT - 1}}#{VALID_WORD}".freeze REGEXP = /\A#{REGEXP_STRING}\Z/o + CODE_LENGTH = (CHAR_COUNT * WORD_COUNT + (WORD_COUNT - 1)).freeze def self.regexp REGEXP @@ -17,6 +18,6 @@ def self.regexp_string end def self.code_length - CHAR_COUNT * WORD_COUNT + (WORD_COUNT - 1) + CODE_LENGTH end end diff --git a/app/services/phone_number_capabilities.rb b/app/services/phone_number_capabilities.rb index f29298e0a36..0318433cc35 100644 --- a/app/services/phone_number_capabilities.rb +++ b/app/services/phone_number_capabilities.rb @@ -9,7 +9,7 @@ def self.load_config INTERNATIONAL_CODES = load_config.freeze ADDRESS_IDENTITY_PROOFING_SUPPORTED_COUNTRY_CODES = - IdentityConfig.store.address_identity_proofing_supported_country_codes + IdentityConfig.store.address_identity_proofing_supported_country_codes.freeze attr_reader :phone, :phone_confirmed @@ -34,7 +34,7 @@ def self.translated_international_codes translated_intl_codes_data[I18n.locale] if translated_intl_codes_data end - TRANSLATED_INTL_CODES_DATA = generate_translated_international_codes_data + TRANSLATED_INTL_CODES_DATA = generate_translated_international_codes_data.freeze def initialize(phone, phone_confirmed:) @phone = phone diff --git a/app/services/pii/attributes.rb b/app/services/pii/attributes.rb index c9edf807fce..47c7dc3a9aa 100644 --- a/app/services/pii/attributes.rb +++ b/app/services/pii/attributes.rb @@ -42,5 +42,5 @@ def eql?(other) def ==(other) eql?(other) end - end + end.freeze end diff --git a/app/services/proofing/aamva/proofer.rb b/app/services/proofing/aamva/proofer.rb index 0daa79f0e08..c73841282d8 100644 --- a/app/services/proofing/aamva/proofer.rb +++ b/app/services/proofing/aamva/proofer.rb @@ -19,7 +19,7 @@ class Proofer :verification_request_timeout, :verification_url, ], - ) + ).freeze attr_reader :config diff --git a/app/services/proofing/lexis_nexis/config.rb b/app/services/proofing/lexis_nexis/config.rb index a09f4e3fd25..e650a58053b 100644 --- a/app/services/proofing/lexis_nexis/config.rb +++ b/app/services/proofing/lexis_nexis/config.rb @@ -23,6 +23,6 @@ module LexisNexis :request_mode, :request_timeout, ], - ) + ).freeze end end diff --git a/app/services/proofing/lexis_nexis/ddp/proofer.rb b/app/services/proofing/lexis_nexis/ddp/proofer.rb index 246c6957955..6b7d296c312 100644 --- a/app/services/proofing/lexis_nexis/ddp/proofer.rb +++ b/app/services/proofing/lexis_nexis/ddp/proofer.rb @@ -4,7 +4,7 @@ module Proofing module LexisNexis module Ddp class Proofer - VALID_REVIEW_STATUSES = %w[pass review reject] + VALID_REVIEW_STATUSES = %w[pass review reject].freeze attr_reader :config diff --git a/app/services/proofing/lexis_nexis/ddp/response_redacter.rb b/app/services/proofing/lexis_nexis/ddp/response_redacter.rb index 377f13e0b5b..fb9cda806d9 100644 --- a/app/services/proofing/lexis_nexis/ddp/response_redacter.rb +++ b/app/services/proofing/lexis_nexis/ddp/response_redacter.rb @@ -191,7 +191,7 @@ class ResponseRedacter true_ip_score true_ip_worst_score unknown_session - ] + ].freeze # @param [Hash, nil] parsed JSON response body def self.redact(hash) diff --git a/app/services/proofing/mock/ddp_mock_client.rb b/app/services/proofing/mock/ddp_mock_client.rb index 18282101261..772443a48b6 100644 --- a/app/services/proofing/mock/ddp_mock_client.rb +++ b/app/services/proofing/mock/ddp_mock_client.rb @@ -37,7 +37,7 @@ def stage 'proofing', 'lexis_nexis', 'ddp', - ) + ).freeze TRANSACTION_ID = 'ddp-mock-transaction-id-123' def initialize(response_fixture_file: 'successful_response.json') diff --git a/app/services/recaptcha_validator.rb b/app/services/recaptcha_validator.rb index 95769a31473..b9af5e24232 100644 --- a/app/services/recaptcha_validator.rb +++ b/app/services/recaptcha_validator.rb @@ -2,8 +2,8 @@ class RecaptchaValidator VERIFICATION_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify' - RESULT_ERRORS = ['missing-input-secret', 'invalid-input-secret'] - VALID_RECAPTCHA_VERSIONS = [2, 3] + RESULT_ERRORS = ['missing-input-secret', 'invalid-input-secret'].freeze + VALID_RECAPTCHA_VERSIONS = [2, 3].freeze attr_reader :recaptcha_version, :recaptcha_action, diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb index d69741d6548..c5b9505f049 100644 --- a/app/services/request_password_reset.rb +++ b/app/services/request_password_reset.rb @@ -85,4 +85,4 @@ def email_address_record EmailAddress.find_with_confirmed_or_unconfirmed_email(email) end end -end +end.freeze diff --git a/app/services/service_provider_updater.rb b/app/services/service_provider_updater.rb index f8bbab9effa..c3a9a8d6bc2 100644 --- a/app/services/service_provider_updater.rb +++ b/app/services/service_provider_updater.rb @@ -10,7 +10,7 @@ class ServiceProviderUpdater SP_IGNORED_ATTRIBUTES = %i[ cert - ] + ].freeze def run(service_provider = nil) if service_provider.present? diff --git a/app/services/usps_in_person_proofing/proofer.rb b/app/services/usps_in_person_proofing/proofer.rb index b4f52e5e8ee..0ad3a48f13c 100644 --- a/app/services/usps_in_person_proofing/proofer.rb +++ b/app/services/usps_in_person_proofing/proofer.rb @@ -4,7 +4,7 @@ module UspsInPersonProofing class Proofer AUTH_TOKEN_CACHE_KEY = :usps_ippaas_api_auth_token # Automatically refresh our auth token if it is within this many minutes of expiring - AUTH_TOKEN_PREEMPTIVE_EXPIRY_MINUTES = 1.minute + AUTH_TOKEN_PREEMPTIVE_EXPIRY_MINUTES = 1.minute.freeze # Makes HTTP request to get nearby in-person proofing facilities # Requires address, city, state and zip code. diff --git a/app/services/vot/component_value.rb b/app/services/vot/component_value.rb index 2eccdf1efec..01c3785350a 100644 --- a/app/services/vot/component_value.rb +++ b/app/services/vot/component_value.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Vot - ComponentValue = Data.define(:name, :description, :implied_component_values, :requirements) + ComponentValue = Data.define(:name, :description, :implied_component_values, :requirements).freeze end diff --git a/app/services/vot/legacy_component_values.rb b/app/services/vot/legacy_component_values.rb index 9d0a905cd3b..5f3d2db3459 100644 --- a/app/services/vot/legacy_component_values.rb +++ b/app/services/vot/legacy_component_values.rb @@ -8,31 +8,31 @@ module LegacyComponentValues description: 'Legacy LOA1', implied_component_values: [], requirements: [], - ) + ).freeze LOA3 = ComponentValue.new( name: Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF, description: 'Legacy LOA3', implied_component_values: [], requirements: [:aal2, :identity_proofing], - ) + ).freeze IAL1 = ComponentValue.new( name: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, description: 'Legacy IAL1', implied_component_values: [], requirements: [], - ) + ).freeze IAL2 = ComponentValue.new( name: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, description: 'Legacy IAL2', implied_component_values: [], requirements: [:aal2, :identity_proofing], - ) + ).freeze IALMAX = ComponentValue.new( name: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF, description: 'Legacy IALMAX', implied_component_values: [], requirements: [:aal2, :ialmax], - ) + ).freeze ## Authentication ACR values DEFAULT = ComponentValue.new( @@ -40,48 +40,48 @@ module LegacyComponentValues description: 'Legacy default authentication', implied_component_values: [], requirements: [], - ) + ).freeze AAL1 = ComponentValue.new( name: Saml::Idp::Constants::AAL1_AUTHN_CONTEXT_CLASSREF, description: 'Legacy AAL1', implied_component_values: [], requirements: [], - ) + ).freeze AAL2 = ComponentValue.new( name: Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, description: 'Legacy AAL2', implied_component_values: [], requirements: [:aal2], - ) + ).freeze AAL2_PHISHING_RESISTANT = ComponentValue.new( name: Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF, description: 'Legacy AAL2 with phishing resistance', implied_component_values: [], requirements: [:aal2, :phishing_resistant], - ) + ).freeze AAL2_HSPD12 = ComponentValue.new( name: Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF, description: 'Legacy AAL2 with HSPD12', implied_component_values: [], requirements: [:aal2, :hspd12], - ) + ).freeze AAL3 = ComponentValue.new( name: Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF, description: 'Legacy AAL3', implied_component_values: [], requirements: [:aal2, :phishing_resistant], - ) + ).freeze AAL3_HSPD12 = ComponentValue.new( name: Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF, description: 'Legacy AAL3 with HSPD12', implied_component_values: [], requirements: [:aal2, :hspd12], - ) + ).freeze NAME_HASH = constants.map do |constant| component_value = const_get(constant) [component_value.name, component_value] - end.to_h + end.to_h.freeze def self.by_name NAME_HASH diff --git a/app/services/vot/parser.rb b/app/services/vot/parser.rb index 2931d3cd9d5..80e7cbee65d 100644 --- a/app/services/vot/parser.rb +++ b/app/services/vot/parser.rb @@ -32,7 +32,7 @@ def identity_proofing_or_ialmax? def expanded_component_values component_values.map(&:name).join('.') end - end + end.freeze attr_reader :vector_of_trust, :acr_values diff --git a/app/services/vot/supported_component_values.rb b/app/services/vot/supported_component_values.rb index 9f41950bb63..d946e355945 100644 --- a/app/services/vot/supported_component_values.rb +++ b/app/services/vot/supported_component_values.rb @@ -7,42 +7,42 @@ module SupportedComponentValues description: 'Multi-factor authentication', implied_component_values: [], requirements: [], - ) + ).freeze C2 = ComponentValue.new( name: 'C2', description: 'AAL2 conformant features are engaged', implied_component_values: [C1], requirements: [:aal2], - ) + ).freeze Ca = ComponentValue.new( name: 'Ca', description: 'A phishing resistant authenticator is required', implied_component_values: [C1], requirements: [:phishing_resistant], - ) + ).freeze Cb = ComponentValue.new( name: 'Cb', description: 'A PIV/CAC card is required', implied_component_values: [C1], requirements: [:hspd12], - ) + ).freeze P1 = ComponentValue.new( name: 'P1', description: 'Identity proofing is performed', implied_component_values: [C2], requirements: [:identity_proofing], - ) + ).freeze Pb = ComponentValue.new( name: 'Pb', description: 'A biometric comparison is required as part of identity proofing', implied_component_values: [P1], requirements: [:biometric_comparison], - ) + ).freeze NAME_HASH = constants.map do |constant| component_value = const_get(constant) [component_value.name, component_value] - end.to_h + end.to_h.freeze def self.by_name NAME_HASH diff --git a/app/validators/form_password_validator.rb b/app/validators/form_password_validator.rb index 0c7dcc906c6..7268e039ea9 100644 --- a/app/validators/form_password_validator.rb +++ b/app/validators/form_password_validator.rb @@ -20,7 +20,7 @@ module FormPasswordValidator private - ZXCVBN_TESTER = ::Zxcvbn::Tester.new + ZXCVBN_TESTER = ::Zxcvbn::Tester.new.freeze def strong_password return unless errors.messages.blank? && password_score.score < min_password_score diff --git a/config/initializers/01_redis.rb b/config/initializers/01_redis.rb index 0c1303739a7..39851d84299 100644 --- a/config/initializers/01_redis.rb +++ b/config/initializers/01_redis.rb @@ -4,8 +4,8 @@ # This is done because rack_attack.rb needs to reference the Throttle pool defined here. REDIS_POOL = ConnectionPool.new(size: IdentityConfig.store.redis_pool_size) do Redis.new(url: IdentityConfig.store.redis_url) -end +end.freeze REDIS_THROTTLE_POOL = ConnectionPool.new(size: IdentityConfig.store.redis_throttle_pool_size) do Redis.new(url: IdentityConfig.store.redis_throttle_url) -end +end.freeze diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb index acedc4faf4f..096647a52c4 100644 --- a/config/initializers/ab_tests.rb +++ b/config/initializers/ab_tests.rb @@ -10,7 +10,7 @@ module AbTests IdentityConfig.store.doc_auth_vendor_randomize_percent : 0, }.compact, - ) + ).freeze ACUANT_SDK = AbTestBucket.new( experiment_name: 'Acuant SDK Upgrade', @@ -19,7 +19,7 @@ module AbTests IdentityConfig.store.idv_acuant_sdk_upgrade_a_b_testing_percent : 0, }, - ) + ).freeze LEXISNEXIS_INSTANT_VERIFY_WORKFLOW = AbTestBucket.new( experiment_name: 'LexisNexis Instant Verify Workflow', @@ -29,5 +29,5 @@ module AbTests IdentityConfig.store.lexisnexis_instant_verify_workflow_ab_testing_percent : 0, }, - ) + ).freeze end diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 9c4ac5e881c..486e24b92d3 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -6,11 +6,11 @@ module Rack class Attack ALLOWED_CIDR_BLOCKS = IdentityConfig.store.requests_per_ip_cidr_allowlist.map do |x| IPAddr.new(x) - end + end.freeze EMAIL_REGISTRATION_PATHS = ['/sign_up/enter_email', '/en/sign_up/enter_email', - '/es/sign_up/enter_email', '/fr/sign_up/enter_email'] - SIGN_IN_PATHS = ['/', '/en', '/es', '/fr'] + '/es/sign_up/enter_email', '/fr/sign_up/enter_email'].freeze + SIGN_IN_PATHS = ['/', '/en', '/es', '/fr'].freeze # If the app is behind a load balancer, `ip` will return the IP of the # load balancer instead of the actual IP the request came from, and since diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb index b378cb3bcf1..4c76e637430 100644 --- a/config/initializers/rack_timeout.rb +++ b/config/initializers/rack_timeout.rb @@ -4,14 +4,14 @@ module Rack class Timeout - EXCLUDES = [ + EXCLUDES = ([ '/verify/verify_info', '/verify/phone', '/verify/document_capture', '/verify/link_sent', ].flat_map do |path| [path] + Idp::Constants::AVAILABLE_LOCALES.map { |locale| "/#{locale}#{path}" } - end + ['/api/verify/images'] + end + ['/api/verify/images']).freeze def call_with_excludes(env) if EXCLUDES.any? { |path| env['REQUEST_URI']&.start_with?(path) } diff --git a/lib/ab_test_bucket.rb b/lib/ab_test_bucket.rb index 046f95190a7..5819dd63f9b 100644 --- a/lib/ab_test_bucket.rb +++ b/lib/ab_test_bucket.rb @@ -3,6 +3,8 @@ class AbTestBucket attr_reader :buckets, :experiment_name, :default_bucket + MAX_SHA = (16 ** 64) - 1 + def initialize(experiment_name:, buckets: {}, default_bucket: :default) @buckets = buckets @experiment_name = experiment_name @@ -30,8 +32,7 @@ def bucket(discriminator = nil) private def percent(discriminator) - max_sha = (16 ** 64) - 1 - Digest::SHA256.hexdigest("#{discriminator}:#{experiment_name}").to_i(16).to_f / max_sha * 100 + Digest::SHA256.hexdigest("#{discriminator}:#{experiment_name}").to_i(16).to_f / MAX_SHA * 100 end def valid_bucket_data_structure? diff --git a/lib/analytics_events_documenter.rb b/lib/analytics_events_documenter.rb index a3b7ecf775d..09ccf8bcf77 100644 --- a/lib/analytics_events_documenter.rb +++ b/lib/analytics_events_documenter.rb @@ -15,7 +15,7 @@ class AnalyticsEventsDocumenter DOCUMENTATION_OPTIONAL_PARAMS = %w[ pii_like_keypaths - ] + ].freeze attr_reader :database_path, :class_name diff --git a/lib/deploy/activate.rb b/lib/deploy/activate.rb index a57feb4ea7d..ea256ff3bab 100644 --- a/lib/deploy/activate.rb +++ b/lib/deploy/activate.rb @@ -10,7 +10,7 @@ module Deploy class Activate FILES_TO_LINK = %w[agencies iaa_gtcs iaa_orders iaa_statuses integration_statuses integrations - partner_account_statuses partner_accounts service_providers] + partner_account_statuses partner_accounts service_providers].freeze attr_reader :logger, :s3_client diff --git a/lib/identity_config.rb b/lib/identity_config.rb index aaf25e09e66..28d828ceff9 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -3,11 +3,11 @@ require 'csv' class IdentityConfig - GIT_SHA = `git rev-parse --short=8 HEAD`.chomp - GIT_TAG = `git tag --points-at HEAD`.chomp.split("\n").first - GIT_BRANCH = `git rev-parse --abbrev-ref HEAD`.chomp + GIT_SHA = `git rev-parse --short=8 HEAD`.chomp.freeze + GIT_TAG = `git tag --points-at HEAD`.chomp.split("\n").first.freeze + GIT_BRANCH = `git rev-parse --abbrev-ref HEAD`.chomp.freeze - VENDOR_STATUS_OPTIONS = %i[operational partial_outage full_outage] + VENDOR_STATUS_OPTIONS = %i[operational partial_outage full_outage].freeze class << self attr_reader :store, :key_types, :unused_keys @@ -60,7 +60,7 @@ class << self Time.parse(value) # rubocop:enable Rails/TimeZone end, - } + }.freeze attr_reader :key_types @@ -81,7 +81,7 @@ def add(key, type: :string, allow_nil: false, enum: nil, options: {}) raise "unexpected #{key}: #{value}, expected one of #{enum}" end - @written_env[key] = converted_value + @written_env[key] = converted_value.freeze @written_env end @@ -510,8 +510,9 @@ def self.build_store(config_map) config.add(:weekly_auth_funnel_report_config, type: :json) config.add(:x509_presented_hash_attribute_requested_issuers, type: :json) - @key_types = config.key_types - @unused_keys = config_map.keys - config.written_env.keys + @key_types = config.key_types.freeze + @unused_keys = (config_map.keys - config.written_env.keys).freeze + config.written_env.freeze @store = RedactedStruct.new('IdentityConfig', *config.written_env.keys, keyword_init: true). new(**config.written_env) end diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index 78b043696f6..7c56c623e06 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -2,7 +2,7 @@ module Idp module Constants - AVAILABLE_LOCALES = %w[en es fr] + AVAILABLE_LOCALES = %w[en es fr].freeze UUID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/ KMS_LOG_FILENAME = 'kms.log' diff --git a/lib/linters/analytics_event_name_linter.rb b/lib/linters/analytics_event_name_linter.rb index 8bd9aa8f5d4..eed859cd3d4 100644 --- a/lib/linters/analytics_event_name_linter.rb +++ b/lib/linters/analytics_event_name_linter.rb @@ -4,7 +4,7 @@ module RuboCop module Cop module IdentityIdp class AnalyticsEventNameLinter < RuboCop::Cop::Cop - RESTRICT_ON_SEND = [:track_event] + RESTRICT_ON_SEND = [:track_event].freeze # DO NOT ADD TO THIS LIST OR YOU WILL MAKE A KITTEN CRY! LEGACY_EVENT_NAMES = %w[ diff --git a/lib/linters/errors_add_linter.rb b/lib/linters/errors_add_linter.rb index a6298430c40..3c36112fe4d 100644 --- a/lib/linters/errors_add_linter.rb +++ b/lib/linters/errors_add_linter.rb @@ -17,7 +17,7 @@ module IdentityIdp class ErrorsAddLinter < RuboCop::Cop::Cop MSG = 'Please set a unique key for this error' - RESTRICT_ON_SEND = [:add] + RESTRICT_ON_SEND = [:add].freeze def_node_matcher :errors_add_match?, <<~PATTERN (send (send nil? :errors) :add $...) diff --git a/lib/linters/image_size_linter.rb b/lib/linters/image_size_linter.rb index d5be254c484..b5f37076489 100644 --- a/lib/linters/image_size_linter.rb +++ b/lib/linters/image_size_linter.rb @@ -18,7 +18,7 @@ module IdentityIdp class ImageSizeLinter < RuboCop::Cop::Cop MSG = 'Assign width and height to images' - RESTRICT_ON_SEND = [:image_tag] + RESTRICT_ON_SEND = [:image_tag].freeze def on_send(node) add_offense(node, location: :expression) if !valid?(node) diff --git a/lib/linters/redirect_back_linter.rb b/lib/linters/redirect_back_linter.rb index 81fea5c0eca..cc756ebd54d 100644 --- a/lib/linters/redirect_back_linter.rb +++ b/lib/linters/redirect_back_linter.rb @@ -19,7 +19,7 @@ module IdentityIdp class RedirectBackLinter < RuboCop::Cop::Cop MSG = 'Please set a fallback_location and the allow_other_host parameter to false' - RESTRICT_ON_SEND = [:redirect_back] + RESTRICT_ON_SEND = [:redirect_back].freeze def_node_matcher :redirect_back_matcher, <<~PATTERN (send nil? :redirect_back $...) diff --git a/lib/pwned_password_downloader.rb b/lib/pwned_password_downloader.rb index 7b4371d18da..21699aed998 100755 --- a/lib/pwned_password_downloader.rb +++ b/lib/pwned_password_downloader.rb @@ -18,7 +18,7 @@ class PwnedPasswordDownloader RANGE_API_ROOT = 'https://api.pwnedpasswords.com/range/' SHA1_LENGTH = 40 HASH_PREFIX_LENGTH = 5 - OCCURRENCE_OFFSET = SHA1_LENGTH - HASH_PREFIX_LENGTH + ':'.length + OCCURRENCE_OFFSET = (SHA1_LENGTH - HASH_PREFIX_LENGTH + ':'.length).freeze def initialize( destination: 'tmp/pwned', diff --git a/lib/saml_idp_constants.rb b/lib/saml_idp_constants.rb index f210d301b82..b50d4e02454 100644 --- a/lib/saml_idp_constants.rb +++ b/lib/saml_idp_constants.rb @@ -34,7 +34,7 @@ module Constants REQUESTED_ATTRIBUTES_CLASSREF = 'http://idmanagement.gov/ns/requested_attributes?ReqAttr=' - VALID_AUTHN_CONTEXTS = IdentityConfig.store.valid_authn_contexts + VALID_AUTHN_CONTEXTS = IdentityConfig.store.valid_authn_contexts.freeze IAL2_AUTHN_CONTEXTS = [IAL2_AUTHN_CONTEXT_CLASSREF, LOA3_AUTHN_CONTEXT_CLASSREF].freeze AUTHN_CONTEXT_CLASSREF_TO_IAL = { diff --git a/lib/session_encryptor.rb b/lib/session_encryptor.rb index 919c06475d3..c7fd896a456 100644 --- a/lib/session_encryptor.rb +++ b/lib/session_encryptor.rb @@ -30,7 +30,7 @@ class SensitiveValueError < StandardError; end ['flash', 'flashes', 'personal_key'], ['flash', 'flashes', 'email'], ['email'], - ] + ].freeze SENSITIVE_DEFAULT_FIELDS = Idp::Constants::MOCK_IDV_APPLICANT.slice( :last_name, @@ -38,7 +38,7 @@ class SensitiveValueError < StandardError; end :city, :dob, :state_id_expiration, - ).values + ).values.freeze SENSITIVE_REGEX = %r{#{SENSITIVE_DEFAULT_FIELDS.join('|')}}i def load(value) diff --git a/lib/telephony/pinpoint/sms_sender.rb b/lib/telephony/pinpoint/sms_sender.rb index ce0700f4f53..c9321fc02fb 100644 --- a/lib/telephony/pinpoint/sms_sender.rb +++ b/lib/telephony/pinpoint/sms_sender.rb @@ -17,6 +17,7 @@ class SmsSender # One connection pool per config (aka per-region) # @param [Hash>] + # rubocop:disable Style/MutableConstant CLIENT_POOL = Hash.new do |h, sms_config| h[sms_config] = ConnectionPool.new(size: IdentityConfig.store.pinpoint_voice_pool_size) do credentials = AwsCredentialBuilder.new(sms_config).call @@ -28,6 +29,7 @@ class SmsSender ) end end + # rubocop:enable Style/MutableConstant # rubocop:disable Metrics/BlockLength # rubocop:disable Lint/UnusedMethodArgument diff --git a/lib/telephony/pinpoint/voice_sender.rb b/lib/telephony/pinpoint/voice_sender.rb index 738e4e0d9a5..0a371936616 100644 --- a/lib/telephony/pinpoint/voice_sender.rb +++ b/lib/telephony/pinpoint/voice_sender.rb @@ -6,6 +6,7 @@ module Telephony module Pinpoint class VoiceSender # One connection pool per config (aka per-region) + # rubocop:disable Style/MutableConstant CLIENT_POOL = Hash.new do |h, voice_config| h[voice_config] = ConnectionPool.new(size: IdentityConfig.store.pinpoint_voice_pool_size) do credentials = AwsCredentialBuilder.new(voice_config).call @@ -17,6 +18,7 @@ class VoiceSender ) end end + # rubocop:enable Style/MutableConstant # rubocop:disable Lint/UnusedMethodArgument # rubocop:disable Metrics/BlockLength diff --git a/scripts/changelog_check.rb b/scripts/changelog_check.rb index 4d5cd9e5a6d..204e1924d57 100755 --- a/scripts/changelog_check.rb +++ b/scripts/changelog_check.rb @@ -11,7 +11,7 @@ 'Bug Fixes', 'Internal', 'Upcoming Features', -] +].freeze MAX_CATEGORY_DISTANCE = 3 SKIP_CHANGELOG_MESSAGE = '[skip changelog]' DEPENDABOT_COMMIT_MESSAGE = 'Signed-off-by: dependabot[bot] ' diff --git a/spec/config/initializers/ab_tests_spec.rb b/spec/config/initializers/ab_tests_spec.rb deleted file mode 100644 index 762a213f6c5..00000000000 --- a/spec/config/initializers/ab_tests_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'rails_helper' - -RSpec.describe AbTests do - def reload_ab_test_initializer! - # undefine the AB tests instances so we can re-initialize them with different config values - AbTests.constants.each do |const_name| - AbTests.class_eval { remove_const(const_name) } - end - load Rails.root.join('config', 'initializers', 'ab_tests.rb').to_s - end - - describe '::NATIVE_CAMERA' do - let(:percent) { 30 } - - before do - allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize). - and_return(true) - allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_percent). - and_return(percent) - - reload_ab_test_initializer! - end - - after do - allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize). - and_call_original - allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_percent). - and_call_original - - reload_ab_test_initializer! - end - - context 'configured with buckets adding up to less than 100 percent' do - let(:subject) { described_class::DOC_AUTH_VENDOR } - let(:a_uuid) { SecureRandom.uuid } - let(:b_uuid) { SecureRandom.uuid } - before do - allow(subject).to receive(:percent).with(a_uuid).and_return(percent) - allow(subject).to receive(:percent).with(b_uuid).and_return(percent + 1) - end - it 'sorts uuids into the buckets' do - expect(subject.bucket(a_uuid)).to eq(:alternate_vendor) - expect(subject.bucket(b_uuid)).to eq(:default) - end - end - end -end diff --git a/spec/controllers/two_factor_authentication/options_controller_spec.rb b/spec/controllers/two_factor_authentication/options_controller_spec.rb index ca715171b2a..6159c46e7ef 100644 --- a/spec/controllers/two_factor_authentication/options_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/options_controller_spec.rb @@ -27,7 +27,7 @@ it 'redirects to login_two_factor_url if user selects sms' do post :create, params: { two_factor_options_form: { selection: 'sms' } } - expect(response).to redirect_to otp_send_url( \ + expect(response).to redirect_to otp_send_url( otp_delivery_selection_form: { otp_delivery_preference: 'sms' }, ) end @@ -35,7 +35,7 @@ it 'redirects to login_two_factor_url if user selects voice' do post :create, params: { two_factor_options_form: { selection: 'voice' } } - expect(response).to redirect_to otp_send_url( \ + expect(response).to redirect_to otp_send_url( otp_delivery_selection_form: { otp_delivery_preference: 'voice' }, ) end diff --git a/spec/features/account_connected_apps_spec.rb b/spec/features/account_connected_apps_spec.rb index f4ac013270c..ff8581a4ea3 100644 --- a/spec/features/account_connected_apps_spec.rb +++ b/spec/features/account_connected_apps_spec.rb @@ -42,13 +42,13 @@ ) expect(page).to_not have_link(identity_without_link.display_name) - expect(page).to have_content( \ + expect(page).to have_content( t( 'event_types.authenticated_at_html', service_provider_link_html: identity_with_link.display_name, ), ) - expect(page).to have_link( \ + expect(page).to have_link( identity_with_link.display_name, href: 'http://localhost:3000' ) diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index c0a46501b95..f4367d1fcaf 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -934,8 +934,6 @@ def wait_for_event(event, wait) to receive(:biometric_comparison_required?). and_return(true) allow_any_instance_of(DocAuth::Response).to receive(:selfie_status).and_return(:success) - allow_any_instance_of(DocumentCaptureSessionResult). - to receive(:selfie_status).and_return(:success) perform_in_browser(:desktop) do sign_in_and_2fa_user(user) diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index 34f4dedeb6b..cd99f9d23de 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -6,7 +6,7 @@ # List of keys allowed to contain different interpolation arguments across locales ALLOWED_INTERPOLATION_MISMATCH_KEYS = [ 'time.formats.event_timestamp_js', -] +].freeze # A set of patterns which are expected to only occur within specific locales. This is an imperfect # solution based on current content, intended to help prevent accidents when adding new translated diff --git a/spec/lib/telephony/pinpoint/sms_sender_spec.rb b/spec/lib/telephony/pinpoint/sms_sender_spec.rb index 4ea38e7d289..b849dd188b2 100644 --- a/spec/lib/telephony/pinpoint/sms_sender_spec.rb +++ b/spec/lib/telephony/pinpoint/sms_sender_spec.rb @@ -24,19 +24,13 @@ def ==(other) let(:status_message) { 'some status message' } before do - mock_build_client + mock_build_clients(client: mock_client, backup_client: nil) Pinpoint::MockClient.message_response_result_status_code = status_code Pinpoint::MockClient.message_response_result_delivery_status = delivery_status Pinpoint::MockClient.message_response_result_status_message = status_message end - around do |ex| - ex.run - ensure - Telephony::Pinpoint::SmsSender::CLIENT_POOL.clear - end - context 'when endpoint is a duplicate' do let(:delivery_status) { 'DUPLICATE' } @@ -177,17 +171,11 @@ def ==(other) } end - around do |ex| - ex.run - ensure - Telephony::Pinpoint::SmsSender::CLIENT_POOL.clear - end - context 'in a country with sender_id' do let(:country_code) { 'PH' } it 'sends a message with a sender_id and no origination number' do - mock_build_client + mock_build_clients(client: mock_client, backup_client: nil) response = subject.deliver( message: 'This is a test!', to: '+1 (604) 456-7890', @@ -219,7 +207,7 @@ def ==(other) context 'in the US' do it 'sends a message with a shortcode and no sender_id' do - mock_build_client + mock_build_clients(client: mock_client, backup_client: nil) response = subject.deliver( message: 'This is a test!', to: '+1 (414) 456-7890', @@ -253,7 +241,7 @@ def ==(other) let(:country_code) { 'PR' } it 'sends a message with a longcode and no sender_id' do - mock_build_client + mock_build_clients(client: mock_client, backup_client: nil) response = subject.deliver( message: 'This is a test!', to: '+1 (939) 456-7890', @@ -287,7 +275,7 @@ def ==(other) let(:country_code) { 'MX' } it 'sends a message with a longcode and no sender_id' do - mock_build_client + mock_build_clients(client: mock_client, backup_client: nil) response = subject.deliver( message: 'This is a test!', to: '+525555555555', @@ -326,14 +314,7 @@ def ==(other) sms.application_id = 'backup-sms-application-id' end - mock_build_client - mock_build_backup_client - end - - around do |ex| - ex.run - ensure - Telephony::Pinpoint::SmsSender::CLIENT_POOL.clear + mock_build_clients end context 'when the first config succeeds' do @@ -448,17 +429,12 @@ def ==(other) end before do - mock_build_client - mock_build_backup_client + mock_build_clients(client: mock_client, backup_client: nil) allow(mock_client).to receive(:send_messages).and_return(phone_numbers) allow(backup_mock_client).to receive(:send_messages).and_return(phone_numbers) end - after do - Telephony::Pinpoint::SmsSender::CLIENT_POOL.clear - end - it 'does not include the phone number in the results' do response = subject.deliver( message: 'This is a test!', @@ -471,13 +447,19 @@ def ==(other) end end - def mock_build_client(client = mock_client) - Telephony::Pinpoint::SmsSender::CLIENT_POOL[sms_config] = FakeConnectionPool.new { client } - end + def mock_build_clients(client: mock_client, backup_client: backup_mock_client) + clients = { + sms_config => FakeConnectionPool.new { client }, + } + + if backup_client + clients[backup_sms_config] = FakeConnectionPool.new { backup_client } + end - def mock_build_backup_client(client = backup_mock_client) - Telephony::Pinpoint::SmsSender::CLIENT_POOL[backup_sms_config] = - FakeConnectionPool.new { client } + stub_const( + 'Telephony::Pinpoint::SmsSender::CLIENT_POOL', + clients, + ) end describe '#phone_info' do @@ -496,13 +478,7 @@ def mock_build_backup_client(client = backup_mock_client) sms.application_id = 'backup-sms-application-id' end - Telephony::Pinpoint::SmsSender::CLIENT_POOL.clear - mock_build_client(pinpoint_client) - mock_build_backup_client(pinpoint_client) - end - - after do - Telephony::Pinpoint::SmsSender::CLIENT_POOL.clear + mock_build_clients(client: pinpoint_client, backup_client: pinpoint_client) end context 'successful network requests' do diff --git a/spec/lib/telephony/pinpoint/voice_sender_spec.rb b/spec/lib/telephony/pinpoint/voice_sender_spec.rb index bf73199dfdb..d0961f64296 100644 --- a/spec/lib/telephony/pinpoint/voice_sender_spec.rb +++ b/spec/lib/telephony/pinpoint/voice_sender_spec.rb @@ -11,14 +11,19 @@ let(:backup_pinpoint_client) { Aws::PinpointSMSVoice::Client.new(stub_responses: true) } let(:backup_voice_config) { Telephony.config.pinpoint.voice_configs.last } - def mock_build_client - Telephony::Pinpoint::VoiceSender::CLIENT_POOL[voice_config] = - FakeConnectionPool.new { pinpoint_client } - end + def mock_build_clients(client: pinpoint_client, backup_client: backup_pinpoint_client) + clients = { + voice_config => FakeConnectionPool.new { client }, + } + + if backup_client + clients[backup_voice_config] = FakeConnectionPool.new { backup_client } + end - def mock_build_backup_client - Telephony::Pinpoint::VoiceSender::CLIENT_POOL[backup_voice_config] = - FakeConnectionPool.new { backup_pinpoint_client } + stub_const( + 'Telephony::Pinpoint::VoiceSender::CLIENT_POOL', + clients, + ) end describe '#deliver' do @@ -46,12 +51,7 @@ def mock_build_backup_client # More deterministic sending phone Telephony.config.pinpoint.voice_configs.first.longcode_pool = [sending_phone] - Telephony::Pinpoint::VoiceSender::CLIENT_POOL.clear - mock_build_client - end - - after do - Telephony::Pinpoint::VoiceSender::CLIENT_POOL.clear + mock_build_clients(client: pinpoint_client, backup_client: nil) end it 'initializes a pinpoint sms and voice client and uses that to send a message' do @@ -203,7 +203,7 @@ def mock_build_backup_client voice.longcode_pool = [backup_longcode] end - mock_build_backup_client + mock_build_clients end let(:backup_longcode) { '+18881112222' } diff --git a/spec/requests/redis_down_spec.rb b/spec/requests/redis_down_spec.rb index 4a60038232d..f7ef4441eee 100644 --- a/spec/requests/redis_down_spec.rb +++ b/spec/requests/redis_down_spec.rb @@ -3,7 +3,7 @@ RSpec.describe 'redis down session error handling' do context 'with bad Redis connection' do it 'fails loudly' do - allow(REDIS_POOL).to receive(:with).and_raise(Redis::CannotConnectError) + allow_any_instance_of(Redis).to receive(:set).and_raise(Redis::CannotConnectError) expect do get forgot_password_path end.to raise_error(Redis::CannotConnectError) diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb index 79938a61654..adf18dced02 100644 --- a/spec/services/doc_auth_router_spec.rb +++ b/spec/services/doc_auth_router_spec.rb @@ -35,7 +35,6 @@ def reload_ab_test_initializer! let(:doc_auth_vendor) { 'test1' } let(:doc_auth_vendor_randomize_alternate_vendor) { 'test2' } - let(:discriminator) { SecureRandom.uuid } let(:analytics) { FakeAnalytics.new } let(:doc_auth_vendor_randomize_percent) { 57 } let(:doc_auth_vendor_randomize) { true } @@ -63,51 +62,14 @@ def reload_ab_test_initializer! end context 'with a nil discriminator' do - let(:discriminator) { nil } - it 'is the default vendor, and logs analytics events' do expect(analytics).to receive(:idv_doc_auth_randomizer_defaulted) - result = DocAuthRouter.doc_auth_vendor(discriminator: discriminator, analytics: analytics) + result = DocAuthRouter.doc_auth_vendor(discriminator: nil, analytics: analytics) expect(result).to eq(doc_auth_vendor) end end - - context 'with a discriminator that hashes inside the test group' do - before do - allow(AbTests::DOC_AUTH_VENDOR). - to receive(:percent).with(discriminator). - and_return(doc_auth_vendor_randomize_percent - 1) - end - - it 'is the alternate vendor' do - expect(DocAuthRouter.doc_auth_vendor(discriminator: discriminator)). - to eq(doc_auth_vendor_randomize_alternate_vendor) - end - - context 'with randomize false' do - let(:doc_auth_vendor_randomize) { false } - - it 'is the original vendor' do - expect(DocAuthRouter.doc_auth_vendor(discriminator: discriminator)). - to eq(doc_auth_vendor) - end - end - end - - context 'with a discriminator that hashes outside the test group' do - before do - allow(AbTests::DOC_AUTH_VENDOR). - to receive(:percent).with(discriminator). - and_return(doc_auth_vendor_randomize_percent + 1) - end - - it 'is the original' do - expect(DocAuthRouter.doc_auth_vendor(discriminator: discriminator)). - to eq(doc_auth_vendor) - end - end end describe DocAuthRouter::DocAuthErrorTranslatorProxy do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d66cd7f1c9c..104cd27bb60 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,7 +9,7 @@ require 'active_support/core_ext/object/blank' require 'active_support' -RSPEC_RUNNING_IN_PARALLEL = ENV['PARALLEL_PID_FILE'].present? +RSPEC_RUNNING_IN_PARALLEL = ENV['PARALLEL_PID_FILE'].present?.freeze RSpec.configure do |config| # see more settings at spec/rails_helper.rb diff --git a/spec/support/fake_analytics.rb b/spec/support/fake_analytics.rb index a0a1acf0c2b..8276bdba3d9 100644 --- a/spec/support/fake_analytics.rb +++ b/spec/support/fake_analytics.rb @@ -1,5 +1,5 @@ class FakeAnalytics < Analytics - PiiDetected = Class.new(StandardError) + PiiDetected = Class.new(StandardError).freeze include AnalyticsEvents prepend Idv::AnalyticsEventsEnhancer @@ -69,7 +69,7 @@ def track_event(event, original_attributes = {}) end end - UndocumentedParams = Class.new(StandardError) + UndocumentedParams = Class.new(StandardError).freeze module UndocumentedParamsChecker mattr_accessor :allowed_extra_analytics diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb index 3ef2fb5377e..4d99e12d389 100644 --- a/spec/support/features/doc_auth_helper.rb +++ b/spec/support/features/doc_auth_helper.rb @@ -7,11 +7,11 @@ module DocAuthHelper include DocumentCaptureStepHelper include UserAgentHelper - GOOD_SSN = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] - GOOD_SSN_MASKED = '9**-**-***4' - SAMPLE_TMX_SUMMARY_REASON_CODE = { tmx_summary_reason_code: ['Identity_Negative_History'] } - SSN_THAT_FAILS_RESOLUTION = '123-45-6666' - SSN_THAT_RAISES_EXCEPTION = '000-00-0000' + GOOD_SSN = (Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn]).freeze + GOOD_SSN_MASKED = '9**-**-***4'.freeze + SAMPLE_TMX_SUMMARY_REASON_CODE = { tmx_summary_reason_code: ['Identity_Negative_History'] }.freeze + SSN_THAT_FAILS_RESOLUTION = '123-45-6666'.freeze + SSN_THAT_RAISES_EXCEPTION = '000-00-0000'.freeze def clear_and_fill_in(field_name, text) fill_in field_name, with: '' diff --git a/spec/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb index b9df37ae853..98cedc76a37 100644 --- a/spec/support/features/in_person_helper.rb +++ b/spec/support/features/in_person_helper.rb @@ -5,31 +5,32 @@ module InPersonHelper include IdvStepHelper include DocAuthHelper - GOOD_FIRST_NAME = Idp::Constants::MOCK_IDV_APPLICANT[:first_name] - GOOD_LAST_NAME = Idp::Constants::MOCK_IDV_APPLICANT[:last_name] + GOOD_FIRST_NAME = (Idp::Constants::MOCK_IDV_APPLICANT[:first_name]).freeze + GOOD_LAST_NAME = (Idp::Constants::MOCK_IDV_APPLICANT[:last_name]).freeze # the date in the format '1938-10-06' - GOOD_DOB = Idp::Constants::MOCK_IDV_APPLICANT[:dob] + GOOD_DOB = (Idp::Constants::MOCK_IDV_APPLICANT[:dob]).freeze # the date in the format 'October 6, 1938' GOOD_DOB_FORMATTED_EVENT = I18n.l( Date.parse(GOOD_DOB), format: I18n.t('time.formats.event_date') - ) + ).freeze GOOD_STATE_ID_JURISDICTION = Idp::Constants::MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION - GOOD_STATE_ID_NUMBER = Idp::Constants::MOCK_IDV_APPLICANT[:state_id_number] + GOOD_STATE_ID_NUMBER = (Idp::Constants::MOCK_IDV_APPLICANT[:state_id_number]).freeze - GOOD_ADDRESS1 = Idp::Constants::MOCK_IDV_APPLICANT[:address1] - GOOD_ADDRESS2 = Idp::Constants::MOCK_IDV_APPLICANT[:address2] - GOOD_CITY = Idp::Constants::MOCK_IDV_APPLICANT[:city] - GOOD_ZIPCODE = Idp::Constants::MOCK_IDV_APPLICANT[:zipcode] + GOOD_ADDRESS1 = (Idp::Constants::MOCK_IDV_APPLICANT[:address1]).freeze + GOOD_ADDRESS2 = (Idp::Constants::MOCK_IDV_APPLICANT[:address2]).freeze + GOOD_CITY = (Idp::Constants::MOCK_IDV_APPLICANT[:city]).freeze + GOOD_ZIPCODE = (Idp::Constants::MOCK_IDV_APPLICANT[:zipcode]).freeze GOOD_STATE = Idp::Constants::MOCK_IDV_APPLICANT_FULL_STATE GOOD_IDENTITY_DOC_ADDRESS1 = - Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_address1] + (Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_address1]).freeze GOOD_IDENTITY_DOC_ADDRESS2 = - Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_address2] + (Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_address2]).freeze GOOD_IDENTITY_DOC_ADDRESS_STATE = Idp::Constants::MOCK_IDV_APPLICANT_FULL_IDENTITY_DOC_ADDRESS_STATE - GOOD_IDENTITY_DOC_CITY = Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_city] + GOOD_IDENTITY_DOC_CITY = + (Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_city]).freeze GOOD_IDENTITY_DOC_ZIPCODE = - Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_zipcode] + (Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_zipcode]).freeze def fill_out_state_id_form_ok(same_address_as_id: false) fill_in t('in_person_proofing.form.state_id.first_name'), with: GOOD_FIRST_NAME diff --git a/spec/support/saml_auth_helper.rb b/spec/support/saml_auth_helper.rb index a59b36d44ec..8db5adbbd2b 100644 --- a/spec/support/saml_auth_helper.rb +++ b/spec/support/saml_auth_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'saml_idp_constants' ## GET /api/saml/auth helper methods From 49d913acf395f2a11bafec9bc348df2fa89daa58 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 4 Apr 2024 11:57:02 -0400 Subject: [PATCH 06/17] Check duplicate element IDs in accessibility tests (#10362) * Update accessibility checkers to latest version * Incorporate latest WCAG 2 AA (ideally 2.2) Section 508 is redundant with WCAG 2 AA: "The Revised 508 Standards incorporate by reference the WCAG 2.0 Level AA Success Criteria, and apply the WCAG 2.0 Level AA success criteria and conformance requirements to both web and non-web electronic content." https://www.section508.gov/develop/applicability-conformance/ * Check duplicate element IDs in accessibility tests changelog: Internal, Automated Testing, Check duplicate element IDs in accessibility tests * Temporary: Demonstrate failure * Revert "Temporary: Demonstrate failure" This reverts commit 12ae6e40e06165df5473b29aab84e2d8fe64d1ef. * Fix duplicate form ID on how to verify page * Check WCAG 2.2 AA The intent of the previous code was "latest 2.x", meaning 2.2 currently. In reality, "wcag2aa" means "2.0" specifically. Ref: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#axe-core-tags --- Gemfile.lock | 6 +++--- app/views/idv/how_to_verify/show.html.erb | 14 ++++++++++---- spec/support/matchers/accessibility.rb | 23 +++++++++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4b07a48e50b..41042eae319 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -199,10 +199,10 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) - axe-core-api (4.7.0) + axe-core-api (4.9.0) dumb_delegator virtus - axe-core-rspec (4.7.0) + axe-core-rspec (4.9.0) axe-core-api dumb_delegator virtus @@ -448,7 +448,7 @@ GEM net-ssh (6.1.0) newrelic_rpm (9.7.0) nio4r (2.7.0) - nokogiri (1.16.2) + nokogiri (1.16.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) openssl (3.0.2) diff --git a/app/views/idv/how_to_verify/show.html.erb b/app/views/idv/how_to_verify/show.html.erb index 2955d67dbce..7a2b5c96e9a 100644 --- a/app/views/idv/how_to_verify/show.html.erb +++ b/app/views/idv/how_to_verify/show.html.erb @@ -16,8 +16,11 @@
<%= simple_form_for( @idv_how_to_verify_form, - html: { autocomplete: 'off', - 'aria-label': t('forms.buttons.continue_remote') }, + html: { + autocomplete: 'off', + id: nil, + aria: { label: t('forms.buttons.continue_remote') }, + }, method: :put, url: idv_how_to_verify_url, ) do |f| @@ -53,8 +56,11 @@
<%= simple_form_for( @idv_how_to_verify_form, - html: { autocomplete: 'off', - 'aria-label': t('forms.buttons.continue_ipp') }, + html: { + autocomplete: 'off', + id: nil, + aria: { label: t('forms.buttons.continue_ipp') }, + }, method: :put, url: idv_how_to_verify_url, ) do |f| diff --git a/spec/support/matchers/accessibility.rb b/spec/support/matchers/accessibility.rb index b9e295ecc66..f32e7e75dd6 100644 --- a/spec/support/matchers/accessibility.rb +++ b/spec/support/matchers/accessibility.rb @@ -170,6 +170,23 @@ def landmarks(page) end end +RSpec::Matchers.define :have_unique_ids do + def ids(page) + page.all(:css, '[id]').map { |element| element[:id] } + end + + match do |page| + page_ids = ids(page) + page_ids.uniq.count == page_ids.count + end + + failure_message do |page| + page_ids = ids(page) + duplicate = page_ids.detect { |id| page_ids.count(id) > 1 } + "Expected no duplicate element IDs. Found duplicate: #{duplicate}" + end +end + RSpec::Matchers.define :tag_decorative_svgs_with_role do def decorative_svgs(page) page.all(:css, 'img[alt=""][src$=".svg" i]') @@ -310,13 +327,11 @@ def fieldset_legend_name(element) end def expect_page_to_have_no_accessibility_violations(page, validate_markup: true) - expect(page).to be_axe_clean.according_to( - :section508, :"best-practice", - :wcag21aa - ). + expect(page).to be_axe_clean.according_to(:wcag22aa, :"best-practice"). # Axe flags redundant img role on img elements, but is necessary for a Safari bug # See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#identifying_svg_as_an_image excluding('img[alt=""][src$=".svg" i]') + expect(page).to have_unique_ids expect(page).to have_valid_idrefs expect(page).to label_required_fields expect(page).to have_valid_markup if validate_markup From b400fff9935c7248928582f823e1533248cd81ff Mon Sep 17 00:00:00 2001 From: Vraj Mohan Date: Thu, 4 Apr 2024 10:27:22 -0700 Subject: [PATCH 07/17] Use version 0.19.3-18f of saml-idp (#10364) This fixes log writing failures of the form 'log writing failed. "\xB5" from ASCII-8BIT to UTF-8'. changelog: Internal, Logging, Update dependency --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index ee8fd4bdcbb..c48a136b2df 100644 --- a/Gemfile +++ b/Gemfile @@ -70,7 +70,7 @@ gem 'rqrcode' gem 'ruby-progressbar' gem 'ruby-saml' gem 'safe_target_blank', '>= 1.0.2' -gem 'saml_idp', github: '18F/saml_idp', tag: '0.19.2-18f' +gem 'saml_idp', github: '18F/saml_idp', tag: '0.19.3-18f' gem 'scrypt' gem 'simple_form', '>= 5.0.2' gem 'stringex', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 41042eae319..38b8a805c49 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,10 +34,10 @@ GIT GIT remote: https://github.com/18F/saml_idp.git - revision: 4c858dab80cfe32081a7e6bd7cd76c43cc3ec778 - tag: 0.19.2-18f + revision: 95369fdd9336773b9983c8de71eb35a8c92e9683 + tag: 0.19.3-18f specs: - saml_idp (0.19.2.pre.18f) + saml_idp (0.19.3.pre.18f) activesupport builder faraday From 25e2326469e09f9aea65e5d0a25a25c152571afb Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:30:25 -0400 Subject: [PATCH 08/17] Fix manifest cache for local JavaScript feature tests (#10365) changelog: Internal, Automated Testing, Fix manifest cache for local JavaScript feature tests --- spec/rails_helper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index bcb9d022908..432897f5efd 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -75,9 +75,13 @@ class Analytics # rubocop:enable Style/GlobalVars # rubocop:disable Rails/Output print ' Bundling JavaScript and stylesheets... ' - system 'WEBPACK_PORT= yarn concurrently "yarn:build:*" > /dev/null 2>&1' + system 'yarn concurrently "yarn:build:*" > /dev/null 2>&1' puts '✨ Done!' # rubocop:enable Rails/Output + + # The JavaScript assets manifest is cached by the application. Since the preceding build will + # write a new manifest, instruct the application to refresh the cache from disk. + Rails.application.config.asset_sources.load_manifest end end From 1b8ce13216bffdb6ec93219c6ce3e0ef1ed13fe6 Mon Sep 17 00:00:00 2001 From: jc-gsa <104452882+jc-gsa@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:02:41 +0000 Subject: [PATCH 09/17] LG-12596: Update security key setup page (#10323) Update security key setup page changelog: User-Facing Improvements, Authentication, Update security key setup form --- .rubocop.yml | 1 - .../images/mfa-options/security_key.svg | 1 + .../mfa-options/security_key_mobile.svg | 1 + app/assets/images/security-key.svg | 1 - .../users/webauthn_setup_controller.rb | 1 + app/presenters/webauthn_setup_presenter.rb | 22 ++++-- app/views/users/webauthn_setup/new.html.erb | 55 ++++++++++---- config/locales/forms/en.yml | 15 +++- config/locales/forms/es.yml | 18 +++-- config/locales/forms/fr.yml | 20 +++-- config/locales/headings/en.yml | 2 +- config/locales/headings/es.yml | 2 +- config/locales/headings/fr.yml | 2 +- .../users/webauthn_setup_controller_spec.rb | 27 ++++++- .../webauthn_setup_presenter_spec.rb | 42 ++++++---- spec/support/features/webauthn_helper.rb | 10 ++- .../users/webauthn_setup/new.html.erb_spec.rb | 76 ++++++++++++++++++- 17 files changed, 233 insertions(+), 63 deletions(-) create mode 100644 app/assets/images/mfa-options/security_key.svg create mode 100644 app/assets/images/mfa-options/security_key_mobile.svg delete mode 100644 app/assets/images/security-key.svg diff --git a/.rubocop.yml b/.rubocop.yml index 96686cf9fc9..b949a8fc0e2 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, - }, - ) %> +
+ <%= render ProcessListComponent.new(connected: true) do |c| %> + <%= c.with_item(heading: t('forms.webauthn_setup.step_1'), heading_id: 'step-1-label') do %> +

<%= t('forms.webauthn_setup.step_1a') %>

+ <%= render ValidatedFieldComponent.new( + form: f, + name: :name, + required: true, + label: false, + hint: @presenter.device_nickname_hint, + wrapper_html: { class: 'margin-bottom-0' }, + input_html: { + aria: { labelledby: 'step-1-label' }, + id: 'nickname', + class: 'font-family-mono', + size: 16, + maxlength: 20, + }, + ) %> + <% end %> + <%= c.with_item(heading: t('forms.webauthn_setup.step_2')) do %> + <% if mobile? %> + <%= image_tag asset_url('mfa-options/security_key_mobile.svg'), width: 420, height: 193, class: 'height-auto', alt: t('forms.webauthn_setup.step_2_image_mobile_alt'), role: 'img' %> + <% else %> + <%= image_tag asset_url('mfa-options/security_key.svg'), width: 420, height: 193, class: 'height-auto', alt: t('forms.webauthn_setup.step_2_image_alt'), role: 'img' %> + <% end %> + <% end %> + <%= c.with_item(heading: t('forms.webauthn_setup.step_3')) do %> +

<%= t('forms.webauthn_setup.step_3a') %>

+ <% end %> + <% end %> +
<% end %>