From 183e52340dc50c65f855f1c17ca5a8626651970c Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Wed, 22 Jan 2025 13:36:39 -0500 Subject: [PATCH 01/14] LG-14973 Add PII check to Socure flow (#11747) * LG-14973 Add Pii check to Socure flow Check PII using DocPiiForm. changelog: Upcoming Features,Socure,Add Idv::DocPiiForm check to Socure flow. * Map Pii errors to general failures. * Feature specs for normal and hybrid flows. --- .../concerns/idv/document_capture_concern.rb | 1 + .../concerns/idv/socure_errors_concern.rb | 2 + .../idv/document_capture_controller.rb | 1 + .../document_capture_controller.rb | 1 + .../socure/document_capture_controller.rb | 1 + .../hybrid_mobile/socure/errors_controller.rb | 1 + .../idv/socure/document_capture_controller.rb | 1 + .../idv/socure/errors_controller.rb | 3 ++ app/jobs/socure_docv_results_job.rb | 1 + .../socure/responses/docv_result_response.rb | 22 ++++++--- .../doc_auth/socure_document_capture_spec.rb | 23 ++++++++++ .../hybrid_socure_mobile_spec.rb | 45 +++++++++++++++++++ spec/forms/idv/api_image_upload_form_spec.rb | 2 + .../responses/docv_result_response_spec.rb | 28 ++++++++++++ 14 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 spec/services/doc_auth/socure/responses/docv_result_response_spec.rb diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index f332cbad540..32807cfea33 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -33,6 +33,7 @@ def error_hash(message) { message: message || I18n.t('doc_auth.errors.general.network_error'), socure: stored_result&.errors&.dig(:socure), + pii_validation: stored_result&.errors&.dig(:pii_validation), } end diff --git a/app/controllers/concerns/idv/socure_errors_concern.rb b/app/controllers/concerns/idv/socure_errors_concern.rb index f22bfe66f21..76d2b8eeb3f 100644 --- a/app/controllers/concerns/idv/socure_errors_concern.rb +++ b/app/controllers/concerns/idv/socure_errors_concern.rb @@ -16,6 +16,8 @@ def error_code_for(result) result.errors.dig(:socure, :reason_codes).first elsif result.errors[:network] :network + elsif result.errors[:pii_validation] + :pii_validation else # No error information available (shouldn't happen). Default # to :network if it does. diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index caaa6695a23..ca73d9f35b4 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -111,6 +111,7 @@ def analytics_arguments skip_hybrid_handoff: idv_session.skip_hybrid_handoff, liveness_checking_required: resolved_authn_context_result.facial_match?, selfie_check_required: resolved_authn_context_result.facial_match?, + pii_like_keypaths: [[:pii]], }.merge(ab_test_analytics_buckets) end diff --git a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb index f3f9e52bbdd..372e73adccc 100644 --- a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb @@ -81,6 +81,7 @@ def analytics_arguments analytics_id: 'Doc Auth', liveness_checking_required: resolved_authn_context_result.facial_match?, selfie_check_required: resolved_authn_context_result.facial_match?, + pii_like_keypaths: [[:pii]], }.merge( ab_test_analytics_buckets, ) diff --git a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb index 7ecc0384e3e..aede97eece4 100644 --- a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb @@ -130,6 +130,7 @@ def analytics_arguments analytics_id: 'Doc Auth', liveness_checking_required: false, selfie_check_required: false, + pii_like_keypaths: [[:pii]], } end end diff --git a/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb b/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb index 43b47ac22cd..37b1d697917 100644 --- a/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb @@ -50,6 +50,7 @@ def track_event(error_code:) attributes = { error_code:, remaining_submit_attempts:, + pii_like_keypaths: [[:pii]], } analytics.idv_doc_auth_socure_error_visited(**attributes) diff --git a/app/controllers/idv/socure/document_capture_controller.rb b/app/controllers/idv/socure/document_capture_controller.rb index a25668f2e40..3af25fad09d 100644 --- a/app/controllers/idv/socure/document_capture_controller.rb +++ b/app/controllers/idv/socure/document_capture_controller.rb @@ -149,6 +149,7 @@ def analytics_arguments skip_hybrid_handoff: idv_session.skip_hybrid_handoff, liveness_checking_required: resolved_authn_context_result.facial_match?, selfie_check_required: resolved_authn_context_result.facial_match?, + pii_like_keypaths: [[:pii]], }.merge(ab_test_analytics_buckets) end end diff --git a/app/controllers/idv/socure/errors_controller.rb b/app/controllers/idv/socure/errors_controller.rb index 47ed86f0df9..7d3259896e4 100644 --- a/app/controllers/idv/socure/errors_controller.rb +++ b/app/controllers/idv/socure/errors_controller.rb @@ -50,6 +50,7 @@ def remaining_submit_attempts def track_event(error_code:) attributes = { error_code: }.merge(ab_test_analytics_buckets) attributes[:remaining_submit_attempts] = remaining_submit_attempts + attributes[:pii_like_keypaths] = [[:pii]] analytics.idv_doc_auth_socure_error_visited(**attributes) end @@ -72,6 +73,8 @@ def error_code_for(result) result.errors.dig(:socure, :reason_codes).first elsif result.errors[:network] :network + elsif result.errors[:pii_validation] + :pii_validation else # No error information available (shouldn't happen). Default # to :network if it does. diff --git a/app/jobs/socure_docv_results_job.rb b/app/jobs/socure_docv_results_job.rb index b4142a3b2b8..38c79c0ec05 100644 --- a/app/jobs/socure_docv_results_job.rb +++ b/app/jobs/socure_docv_results_job.rb @@ -44,6 +44,7 @@ def log_verification_request(docv_result_response:, vendor_request_time_in_ms:) remaining_submit_attempts: rate_limiter&.remaining_count, vendor_request_time_in_ms:, async:, + pii_like_keypaths: [[:pii]], ).except(:attention_with_barcode, :selfie_live, :selfie_quality_good, :selfie_status), ) diff --git a/app/services/doc_auth/socure/responses/docv_result_response.rb b/app/services/doc_auth/socure/responses/docv_result_response.rb index a2ccd250cae..bc89bab0d22 100644 --- a/app/services/doc_auth/socure/responses/docv_result_response.rb +++ b/app/services/doc_auth/socure/responses/docv_result_response.rb @@ -42,10 +42,10 @@ def initialize(http_response:, @pii_from_doc = read_pii super( - success: successful_result?, + success: successful_result? && pii_valid?, errors: error_messages, + pii_from_doc:, extra: extra_attributes, - pii_from_doc: @pii_from_doc, ) rescue StandardError => e NewRelic::Agent.notice_error(e) @@ -96,11 +96,13 @@ def successful_result? end def error_messages - return {} if successful_result? - - { - socure: { reason_codes: get_data(DATA_PATHS[:reason_codes]) }, - } + if !successful_result? + { socure: { reason_codes: get_data(DATA_PATHS[:reason_codes]) } } + elsif !pii_valid? + { pii_validation: 'failed' } + else + {} + end end def read_pii @@ -176,6 +178,12 @@ def parse_date(date_string) Rails.logger.info(message) nil end + + def pii_valid? + return @pii_valid if !@pii_valid.nil? + + @pii_valid = Idv::DocPiiForm.new(pii: pii_from_doc.to_h).submit.success? + end end end end diff --git a/spec/features/idv/doc_auth/socure_document_capture_spec.rb b/spec/features/idv/doc_auth/socure_document_capture_spec.rb index 10a09a4e27e..247b31b3a77 100644 --- a/spec/features/idv/doc_auth/socure_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/socure_document_capture_spec.rb @@ -460,6 +460,29 @@ it_behaves_like 'a properly categorized Socure error', 'I856', 'doc_auth.headers.id_not_found' end + context 'Pii validation fails' do + before do + allow_any_instance_of(Idv::DocPiiForm).to receive(:zipcode).and_return(:invalid_junk) + end + + it 'presents as a type 1 error' do + stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + + visit_idp_from_oidc_sp_with_ial2 + @user = sign_in_and_2fa_user + + complete_doc_auth_steps_before_document_capture_step + click_idv_continue + + socure_docv_upload_documents( + docv_transaction_token: @docv_transaction_token, + ) + visit idv_socure_document_capture_update_path + + expect(page).to have_content(t('doc_auth.headers.unreadable_id')) + end + end + def expect_rate_limited_header(expected_to_be_present) review_issues_h1_heading = strip_tags(t('doc_auth.errors.rate_limited_heading')) if expected_to_be_present diff --git a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb index d7e70bc5a44..768614a0afe 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb @@ -573,5 +573,50 @@ it_behaves_like 'document request API failure' end + + context 'Pii validation fails' do + before do + allow_any_instance_of(Idv::DocPiiForm).to receive(:zipcode).and_return(:invalid_junk) + end + + it 'presents as a type 1 error', js: true do + user = nil + + perform_in_browser(:desktop) do + visit_idp_from_sp_with_ial2(sp) + user = sign_up_and_2fa_ial1_user + + complete_doc_auth_steps_before_hybrid_handoff_step + clear_and_fill_in(:doc_auth_phone, phone_number) + click_send_link + + expect(page).to have_content(t('doc_auth.headings.text_message')) + expect(page).to have_content(t('doc_auth.info.you_entered')) + expect(page).to have_content('+1 415-555-0199') + + # Confirm that Continue button is not shown when polling is enabled + expect(page).not_to have_content(t('doc_auth.buttons.continue')) + end + + expect(@sms_link).to be_present + + perform_in_browser(:mobile) do + visit @sms_link + + stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + + click_idv_continue + + socure_docv_upload_documents(docv_transaction_token: @docv_transaction_token) + visit idv_hybrid_mobile_socure_document_capture_update_url + + expect(page).to have_content(t('doc_auth.headers.unreadable_id')) + end + + perform_in_browser(:desktop) do + expect(page).to have_current_path(idv_link_sent_path) + end + end + end end end diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb index 8c74bbb742c..e87d699675b 100644 --- a/spec/forms/idv/api_image_upload_form_spec.rb +++ b/spec/forms/idv/api_image_upload_form_spec.rb @@ -40,6 +40,7 @@ fileName: front_image_file_name, } end + let(:back_image_metadata) do { width: 20, @@ -732,6 +733,7 @@ end end end + describe '#store_failed_images' do let(:doc_pii_response) { instance_double(Idv::DocAuthFormResponse) } let(:client_response) { instance_double(DocAuth::Response) } diff --git a/spec/services/doc_auth/socure/responses/docv_result_response_spec.rb b/spec/services/doc_auth/socure/responses/docv_result_response_spec.rb new file mode 100644 index 00000000000..dc8525994d4 --- /dev/null +++ b/spec/services/doc_auth/socure/responses/docv_result_response_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe DocAuth::Socure::Responses::DocvResultResponse do + subject(:docv_response) do + http_response = Struct.new(:body).new(SocureDocvFixtures.pass_json) + described_class.new(http_response:) + end + + context 'Socure says OK and the PII is valid' do + it 'succeeds' do + expect(docv_response.success?).to be(true) + end + end + + context 'Socure says OK but the PII is invalid' do + before do + allow_any_instance_of(Idv::DocPiiForm).to receive(:zipcode).and_return(:invalid_junk) + end + + it 'fails' do + expect(docv_response.success?).to be(false) + end + + it 'with a pii failure error' do + expect(docv_response.errors).to eq({ pii_validation: 'failed' }) + end + end +end From c1cda40e189ec20c0d8dec77f4af5dbbf8a325dd Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Wed, 22 Jan 2025 14:04:23 -0800 Subject: [PATCH 02/14] LG-15538: Add warning when returning local data (#11784) changelog: Internal, Scripts, Warn when data-pull is used locally --- lib/action_account.rb | 6 ++++-- lib/script_base.rb | 19 ++++++++++++++++--- spec/lib/action_account_spec.rb | 3 ++- spec/lib/script_base_spec.rb | 26 ++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lib/action_account.rb b/lib/action_account.rb index 74b920e3c9d..4d32fcea0dd 100644 --- a/lib/action_account.rb +++ b/lib/action_account.rb @@ -4,12 +4,13 @@ # rubocop:disable Metrics/BlockLength class ActionAccount - attr_reader :argv, :stdout, :stderr + attr_reader :argv, :stdout, :stderr, :rails_env - def initialize(argv:, stdout:, stderr:) + def initialize(argv:, stdout:, stderr:, rails_env: Rails.env) @argv = argv @stdout = stdout @stderr = stderr + @rails_env = rails_env end def script_base @@ -20,6 +21,7 @@ def script_base subtask_class: subtask(argv.shift), banner: banner, reason_arg: true, + rails_env:, ) end diff --git a/lib/script_base.rb b/lib/script_base.rb index c6e0c95f790..c1f6224af67 100644 --- a/lib/script_base.rb +++ b/lib/script_base.rb @@ -3,15 +3,24 @@ require 'optparse' class ScriptBase - attr_reader :argv, :stdout, :stderr, :subtask_class, :banner - - def initialize(argv:, stdout:, stderr:, subtask_class:, banner:, reason_arg:) + attr_reader :argv, :stdout, :stderr, :subtask_class, :banner, :rails_env + + def initialize( + argv:, + stdout:, + stderr:, + subtask_class:, + banner:, + reason_arg:, + rails_env: Rails.env + ) @argv = argv @stdout = stdout @stderr = stderr @subtask_class = subtask_class @banner = banner @reason_arg = reason_arg + @rails_env = rails_env end def reason_arg? @@ -56,6 +65,10 @@ def config def run option_parser.parse!(argv) + if rails_env.local? + stderr.puts "⚠️ WARNING: returning local data for #{File.basename($PROGRAM_NAME)}" + end + if config.show_help? || !subtask_class stderr.puts '*Task*: `help`' stderr.puts '*UUIDs*: N/A' diff --git a/spec/lib/action_account_spec.rb b/spec/lib/action_account_spec.rb index 30382fdf9e6..939fcca0c53 100644 --- a/spec/lib/action_account_spec.rb +++ b/spec/lib/action_account_spec.rb @@ -6,8 +6,9 @@ let(:stdout) { StringIO.new } let(:stderr) { StringIO.new } let(:argv) { [] } + let(:rails_env) { ActiveSupport::EnvironmentInquirer.new('production') } - subject(:action_account) { ActionAccount.new(argv:, stdout:, stderr:) } + subject(:action_account) { ActionAccount.new(argv:, stdout:, stderr:, rails_env:) } describe 'command line run' do let(:argv) { ['review-pass', user.uuid, '--reason', 'INV1234'] } diff --git a/spec/lib/script_base_spec.rb b/spec/lib/script_base_spec.rb index f3e2c1379cb..dbbddf93ab5 100644 --- a/spec/lib/script_base_spec.rb +++ b/spec/lib/script_base_spec.rb @@ -22,6 +22,7 @@ def run(args:, config:) # rubocop:disable Lint/UnusedMethodArgument describe '#run' do let(:argv) { [] } + let(:env) { 'production' } subject(:base) do ScriptBase.new( @@ -31,9 +32,34 @@ def run(args:, config:) # rubocop:disable Lint/UnusedMethodArgument subtask_class:, banner: '', reason_arg: false, + rails_env: ActiveSupport::EnvironmentInquirer.new(env), ) end + context 'running in production vs locally' do + subject(:run) { base.run } + + context 'in production' do + let(:env) { 'production' } + + it 'does not warn' do + run + + expect(stderr.string).to_not include('WARNING') + end + end + + context 'in development' do + let(:env) { 'development' } + + it 'warns that it is in development' do + run + + expect(stderr.string).to include('WARNING: returning local data') + end + end + end + context 'with --deflate' do let(:argv) { %w[--deflate] } From cb9bccd6427f3de5150d8864b08922c351804486 Mon Sep 17 00:00:00 2001 From: Vraj Mohan Date: Thu, 23 Jan 2025 07:29:31 -0800 Subject: [PATCH 03/14] Update translations from LQA (#11763) changelog: User-Facing Improvements, Translations, Update translations from LQA 1. In French, change NIP to PIN. 2. In Chinese, use full-width parentheses with no extra spaces --- app/assets/fonts/glyphs.txt | 2 +- config/locales/en.yml | 4 ++-- config/locales/es.yml | 4 ++-- config/locales/fr.yml | 12 ++++++------ config/locales/zh.yml | 28 ++++++++++++++-------------- spec/i18n_spec.rb | 18 ++++++++++++++++++ 6 files changed, 43 insertions(+), 25 deletions(-) diff --git a/app/assets/fonts/glyphs.txt b/app/assets/fonts/glyphs.txt index cf22ca514ed..e005b16dcfc 100644 --- a/app/assets/fonts/glyphs.txt +++ b/app/assets/fonts/glyphs.txt @@ -1 +1 @@ - !"#$%&'()+,-./0123456789:;>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ «»¿ÀÁÈÉÊÎÓÚàáâãçèéêëíîïñóôùúû ‑—‘’“”…‹中体文简 + !"#$%&'()+,-./0123456789:;>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ «»¿ÀÁÈÉÊÎÓÚàáâãçèéêëíîïñóôùúû ‑—‘’“”…‹中体文简() diff --git a/config/locales/en.yml b/config/locales/en.yml index cbb62331643..d8a6e5f7916 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -49,7 +49,7 @@ account.email_language.languages_list: You will receive emails from %{app_name} account.email_language.name.en: English account.email_language.name.es: Español account.email_language.name.fr: Français -account.email_language.name.zh: 中文 (简体) +account.email_language.name.zh: 中文(简体) account.email_language.updated: Your email language preference has been updated. account.emails.confirmed_html: You have confirmed your email address. Go to your connected accounts to update the email you share with connected agencies. account.forget_all_browsers.longer_description: Once you choose to ‘forget all browsers,’ we’ll need additional information to know that it’s actually you signing in to your account. We’ll ask for a multi-factor authentication method (such as text/SMS code or a security key) each time you want to access your account. @@ -1007,7 +1007,7 @@ i18n.language: Language i18n.locale.en: English i18n.locale.es: Español i18n.locale.fr: Français -i18n.locale.zh: 中文 (简体) +i18n.locale.zh: 中文(简体) idv.accessible_labels.masked_ssn: secure text, starting with %{first_number} and ending with %{last_number} idv.buttons.change_address_label: Update address idv.buttons.change_label: Update diff --git a/config/locales/es.yml b/config/locales/es.yml index 352ce29a08c..5cc9d9b3fce 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -49,7 +49,7 @@ account.email_language.languages_list: Recibirá mensajes de correo electrónico account.email_language.name.en: English account.email_language.name.es: Español account.email_language.name.fr: Français -account.email_language.name.zh: 中文 (简体) +account.email_language.name.zh: 中文(简体) account.email_language.updated: Se actualizó su preferencia de idioma del correo electrónico. account.emails.confirmed_html: Usted confirmó su dirección de correo electrónico. Vaya a Sus cuentas conectadas para actualizar el correo electrónico que proporcionó a las agencias conectadas. account.forget_all_browsers.longer_description: Una vez que elija “Olvidar todos los navegadores”, necesitaremos más información para saber que realmente es usted quien está iniciando sesión en su cuenta. Le pediremos un método de autenticación multifactor (como código de texto o de SMS, o una clave de seguridad) cada vez que desee acceder a su cuenta. @@ -1018,7 +1018,7 @@ i18n.language: Idioma i18n.locale.en: English i18n.locale.es: Español i18n.locale.fr: Français -i18n.locale.zh: 中文 (简体) +i18n.locale.zh: 中文(简体) idv.accessible_labels.masked_ssn: texto seguro, a partir de %{first_number} y hasta %{last_number} idv.buttons.change_address_label: Actualizar dirección idv.buttons.change_label: Actualizar diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4fd1f5c090d..d5b3c779bb2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -49,7 +49,7 @@ account.email_language.languages_list: Vous recevrez des e-mails de %{app_name} account.email_language.name.en: English account.email_language.name.es: Español account.email_language.name.fr: Français -account.email_language.name.zh: 中文 (简体) +account.email_language.name.zh: 中文(简体) account.email_language.updated: Votre langue de préférence pour les e-mails a été mise à jour. account.emails.confirmed_html: Vous avez confirmé votre adresse e-mail. Rendez-vous sur vos comptes connectés pour actualiser l’adresse e-mail que vous communiquez aux organismes connectés. account.forget_all_browsers.longer_description: Une fois que vous aurez choisi d’« oublier tous les navigateurs », nous aurons besoin d’informations supplémentaires pour savoir que c’est bien vous qui vous connectez à votre compte. Nous vous demanderons une méthode d’authentification multi-facteurs (comme un code SMS/texto ou une clé de sécurité) chaque fois que vous souhaiterez accéder à votre compte. @@ -899,7 +899,7 @@ forms.piv_cac_login.submit: Insérer la carte PIV/CAC forms.piv_cac_mfa.submit: Insérer la carte PIV/CAC forms.piv_cac_setup.nickname: Surnom de la carte PIV/CAC forms.piv_cac_setup.no_thanks: Non, merci -forms.piv_cac_setup.piv_cac_intro_html: Nous vous demanderons de présenter votre carte PIV/CAC chaque fois que vous vous connecterez sur dans le cadre de l’authentification à deux facteurs.

Après avoir cliqué sur « Ajouter une carte PIV/CAC », votre navigateur vous demandera de saisir le NIP de votre PIV/CAC et de choisir un certificat. +forms.piv_cac_setup.piv_cac_intro_html: Nous vous demanderons de présenter votre carte PIV/CAC chaque fois que vous vous connecterez sur dans le cadre de l’authentification à deux facteurs.

Après avoir cliqué sur « Ajouter une carte PIV/CAC », votre navigateur vous demandera de saisir le PIN de votre PIV/CAC et de choisir un certificat. forms.piv_cac_setup.submit: Insérer la carte PIV/CAC forms.piv_cac_setup.try_again: Réessayer forms.registration.labels.email: Saisissez votre adresse e-mail @@ -1007,7 +1007,7 @@ i18n.language: Langue i18n.locale.en: English i18n.locale.es: Español i18n.locale.fr: Français -i18n.locale.zh: 中文 (简体) +i18n.locale.zh: 中文(简体) idv.accessible_labels.masked_ssn: SMS sécurisé, commençant par %{first_number} et finissant par %{last_number} idv.buttons.change_address_label: Mettre à jour votre adresse idv.buttons.change_label: Mettre à jour @@ -1373,7 +1373,7 @@ instructions.go_back_to_mobile_app: Pour continuer, veuillez retourner à l’ap instructions.mfa.authenticator.confirm_code_html: Saisissez le code à partir de votre appli d’authentification. Si vous avez plusieurs comptes configurés dans votre appli, saisissez le code correspondant à %{app_name_html}. instructions.mfa.authenticator.manual_entry: Ou saisissez ce code manuellement dans votre appli d’authentification instructions.mfa.piv_cac.account_not_found_html: '

%{sign_in} à l’aide de votre adresse e-mail et de votre mot de passe. Introduisez ensuite la carte PIV/CAC dans un lecteur de carte à puce pour l’ajouter à votre compte.

Vous n’avez pas de compte %{app_name}? %{create_account}

' -instructions.mfa.piv_cac.add_from_sign_in_html: ' Instructions : Insérez votre carte PIV ou CAC dans AJOUTER PIV/CAC. Vous devrez choisir un certificat (le bon a probablement le nom que vous lui avez donné) et saisir votre NIP (votre NIP a été créé lors de la configuration de votre carte PIV/CAC).' +instructions.mfa.piv_cac.add_from_sign_in_html: ' Instructions : Insérez votre carte PIV ou CAC dans AJOUTER PIV/CAC. Vous devrez choisir un certificat (le bon a probablement le nom que vous lui avez donné) et saisir votre PIN (votre PIN a été créé lors de la configuration de votre carte PIV/CAC).' instructions.mfa.piv_cac.already_associated_html: Veuillez choisir un certificat associé à une autre carte PIV/CAC ; contactez votre administrateur afin de vérifier que votre carte PIV/CAC est bien à jour. Si vous pensez qu’il s’agit d’une erreur, veuillez %{try_again_html}. instructions.mfa.piv_cac.back_to_sign_in: Retourner vous connecter instructions.mfa.piv_cac.confirm_piv_cac: Introduisez la carte PIV/CAC que vous avez associée à votre compte dans un lecteur de carte à puce. @@ -1387,7 +1387,7 @@ instructions.mfa.piv_cac.step_1: Donnez-lui un surnom instructions.mfa.piv_cac.step_1_info: Si vous ajoutez plus d’une carte PIV/CAC, vous saurez laquelle. instructions.mfa.piv_cac.step_2: Insérez votre PIV/CAC dans votre lecteur de carte instructions.mfa.piv_cac.step_3: Ajoutez votre carte PIV/CAC -instructions.mfa.piv_cac.step_3_info_html: Vous devrez choisir un certificat (le bon a probablement votre nom) et saisir votre NIP (votre NIP a été créé lors de la configuration de votre carte PIV/CAC). +instructions.mfa.piv_cac.step_3_info_html: Vous devrez choisir un certificat (le bon a probablement votre nom) et saisir votre PIN (votre PIN a été créé lors de la configuration de votre carte PIV/CAC). instructions.mfa.piv_cac.try_again: réessayer instructions.mfa.sms.number_message_html: Nous avons envoyé un SMS (texto) avec un code à usage unique au %{number_html}. Ce code expirera dans %{expiration} minutes. instructions.mfa.voice.number_message_html: Nous avons passé un appel avec un code à usage unique au %{number_html}. Ce code expirera dans %{expiration} minutes. @@ -1693,7 +1693,7 @@ two_factor_authentication.important_alert_icon: icône d’alerte importante two_factor_authentication.invalid_backup_code: Ce code de sauvegarde n’est pas valide. two_factor_authentication.invalid_otp: Ce code à usage unique n’est pas valide. Veuillez réessayer ou demander un nouveau code. two_factor_authentication.invalid_personal_key: Cette clé personnelle n’est pas valide. -two_factor_authentication.invalid_piv_cac: Cette carte PIV/CAC n’a pas fonctionné. Assurez-vous qu’il s’agit de la bonne carte PIV/CAC pour ce compte. Si c’est le cas, il se peut qu’il y ait un problème avec votre carte PIV/CAC ou votre NIP ou qu’un problème soit survenu de notre côté. Réessayez ou choisissez une autre méthode d’authentification. +two_factor_authentication.invalid_piv_cac: Cette carte PIV/CAC n’a pas fonctionné. Assurez-vous qu’il s’agit de la bonne carte PIV/CAC pour ce compte. Si c’est le cas, il se peut qu’il y ait un problème avec votre carte PIV/CAC ou votre PIN ou qu’un problème soit survenu de notre côté. Réessayez ou choisissez une autre méthode d’authentification. two_factor_authentication.learn_more: En savoir plus sur les options d’authentification two_factor_authentication.login_intro: Vous les avez configurées lorsque vous avez créé votre compte. two_factor_authentication.login_intro_reauthentication: Avant que vous puissiez apporter des modifications à votre compte, nous devons nous assurer qu’il s’agit bien de vous en utilisant l’une de vos méthodes d’authentification. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index ef97626b0ef..6d50599de34 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -43,13 +43,13 @@ account.connected_apps.associated_html: 已连接 %{timestamp_html} account.connected_apps.description: 使用你的 %{app_name} 账户,你可以安全地连接到网上多个政府账户。以下列出了你目前已连接的所有账户。 account.connected_apps.email_not_selected: 尚未选择电邮 account.connected_apps.email_update_success_html: 你给%{sp_name}的电邮更新成功了。 -account.email_language.default: '%{language} (默认)' +account.email_language.default: '%{language}(默认)' account.email_language.edit_title: 编辑电邮语言选择 account.email_language.languages_list: 您将收到%{app_name}用您选择的语言发送的电子邮件。 account.email_language.name.en: English account.email_language.name.es: Español account.email_language.name.fr: Français -account.email_language.name.zh: 中文 (简体) +account.email_language.name.zh: 中文(简体) account.email_language.updated: 你的电邮语言选择已更新。 account.emails.confirmed_html: 你已确认了你的电邮地址。请到你已连接的账户来更新你与已连接机构所分享的电邮。 account.forget_all_browsers.longer_description: 你选择“忘掉所有浏览器”后,我们将需要额外信息来知道的确是你在登录你自己的账户。每次你要访问自己的账户时,我们都会向你要一个多因素身份证实方法(比如短信/SMS 代码或安全密钥) @@ -158,7 +158,7 @@ banned_user.title: 访问受限。 components.barcode.image_alt: 条形码 components.captcha_submit_button.action_message: 验证中 components.captcha_submit_button.mock_score_disclaimer: 只针对内部 -components.captcha_submit_button.mock_score_label: reCAPTCHA 分数: (0.0 - 1.0) +components.captcha_submit_button.mock_score_label: reCAPTCHA 分数:(0.0 - 1.0) components.clipboard_button.label: 复制 components.clipboard_button.tooltip: 复制了! components.countdown_alert.time_remaining_html: 余下 %{countdown_html} @@ -735,7 +735,7 @@ doc_auth.tips.review_issues_id_text4: 是否所有细节都清晰可见? email_address.not_found: 未找到电子邮件 email_addresses.add.duplicate: 该电邮地址已注册到你的账户。 email_addresses.add.limit: 你添加的电邮地址数目已达最多。 -email_addresses.delete.bullet1: 使用该电邮地址你无法登录进入 %{app_name} (或任何其他与你账户关联的政府应用程序)。 +email_addresses.delete.bullet1: 使用该电邮地址你无法登录进入 %{app_name}(或任何其他与你账户关联的政府应用程序)。 email_addresses.delete.bullet2: 你不会在该电邮地址得到账户通知。 email_addresses.delete.confirm: 你确定要删除 %{email}吗? email_addresses.delete.failure: 无法删除这一电邮地址。 @@ -836,7 +836,7 @@ event_types.authenticated_at_html: 已在 %{service_provider_link_html}登录 event_types.authenticator_disabled: 身份证实器应用程序已去掉 event_types.authenticator_enabled: 身份证实器应用程序已添加 event_types.backup_codes_added: 备用代码已添加 -event_types.eastern_timestamp: '%{timestamp} (东部)' +event_types.eastern_timestamp: '%{timestamp}(东部)' event_types.email_changed: 电邮地址已更改 event_types.email_deleted: 电邮地址已删除 event_types.gpo_mail_sent: 信已发送 @@ -969,7 +969,7 @@ headings.passwords.confirm: 确认你目前密码以继续 headings.passwords.confirm_for_personal_key: 输入密码并获得一个新个人密钥 headings.passwords.forgot: 忘了你的密码? headings.piv_cac_login.account_not_found: 您的政府雇员ID未与帐户连接 -headings.piv_cac_login.add: 使用智能卡读卡器设置你的个人身份验证 (PIV) 或通用访问卡 (CAC)。你可以使用其中任何一种作为双因素身份证实方法来登录。 +headings.piv_cac_login.add: 使用智能卡读卡器设置你的个人身份验证(PIV)或通用访问卡(CAC)。你可以使用其中任何一种作为双因素身份证实方法来登录。 headings.piv_cac_login.new: 插入您的政府雇员ID headings.piv_cac_login.success: 你已成功把 PIV/CAC 设为一个身份证实方法 headings.piv_cac_setup.already_associated: 你提供的 PIV/CAC 与另外一个用户相关。 @@ -1020,7 +1020,7 @@ i18n.language: 语言 i18n.locale.en: English i18n.locale.es: Español i18n.locale.fr: Français -i18n.locale.zh: 中文 (简体) +i18n.locale.zh: 中文(简体) idv.accessible_labels.masked_ssn: 保护文本安全,从 %{first_number} 开始,到 %{last_number}结束 idv.buttons.change_address_label: 更新地址 idv.buttons.change_label: 更新 @@ -1183,7 +1183,7 @@ idv.messages.phone.rules: - 你的主要号码(或者你最常用的号码) idv.messages.return_to_profile: '‹ 返回你的 %{app_name} 用户资料' idv.messages.sessions.enter_password_message: 你重新输入密码时, %{app_name} 会保护你给我们的信息,这样只有你能访问这些信息。 -idv.messages.sessions.no_pii: 测试站点 - 请勿使用真实个人信息(仅为演示目的) - 测试站点 +idv.messages.sessions.no_pii: 测试站点 - 请勿使用真实个人信息(仅为演示目的)- 测试站点 idv.messages.verify_info: 我们从你的身份证件上读取你的信息。提交进行验证之前请检查一下并做出必要更新。 idv.messages.verifying: 验证中。。。 idv.titles.activated: 你的身份已验证 @@ -1386,7 +1386,7 @@ instructions.go_back_to_mobile_app: 要继续的话,请回到 %{friendly_name} instructions.mfa.authenticator.confirm_code_html: 输入来自你身份证实应用程序的代码。如果你在应用程序中设了几个账户,请输入与在 %{app_name_html}对应的代码。 instructions.mfa.authenticator.manual_entry: 或者动手将这个密码输入你的身份证实应用程序。 instructions.mfa.piv_cac.account_not_found_html: '

使用您的电子邮件地址和密码 %{sign_in}。然后将您的 PIV/CAC 插入智能卡读卡器以添加到您的帐户。

没有 %{app_name} 帐户?%{create_account}

' -instructions.mfa.piv_cac.add_from_sign_in_html: '说明: 看到“添加 PIV/CAC”时插入你的 PIV or CAC 。你将需要选择一个证书 (恰当的证书可能会有你的名字)而且输入你的 PIN (你的 PIN 是在设置 PIV/CAC 时设立的)。' +instructions.mfa.piv_cac.add_from_sign_in_html: '说明: 看到“添加 PIV/CAC”时插入你的 PIV or CAC 。你将需要选择一个证书(恰当的证书可能会有你的名字)而且输入你的 PIN (你的 PIN 是在设置 PIV/CAC 时设立的)。' instructions.mfa.piv_cac.already_associated_html: 请从另一个 PIV/CAC 选择证书,联系管理员以保证你的 PIV/CAC 是最新的。如果你认为这是一个错误, %{try_again_html}。 instructions.mfa.piv_cac.back_to_sign_in: 返回去登录 instructions.mfa.piv_cac.confirm_piv_cac: 将与您的帐户关联的 PIV/CAC 插入智能卡读卡器。 @@ -1395,12 +1395,12 @@ instructions.mfa.piv_cac.http_failure: 服务器反应时间过长。请再试 instructions.mfa.piv_cac.no_certificate_html: 请确保您的 PIV/CAC 已正确插入智能卡读卡器,%{try_again_html}。如果这个问题持续存在,请联系您机构的管理员。 instructions.mfa.piv_cac.not_auth_cert_html: 你选择的证书对这个账户无效。请用另外一个证书 %{please_try_again_html}。如果这一问题持续的话,请联系你机构的管理员。 instructions.mfa.piv_cac.please_try_again: 再试一次 -instructions.mfa.piv_cac.sign_in: 确保您有 %{app_name} 帐户并且已设置个人身份验证 (PIV) 或通用访问卡 (CAC) 作为双因素身份证实方法。 +instructions.mfa.piv_cac.sign_in: 确保您有 %{app_name} 帐户并且已设置个人身份验证(PIV)或通用访问卡(CAC)作为双因素身份证实方法。 instructions.mfa.piv_cac.step_1: 给它一个昵称 instructions.mfa.piv_cac.step_1_info: 这样如果你添加了一个以上 PIV/CAC 话,你就能把它们分辨开来。 instructions.mfa.piv_cac.step_2: 把 PIV/CAC 插入读卡器 instructions.mfa.piv_cac.step_3: 添加 PIV/CAC -instructions.mfa.piv_cac.step_3_info_html: 你将需要选择一个证书 (恰当的证书可能会有你的名字)而且输入你的 PIN (你的 PIN 是在设置 PIV/CAC 时设立的)。 +instructions.mfa.piv_cac.step_3_info_html: 你将需要选择一个证书(恰当的证书可能会有你的名字)而且输入你的 PIN (你的 PIN 是在设置 PIV/CAC 时设立的)。 instructions.mfa.piv_cac.try_again: 再试一次 instructions.mfa.sms.number_message_html: 我们把带有一次性代码的短信发到了 %{number_html}。这一代码 %{expiration} 分钟后会作废。 instructions.mfa.voice.number_message_html: 我们给 %{number_html}打了电话告知一次性代码。这一代码 %{expiration} 分钟后会作废。 @@ -1572,7 +1572,7 @@ shared.banner.how: 这里告诉你如何知道 shared.banner.landmark_label: 官方政府网站 shared.banner.lock_description: 锁上的锁头 shared.banner.official_site: 美国政府的一个官方网站 -shared.banner.secure_description_html: 一把 ( %{lock_icon} )或者 https:// 意味着你已安全连接到 .gov 网站。只在官方、安全的网站上分享敏感信息。 +shared.banner.secure_description_html: 一把 ( %{lock_icon} )或者 https:// 意味着你已安全连接到 .gov 网站。只在官方、安全的网站上分享敏感信息。 shared.banner.secure_heading: 安全的 .gov 网站使用 HTTPS shared.footer_lite.gsa: 美国联邦总务管理局 shared.skip_link: 跳到主要内容 @@ -1793,7 +1793,7 @@ two_factor_authentication.recaptcha.login_tos_link: 移动使用条款 two_factor_authentication.recommended: 建议 two_factor_authentication.totp_header_text: 输入你的身份证实应用程序代码 two_factor_authentication.two_factor_aal3_choice: 要求额外的身份证实 -two_factor_authentication.two_factor_aal3_choice_intro: 该应用程序要求更高的安全级别。要访问你信息,你需要使用一个实体设备 - 比如安全密钥或政府雇员身份证件(PIV 或 CAC) - 来验证你的身份。 +two_factor_authentication.two_factor_aal3_choice_intro: 该应用程序要求更高的安全级别。要访问你信息,你需要使用一个实体设备 - 比如安全密钥或政府雇员身份证件(PIV 或 CAC)- 来验证你的身份。 two_factor_authentication.two_factor_choice: 身份证实方法设置 two_factor_authentication.two_factor_choice_options.auth_app: 身份证实应用程序 two_factor_authentication.two_factor_choice_options.auth_app_info: 下载或使用你选择的身份证实应用程序来生成安全代码。 @@ -1812,7 +1812,7 @@ two_factor_authentication.two_factor_choice_options.webauthn_info: 将您的实 two_factor_authentication.two_factor_choice_options.webauthn_platform: 人脸或触摸解锁 two_factor_authentication.two_factor_choice_options.webauthn_platform_info: 不用一次性代码,而是用你的面孔或指纹来访问你的账户。 two_factor_authentication.two_factor_hspd12_choice: 要求额外的身份证实 -two_factor_authentication.two_factor_hspd12_choice_intro: 该应用程序要求更高的安全级别。要访问你的信息,你需要使用政府雇员身份证件 (PIV/CAC) 来验证身份。 +two_factor_authentication.two_factor_hspd12_choice_intro: 该应用程序要求更高的安全级别。要访问你的信息,你需要使用政府雇员身份证件(PIV/CAC)来验证身份。 two_factor_authentication.webauthn_authenticating: 正在证实你的凭据… two_factor_authentication.webauthn_error.additional_methods_link: 选择另一个身份证实方法 two_factor_authentication.webauthn_error.connect_html: 我们无法连接安全密钥。请再试一次或者 %{link_html}。 diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index c151aaf3e70..11f98f4fe9d 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -35,6 +35,14 @@ es: /¿|ó/, }.freeze +# Set of patterns which violate content conventions for a specific locale, including suggested +# alternatives. +LOCALE_BANNED_CONTENT = { + zh: [ + { pattern: /[()]/, suggestion: 'Use full-width parentheses ( or ) instead of ( or )' }, + ], +}.freeze + # Regex patterns for commonly misspelled words by locale. Match on word boundaries ignoring case. # The current design should be adequate for a small number of words in each language. # If we encounter false positives we should come up with a scheme to ignore those cases. @@ -384,6 +392,16 @@ def allowed_untranslated_key?(locale, key) expect(value).not_to match(COMMONLY_MISSPELLED_WORDS[locale]) end end + + it 'does not contain banned content', if: LOCALE_BANNED_CONTENT.key?(locale) do + bans = LOCALE_BANNED_CONTENT[locale] + flattened_yaml_data.each do |key, value| + bans.each do |ban| + expect(value).not_to match(ban[:pattern]), + "Key `#{key}` contains unexpected content. #{ban[:suggestion]}." + end + end + end end end From b1be266d5c98a9438bbd568e37aacac6caf64988 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:56:26 -0500 Subject: [PATCH 04/14] Avoid printing preload_links_header attribute for scripts (#11790) changelog: Internal, JavaScript Helper, Avoid printing preload_links_header attribute for scripts --- app/helpers/script_helper.rb | 2 +- spec/helpers/script_helper_spec.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/helpers/script_helper.rb b/app/helpers/script_helper.rb index ae35373dc69..d2cb41973fa 100644 --- a/app/helpers/script_helper.rb +++ b/app/helpers/script_helper.rb @@ -19,7 +19,7 @@ def render_javascript_pack_once_tags(...) asset_sources.get_sources(name).each do |source| integrity = asset_sources.get_integrity(source) - if attributes[:preload_links_header] != false + if attributes.delete(:preload_links_header) != false AssetPreloadLinker.append( headers: response.headers, as: :script, diff --git a/spec/helpers/script_helper_spec.rb b/spec/helpers/script_helper_spec.rb index 432550d65af..2aba34a7c32 100644 --- a/spec/helpers/script_helper_spec.rb +++ b/spec/helpers/script_helper_spec.rb @@ -112,6 +112,17 @@ javascript_packs_tag_once('application', preload_links_header: false) end + it 'prints tags with expected attributes' do + output = render_javascript_pack_once_tags + + expect(output).to have_css( + "script:not([preload_links_header]):not([crossorigin])[src^='/application.js'] ~ \ + script:not([preload_links_header]):not([crossorigin])[src^='/document-capture.js']", + count: 1, + visible: :all, + ) + end + it 'does not append preload header' do render_javascript_pack_once_tags From 3987266d6a2c8c3a5612f6dfc9e63ed151f0a246 Mon Sep 17 00:00:00 2001 From: A Shukla Date: Thu, 23 Jan 2025 11:29:52 -0600 Subject: [PATCH 05/14] Lg 15159 A user who has reached the capture complete page in hybrid flow should not be able to recapture (#11782) * Bug Fixes, socure, user who reached capture complete page should not be able to recapture * changelog: Bug Fixes, socure, user who reached capture complete page should not be able to recapture * Addressing pr comments for improved code styling --- .../hybrid_mobile/socure/document_capture_controller.rb | 6 ++++++ .../idv/hybrid_mobile/hybrid_socure_mobile_spec.rb | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb index aede97eece4..f9fd639c4db 100644 --- a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb @@ -11,6 +11,7 @@ class DocumentCaptureController < ApplicationController include SocureErrorsConcern check_or_render_not_found -> { IdentityConfig.store.socure_docv_enabled } + before_action :validate_step_not_completed, only: [:show] before_action :check_valid_document_capture_session, except: [:update] before_action -> do redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, in_hybrid_mobile: true) @@ -84,6 +85,11 @@ def errors private + def validate_step_not_completed + return if stored_result.blank? || !stored_result.success? + redirect_to idv_hybrid_mobile_capture_complete_url + end + def socure_errors_presenter(result) SocureErrorPresenter.new( error_code: error_code_for(result), diff --git a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb index 768614a0afe..f5729f69374 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb @@ -101,10 +101,11 @@ expect(page).to have_text(t('doc_auth.instructions.switch_back')) expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id')) - # To be fixed in app: # Confirm app disallows jumping back to DocumentCapture page - # visit idv_hybrid_mobile_socure_document_capture_url - # expect(page).to have_current_path(idv_hybrid_mobile_capture_complete_url) + visit idv_hybrid_mobile_socure_document_capture_url + expect(page).to have_current_path(idv_hybrid_mobile_capture_complete_url) + visit idv_hybrid_mobile_socure_document_capture_update_url + expect(page).to have_current_path(idv_hybrid_mobile_capture_complete_url) end perform_in_browser(:desktop) do From e8999172f1e1e3d70beb25b8c4f11d96a6be0ec4 Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Thu, 23 Jan 2025 09:42:41 -0800 Subject: [PATCH 06/14] Remove Faker use in UserMailerPreview (#11791) - Mailer previews occasionally load in production environments (sandbox) but in #11757, we moved the Faker gem to the test group (which breaks these previews in sandbox) - Removing the usage of Faker in the previews helps keep the previews working, and keeps the production dependencies smaller changelog: Internal, Source code, Remove gem dependency from mailer previews --- spec/mailers/previews/user_mailer_preview.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 66edf8f05ee..f4ab34cf2af 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -295,8 +295,8 @@ def user devices: [ unsaveable( Device.new( - user_agent: Faker::Internet.user_agent, - last_ip: Faker::Internet.ip_v4_address, + user_agent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', # rubocop:disable Layout/LineLength + last_ip: '8.8.8.8', ), ), ], From 0ca2e57ae32654197d2e2afecec85582324eef10 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 23 Jan 2025 12:56:36 -0500 Subject: [PATCH 07/14] LG-15273 Create a Redis Set to track Socure users (#11773) * add a redis pool to track the number of socure users * add specs for socure_user service Co-authored-by: Abir Shukla * linty mclinterson * add changelog changelog: Internal, Doc Auth Socure, Create a Redis set to track Socure users * move socure_users_pool to use throttle pool * remove flushdb for socure_users_spec * use REDIS_POOL rather than REDIS_THROTTLE_POOL * flush REDIS_POOL in specs * SocureUser -> SocureUserSet * socure_user_set --------- Co-authored-by: Abir Shukla --- app/services/idv/socure_user_set.rb | 35 ++++++++++++++ spec/services/idv/socure_user_set_spec.rb | 58 +++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 app/services/idv/socure_user_set.rb create mode 100644 spec/services/idv/socure_user_set_spec.rb diff --git a/app/services/idv/socure_user_set.rb b/app/services/idv/socure_user_set.rb new file mode 100644 index 00000000000..be0e123ae7a --- /dev/null +++ b/app/services/idv/socure_user_set.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Idv + class SocureUserSet + attr_reader :redis_pool + + def initialize(redis_pool: REDIS_POOL) + @redis_pool = redis_pool + end + + def add_user!(user_uuid:) + return if maxed_users? + + redis_pool.with do |client| + client.sadd(key, user_uuid) + end + end + + def count + redis_pool.with do |client| + client.scard(key) + end + end + + private + + def maxed_users? + count >= IdentityConfig.store.doc_auth_socure_max_allowed_users + end + + def key + 'idv:socure:users' + end + end +end diff --git a/spec/services/idv/socure_user_set_spec.rb b/spec/services/idv/socure_user_set_spec.rb new file mode 100644 index 00000000000..9b28a6b4e7b --- /dev/null +++ b/spec/services/idv/socure_user_set_spec.rb @@ -0,0 +1,58 @@ +require 'rails_helper' + +RSpec.describe Idv::SocureUserSet do + let(:socure_user_set) { Idv::SocureUserSet.new } + let(:dummy_uuid_1) { 'ABC0001' } + let(:dummy_uuid_2) { 'ABC0002' } + let(:dummy_uuid_3) { 'ABC0003' } + + around do |ex| + REDIS_POOL.with { |client| client.flushdb } + ex.run + REDIS_POOL.with { |client| client.flushdb } + end + + describe '#add_user!' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_socure_max_allowed_users).and_return(2) + end + + it 'correctly adds user and tracks count' do + socure_user_set.add_user!(user_uuid: dummy_uuid_1) + expect(socure_user_set.count).to eq(1) + end + + it 'does not add duplicates' do + socure_user_set.add_user!(user_uuid: dummy_uuid_1) + expect(socure_user_set.count).to eq(1) + socure_user_set.add_user!(user_uuid: dummy_uuid_1) + expect(socure_user_set.count).to eq(1) + end + + it 'does not allow more than doc_auth_socure_max_allowed_users to be added to set' do + socure_user_set.add_user!(user_uuid: dummy_uuid_1) + expect(socure_user_set.count).to eq(1) + socure_user_set.add_user!(user_uuid: dummy_uuid_2) + expect(socure_user_set.count).to eq(2) + socure_user_set.add_user!(user_uuid: dummy_uuid_3) + expect(socure_user_set.count).to eq(2) + end + end + + describe '#count' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_socure_max_allowed_users).and_return(10) + end + it 'count is zero when there are no users in the redis store' do + expect(socure_user_set.count).to eq(0) + end + + it 'gives the user count' do + 10.times.each do |index| + socure_user_set.add_user!(user_uuid: "ABC000#{index}") + end + + expect(socure_user_set.count).to eq(10) + end + end +end From f39424c9dcbfc264f53490766ad11fb7b58f8df1 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:58:07 -0500 Subject: [PATCH 08/14] LG-10965: Show backup code reminder for partner-initiated requests (#11744) * LG-10965: Show backup code reminder for partner-initiated requests changelog: User-Facing Improvements, Backup Codes, Show backup code reminder for partner-initiated requests * Exclude backup code and remembered device auth for backup code reminders * Fix typo "user" --- app/controllers/application_controller.rb | 2 +- .../concerns/backup_code_reminder_concern.rb | 19 +++- .../users/backup_code_reminder/show.html.erb | 10 ++- .../backup_code_reminder_concern_spec.rb | 30 +++++++ .../backup_code_sign_in_spec.rb | 88 ++++++++++++++++++- .../show.html.erb_spec.rb | 7 +- 6 files changed, 143 insertions(+), 13 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 78ae5c5d697..faeb5f09a97 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -239,13 +239,13 @@ def after_sign_in_path_for(_user) :recommend_for_authentication, ) return second_mfa_reminder_url if user_needs_second_mfa_reminder? + return backup_code_reminder_url if user_needs_backup_code_reminder? return sp_session_request_url_with_updated_params if sp_session.key?(:request_url) signed_in_url end def signed_in_url return idv_verify_by_mail_enter_code_url if current_user.gpo_verification_pending_profile? - return backup_code_reminder_url if user_needs_backup_code_reminder? account_path end diff --git a/app/controllers/concerns/backup_code_reminder_concern.rb b/app/controllers/concerns/backup_code_reminder_concern.rb index b521f6330eb..32332ba555f 100644 --- a/app/controllers/concerns/backup_code_reminder_concern.rb +++ b/app/controllers/concerns/backup_code_reminder_concern.rb @@ -2,12 +2,27 @@ module BackupCodeReminderConcern def user_needs_backup_code_reminder? - return false if user_session[:dismissed_backup_code_reminder] - user_backup_codes_configured? && user_last_signed_in_more_than_5_months_ago? + user_session[:dismissed_backup_code_reminder].blank? && + auth_events_valid_for_backup_code_reminder? && + user_backup_codes_configured? && + user_last_signed_in_more_than_5_months_ago? end private + def auth_events_valid_for_backup_code_reminder? + # Exclude backup codes and remembered device for backup code reminders. + auth_methods_session.auth_events.none? do |auth_event| + # If the user authenticated using remembered device, they have signed in more recently than + # 5 months ago. Remembered device authentications do not produce `after_sign_in_2fa` events, + # which is what's used to consider whether user signed in more than 5 months ago. + auth_event[:auth_method] == TwoFactorAuthenticatable::AuthMethod::REMEMBER_DEVICE || + # If the user authenticated using backup code in the same session, it can be inferred that + # they still have possession of their backup codes + auth_event[:auth_method] == TwoFactorAuthenticatable::AuthMethod::BACKUP_CODE + end + end + def user_backup_codes_configured? MfaContext.new(current_user).backup_code_configurations.present? end diff --git a/app/views/users/backup_code_reminder/show.html.erb b/app/views/users/backup_code_reminder/show.html.erb index fec8fe6e8d1..536280b8c48 100644 --- a/app/views/users/backup_code_reminder/show.html.erb +++ b/app/views/users/backup_code_reminder/show.html.erb @@ -10,10 +10,16 @@
<%= render ButtonComponent.new( - url: account_path, + url: backup_code_reminder_path, + method: :post, + params: { has_codes: true }, big: true, wide: true, ).with_content(t('forms.backup_code_reminder.have_codes')) %>
-<%= link_to t('forms.backup_code_reminder.need_new_codes'), backup_code_regenerate_path %> +<%= render ButtonComponent.new( + url: backup_code_reminder_path, + method: :post, + unstyled: true, + ).with_content(t('forms.backup_code_reminder.need_new_codes')) %> diff --git a/spec/controllers/concerns/backup_code_reminder_concern_spec.rb b/spec/controllers/concerns/backup_code_reminder_concern_spec.rb index 3cd33dfa65e..269f6428e0d 100644 --- a/spec/controllers/concerns/backup_code_reminder_concern_spec.rb +++ b/spec/controllers/concerns/backup_code_reminder_concern_spec.rb @@ -62,6 +62,26 @@ end it { is_expected.to eq(true) } + + context 'if the user authenticated with backup codes' do + before do + controller.auth_methods_session.authenticate!( + TwoFactorAuthenticatable::AuthMethod::BACKUP_CODE, + ) + end + + it { is_expected.to eq(false) } + end + + context 'if the user authenticated with remember device' do + before do + controller.auth_methods_session.authenticate!( + TwoFactorAuthenticatable::AuthMethod::REMEMBER_DEVICE, + ) + end + + it { is_expected.to eq(false) } + end end context 'if the user is fully authenticating for the first time' do @@ -70,6 +90,16 @@ end it { is_expected.to eq(true) } + + context 'if the user authenticated with backup codes' do + before do + controller.auth_methods_session.authenticate!( + TwoFactorAuthenticatable::AuthMethod::BACKUP_CODE, + ) + end + + it { is_expected.to eq(false) } + end end end end diff --git a/spec/features/two_factor_authentication/backup_code_sign_in_spec.rb b/spec/features/two_factor_authentication/backup_code_sign_in_spec.rb index 0ee7efdc365..7a2c5fa341d 100644 --- a/spec/features/two_factor_authentication/backup_code_sign_in_spec.rb +++ b/spec/features/two_factor_authentication/backup_code_sign_in_spec.rb @@ -1,7 +1,9 @@ require 'rails_helper' RSpec.feature 'sign in with backup code' do + include SamlAuthHelper include InteractionHelper + include NavigationHelper let(:user) { create(:user) } let!(:codes) { BackupCodeGenerator.new(user).delete_and_regenerate } @@ -48,7 +50,7 @@ context 'when the user needs a backup code reminder' do let(:user) do - create(:user, created_at: 10.months.ago, second_mfa_reminder_dismissed_at: 8.months.ago) + create(:user, :with_phone, created_at: 10.months.ago) end let!(:event) do @@ -57,7 +59,7 @@ end it 'redirects the user to the backup code reminder url and allows user to confirm possession' do - fill_in t('forms.two_factor.backup_code'), with: codes.sample + fill_in_code_with_last_phone_otp click_submit_default expect(page).to have_current_path(backup_code_reminder_path) @@ -68,7 +70,7 @@ end it 'redirects the user to the backup code reminder url and allows user to create new codes' do - fill_in t('forms.two_factor.backup_code'), with: codes.sample + fill_in_code_with_last_phone_otp click_submit_default expect(page).to have_current_path(backup_code_reminder_path) @@ -77,5 +79,85 @@ expect(page).to have_current_path(backup_code_regenerate_path) end + + context 'authenticating with backup code' do + before do + sign_in_before_2fa(user) + choose_another_security_option(:backup_code) + fill_in t('forms.two_factor.backup_code'), with: codes.sample + click_submit_default + end + + it 'does not prompt the user to confirm backup code possession' do + expect(page).to have_current_path(account_path) + end + end + + context 'after dismissing the prompt (remembered device)' do + before do + fill_in_code_with_last_phone_otp + check t('forms.messages.remember_device') + click_submit_default + click_on t('forms.backup_code_reminder.have_codes') + click_on t('links.sign_out') + end + + it 'does not prompt again the next sign in' do + sign_in_before_2fa(user) + + expect(page).to have_current_path(account_path) + end + end + + context 'after dismissing the prompt (non-remembered device)' do + before do + fill_in_code_with_last_phone_otp + uncheck t('forms.messages.remember_device') + click_submit_default + click_on t('forms.backup_code_reminder.have_codes') + click_on t('links.sign_out') + end + + it 'does not prompt again the next sign in' do + sign_in_before_2fa(user) + + fill_in_code_with_last_phone_otp + click_submit_default + + expect(page).to have_current_path(account_path) + end + end + + context 'when signing in to partner application' do + before do + visit_idp_from_sp_with_ial1(:oidc) + end + + it 'redirects the user to backup code reminder url and allows user to confirm possession' do + fill_in_code_with_last_phone_otp + click_submit_default + + expect(page).to have_current_path(backup_code_reminder_path) + + click_on t('forms.backup_code_reminder.have_codes') + + expect(page).to have_current_path(sign_up_completed_path) + end + + it 'redirects the user to the backup code reminder url and allows user to create new codes' do + fill_in_code_with_last_phone_otp + click_submit_default + + expect(page).to have_current_path(backup_code_reminder_path) + click_on t('forms.backup_code_reminder.need_new_codes') + + expect(page).to have_current_path(backup_code_regenerate_path) + click_on t('account.index.backup_code_confirm_regenerate') + + click_continue + + expect(page).to have_current_path(sign_up_completed_path) + end + end end end diff --git a/spec/views/users/backup_code_reminder/show.html.erb_spec.rb b/spec/views/users/backup_code_reminder/show.html.erb_spec.rb index bc66a99328b..32321c739ce 100644 --- a/spec/views/users/backup_code_reminder/show.html.erb_spec.rb +++ b/spec/views/users/backup_code_reminder/show.html.erb_spec.rb @@ -22,15 +22,12 @@ it 'has a cancel link to account path' do render - expect(rendered).to have_link(t('forms.backup_code_reminder.have_codes')) + expect(rendered).to have_button(t('forms.backup_code_reminder.have_codes')) end it 'has a regenerate backup code link' do render - expect(rendered).to have_link( - t('forms.backup_code_reminder.need_new_codes'), - href: backup_code_regenerate_path, - ) + expect(rendered).to have_button(t('forms.backup_code_reminder.need_new_codes')) end end From 19b610a35c2b1b3e6c164fa2bc80c8eacce63e26 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 23 Jan 2025 13:56:38 -0600 Subject: [PATCH 09/14] Upgrade to Rails 8 (#11793) * Upgrade to Rails 8 changelog: Internal, Maintenance, Upgrade to Rails 8 * load 8.0 defaults * update rubocop target --- .rubocop.yml | 2 +- Gemfile | 4 +- Gemfile.lock | 145 +++++++++--------- config/application.rb | 2 +- db/schema.rb | 4 +- db/worker_jobs_schema.rb | 4 +- lib/asset_sources.rb | 2 +- spec/services/deleted_accounts_report_spec.rb | 2 +- 8 files changed, 83 insertions(+), 82 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 0d369481f1b..745afde2679 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -30,7 +30,7 @@ AllCops: - 'vendor/**/*' - 'public/**/*' TargetRubyVersion: 3.3.0 - TargetRailsVersion: 7.2 + TargetRailsVersion: 8.0 UseCache: true DisabledByDefault: true SuggestExtensions: false diff --git a/Gemfile b/Gemfile index 75507ed7336..1029e6b7c88 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } ruby "~> #{File.read(File.join(__dir__, '.ruby-version')).strip}" -gem 'rails', '~> 7.2.1' +gem 'rails', '~> 8.0.0' gem 'ahoy_matey', '~> 3.0' # pod identity requires 3.188.0 @@ -102,7 +102,7 @@ end group :development, :test do gem 'brakeman', require: false - gem 'bullet', '~> 7.0' + gem 'bullet', '~> 8.0' gem 'capybara-webmock', git: 'https://github.com/hashrocket/capybara-webmock.git', ref: 'd3f3b7c' gem 'erb_lint', '~> 0.7.0', require: false gem 'i18n-tasks', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index aa5b10f1a52..42f43201677 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -80,66 +80,65 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.2.2.1) - actionpack (= 7.2.2.1) - activesupport (= 7.2.2.1) + actioncable (8.0.1) + actionpack (= 8.0.1) + activesupport (= 8.0.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.2.1) - actionpack (= 7.2.2.1) - activejob (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionmailbox (8.0.1) + actionpack (= 8.0.1) + activejob (= 8.0.1) + activerecord (= 8.0.1) + activestorage (= 8.0.1) + activesupport (= 8.0.1) mail (>= 2.8.0) - actionmailer (7.2.2.1) - actionpack (= 7.2.2.1) - actionview (= 7.2.2.1) - activejob (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionmailer (8.0.1) + actionpack (= 8.0.1) + actionview (= 8.0.1) + activejob (= 8.0.1) + activesupport (= 8.0.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.2.1) - actionview (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionpack (8.0.1) + actionview (= 8.0.1) + activesupport (= 8.0.1) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.2) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.2.1) - actionpack (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + actiontext (8.0.1) + actionpack (= 8.0.1) + activerecord (= 8.0.1) + activestorage (= 8.0.1) + activesupport (= 8.0.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.2.1) - activesupport (= 7.2.2.1) + actionview (8.0.1) + activesupport (= 8.0.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.2.1) - activesupport (= 7.2.2.1) + activejob (8.0.1) + activesupport (= 8.0.1) globalid (>= 0.3.6) - activemodel (7.2.2.1) - activesupport (= 7.2.2.1) - activerecord (7.2.2.1) - activemodel (= 7.2.2.1) - activesupport (= 7.2.2.1) + activemodel (8.0.1) + activesupport (= 8.0.1) + activerecord (8.0.1) + activemodel (= 8.0.1) + activesupport (= 8.0.1) timeout (>= 0.4.0) - activestorage (7.2.2.1) - actionpack (= 7.2.2.1) - activejob (= 7.2.2.1) - activerecord (= 7.2.2.1) - activesupport (= 7.2.2.1) + activestorage (8.0.1) + actionpack (= 8.0.1) + activejob (= 8.0.1) + activerecord (= 8.0.1) + activesupport (= 8.0.1) marcel (~> 1.0) - activesupport (7.2.2.1) + activesupport (8.0.1) base64 benchmark (>= 0.3) bigdecimal @@ -151,6 +150,7 @@ GEM minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) ahoy_matey (3.3.0) @@ -228,7 +228,7 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bigdecimal (3.1.8) + bigdecimal (3.1.9) bindata (2.4.15) bootsnap (1.18.3) msgpack (~> 1.2) @@ -236,7 +236,7 @@ GEM racc browser (6.0.0) builder (3.3.0) - bullet (7.2.0) + bullet (8.0.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundler-audit (0.9.2) @@ -264,8 +264,8 @@ GEM coderay (1.1.3) coercible (1.0.0) descendants_tracker (~> 0.0.1) - concurrent-ruby (1.3.4) - connection_pool (2.4.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) cose (1.3.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) @@ -369,7 +369,7 @@ GEM htmlbeautifier (1.4.3) htmlentities (4.3.4) http_accept_language (2.1.1) - i18n (1.14.6) + i18n (1.14.7) concurrent-ruby (~> 1.0) i18n-tasks (1.0.12) activesupport (>= 4.0.2) @@ -404,7 +404,7 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.2) + logger (1.6.5) lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) @@ -533,20 +533,20 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails (7.2.2.1) - actioncable (= 7.2.2.1) - actionmailbox (= 7.2.2.1) - actionmailer (= 7.2.2.1) - actionpack (= 7.2.2.1) - actiontext (= 7.2.2.1) - actionview (= 7.2.2.1) - activejob (= 7.2.2.1) - activemodel (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + rails (8.0.1) + actioncable (= 8.0.1) + actionmailbox (= 8.0.1) + actionmailer (= 8.0.1) + actionpack (= 8.0.1) + actiontext (= 8.0.1) + actionview (= 8.0.1) + activejob (= 8.0.1) + activemodel (= 8.0.1) + activerecord (= 8.0.1) + activestorage (= 8.0.1) + activesupport (= 8.0.1) bundler (>= 1.15.0) - railties (= 7.2.2.1) + railties (= 8.0.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -558,12 +558,12 @@ GEM rails-html-sanitizer (1.6.1) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - rails-i18n (7.0.6) + rails-i18n (8.0.1) i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 8) - railties (7.2.2.1) - actionpack (= 7.2.2.1) - activesupport (= 7.2.2.1) + railties (>= 8.0.0, < 9) + railties (8.0.1) + actionpack (= 8.0.1) + activesupport (= 8.0.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -662,7 +662,7 @@ GEM scrypt (3.0.8) ffi-compiler (>= 1.0, < 2.0) rake (>= 9, < 14) - securerandom (0.4.0) + securerandom (0.4.1) selenium-webdriver (4.27.0) base64 (~> 0.2) logger (~> 1.4) @@ -692,14 +692,14 @@ GEM mini_portile2 (~> 2.8.0) stringex (2.8.5) stringio (3.1.2) - strong_migrations (2.0.0) + strong_migrations (2.1.0) activerecord (>= 6.1) tableparser (1.0.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) thor (1.3.2) thread_safe (0.3.6) - timeout (0.4.2) + timeout (0.4.3) tpm-key_attestation (0.11.0) bindata (~> 2.4) openssl (> 2.0, < 3.1) @@ -708,10 +708,10 @@ GEM concurrent-ruby (~> 1.0) unicode-display_width (2.6.0) uniform_notifier (1.16.0) - uri (0.13.0) + uri (1.0.2) useragent (0.16.11) - view_component (3.9.0) - activesupport (>= 5.2.0, < 8.0) + view_component (3.21.0) + activesupport (>= 5.2.0, < 8.1) concurrent-ruby (~> 1.0) method_source (~> 1.0) virtus (2.0.0) @@ -735,7 +735,8 @@ GEM hashdiff (>= 0.4.0, < 2.0.0) webrick (1.9.1) websocket (1.2.11) - websocket-driver (0.7.6) + websocket-driver (0.7.7) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xmldsig (0.6.6) @@ -777,7 +778,7 @@ DEPENDENCIES bootsnap (~> 1.0) brakeman browser - bullet (~> 7.0) + bullet (~> 8.0) bundler-audit capybara-webmock! caxlsx @@ -841,7 +842,7 @@ DEPENDENCIES rack-test (>= 1.1.0) rack-timeout rack_session_access (>= 0.2.0) - rails (~> 7.2.1) + rails (~> 8.0.0) rails-controller-testing (>= 1.0.4) redacted_struct redis (>= 3.2.0) diff --git a/config/application.rb b/config/application.rb index 64f212ce46f..7311db07e49 100644 --- a/config/application.rb +++ b/config/application.rb @@ -59,7 +59,7 @@ class Application < Rails::Application end end - config.load_defaults '7.2' + config.load_defaults '8.0' config.active_record.belongs_to_required_by_default = false config.active_job.queue_adapter = :good_job diff --git a/db/schema.rb b/db/schema.rb index 376cc842b52..3433618343e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,11 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_01_06_232958) do +ActiveRecord::Schema[8.0].define(version: 2025_01_06_232958) do # These are extensions that must be enabled in order to support this database enable_extension "citext" + enable_extension "pg_catalog.plpgsql" enable_extension "pg_stat_statements" - enable_extension "plpgsql" create_table "account_reset_requests", force: :cascade do |t| t.integer "user_id", null: false, comment: "sensitive=false" diff --git a/db/worker_jobs_schema.rb b/db/worker_jobs_schema.rb index 2ce7f8fc09c..2db9037026e 100644 --- a/db/worker_jobs_schema.rb +++ b/db/worker_jobs_schema.rb @@ -10,10 +10,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_10_22_162624) do +ActiveRecord::Schema[8.0].define(version: 2024_10_22_162624) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_catalog.plpgsql" enable_extension "pgcrypto" - enable_extension "plpgsql" create_table "good_job_batches", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false diff --git a/lib/asset_sources.rb b/lib/asset_sources.rb index 676473a544c..3bbd8913812 100644 --- a/lib/asset_sources.rb +++ b/lib/asset_sources.rb @@ -23,7 +23,7 @@ def get_sources(*names) locale_sources, sources = names.flat_map do |name| manifest&.dig('entrypoints', name, 'assets', 'js').presence || begin - [name] if name.match?(URI::DEFAULT_PARSER.regexp[:ABS_URI]) + [name] if name.match?(URI::ABS_URI) end end.uniq.compact.partition { |source| @regexp_locale_suffix.match?(source) } diff --git a/spec/services/deleted_accounts_report_spec.rb b/spec/services/deleted_accounts_report_spec.rb index a82663287aa..da29181fde4 100644 --- a/spec/services/deleted_accounts_report_spec.rb +++ b/spec/services/deleted_accounts_report_spec.rb @@ -47,7 +47,7 @@ :service_provider_identity, service_provider: service_provider, user: user, - last_authenticated_at: days_ago + 1, + last_authenticated_at: (days_ago + 1).days.ago, ) user.destroy! rows = DeletedAccountsReport.call(service_provider, days_ago) From 9d0909f11faf5051adf13994f65c380837e005b9 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Fri, 24 Jan 2025 08:28:00 -0600 Subject: [PATCH 10/14] Reduce RiscDeliveryJob exception noise (#11794) changelog: Internal, Alerting, Reduce RiscDeliveryJob exception noise --- app/jobs/risc_delivery_job.rb | 8 ++++++-- config/newrelic.yml | 1 + lib/identity_job_log_subscriber.rb | 1 + spec/jobs/risc_delivery_job_spec.rb | 8 ++++---- spec/lib/identity_job_log_subscriber_spec.rb | 18 +++++++++--------- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/jobs/risc_delivery_job.rb b/app/jobs/risc_delivery_job.rb index abe511bd2cd..40e32d76f9e 100644 --- a/app/jobs/risc_delivery_job.rb +++ b/app/jobs/risc_delivery_job.rb @@ -3,6 +3,8 @@ class RiscDeliveryJob < ApplicationJob queue_as :low + class DeliveryError < StandardError; end + NETWORK_ERRORS = [ Faraday::TimeoutError, Faraday::ConnectionFailed, @@ -11,7 +13,7 @@ class RiscDeliveryJob < ApplicationJob ].freeze retry_on( - *NETWORK_ERRORS, + DeliveryError, wait: :polynomially_longer, attempts: 2, ) do |job, exception| @@ -39,7 +41,7 @@ class RiscDeliveryJob < ApplicationJob end def self.warning_error_classes - NETWORK_ERRORS + [RedisRateLimiter::LimitError] + [DeliveryError, RedisRateLimiter::LimitError] end def perform( @@ -71,6 +73,8 @@ def perform( success: response.success?, user:, ) + rescue *NETWORK_ERRORS => e + raise DeliveryError.new(e.message) end def rate_limiter(url) diff --git a/config/newrelic.yml b/config/newrelic.yml index 9e2ab1f3b30..13399ae57ab 100644 --- a/config/newrelic.yml +++ b/config/newrelic.yml @@ -29,6 +29,7 @@ production: ActionDispatch::Http::MimeNegotiation::InvalidType ActionDispatch::Http::Parameters::ParseError GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError + RiscDeliveryJob::DeliveryError ].join(',') %>" license_key: <%= IdentityConfig.store.newrelic_license_key %> log_level: info diff --git a/lib/identity_job_log_subscriber.rb b/lib/identity_job_log_subscriber.rb index e9d981dd531..859e2555028 100644 --- a/lib/identity_job_log_subscriber.rb +++ b/lib/identity_job_log_subscriber.rb @@ -106,6 +106,7 @@ def retry_stopped(event) error_or_warn( event: event, extra_attributes: { attempts: job.executions }, + include_exception_message: true, ) end diff --git a/spec/jobs/risc_delivery_job_spec.rb b/spec/jobs/risc_delivery_job_spec.rb index 2341109e0a7..3ad8a4fbdfa 100644 --- a/spec/jobs/risc_delivery_job_spec.rb +++ b/spec/jobs/risc_delivery_job_spec.rb @@ -70,7 +70,7 @@ context 'when the job fails for the 1st time' do it 'raises and retries via ActiveJob' do - expect { perform }.to raise_error(Faraday::SSLError) + expect { perform }.to raise_error(RiscDeliveryJob::DeliveryError) expect(job_analytics).not_to have_logged_event( :risc_security_event_pushed, @@ -111,7 +111,7 @@ context 'when the job fails for the 1st time' do it 'raises and retries via ActiveJob' do - expect { perform }.to raise_error(Faraday::ConnectionFailed) + expect { perform }.to raise_error(RiscDeliveryJob::DeliveryError) expect(job_analytics).not_to have_logged_event( :risc_security_event_pushed, @@ -156,7 +156,7 @@ context 'when the job fails for the 1st time' do it 'raises and retries via ActiveJob' do - expect { perform }.to raise_error(Errno::ECONNREFUSED) + expect { perform }.to raise_error(RiscDeliveryJob::DeliveryError) expect(job_analytics).not_to have_logged_event( :risc_security_event_pushed, @@ -300,7 +300,7 @@ describe '.warning_error_classes' do it 'is all the network errors and rate limiting errors' do expect(described_class.warning_error_classes).to match_array( - [*described_class::NETWORK_ERRORS, RedisRateLimiter::LimitError], + [RiscDeliveryJob::DeliveryError, RedisRateLimiter::LimitError], ) end end diff --git a/spec/lib/identity_job_log_subscriber_spec.rb b/spec/lib/identity_job_log_subscriber_spec.rb index 7162f1cbf0d..4398299db36 100644 --- a/spec/lib/identity_job_log_subscriber_spec.rb +++ b/spec/lib/identity_job_log_subscriber_spec.rb @@ -152,7 +152,7 @@ now, event_uuid, job: job, - exception_object: Errno::ECONNREFUSED.new, + exception_object: RedisRateLimiter::LimitError.new('message'), ) expect(subscriber).to_not receive(:error) @@ -166,8 +166,8 @@ duration_ms: kind_of(Float), cpu_time_ms: kind_of(Numeric), idle_time_ms: kind_of(Numeric), - exception_class_warn: 'Errno::ECONNREFUSED', - exception_message_warn: 'Connection refused', + exception_class_warn: 'RedisRateLimiter::LimitError', + exception_message_warn: 'message', job_class: 'RiscDeliveryJob', job_id: job.job_id, name: 'enqueue.active_job', @@ -294,7 +294,7 @@ now, event_uuid, job: job, - exception_object: Errno::ECONNREFUSED.new, + exception_object: RedisRateLimiter::LimitError.new('message'), ) expect(subscriber).to_not receive(:error) @@ -308,8 +308,8 @@ duration_ms: kind_of(Float), cpu_time_ms: kind_of(Numeric), idle_time_ms: kind_of(Numeric), - exception_class_warn: 'Errno::ECONNREFUSED', - exception_message_warn: 'Connection refused', + exception_class_warn: 'RedisRateLimiter::LimitError', + exception_message_warn: 'message', job_class: 'RiscDeliveryJob', job_id: job.job_id, name: 'enqueue.active_job', @@ -338,7 +338,7 @@ def perform(_); end now, event_uuid, job: job, - exception_object: Errno::ECONNREFUSED.new, + exception_object: RedisRateLimiter::LimitError.new, ) subscriber.enqueue_at(event) @@ -436,7 +436,7 @@ def perform(_); end now, event_uuid, job: job, - error: Errno::ECONNREFUSED.new, + error: RedisRateLimiter::LimitError.new, ) expect(subscriber).to_not receive(:error) @@ -456,7 +456,7 @@ def perform(_); end trace_id: nil, queue_name: kind_of(String), job_id: job.job_id, - exception_class_warn: 'Errno::ECONNREFUSED', + exception_class_warn: 'RedisRateLimiter::LimitError', log_filename: Idp::Constants::WORKER_LOG_FILENAME, ) end From bfc5404b5572914d7aac18835bf0bcdbfff84543 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:43:46 -0500 Subject: [PATCH 11/14] LG-14052: Add routes for mismatched WebAuthn platform attachment (#11795) * LG-14052: Add routes for mismatched WebAuthn platform attachment changelog: Upcoming Features, Multi-Factor Authentication, Convert Security Key to Face or Touch Unlock when detected as platform authenticator * Reverse unintended locale string changes --- .../webauthn-mismatch/webauthn-checked.svg | 1 + .../webauthn-platform-checked.svg | 1 + .../webauthn-platform-unchecked.svg | 1 + .../webauthn-mismatch/webauthn-unchecked.svg | 1 + .../concerns/mfa_deletion_concern.rb | 13 + .../webauthn_setup_mismatch_controller.rb | 82 +++++++ .../webauthn_delete_form.rb | 12 +- .../webauthn_setup_mismatch_presenter.rb | 47 ++++ app/services/analytics_events.rb | 49 +++- .../webauthn_setup_mismatch/show.html.erb | 42 ++++ config/locales/en.yml | 6 + config/locales/es.yml | 6 + config/locales/fr.yml | 6 + config/locales/zh.yml | 6 + config/routes.rb | 4 + .../concerns/mfa_deletion_concern_spec.rb | 42 ++++ ...webauthn_setup_mismatch_controller_spec.rb | 231 ++++++++++++++++++ .../webauthn_delete_form_spec.rb | 18 +- .../webauthn_setup_mismatch_presenter_spec.rb | 71 ++++++ spec/support/controller_helper.rb | 6 + .../show.html.erb_spec.rb | 30 +++ 21 files changed, 671 insertions(+), 4 deletions(-) create mode 100644 app/assets/images/webauthn-mismatch/webauthn-checked.svg create mode 100644 app/assets/images/webauthn-mismatch/webauthn-platform-checked.svg create mode 100644 app/assets/images/webauthn-mismatch/webauthn-platform-unchecked.svg create mode 100644 app/assets/images/webauthn-mismatch/webauthn-unchecked.svg create mode 100644 app/controllers/concerns/mfa_deletion_concern.rb create mode 100644 app/controllers/users/webauthn_setup_mismatch_controller.rb create mode 100644 app/presenters/webauthn_setup_mismatch_presenter.rb create mode 100644 app/views/users/webauthn_setup_mismatch/show.html.erb create mode 100644 spec/controllers/concerns/mfa_deletion_concern_spec.rb create mode 100644 spec/controllers/users/webauthn_setup_mismatch_controller_spec.rb create mode 100644 spec/presenters/webauthn_setup_mismatch_presenter_spec.rb create mode 100644 spec/views/users/webauthn_setup_mismatch/show.html.erb_spec.rb diff --git a/app/assets/images/webauthn-mismatch/webauthn-checked.svg b/app/assets/images/webauthn-mismatch/webauthn-checked.svg new file mode 100644 index 00000000000..1c6efd9a372 --- /dev/null +++ b/app/assets/images/webauthn-mismatch/webauthn-checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/webauthn-mismatch/webauthn-platform-checked.svg b/app/assets/images/webauthn-mismatch/webauthn-platform-checked.svg new file mode 100644 index 00000000000..f3aa64c183a --- /dev/null +++ b/app/assets/images/webauthn-mismatch/webauthn-platform-checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/webauthn-mismatch/webauthn-platform-unchecked.svg b/app/assets/images/webauthn-mismatch/webauthn-platform-unchecked.svg new file mode 100644 index 00000000000..8383e9280d2 --- /dev/null +++ b/app/assets/images/webauthn-mismatch/webauthn-platform-unchecked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/webauthn-mismatch/webauthn-unchecked.svg b/app/assets/images/webauthn-mismatch/webauthn-unchecked.svg new file mode 100644 index 00000000000..f70cb8438ff --- /dev/null +++ b/app/assets/images/webauthn-mismatch/webauthn-unchecked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/controllers/concerns/mfa_deletion_concern.rb b/app/controllers/concerns/mfa_deletion_concern.rb new file mode 100644 index 00000000000..0f4c647aa2e --- /dev/null +++ b/app/controllers/concerns/mfa_deletion_concern.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module MfaDeletionConcern + include RememberDeviceConcern + + def handle_successful_mfa_deletion(event_type:) + create_user_event(event_type) + revoke_remember_device(current_user) + event = PushNotification::RecoveryInformationChangedEvent.new(user: current_user) + PushNotification::HttpPush.deliver(event) + nil + end +end diff --git a/app/controllers/users/webauthn_setup_mismatch_controller.rb b/app/controllers/users/webauthn_setup_mismatch_controller.rb new file mode 100644 index 00000000000..719d45dce24 --- /dev/null +++ b/app/controllers/users/webauthn_setup_mismatch_controller.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module Users + class WebauthnSetupMismatchController < ApplicationController + include MfaSetupConcern + include MfaDeletionConcern + include SecureHeadersConcern + include ReauthenticationRequiredConcern + + before_action :confirm_user_authenticated_for_2fa_setup + before_action :apply_secure_headers_override + before_action :confirm_recently_authenticated_2fa + before_action :validate_session_mismatch_id + + def show + analytics.webauthn_setup_mismatch_visited( + configuration_id: configuration.id, + platform_authenticator: platform_authenticator?, + ) + + @presenter = WebauthnSetupMismatchPresenter.new(configuration:) + end + + def update + analytics.webauthn_setup_mismatch_submitted( + configuration_id: configuration.id, + platform_authenticator: platform_authenticator?, + confirmed_mismatch: true, + ) + + redirect_to next_setup_path || after_mfa_setup_path + end + + def destroy + result = ::TwoFactorAuthentication::WebauthnDeleteForm.new( + user: current_user, + configuration_id: webauthn_mismatch_id, + skip_multiple_mfa_validation: in_multi_mfa_selection_flow?, + ).submit + + analytics.webauthn_setup_mismatch_submitted(**result.to_h, confirmed_mismatch: false) + + if result.success? + handle_successful_mfa_deletion(event_type: :webauthn_key_removed) + redirect_to retry_setup_url + else + flash.now[:error] = result.first_error_message + @presenter = WebauthnSetupMismatchPresenter.new(configuration:) + render :show + end + end + + private + + def retry_setup_url + # These are intentionally inverted: if the authenticator was set up as a platform + # authenticator but was flagged as a mismatch, it implies that the user had originally + # intended to add a security key. + if platform_authenticator? + webauthn_setup_url + else + webauthn_setup_url(platform: true) + end + end + + def webauthn_mismatch_id + user_session[:webauthn_mismatch_id] + end + + def configuration + return @configuration if defined?(@configuration) + @configuration = current_user.webauthn_configurations.find_by(id: webauthn_mismatch_id) + end + + def validate_session_mismatch_id + return if configuration.present? + redirect_to next_setup_path || after_mfa_setup_path + end + + delegate :platform_authenticator?, to: :configuration + end +end diff --git a/app/forms/two_factor_authentication/webauthn_delete_form.rb b/app/forms/two_factor_authentication/webauthn_delete_form.rb index 8d965a66dd5..ee282c0898d 100644 --- a/app/forms/two_factor_authentication/webauthn_delete_form.rb +++ b/app/forms/two_factor_authentication/webauthn_delete_form.rb @@ -10,9 +10,10 @@ class WebauthnDeleteForm validate :validate_configuration_exists validate :validate_has_multiple_mfa - def initialize(user:, configuration_id:) + def initialize(user:, configuration_id:, skip_multiple_mfa_validation: false) @user = user @configuration_id = configuration_id + @skip_multiple_mfa_validation = skip_multiple_mfa_validation end def submit @@ -34,6 +35,10 @@ def configuration private + attr_reader :skip_multiple_mfa_validation + + alias_method :skip_multiple_mfa_validation?, :skip_multiple_mfa_validation + def validate_configuration_exists return if configuration.present? errors.add( @@ -44,7 +49,10 @@ def validate_configuration_exists end def validate_has_multiple_mfa - return if !configuration || MfaPolicy.new(user).multiple_factors_enabled? + return if skip_multiple_mfa_validation? || + !configuration || + MfaPolicy.new(user).multiple_factors_enabled? + errors.add( :configuration_id, :only_method, diff --git a/app/presenters/webauthn_setup_mismatch_presenter.rb b/app/presenters/webauthn_setup_mismatch_presenter.rb new file mode 100644 index 00000000000..22187cf1c35 --- /dev/null +++ b/app/presenters/webauthn_setup_mismatch_presenter.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class WebauthnSetupMismatchPresenter + include ActionView::Helpers::TranslationHelper + + attr_reader :configuration + + def initialize(configuration:) + @configuration = configuration + end + + def heading + if platform_authenticator? + t('webauthn_setup_mismatch.heading.webauthn_platform') + else + t('webauthn_setup_mismatch.heading.webauthn') + end + end + + def description + if platform_authenticator? + t('webauthn_setup_mismatch.description.webauthn_platform') + else + t('webauthn_setup_mismatch.description.webauthn') + end + end + + def correct_image_path + if platform_authenticator? + 'webauthn-mismatch/webauthn-platform-checked.svg' + else + 'webauthn-mismatch/webauthn-checked.svg' + end + end + + def incorrect_image_path + if platform_authenticator? + 'webauthn-mismatch/webauthn-unchecked.svg' + else + 'webauthn-mismatch/webauthn-platform-unchecked.svg' + end + end + + private + + delegate :platform_authenticator?, to: :configuration +end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 377a3f4b74d..89b6ffd2c57 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -7706,7 +7706,54 @@ def webauthn_platform_recommended_visited track_event(:webauthn_platform_recommended_visited) end - # @param [Hash] platform_authenticator + # @param [Boolean] platform_authenticator Whether authentication method was registered as platform + # authenticator + # @param [Number] configuration_id Database ID of WebAuthn configuration + # @param [Boolean] confirmed_mismatch Whether user chose to confirm and continue with interpreted + # platform attachment + # @param [Boolean] success Whether the deletion was successful, if user chose to undo interpreted + # platform attachment + # @param [Hash] error_details Details for errors that occurred in unsuccessful deletion + # User submitted confirmation screen after setting up WebAuthn with transports mismatched with the + # expected platform attachment + def webauthn_setup_mismatch_submitted( + configuration_id:, + platform_authenticator:, + confirmed_mismatch:, + success: nil, + error_details: nil, + **extra + ) + track_event( + :webauthn_setup_mismatch_submitted, + configuration_id:, + platform_authenticator:, + confirmed_mismatch:, + success:, + error_details:, + **extra, + ) + end + + # @param [Boolean] platform_authenticator Whether authentication method was registered as platform + # authenticator + # @param [Number] configuration_id Database ID of WebAuthn configuration + # User visited confirmation screen after setting up WebAuthn with transports mismatched with the + # expected platform attachment + def webauthn_setup_mismatch_visited( + configuration_id:, + platform_authenticator:, + **extra + ) + track_event( + :webauthn_setup_mismatch_visited, + configuration_id:, + platform_authenticator:, + **extra, + ) + end + + # @param [Boolean] platform_authenticator # @param [Boolean] success # @param [Hash, nil] errors # @param [Boolean] in_account_creation_flow Whether user is going through account creation flow diff --git a/app/views/users/webauthn_setup_mismatch/show.html.erb b/app/views/users/webauthn_setup_mismatch/show.html.erb new file mode 100644 index 00000000000..2891e30b4f3 --- /dev/null +++ b/app/views/users/webauthn_setup_mismatch/show.html.erb @@ -0,0 +1,42 @@ +<% self.title = @presenter.heading %> + +
+ <%= image_tag( + @presenter.correct_image_path, + width: 104, + height: 116, + alt: '', + aria: { hidden: true }, + ) %> + <%= image_tag( + @presenter.incorrect_image_path, + width: 64, + height: 71, + class: 'margin-left-2', + alt: '', + aria: { hidden: true }, + ) %> +
+ +<%= render PageHeadingComponent.new.with_content(@presenter.heading) %> + +

<%= @presenter.description %>

+ +

<%= t('webauthn_setup_mismatch.description_undo') %> + +

+ <%= render ButtonComponent.new( + url: webauthn_setup_mismatch_url, + method: :patch, + big: true, + wide: true, + ).with_content(t('forms.buttons.continue')) %> +
+ +<%= render ButtonComponent.new( + url: webauthn_setup_mismatch_url, + method: :delete, + big: true, + wide: true, + outline: true, + ).with_content(t('webauthn_setup_mismatch.undo')) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index d8a6e5f7916..85a5c2fdbd2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2044,6 +2044,12 @@ webauthn_platform_recommended.cta: Set up face or touch unlock webauthn_platform_recommended.description_save_time: Save time by using your face, fingerprint, password, or another method to access your account. This method is faster than receiving a one-time code through text or voice message. webauthn_platform_recommended.heading: Set up face or touch unlock for a quick and easy sign in webauthn_platform_recommended.skip: Skip +webauthn_setup_mismatch.description_undo: Click “Undo” to remove this option. +webauthn_setup_mismatch.description.webauthn: We noticed you’re using a security key instead of face or touch unlock. Click “Continue” to use your security key to sign in from now on. +webauthn_setup_mismatch.description.webauthn_platform: We noticed you’re using face or touch unlock instead of a security key. Click “Continue” to use face or touch unlock to sign in from now on. +webauthn_setup_mismatch.heading.webauthn: Security key detected +webauthn_setup_mismatch.heading.webauthn_platform: Face or touch unlock detected +webauthn_setup_mismatch.undo: Undo zxcvbn.feedback.a_word_by_itself_is_easy_to_guess: A word by itself is easy to guess zxcvbn.feedback.add_another_word_or_two_uncommon_words_are_better: Add another word or two. Uncommon words are better zxcvbn.feedback.all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: All-uppercase is almost as easy to guess as all-lowercase diff --git a/config/locales/es.yml b/config/locales/es.yml index 5cc9d9b3fce..fd63d3dc7c5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -2056,6 +2056,12 @@ webauthn_platform_recommended.cta: Set up face or touch unlock webauthn_platform_recommended.description_save_time: Save time by using your face, fingerprint, password, or another method to access your account. This method is faster than receiving a one-time code through text or voice message. webauthn_platform_recommended.heading: Set up face or touch unlock for a quick and easy sign in webauthn_platform_recommended.skip: Skip +webauthn_setup_mismatch.description_undo: Haga clic en “Deshacer” para quitar esta opción. +webauthn_setup_mismatch.description.webauthn: Sabemos que está usando una clave de seguridad en lugar de desbloqueo facial o táctil. Haga clic en “Continuar” para iniciar sesión con su clave de seguridad de aquí en adelante. +webauthn_setup_mismatch.description.webauthn_platform: Sabemos que está usando desbloqueo facial o táctil en lugar de una clave de seguridad. Haga clic en “Continuar” para iniciar sesión con desbloqueo facial o táctil de aquí en adelante. +webauthn_setup_mismatch.heading.webauthn: Se detectó clave de seguridad +webauthn_setup_mismatch.heading.webauthn_platform: Se detectó desbloqueo facial o táctil +webauthn_setup_mismatch.undo: Deshacer zxcvbn.feedback.a_word_by_itself_is_easy_to_guess: Una sola palabra es fácil de adivinar. zxcvbn.feedback.add_another_word_or_two_uncommon_words_are_better: Añada otra palabra o dos. Es mejor usar palabras poco comunes. zxcvbn.feedback.all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: Todo en mayúsculas es casi tan fácil de adivinar como todo en minúsculas. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d5b3c779bb2..9741c609887 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -2044,6 +2044,12 @@ webauthn_platform_recommended.cta: Set up face or touch unlock webauthn_platform_recommended.description_save_time: Save time by using your face, fingerprint, password, or another method to access your account. This method is faster than receiving a one-time code through text or voice message. webauthn_platform_recommended.heading: Set up face or touch unlock for a quick and easy sign in webauthn_platform_recommended.skip: Skip +webauthn_setup_mismatch.description_undo: Cliquez sur « Annuler » pour supprimer cette option. +webauthn_setup_mismatch.description.webauthn: Nous avons remarqué que vous utilisiez une clé de sécurité au lieu du déverrouillage facial ou tactile. Cliquez sur « Suite » pour utiliser votre clé de sécurité afin de vous connecter à partir de maintenant. +webauthn_setup_mismatch.description.webauthn_platform: Nous avons remarqué que vous utilisiez le déverrouillage facial ou tactile au lieu d’une clé de sécurité. Cliquez sur « Suite » pour utiliser le déverrouillage facial ou tactile afin de vous connecter à partir de maintenant. +webauthn_setup_mismatch.heading.webauthn: Clé de sécurité détectée +webauthn_setup_mismatch.heading.webauthn_platform: Déverrouillage facial ou tactile détecté +webauthn_setup_mismatch.undo: Annuler zxcvbn.feedback.a_word_by_itself_is_easy_to_guess: Un mot seul est facile à deviner zxcvbn.feedback.add_another_word_or_two_uncommon_words_are_better: Ajoutez un ou deux autres mots. Il est préférable d’utiliser des mots peu communs. zxcvbn.feedback.all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: Tout en majuscules est presque aussi facile à deviner que tout en minuscules. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 6d50599de34..a16d5822c49 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -2057,6 +2057,12 @@ webauthn_platform_recommended.cta: Set up face or touch unlock webauthn_platform_recommended.description_save_time: Save time by using your face, fingerprint, password, or another method to access your account. This method is faster than receiving a one-time code through text or voice message. webauthn_platform_recommended.heading: Set up face or touch unlock for a quick and easy sign in webauthn_platform_recommended.skip: Skip +webauthn_setup_mismatch.description_undo: 点击“撤消”可删除此选项。 +webauthn_setup_mismatch.description.webauthn: 我们注意到您正在使用安全密钥而不是人脸或触摸解锁。点击“继续”即可从现在开始使用您的安全密钥登录。 +webauthn_setup_mismatch.description.webauthn_platform: 我们注意到您正在使用人脸或触摸解锁,而不是安全密钥。点击“继续”即可从现在开始使用人脸或触摸解锁登录。 +webauthn_setup_mismatch.heading.webauthn: 发现安全密钥 +webauthn_setup_mismatch.heading.webauthn_platform: 发现人脸或触摸解锁 +webauthn_setup_mismatch.undo: 撤消 zxcvbn.feedback.a_word_by_itself_is_easy_to_guess: 单字容易被人猜出 zxcvbn.feedback.add_another_word_or_two_uncommon_words_are_better: 再加一两个字不常见的字更好 zxcvbn.feedback.all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: 都是大写几乎和都是小写一样容易被人猜出 diff --git a/config/routes.rb b/config/routes.rb index 4a45d208115..5114f550a78 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -250,6 +250,10 @@ get '/webauthn_setup' => 'users/webauthn_setup#new', as: :webauthn_setup patch '/webauthn_setup' => 'users/webauthn_setup#confirm' + get '/webauthn_setup_mismatch' => 'users/webauthn_setup_mismatch#show' + patch '/webauthn_setup_mismatch' => 'users/webauthn_setup_mismatch#update' + delete '/webauthn_setup_mismatch' => 'users/webauthn_setup_mismatch#destroy' + get '/authenticator_setup' => 'users/totp_setup#new' patch '/authenticator_setup' => 'users/totp_setup#confirm' diff --git a/spec/controllers/concerns/mfa_deletion_concern_spec.rb b/spec/controllers/concerns/mfa_deletion_concern_spec.rb new file mode 100644 index 00000000000..0193cbffaae --- /dev/null +++ b/spec/controllers/concerns/mfa_deletion_concern_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +RSpec.describe MfaDeletionConcern do + controller ApplicationController do + include MfaDeletionConcern + end + + let(:user) { create(:user, :fully_registered) } + + before do + stub_sign_in(user) + end + + describe '#handle_successful_mfa_deletion' do + let(:event_type) { Event.event_types.keys.sample.to_sym } + subject(:result) { controller.handle_successful_mfa_deletion(event_type:) } + + it 'does not return a value' do + expect(result).to be_nil + end + + it 'creates user event using event_type argument' do + expect(controller).to receive(:create_user_event).with(event_type) + + result + end + + it 'revokes remembered device for user' do + expect(controller).to receive(:revoke_remember_device).with(user) + + result + end + + it 'sends risc push notification' do + expect(PushNotification::HttpPush).to receive(:deliver) do |event| + expect(event.user).to eq(user) + end + + result + end + end +end diff --git a/spec/controllers/users/webauthn_setup_mismatch_controller_spec.rb b/spec/controllers/users/webauthn_setup_mismatch_controller_spec.rb new file mode 100644 index 00000000000..9b2042b11b0 --- /dev/null +++ b/spec/controllers/users/webauthn_setup_mismatch_controller_spec.rb @@ -0,0 +1,231 @@ +require 'rails_helper' + +RSpec.describe Users::WebauthnSetupMismatchController do + let(:user) { create(:user, :fully_registered, :with_webauthn) } + let(:webauthn_mismatch_id) { user.webauthn_configurations.take.id } + + before do + stub_sign_in(user) if user + controller.user_session&.[]=(:webauthn_mismatch_id, webauthn_mismatch_id) + end + + shared_examples 'a validated mismatch controller action' do + it 'applies secure headers override' do + expect(controller).to receive(:apply_secure_headers_override) + + response + end + + context 'user is not signed in' do + let(:user) { nil } + + it 'redirects user to sign in' do + expect(response).to redirect_to(new_user_session_url) + end + end + + context 'user is not fully-authenticated' do + let(:user) { nil } + + before do + stub_sign_in_before_2fa(create(:user, :fully_registered)) + end + + it 'redirects user to authenticate' do + expect(response).to redirect_to(user_two_factor_authentication_url) + end + end + + context 'user is not recently authenticated' do + before do + expire_reauthn_window + end + + it 'redirects user to authenticate' do + expect(response).to redirect_to(login_two_factor_options_url) + end + end + + context 'session configuration id is missing' do + let(:webauthn_mismatch_id) { nil } + + it 'redirects to next setup path' do + expect(response).to redirect_to(account_url) + end + end + + context 'session configuration id is invalid' do + let(:webauthn_mismatch_id) { 1 } + + it 'redirects to next setup path' do + expect(response).to redirect_to(account_url) + end + end + end + + describe '#show' do + subject(:response) { get :show } + + it_behaves_like 'a validated mismatch controller action' + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_visited, + configuration_id: webauthn_mismatch_id, + platform_authenticator: false, + ) + end + + it 'assigns presenter instance variable for view' do + response + + presenter = assigns(:presenter) + expect(presenter).to be_kind_of(WebauthnSetupMismatchPresenter) + expect(presenter.configuration.id).to eq(webauthn_mismatch_id) + end + + context 'with platform authenticator' do + let(:user) { create(:user, :fully_registered, :with_webauthn_platform) } + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_visited, + configuration_id: webauthn_mismatch_id, + platform_authenticator: true, + ) + end + end + end + + describe '#update' do + subject(:response) { patch :update } + + it_behaves_like 'a validated mismatch controller action' + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_submitted, + configuration_id: webauthn_mismatch_id, + platform_authenticator: false, + confirmed_mismatch: true, + ) + end + + it 'redirects to next setup path' do + expect(response).to redirect_to(account_url) + end + + context 'with platform authenticator' do + let(:user) { create(:user, :fully_registered, :with_webauthn_platform) } + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_submitted, + configuration_id: webauthn_mismatch_id, + platform_authenticator: true, + confirmed_mismatch: true, + ) + end + end + end + + describe '#destroy' do + subject(:response) { delete :destroy } + + it_behaves_like 'a validated mismatch controller action' + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_submitted, + success: true, + configuration_id: webauthn_mismatch_id, + platform_authenticator: false, + confirmed_mismatch: false, + ) + end + + it 'invalidates deleted authenticator' do + expect(controller).to receive(:handle_successful_mfa_deletion) + .with(event_type: :webauthn_key_removed) + + response + end + + context 'if deletion is unsuccessful' do + before do + user.phone_configurations.delete_all + end + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_submitted, + success: false, + error_details: { configuration_id: { only_method: true } }, + configuration_id: webauthn_mismatch_id, + platform_authenticator: false, + confirmed_mismatch: false, + ) + end + + it 'assigns presenter instance variable for view' do + response + + presenter = assigns(:presenter) + expect(presenter).to be_kind_of(WebauthnSetupMismatchPresenter) + expect(presenter.configuration.id).to eq(webauthn_mismatch_id) + end + + it 'flashes error message' do + response + + expect(flash.now[:error]).to eq(t('errors.manage_authenticator.remove_only_method_error')) + end + + it 'renders new view' do + expect(response).to render_template(:show) + end + end + + context 'with platform authenticator' do + let(:user) { create(:user, :fully_registered, :with_webauthn_platform) } + + it 'logs analytics event' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + :webauthn_setup_mismatch_submitted, + success: true, + configuration_id: webauthn_mismatch_id, + platform_authenticator: true, + confirmed_mismatch: false, + ) + end + end + end +end diff --git a/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb b/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb index f0c210aceb9..be7244a503a 100644 --- a/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb +++ b/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb @@ -4,7 +4,10 @@ let(:user) { create(:user) } let(:configuration) { create(:webauthn_configuration, user:) } let(:configuration_id) { configuration&.id } - let(:form) { described_class.new(user:, configuration_id:) } + let(:skip_multiple_mfa_validation) {} + let(:form) do + described_class.new(user:, configuration_id:, **{ skip_multiple_mfa_validation: }.compact) + end describe '#submit' do let(:result) { form.submit } @@ -95,6 +98,19 @@ ) end + context 'with skipped multiple mfa validation' do + let(:skip_multiple_mfa_validation) { true } + + it 'returns a successful result' do + expect(result.success?).to eq(true) + expect(result.to_h).to eq( + success: true, + configuration_id:, + platform_authenticator: false, + ) + end + end + context 'with platform authenticator' do let(:configuration) do create(:webauthn_configuration, :platform_authenticator, user:) diff --git a/spec/presenters/webauthn_setup_mismatch_presenter_spec.rb b/spec/presenters/webauthn_setup_mismatch_presenter_spec.rb new file mode 100644 index 00000000000..7fafd793ecc --- /dev/null +++ b/spec/presenters/webauthn_setup_mismatch_presenter_spec.rb @@ -0,0 +1,71 @@ +require 'rails_helper' + +RSpec.describe WebauthnSetupMismatchPresenter do + subject(:presenter) { described_class.new(configuration:) } + let(:platform_authenticator) {} + let(:configuration) { create(:webauthn_configuration, platform_authenticator:) } + + describe '#heading' do + subject(:heading) { presenter.heading } + + context 'with non-platform authenticator' do + let(:platform_authenticator) { false } + + it { is_expected.to eq(t('webauthn_setup_mismatch.heading.webauthn')) } + end + + context 'with platform authenticator' do + let(:platform_authenticator) { true } + + it { is_expected.to eq(t('webauthn_setup_mismatch.heading.webauthn_platform')) } + end + end + + describe '#description' do + subject(:description) { presenter.description } + + context 'with non-platform authenticator' do + let(:platform_authenticator) { false } + + it { is_expected.to eq(t('webauthn_setup_mismatch.description.webauthn')) } + end + + context 'with platform authenticator' do + let(:platform_authenticator) { true } + + it { is_expected.to eq(t('webauthn_setup_mismatch.description.webauthn_platform')) } + end + end + + describe '#correct_image_path' do + subject(:correct_image_path) { presenter.correct_image_path } + + context 'with non-platform authenticator' do + let(:platform_authenticator) { false } + + it { is_expected.to eq('webauthn-mismatch/webauthn-checked.svg') } + end + + context 'with platform authenticator' do + let(:platform_authenticator) { true } + + it { is_expected.to eq('webauthn-mismatch/webauthn-platform-checked.svg') } + end + end + + describe '#incorrect_image_path' do + subject(:incorrect_image_path) { presenter.incorrect_image_path } + + context 'with non-platform authenticator' do + let(:platform_authenticator) { false } + + it { is_expected.to eq('webauthn-mismatch/webauthn-platform-unchecked.svg') } + end + + context 'with platform authenticator' do + let(:platform_authenticator) { true } + + it { is_expected.to eq('webauthn-mismatch/webauthn-unchecked.svg') } + end + end +end diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index d03875df7cf..ce03cfd2f8c 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -37,6 +37,12 @@ def stub_sign_in_before_2fa(user = build(:user, password: VALID_PASSWORD)) controller.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION] = true end + def expire_reauthn_window + controller.user_session[:auth_events].each do |auth_event| + auth_event['at'] -= IdentityConfig.store.reauthn_window.seconds + end + end + def stub_verify_steps_one_and_two( user, applicant: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE diff --git a/spec/views/users/webauthn_setup_mismatch/show.html.erb_spec.rb b/spec/views/users/webauthn_setup_mismatch/show.html.erb_spec.rb new file mode 100644 index 00000000000..b9e6bbfbfd5 --- /dev/null +++ b/spec/views/users/webauthn_setup_mismatch/show.html.erb_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe 'users/webauthn_setup_mismatch/show.html.erb' do + subject(:rendered) { render } + let(:configuration) { create(:webauthn_configuration) } + let(:presenter) { WebauthnSetupMismatchPresenter.new(configuration:) } + + before do + assign(:presenter, presenter) + end + + it 'sets title from presenter heading' do + expect(view).to receive(:title=).with(presenter.heading) + + render + end + + it 'renders heading from presenter heading' do + expect(rendered).to have_css('h1', text: presenter.heading) + end + + it 'renders description from presenter description' do + expect(rendered).to have_css('p', text: presenter.description) + end + + it 'renders buttons to continue or undo' do + expect(rendered).to have_button(t('forms.buttons.continue')) + expect(rendered).to have_button(t('webauthn_setup_mismatch.undo')) + end +end From c3112740fb2ede6f2e9fdd796128b84e1874b8ad Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Fri, 24 Jan 2025 12:44:24 -0600 Subject: [PATCH 12/14] Avoid queries for ServiceProvider with blank issuer (#11798) * Do not query for service provider with blank issuer changelog: Internal, Performance, Avoid queries for ServiceProvider with blank issuer * Move Regexp to constant * add spec --- app/controllers/concerns/saml_idp_auth_concern.rb | 7 ++++++- app/forms/openid_connect_authorize_form.rb | 7 ++++++- app/services/saml_request_parser.rb | 3 ++- spec/forms/openid_connect_authorize_form_spec.rb | 12 ++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/controllers/concerns/saml_idp_auth_concern.rb b/app/controllers/concerns/saml_idp_auth_concern.rb index db3f2015c02..80fd3eb54ee 100644 --- a/app/controllers/concerns/saml_idp_auth_concern.rb +++ b/app/controllers/concerns/saml_idp_auth_concern.rb @@ -237,7 +237,12 @@ def saml_response_signature_options def saml_request_service_provider return @saml_request_service_provider if defined?(@saml_request_service_provider) - @saml_request_service_provider = ServiceProvider.find_by(issuer: current_issuer) + @saml_request_service_provider = + if current_issuer.blank? + nil + else + ServiceProvider.find_by(issuer: current_issuer) + end end def current_issuer diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index c3bbca2b17c..0b60951228a 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -101,7 +101,12 @@ def cannot_validate_redirect_uri? def service_provider return @service_provider if defined?(@service_provider) - @service_provider = ServiceProvider.find_by(issuer: client_id) + @service_provider = + if client_id.blank? + nil + else + ServiceProvider.find_by(issuer: client_id) + end end def link_identity_to_service_provider( diff --git a/app/services/saml_request_parser.rb b/app/services/saml_request_parser.rb index bf1e3a760ee..c85d779d5eb 100644 --- a/app/services/saml_request_parser.rb +++ b/app/services/saml_request_parser.rb @@ -2,6 +2,7 @@ class SamlRequestParser URI_PATTERN = Saml::Idp::Constants::REQUESTED_ATTRIBUTES_CLASSREF + ESCAPED_URI_PATTERN = /#{Regexp.escape(URI_PATTERN)}/ def initialize(request) @request = request @@ -24,7 +25,7 @@ def authn_context_attr_nodes samlp: Saml::XML::Namespaces::PROTOCOL, saml: Saml::XML::Namespaces::ASSERTION, ).select do |node| - node.content =~ /#{Regexp.escape(URI_PATTERN)}/ + node.content =~ ESCAPED_URI_PATTERN end end end diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index ccdfa5f3b7f..86a71c0e0f3 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -784,4 +784,16 @@ end end end + + describe '#service_provider' do + context 'empty client_id' do + let(:client_id) { '' } + + it 'does not query the database' do + expect(ServiceProvider).to_not receive(:find_by) + + expect(form.service_provider).to be_nil + end + end + end end From d32e350e4af1b6ddf182b7aeba07871325452b42 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Mon, 27 Jan 2025 08:34:43 -0500 Subject: [PATCH 13/14] Omit empty FormResponse errors from analytics logging (#11799) changelog: Internal, Analytics, Omit empty FormResponse errors from analytics logging --- app/services/form_response.rb | 2 +- .../delete_account_controller_spec.rb | 4 - .../account_reset/request_controller_spec.rb | 3 - ...two_factor_authenticatable_methods_spec.rb | 2 - .../event_disavowal_controller_spec.rb | 2 +- .../idv/address_controller_spec.rb | 1 - .../idv/agreement_controller_spec.rb | 1 - .../idv/by_mail/enter_code_controller_spec.rb | 9 +- .../idv/document_capture_controller_spec.rb | 1 - .../idv/how_to_verify_controller_spec.rb | 2 - .../idv/hybrid_handoff_controller_spec.rb | 1 - .../document_capture_controller_spec.rb | 1 - .../idv/image_uploads_controller_spec.rb | 9 -- .../idv/in_person/address_controller_spec.rb | 2 - .../idv/in_person/ssn_controller_spec.rb | 2 - .../idv/in_person/state_id_controller_spec.rb | 1 - .../idv/otp_verification_controller_spec.rb | 1 - spec/controllers/idv/phone_controller_spec.rb | 2 - .../idv/resend_otp_controller_spec.rb | 1 - spec/controllers/idv/ssn_controller_spec.rb | 2 - .../authorization_controller_spec.rb | 15 --- .../openid_connect/logout_controller_spec.rb | 8 -- .../openid_connect/token_controller_spec.rb | 1 - .../user_info_controller_spec.rb | 1 - .../risc/security_events_controller_spec.rb | 1 - spec/controllers/saml_idp_controller_spec.rb | 6 -- .../email_confirmations_controller_spec.rb | 1 - .../sign_up/passwords_controller_spec.rb | 1 - .../sign_up/registrations_controller_spec.rb | 2 - .../options_controller_spec.rb | 1 - ...rsonal_key_verification_controller_spec.rb | 1 - .../piv_cac_verification_controller_spec.rb | 1 - .../totp_verification_controller_spec.rb | 2 - .../backup_code_setup_controller_spec.rb | 1 - .../users/edit_phone_controller_spec.rb | 1 - .../email_confirmations_controller_spec.rb | 2 - .../users/emails_controller_spec.rb | 4 - .../users/passwords_controller_spec.rb | 2 - .../users/phone_setup_controller_spec.rb | 3 - ...ac_authentication_setup_controller_spec.rb | 2 - .../users/piv_cac_login_controller_spec.rb | 3 - .../users/reset_passwords_controller_spec.rb | 7 -- .../users/totp_setup_controller_spec.rb | 8 -- ...o_factor_authentication_controller_spec.rb | 2 - ...or_authentication_setup_controller_spec.rb | 1 - .../verify_personal_key_controller_spec.rb | 1 - .../users/webauthn_setup_controller_spec.rb | 4 - spec/features/idv/analytics_spec.rb | 100 +++++++++--------- spec/forms/add_user_email_form_spec.rb | 4 +- spec/forms/idv/api_image_upload_form_spec.rb | 3 - .../openid_connect_authorize_form_spec.rb | 2 +- spec/forms/openid_connect_token_form_spec.rb | 2 +- .../forms/otp_delivery_selection_form_spec.rb | 2 +- spec/forms/password_form_spec.rb | 4 +- spec/forms/password_reset_email_form_spec.rb | 4 +- spec/forms/personal_key_form_spec.rb | 2 +- spec/forms/register_user_email_form_spec.rb | 12 +-- spec/forms/reset_password_form_spec.rb | 2 +- spec/forms/totp_setup_form_spec.rb | 6 +- spec/forms/totp_verification_form_spec.rb | 6 +- .../two_factor_login_options_form_spec.rb | 2 +- spec/forms/update_user_password_form_spec.rb | 2 +- .../user_piv_cac_verification_form_spec.rb | 4 +- spec/forms/webauthn_setup_form_spec.rb | 4 +- spec/forms/webauthn_visit_form_spec.rb | 4 +- .../socure_shadow_mode_proofing_job_spec.rb | 2 +- spec/services/form_response_spec.rb | 4 +- spec/services/saml_request_validator_spec.rb | 26 ++--- 68 files changed, 101 insertions(+), 227 deletions(-) diff --git a/app/services/form_response.rb b/app/services/form_response.rb index 3fdbbe93384..a2b86a77d09 100644 --- a/app/services/form_response.rb +++ b/app/services/form_response.rb @@ -19,7 +19,7 @@ def success? def to_h hash = { success: success } - hash[:errors] = errors if !serialize_error_details_only? + hash[:errors] = errors.presence if !serialize_error_details_only? hash[:error_details] = flatten_details(error_details) if error_details.present? hash.merge!(extra) hash diff --git a/spec/controllers/account_reset/delete_account_controller_spec.rb b/spec/controllers/account_reset/delete_account_controller_spec.rb index 66fbb96a24f..94b847b35d5 100644 --- a/spec/controllers/account_reset/delete_account_controller_spec.rb +++ b/spec/controllers/account_reset/delete_account_controller_spec.rb @@ -24,7 +24,6 @@ 'Account Reset: delete', user_id: user.uuid, success: true, - errors: {}, mfa_method_counts: { backup_codes: BackupCodeGenerator::NUMBER_OF_CODES, webauthn: 2, @@ -117,7 +116,6 @@ 'Account Reset: delete', user_id: user.uuid, success: true, - errors: {}, mfa_method_counts: { phone: 1 }, profile_idv_level: 'legacy_unsupervised', identity_verified: true, @@ -140,7 +138,6 @@ 'Account Reset: delete', user_id: user.uuid, success: true, - errors: {}, mfa_method_counts: { phone: 1 }, profile_idv_level: 'unsupervised_with_selfie', identity_verified: true, @@ -165,7 +162,6 @@ 'Account Reset: delete', user_id: user.uuid, success: true, - errors: {}, mfa_method_counts: { phone: 1 }, profile_idv_level: 'in_person', identity_verified: true, diff --git a/spec/controllers/account_reset/request_controller_spec.rb b/spec/controllers/account_reset/request_controller_spec.rb index 645f4c6bd4f..4b08c79da24 100644 --- a/spec/controllers/account_reset/request_controller_spec.rb +++ b/spec/controllers/account_reset/request_controller_spec.rb @@ -107,7 +107,6 @@ totp: true, piv_cac: false, email_addresses: 1, - errors: {}, ) end @@ -127,7 +126,6 @@ email_addresses: 1, request_id: 'fake-message-request-id', message_id: 'fake-message-id', - errors: {}, ) end @@ -145,7 +143,6 @@ totp: false, piv_cac: true, email_addresses: 1, - errors: {}, ) end diff --git a/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb b/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb index 6516f589081..8bd4e65e97d 100644 --- a/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb +++ b/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb @@ -32,7 +32,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: true, - errors: {}, multi_factor_auth_method: TwoFactorAuthenticatable::AuthMethod::REMEMBER_DEVICE, enabled_mfa_methods_count: 0, new_device: true, @@ -219,7 +218,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: true, - errors: {}, multi_factor_auth_method: TwoFactorAuthenticatable::AuthMethod::SMS, enabled_mfa_methods_count: 1, new_device: true, diff --git a/spec/controllers/event_disavowal_controller_spec.rb b/spec/controllers/event_disavowal_controller_spec.rb index e1e6c8b337a..f4e4b6180b2 100644 --- a/spec/controllers/event_disavowal_controller_spec.rb +++ b/spec/controllers/event_disavowal_controller_spec.rb @@ -172,7 +172,7 @@ end end - def build_analytics_hash(success: true, errors: {}, user_id: nil) + def build_analytics_hash(success: true, errors: nil, user_id: nil) hash_including( { event_created_at: event.created_at, diff --git a/spec/controllers/idv/address_controller_spec.rb b/spec/controllers/idv/address_controller_spec.rb index 9a42b6aab75..7b7e5ffe231 100644 --- a/spec/controllers/idv/address_controller_spec.rb +++ b/spec/controllers/idv/address_controller_spec.rb @@ -90,7 +90,6 @@ expect(@analytics).to have_logged_event( 'IdV: address submitted', success: true, - errors: {}, address_edited: true, ) end diff --git a/spec/controllers/idv/agreement_controller_spec.rb b/spec/controllers/idv/agreement_controller_spec.rb index d4905310069..fd3669ba7e7 100644 --- a/spec/controllers/idv/agreement_controller_spec.rb +++ b/spec/controllers/idv/agreement_controller_spec.rb @@ -100,7 +100,6 @@ let(:analytics_args) do { success: true, - errors: {}, step: 'agreement', analytics_id: 'Doc Auth', } diff --git a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb index 168558f6752..24102dbc1ea 100644 --- a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb @@ -200,7 +200,6 @@ expect(@analytics).to have_logged_event( 'IdV: enter verify by mail code submitted', success: true, - errors: {}, pending_in_person_enrollment: false, fraud_check_failed: false, enqueued_at: pending_profile.gpo_confirmation_codes.last.code_sent_at, @@ -246,7 +245,6 @@ expect(@analytics).to have_logged_event( 'IdV: enter verify by mail code submitted', success: true, - errors: {}, pending_in_person_enrollment: true, fraud_check_failed: false, enqueued_at: pending_profile.gpo_confirmation_codes.last.code_sent_at, @@ -276,7 +274,6 @@ expect(@analytics).to have_logged_event( 'IdV: enter verify by mail code submitted', success: true, - errors: {}, pending_in_person_enrollment: false, fraud_check_failed: true, enqueued_at: pending_profile.gpo_confirmation_codes.last.code_sent_at, @@ -314,7 +311,6 @@ expect(@analytics).to have_logged_event( 'IdV: enter verify by mail code submitted', success: true, - errors: {}, pending_in_person_enrollment: false, fraud_check_failed: true, enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, @@ -357,7 +353,6 @@ expect(@analytics).to have_logged_event( 'IdV: enter verify by mail code submitted', success: true, - errors: {}, pending_in_person_enrollment: false, fraud_check_failed: true, enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, @@ -455,11 +450,11 @@ failed_gpo_submission_events = @analytics.events['IdV: enter verify by mail code submitted'] - .reject { |event_attributes| event_attributes[:errors].empty? } + .reject { |event_attributes| event_attributes[:errors].blank? } successful_gpo_submission_events = @analytics.events['IdV: enter verify by mail code submitted'] - .select { |event_attributes| event_attributes[:errors].empty? } + .select { |event_attributes| event_attributes[:errors].blank? } expect(failed_gpo_submission_events.count).to eq(max_attempts - 1) expect(successful_gpo_submission_events.count).to eq(1) diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index 83710a44d98..9966a2969b1 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -348,7 +348,6 @@ let(:analytics_args) do { success: true, - errors: {}, analytics_id: 'Doc Auth', flow_path: 'standard', step: 'document_capture', diff --git a/spec/controllers/idv/how_to_verify_controller_spec.rb b/spec/controllers/idv/how_to_verify_controller_spec.rb index 4ce5e539e69..8233aa8992f 100644 --- a/spec/controllers/idv/how_to_verify_controller_spec.rb +++ b/spec/controllers/idv/how_to_verify_controller_spec.rb @@ -205,7 +205,6 @@ { analytics_id: 'Doc Auth', step: 'how_to_verify', - errors: {}, success: true, selection:, } @@ -230,7 +229,6 @@ { analytics_id: 'Doc Auth', step: 'how_to_verify', - errors: {}, success: true, selection:, } diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb index 8b161f4d779..94eadcf60b3 100644 --- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb +++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb @@ -342,7 +342,6 @@ let(:analytics_args) do { success: true, - errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', diff --git a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb index f6d744dbc67..cd00a98b289 100644 --- a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb @@ -196,7 +196,6 @@ let(:analytics_args) do { success: true, - errors: {}, analytics_id: 'Doc Auth', flow_path: 'hybrid', step: 'document_capture', diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index 5b1efff6ded..545301c94f7 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -346,7 +346,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -392,7 +391,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload vendor pii validation', success: true, - errors: {}, attention_with_barcode: false, user_id: user.uuid, submit_attempts: 1, @@ -479,7 +477,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -558,7 +555,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -637,7 +633,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -713,7 +708,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -788,7 +782,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -890,7 +883,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -957,7 +949,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, diff --git a/spec/controllers/idv/in_person/address_controller_spec.rb b/spec/controllers/idv/in_person/address_controller_spec.rb index afd32c3a37a..532ae025764 100644 --- a/spec/controllers/idv/in_person/address_controller_spec.rb +++ b/spec/controllers/idv/in_person/address_controller_spec.rb @@ -136,7 +136,6 @@ let(:analytics_args) do { success: true, - errors: {}, analytics_id: 'In Person Proofing', flow_path: 'standard', step: 'address', @@ -217,7 +216,6 @@ let(:analytics_args) do { success: false, - errors: {}, analytics_id: 'In Person Proofing', flow_path: 'standard', step: 'address', diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index 9e56e2e319c..48fef202097 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -138,7 +138,6 @@ flow_path: 'standard', step: 'ssn', success: true, - errors: {}, } end @@ -176,7 +175,6 @@ step: 'ssn', success: true, previous_ssn_edit_distance: 6, - errors: {}, } end diff --git a/spec/controllers/idv/in_person/state_id_controller_spec.rb b/spec/controllers/idv/in_person/state_id_controller_spec.rb index fe40682d3e7..d8a51fa13b2 100644 --- a/spec/controllers/idv/in_person/state_id_controller_spec.rb +++ b/spec/controllers/idv/in_person/state_id_controller_spec.rb @@ -150,7 +150,6 @@ let(:analytics_args) do { success: true, - errors: {}, analytics_id: 'In Person Proofing', flow_path: 'standard', step: 'state_id', diff --git a/spec/controllers/idv/otp_verification_controller_spec.rb b/spec/controllers/idv/otp_verification_controller_spec.rb index 64cd5dc6318..f308199a8e8 100644 --- a/spec/controllers/idv/otp_verification_controller_spec.rb +++ b/spec/controllers/idv/otp_verification_controller_spec.rb @@ -160,7 +160,6 @@ 'IdV: phone confirmation otp submitted', hash_including( success: true, - errors: {}, code_expired: false, code_matches: true, otp_delivery_preference: :sms, diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index f2a13f69406..8295394f36a 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -337,7 +337,6 @@ 'IdV: phone confirmation form', hash_including( success: true, - errors: {}, area_code: '703', country_code: 'US', carrier: 'Test Mobile Carrier', @@ -441,7 +440,6 @@ success: true, new_phone_added: true, hybrid_handoff_phone_used: false, - errors: {}, phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), country_code: proofing_phone.country, area_code: proofing_phone.area_code, diff --git a/spec/controllers/idv/resend_otp_controller_spec.rb b/spec/controllers/idv/resend_otp_controller_spec.rb index 71c12351174..fc68c94a0a1 100644 --- a/spec/controllers/idv/resend_otp_controller_spec.rb +++ b/spec/controllers/idv/resend_otp_controller_spec.rb @@ -57,7 +57,6 @@ hash_including( success: true, phone_fingerprint: Pii::Fingerprinter.fingerprint(Phonelib.parse(phone).e164), - errors: {}, otp_delivery_preference: :sms, country_code: 'US', area_code: '225', diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index b330a154d4d..22f315c532a 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -159,7 +159,6 @@ flow_path: 'standard', step: 'ssn', success: true, - errors: {}, } end @@ -177,7 +176,6 @@ step: 'ssn', success: true, previous_ssn_edit_distance: 6, - errors: {}, } end diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index 90637ce9bd4..68287933ad6 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -131,7 +131,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: true, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', @@ -186,7 +185,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: true, user_fully_authenticated: true, acr_values: '', @@ -381,7 +379,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/2', @@ -713,7 +710,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', @@ -801,7 +797,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', @@ -892,7 +887,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', @@ -1054,7 +1048,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: true, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', @@ -1107,7 +1100,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: true, user_fully_authenticated: true, acr_values: '', @@ -1303,7 +1295,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/2', @@ -1637,7 +1628,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', @@ -1725,7 +1715,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', @@ -1816,7 +1805,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: false, user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', @@ -2129,7 +2117,6 @@ code_challenge_present: false, scope: 'openid profile', unknown_authn_contexts: unknown_value, - errors: {}, ) end end @@ -2447,7 +2434,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: true, user_fully_authenticated: false, acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', @@ -2576,7 +2562,6 @@ client_id: client_id, prompt: 'select_account', allow_prompt_login: true, - errors: {}, unauthorized_scope: true, user_fully_authenticated: false, acr_values: '', diff --git a/spec/controllers/openid_connect/logout_controller_spec.rb b/spec/controllers/openid_connect/logout_controller_spec.rb index 3c2780cde83..690e59f66bd 100644 --- a/spec/controllers/openid_connect/logout_controller_spec.rb +++ b/spec/controllers/openid_connect/logout_controller_spec.rb @@ -159,7 +159,6 @@ client_id: service_provider.issuer, client_id_parameter_present: false, id_token_hint_parameter_present: true, - errors: {}, sp_initiated: true, oidc: true, ), @@ -171,7 +170,6 @@ client_id: service_provider.issuer, client_id_parameter_present: false, id_token_hint_parameter_present: true, - errors: {}, sp_initiated: true, oidc: true, ), @@ -325,7 +323,6 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: {}, sp_initiated: true, oidc: true, ), @@ -337,7 +334,6 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: {}, sp_initiated: true, oidc: true, ), @@ -466,7 +462,6 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: {}, sp_initiated: true, oidc: true, ), @@ -478,7 +473,6 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: {}, sp_initiated: true, oidc: true, ), @@ -849,7 +843,6 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: {}, sp_initiated: true, oidc: true, ) @@ -859,7 +852,6 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: {}, sp_initiated: true, oidc: true, ) diff --git a/spec/controllers/openid_connect/token_controller_spec.rb b/spec/controllers/openid_connect/token_controller_spec.rb index 037299f9590..e4212c73a53 100644 --- a/spec/controllers/openid_connect/token_controller_spec.rb +++ b/spec/controllers/openid_connect/token_controller_spec.rb @@ -60,7 +60,6 @@ success: true, client_id: client_id, user_id: user.uuid, - errors: {}, code_digest: kind_of(String), code_verifier_present: false, expires_in: 0, diff --git a/spec/controllers/openid_connect/user_info_controller_spec.rb b/spec/controllers/openid_connect/user_info_controller_spec.rb index d0eb23d054f..a1d90ad6952 100644 --- a/spec/controllers/openid_connect/user_info_controller_spec.rb +++ b/spec/controllers/openid_connect/user_info_controller_spec.rb @@ -164,7 +164,6 @@ success: true, client_id: identity.service_provider, ial: identity.ial, - errors: {}, ) expect(@analytics).to_not have_logged_event( diff --git a/spec/controllers/risc/security_events_controller_spec.rb b/spec/controllers/risc/security_events_controller_spec.rb index f7709d39413..2a9ed0bdb79 100644 --- a/spec/controllers/risc/security_events_controller_spec.rb +++ b/spec/controllers/risc/security_events_controller_spec.rb @@ -54,7 +54,6 @@ 'RISC: Security event received', client_id: service_provider.issuer, event_type: event_type, - errors: {}, jti: jti, success: true, user_id: user.uuid, diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 98250fea260..2557043d324 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -881,7 +881,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: true, - errors: {}, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], authn_context_comparison: 'exact', @@ -1034,7 +1033,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: true, - errors: {}, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: ['http://idmanagement.gov/ns/assurance/ial/1'], authn_context_comparison: 'minimum', @@ -1953,7 +1951,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: true, - errors: {}, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: [Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF], authn_context_comparison: 'exact', @@ -2637,7 +2634,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: true, - errors: {}, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: [ Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, @@ -2690,7 +2686,6 @@ def stub_requested_attributes 'SAML Auth', hash_including( success: true, - errors: {}, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: request_authn_contexts, authn_context_comparison: 'exact', @@ -2742,7 +2737,6 @@ def stub_requested_attributes 'SAML Auth', hash_including( success: true, - errors: {}, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: request_authn_contexts, authn_context_comparison: 'exact', diff --git a/spec/controllers/sign_up/email_confirmations_controller_spec.rb b/spec/controllers/sign_up/email_confirmations_controller_spec.rb index 2951e108504..56ee0371e5b 100644 --- a/spec/controllers/sign_up/email_confirmations_controller_spec.rb +++ b/spec/controllers/sign_up/email_confirmations_controller_spec.rb @@ -174,7 +174,6 @@ expect(@analytics).to have_logged_event( 'User Registration: Email Confirmation', success: true, - errors: {}, user_id: user.uuid, ) end diff --git a/spec/controllers/sign_up/passwords_controller_spec.rb b/spec/controllers/sign_up/passwords_controller_spec.rb index 6b6769b0bf2..b90c747a195 100644 --- a/spec/controllers/sign_up/passwords_controller_spec.rb +++ b/spec/controllers/sign_up/passwords_controller_spec.rb @@ -72,7 +72,6 @@ expect(@analytics).to have_logged_event( 'Password Creation', success: true, - errors: {}, user_id: user.uuid, request_id_present: false, ) diff --git a/spec/controllers/sign_up/registrations_controller_spec.rb b/spec/controllers/sign_up/registrations_controller_spec.rb index ee483e0e8d7..e6156bf038b 100644 --- a/spec/controllers/sign_up/registrations_controller_spec.rb +++ b/spec/controllers/sign_up/registrations_controller_spec.rb @@ -77,7 +77,6 @@ 'User Registration: Email Submitted', success: true, rate_limited: false, - errors: {}, email_already_exists: false, user_id: user.uuid, domain_name: 'example.com', @@ -129,7 +128,6 @@ 'User Registration: Email Submitted', success: true, rate_limited: false, - errors: {}, email_already_exists: true, user_id: existing_user.uuid, domain_name: 'example.com', diff --git a/spec/controllers/two_factor_authentication/options_controller_spec.rb b/spec/controllers/two_factor_authentication/options_controller_spec.rb index 6b61bfe97e2..dc192367488 100644 --- a/spec/controllers/two_factor_authentication/options_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/options_controller_spec.rb @@ -86,7 +86,6 @@ 'Multi-Factor Authentication: option list', selection: 'sms', success: true, - errors: {}, enabled_mfa_methods_count: 2, mfa_method_counts: { phone: 1, piv_cac: 1 }, ) 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 0683b23159e..34b08cf63e3 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 @@ -71,7 +71,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: true, - errors: {}, enabled_mfa_methods_count: 1, multi_factor_auth_method: 'personal-key', multi_factor_auth_method_created_at:, 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 07523165631..da9a2561f61 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 @@ -122,7 +122,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: true, - errors: {}, context: 'authentication', multi_factor_auth_method: 'piv_cac', new_device: true, 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 989a185b01e..526b41f0251 100644 --- a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb @@ -53,7 +53,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: true, - errors: {}, enabled_mfa_methods_count: 2, multi_factor_auth_method: 'totp', multi_factor_auth_method_created_at: cfg.created_at.strftime('%s%L'), @@ -174,7 +173,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: false, - errors: {}, enabled_mfa_methods_count: 2, multi_factor_auth_method: 'totp', new_device: true, diff --git a/spec/controllers/users/backup_code_setup_controller_spec.rb b/spec/controllers/users/backup_code_setup_controller_spec.rb index 5e5051e95c1..a419fd620b0 100644 --- a/spec/controllers/users/backup_code_setup_controller_spec.rb +++ b/spec/controllers/users/backup_code_setup_controller_spec.rb @@ -41,7 +41,6 @@ expect(@analytics).to have_logged_event( 'Backup Code Setup Visited', success: true, - errors: {}, mfa_method_counts: { phone: 1 }, enabled_mfa_methods_count: 1, in_account_creation_flow: false, diff --git a/spec/controllers/users/edit_phone_controller_spec.rb b/spec/controllers/users/edit_phone_controller_spec.rb index 9b7c77b0a1e..a1f25d7748d 100644 --- a/spec/controllers/users/edit_phone_controller_spec.rb +++ b/spec/controllers/users/edit_phone_controller_spec.rb @@ -21,7 +21,6 @@ expect(@analytics).to have_logged_event( 'Phone Number Change: Form submitted', success: true, - errors: {}, delivery_preference: 'voice', make_default_number: true, phone_configuration_id: phone_configuration.id, diff --git a/spec/controllers/users/email_confirmations_controller_spec.rb b/spec/controllers/users/email_confirmations_controller_spec.rb index 96a5e0d4711..b6206a1b810 100644 --- a/spec/controllers/users/email_confirmations_controller_spec.rb +++ b/spec/controllers/users/email_confirmations_controller_spec.rb @@ -29,7 +29,6 @@ expect(@analytics).to have_logged_event( 'Add Email: Email Confirmation', success: true, - errors: {}, from_select_email_flow: false, user_id: user.uuid, ) @@ -151,7 +150,6 @@ expect(@analytics).to have_logged_event( 'Add Email: Email Confirmation', success: true, - errors: {}, from_select_email_flow: true, user_id: user.uuid, ) diff --git a/spec/controllers/users/emails_controller_spec.rb b/spec/controllers/users/emails_controller_spec.rb index 3fc9869c113..08c98970eed 100644 --- a/spec/controllers/users/emails_controller_spec.rb +++ b/spec/controllers/users/emails_controller_spec.rb @@ -64,7 +64,6 @@ expect(@analytics).to have_logged_event( 'Add Email Requested', success: true, - errors: {}, domain_name: 'example.com', in_select_email_flow: false, user_id: user.uuid, @@ -174,7 +173,6 @@ expect(@analytics).to have_logged_event( 'Add Email Requested', success: true, - errors: {}, user_id: user.uuid, domain_name: email.split('@').last, in_select_email_flow: false, @@ -237,7 +235,6 @@ expect(@analytics).to have_logged_event( 'Email Deletion Requested', success: false, - errors: {}, ) end @@ -257,7 +254,6 @@ expect(@analytics).to have_logged_event( 'Email Deletion Requested', success: true, - errors: {}, ) end diff --git a/spec/controllers/users/passwords_controller_spec.rb b/spec/controllers/users/passwords_controller_spec.rb index e36beb92f52..73701202663 100644 --- a/spec/controllers/users/passwords_controller_spec.rb +++ b/spec/controllers/users/passwords_controller_spec.rb @@ -49,7 +49,6 @@ expect(@analytics).to have_logged_event( 'Password Changed', success: true, - errors: {}, pending_profile_present: false, active_profile_present: false, user_id: subject.current_user.uuid, @@ -152,7 +151,6 @@ expect(@analytics).to have_logged_event( 'Password Changed', success: true, - errors: {}, pending_profile_present: false, active_profile_present: false, user_id: subject.current_user.uuid, diff --git a/spec/controllers/users/phone_setup_controller_spec.rb b/spec/controllers/users/phone_setup_controller_spec.rb index ef05e05f71d..b1e1e60e18e 100644 --- a/spec/controllers/users/phone_setup_controller_spec.rb +++ b/spec/controllers/users/phone_setup_controller_spec.rb @@ -154,7 +154,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication: phone setup', success: true, - errors: {}, otp_delivery_preference: 'voice', area_code: '703', carrier: 'Test Mobile Carrier', @@ -188,7 +187,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication: phone setup', success: true, - errors: {}, otp_delivery_preference: 'sms', area_code: '703', carrier: 'Test Mobile Carrier', @@ -222,7 +220,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication: phone setup', success: true, - errors: {}, otp_delivery_preference: 'sms', area_code: '703', carrier: 'Test Mobile Carrier', diff --git a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb index 1d1e42bcef7..36fcaebac6e 100644 --- a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb +++ b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb @@ -130,7 +130,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', enabled_mfa_methods_count: 1, - errors: {}, multi_factor_auth_method: 'piv_cac', in_account_creation_flow: false, success: true, @@ -147,7 +146,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', enabled_mfa_methods_count: 1, - errors: {}, multi_factor_auth_method: 'piv_cac', in_account_creation_flow: false, success: true, diff --git a/spec/controllers/users/piv_cac_login_controller_spec.rb b/spec/controllers/users/piv_cac_login_controller_spec.rb index 019effa587c..0a3ac9081ac 100644 --- a/spec/controllers/users/piv_cac_login_controller_spec.rb +++ b/spec/controllers/users/piv_cac_login_controller_spec.rb @@ -31,7 +31,6 @@ expect(@analytics).to have_logged_event( :piv_cac_login, - errors: {}, success: false, ) end @@ -120,7 +119,6 @@ expect(@analytics).to have_logged_event( :piv_cac_login, - errors: {}, success: true, new_device: true, ) @@ -180,7 +178,6 @@ expect(@analytics).to have_logged_event( :piv_cac_login, - errors: {}, success: true, new_device: false, ) diff --git a/spec/controllers/users/reset_passwords_controller_spec.rb b/spec/controllers/users/reset_passwords_controller_spec.rb index ceca9e9deee..65ce5a3e402 100644 --- a/spec/controllers/users/reset_passwords_controller_spec.rb +++ b/spec/controllers/users/reset_passwords_controller_spec.rb @@ -344,7 +344,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - errors: {}, error_details: {}, user_id: user.uuid, profile_deactivated: false, @@ -392,7 +391,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - errors: {}, error_details: {}, user_id: user.uuid, profile_deactivated: true, @@ -437,7 +435,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - errors: {}, error_details: {}, user_id: user.uuid, profile_deactivated: false, @@ -468,7 +465,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, error_details: {}, user_id: 'nonexistent-uuid', confirmed: false, @@ -495,7 +491,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, error_details: {}, user_id: user.uuid, confirmed: true, @@ -526,7 +521,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, error_details: {}, user_id: user.uuid, confirmed: false, @@ -551,7 +545,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, error_details: {}, user_id: user.uuid, confirmed: true, diff --git a/spec/controllers/users/totp_setup_controller_spec.rb b/spec/controllers/users/totp_setup_controller_spec.rb index cb4bd345ae5..1dee71f1f2c 100644 --- a/spec/controllers/users/totp_setup_controller_spec.rb +++ b/spec/controllers/users/totp_setup_controller_spec.rb @@ -103,7 +103,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: false, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', enabled_mfa_methods_count: 0, @@ -131,7 +130,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: true, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', auth_app_configuration_id: next_auth_app_id, @@ -162,7 +160,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: true, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', auth_app_configuration_id: next_auth_app_id, @@ -192,7 +189,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: false, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', enabled_mfa_methods_count: 1, @@ -251,7 +247,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: false, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', enabled_mfa_methods_count: 0, @@ -282,7 +277,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: true, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', auth_app_configuration_id: next_auth_app_id, @@ -302,7 +296,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: true, - errors: {}, totp_secret_present: true, multi_factor_auth_method: 'totp', auth_app_configuration_id: next_auth_app_id, @@ -330,7 +323,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', success: false, - errors: {}, totp_secret_present: false, multi_factor_auth_method: 'totp', enabled_mfa_methods_count: 0, diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index 2af71ae525d..ad40ca53e7b 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -344,7 +344,6 @@ def index expect(@analytics).to have_logged_event( 'OTP: Delivery Selection', success: true, - errors: {}, **otp_preference_sms, resend: true, context: 'authentication', @@ -510,7 +509,6 @@ def index expect(@analytics).to have_logged_event( 'OTP: Delivery Selection', success: true, - errors: {}, otp_delivery_preference: 'voice', resend: false, context: 'authentication', diff --git a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb index 97810f3400d..f661bf31ae0 100644 --- a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb @@ -191,7 +191,6 @@ selection: ['voice'], success: true, selected_mfa_count: 1, - errors: {}, ) end diff --git a/spec/controllers/users/verify_personal_key_controller_spec.rb b/spec/controllers/users/verify_personal_key_controller_spec.rb index 3f685838953..f54ea385343 100644 --- a/spec/controllers/users/verify_personal_key_controller_spec.rb +++ b/spec/controllers/users/verify_personal_key_controller_spec.rb @@ -102,7 +102,6 @@ expect(@analytics).to have_logged_event( 'Personal key reactivation: Personal key form submitted', - errors: {}, success: true, ) expect(@analytics).to have_logged_event( diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb index e12bc5943ec..7f9886240bc 100644 --- a/spec/controllers/users/webauthn_setup_controller_spec.rb +++ b/spec/controllers/users/webauthn_setup_controller_spec.rb @@ -120,7 +120,6 @@ }, multi_factor_auth_method: 'webauthn', success: true, - errors: {}, in_account_creation_flow: false, authenticator_data_flags: { up: true, @@ -279,7 +278,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', enabled_mfa_methods_count: 1, - errors: {}, in_account_creation_flow: true, mfa_method_counts: { webauthn: 1 }, multi_factor_auth_method: 'webauthn', @@ -341,7 +339,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', enabled_mfa_methods_count: 1, - errors: {}, in_account_creation_flow: true, mfa_method_counts: { webauthn_platform: 1 }, multi_factor_auth_method: 'webauthn_platform', @@ -441,7 +438,6 @@ }, multi_factor_auth_method: 'webauthn', success: true, - errors: {}, in_account_creation_flow: true, authenticator_data_flags: { up: true, diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index fb4855ab711..859d53fad37 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -197,13 +197,13 @@ checked: true, }, 'IdV: doc auth agreement submitted' => { - success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth' + success: true, step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth hybrid handoff visited' => { step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth hybrid handoff submitted' => { - success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean + success: true, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth document_capture visited' => { flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean @@ -215,14 +215,14 @@ width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: kind_of(String), fingerprint: anything, failedImageResubmission: boolean, liveness_checking_required: boolean }, 'IdV: doc auth image upload form submitted' => { - success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean + success: true, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean }, 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean), 'IdV: doc auth image upload vendor pii validation' => { - success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' + success: true, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' }, 'IdV: doc auth document_capture submitted' => { - success: true, errors: {}, flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean, + success: true, flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean, proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth ssn visited' => { @@ -230,7 +230,7 @@ proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth ssn submitted' => { - success: true, errors: {}, flow_path: 'standard', step: 'ssn', analytics_id: 'Doc Auth', + success: true, flow_path: 'standard', step: 'ssn', analytics_id: 'Doc Auth', proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth verify visited' => { @@ -247,7 +247,7 @@ end ), 'IdV: doc auth verify proofing results' => { - success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', + success: true, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', proofing_results: doc_auth_verify_proofing_results, proofing_components: base_proofing_components }, @@ -255,22 +255,22 @@ proofing_components: base_proofing_components, }, 'IdV: phone confirmation form' => { - success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', + success: true, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', proofing_components: base_proofing_components }, 'IdV: phone confirmation vendor' => { - success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, + success: true, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, proofing_components: lexis_nexis_address_proofing_components }, 'IdV: phone confirmation otp sent' => { - success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, errors: {}, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, + success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, proofing_components: lexis_nexis_address_proofing_components }, 'IdV: phone confirmation otp visited' => { proofing_components: lexis_nexis_address_proofing_components, }, 'IdV: phone confirmation otp submitted' => { - success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, errors: {}, + success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, proofing_components: lexis_nexis_address_proofing_components }, :idv_enter_password_visited => { @@ -323,7 +323,7 @@ checked: true, }, 'IdV: doc auth agreement submitted' => { - success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth' + success: true, step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth hybrid handoff visited' => { step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean @@ -341,21 +341,21 @@ width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'hybrid', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: kind_of(String), fingerprint: anything, failedImageResubmission: boolean, liveness_checking_required: boolean }, 'IdV: doc auth image upload form submitted' => { - success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'hybrid', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean + success: true, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'hybrid', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean }, 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'hybrid', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean), 'IdV: doc auth image upload vendor pii validation' => { - success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'hybrid', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' + success: true, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'hybrid', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' }, 'IdV: doc auth document_capture submitted' => { - success: true, errors: {}, flow_path: 'hybrid', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean + success: true, flow_path: 'hybrid', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean }, 'IdV: doc auth ssn visited' => { flow_path: 'hybrid', step: 'ssn', analytics_id: 'Doc Auth', proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth ssn submitted' => { - success: true, errors: {}, flow_path: 'hybrid', step: 'ssn', analytics_id: 'Doc Auth', + success: true, flow_path: 'hybrid', step: 'ssn', analytics_id: 'Doc Auth', proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth verify visited' => { @@ -372,7 +372,7 @@ end ), 'IdV: doc auth verify proofing results' => { - success: true, errors: {}, flow_path: 'hybrid', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', + success: true, flow_path: 'hybrid', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', proofing_results: doc_auth_verify_proofing_results, proofing_components: base_proofing_components }, @@ -380,22 +380,22 @@ proofing_components: base_proofing_components, }, 'IdV: phone confirmation form' => { - success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', + success: true, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', proofing_components: base_proofing_components }, 'IdV: phone confirmation vendor' => { - success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: true, area_code: '202', country_code: 'US', phone_fingerprint: anything, + success: true, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: true, area_code: '202', country_code: 'US', phone_fingerprint: anything, proofing_components: lexis_nexis_address_proofing_components }, 'IdV: phone confirmation otp sent' => { - success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, errors: {}, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, + success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, proofing_components: lexis_nexis_address_proofing_components }, 'IdV: phone confirmation otp visited' => { proofing_components: lexis_nexis_address_proofing_components, }, 'IdV: phone confirmation otp submitted' => { - success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, errors: {}, + success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, proofing_components: lexis_nexis_address_proofing_components }, :idv_enter_password_visited => { @@ -445,13 +445,13 @@ step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth agreement submitted' => { - success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth' + success: true, step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth hybrid handoff visited' => { step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth hybrid handoff submitted' => { - success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean + success: true, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth document_capture visited' => { flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean @@ -463,14 +463,14 @@ width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: kind_of(String), fingerprint: anything, failedImageResubmission: boolean, liveness_checking_required: boolean }, 'IdV: doc auth image upload form submitted' => { - success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean + success: true, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean }, 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean), 'IdV: doc auth image upload vendor pii validation' => { - success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' + success: true, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' }, 'IdV: doc auth document_capture submitted' => { - success: true, errors: {}, flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean, + success: true, flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean, proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth ssn visited' => { @@ -478,7 +478,7 @@ proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth ssn submitted' => { - success: true, errors: {}, flow_path: 'standard', step: 'ssn', analytics_id: 'Doc Auth', + success: true, flow_path: 'standard', step: 'ssn', analytics_id: 'Doc Auth', proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth verify visited' => { @@ -495,7 +495,7 @@ end ), 'IdV: doc auth verify proofing results' => { - success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', + success: true, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', proofing_results: doc_auth_verify_proofing_results, proofing_components: base_proofing_components }, @@ -547,13 +547,13 @@ step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth agreement submitted' => { - success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth' + success: true, step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth hybrid handoff visited' => { step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth hybrid handoff submitted' => { - success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean + success: true, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth document_capture visited' => { flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: boolean @@ -565,7 +565,7 @@ width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: kind_of(String), fingerprint: anything, failedImageResubmission: boolean, liveness_checking_required: boolean }, 'IdV: doc auth image upload form submitted' => { - success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean + success: true, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean }, 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: true, doc_auth_result: 'Attention', liveness_checking_required: boolean), 'IdV: verify in person troubleshooting option clicked' => { @@ -587,19 +587,19 @@ step: 'state_id', flow_path: 'standard', analytics_id: 'In Person Proofing', proofing_components: { document_check: 'usps' } }, 'IdV: in person proofing state_id submitted' => { - success: true, flow_path: 'standard', step: 'state_id', analytics_id: 'In Person Proofing', errors: {}, birth_year: '1938', document_zip_code: '12345', proofing_components: { document_check: 'usps' } + success: true, flow_path: 'standard', step: 'state_id', analytics_id: 'In Person Proofing', birth_year: '1938', document_zip_code: '12345', proofing_components: { document_check: 'usps' } }, 'IdV: in person proofing address visited' => { step: 'address', flow_path: 'standard', analytics_id: 'In Person Proofing', proofing_components: { document_check: 'usps' } }, 'IdV: in person proofing residential address submitted' => { - success: true, step: 'address', flow_path: 'standard', analytics_id: 'In Person Proofing', errors: {}, current_address_zip_code: '59010', proofing_components: { document_check: 'usps' } + success: true, step: 'address', flow_path: 'standard', analytics_id: 'In Person Proofing', current_address_zip_code: '59010', proofing_components: { document_check: 'usps' } }, 'IdV: doc auth ssn visited' => { analytics_id: 'In Person Proofing', step: 'ssn', flow_path: 'standard', proofing_components: { document_check: 'usps' } }, 'IdV: doc auth ssn submitted' => { - analytics_id: 'In Person Proofing', success: true, step: 'ssn', flow_path: 'standard', errors: {}, proofing_components: { document_check: 'usps' } + analytics_id: 'In Person Proofing', success: true, step: 'ssn', flow_path: 'standard', proofing_components: { document_check: 'usps' } }, 'IdV: doc auth verify visited' => { analytics_id: 'In Person Proofing', step: 'verify', flow_path: 'standard', proofing_components: { document_check: 'usps' } @@ -613,27 +613,27 @@ end ), 'IdV: doc auth verify proofing results' => { - success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'In Person Proofing', step: 'verify', + success: true, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'In Person Proofing', step: 'verify', proofing_results: in_person_path_proofing_results, proofing_components: { document_check: 'usps', resolution_check: 'ResolutionMock', residential_resolution_check: 'ResolutionMock', source_check: 'StateIdMock', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' } }, 'IdV: phone confirmation form' => { - success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', + success: true, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', proofing_components: { document_check: 'usps', resolution_check: 'ResolutionMock', residential_resolution_check: 'ResolutionMock', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'StateIdMock' } }, 'IdV: phone confirmation vendor' => { - success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, + success: true, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, proofing_components: { address_check: 'lexis_nexis_address', document_check: 'usps', resolution_check: 'ResolutionMock', residential_resolution_check: 'ResolutionMock', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'StateIdMock' } }, 'IdV: phone confirmation otp sent' => { - success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, errors: {}, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, + success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, proofing_components: { address_check: 'lexis_nexis_address', document_check: 'usps', resolution_check: 'ResolutionMock', residential_resolution_check: 'ResolutionMock', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'StateIdMock' } }, 'IdV: phone confirmation otp visited' => { proofing_components: { address_check: 'lexis_nexis_address', document_check: 'usps', resolution_check: 'ResolutionMock', residential_resolution_check: 'ResolutionMock', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'StateIdMock' }, }, 'IdV: phone confirmation otp submitted' => { - success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, errors: {}, + success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, proofing_components: { document_check: 'usps', source_check: 'StateIdMock', resolution_check: 'ResolutionMock', residential_resolution_check: 'ResolutionMock', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' } }, :idv_enter_password_visited => { @@ -689,13 +689,13 @@ checked: true, }, 'IdV: doc auth agreement submitted' => { - success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth' + success: true, step: 'agreement', analytics_id: 'Doc Auth' }, 'IdV: doc auth hybrid handoff visited' => { step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth hybrid handoff submitted' => { - success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean + success: true, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: boolean }, 'IdV: doc auth document_capture visited' => { flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: true @@ -707,14 +707,14 @@ width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: kind_of(String), fingerprint: anything, failedImageResubmission: boolean, liveness_checking_required: boolean }, 'IdV: doc auth image upload form submitted' => { - success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean + success: true, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean }, 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: false, doc_auth_result: 'Passed'), 'IdV: doc auth image upload vendor pii validation' => { - success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' + success: true, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}, id_issued_status: 'present', id_expiration_status: 'present' }, 'IdV: doc auth document_capture submitted' => { - success: true, errors: {}, flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: true, + success: true, flow_path: 'standard', step: 'document_capture', analytics_id: 'Doc Auth', selfie_check_required: boolean, liveness_checking_required: true, proofing_components: { document_check: 'mock', document_type: 'state_id' } }, :idv_selfie_image_added => { @@ -725,7 +725,7 @@ proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth ssn submitted' => { - success: true, errors: {}, flow_path: 'standard', step: 'ssn', analytics_id: 'Doc Auth', + success: true, flow_path: 'standard', step: 'ssn', analytics_id: 'Doc Auth', proofing_components: { document_check: 'mock', document_type: 'state_id' } }, 'IdV: doc auth verify visited' => { @@ -742,7 +742,7 @@ end ), 'IdV: doc auth verify proofing results' => { - success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', + success: true, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify', proofing_results: doc_auth_verify_proofing_results, proofing_components: base_proofing_components }, @@ -750,22 +750,22 @@ proofing_components: base_proofing_components, }, 'IdV: phone confirmation form' => { - success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', + success: true, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', otp_delivery_preference: 'sms', proofing_components: base_proofing_components }, 'IdV: phone confirmation vendor' => { - success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, + success: true, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, proofing_components: lexis_nexis_address_proofing_components }, 'IdV: phone confirmation otp sent' => { - success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, errors: {}, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, + success: true, otp_delivery_preference: :sms, country_code: 'US', area_code: '202', adapter: :test, phone_fingerprint: anything, rate_limit_exceeded: false, telephony_response: anything, proofing_components: lexis_nexis_address_proofing_components }, 'IdV: phone confirmation otp visited' => { proofing_components: lexis_nexis_address_proofing_components, }, 'IdV: phone confirmation otp submitted' => { - success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, errors: {}, + success: true, code_expired: false, code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, proofing_components: lexis_nexis_address_proofing_components }, :idv_enter_password_visited => { diff --git a/spec/forms/add_user_email_form_spec.rb b/spec/forms/add_user_email_form_spec.rb index a6d1d45c1b6..451c323efc4 100644 --- a/spec/forms/add_user_email_form_spec.rb +++ b/spec/forms/add_user_email_form_spec.rb @@ -15,7 +15,7 @@ it 'returns a successful result' do expect(submit.to_h).to eq( success: true, - errors: {}, + errors: nil, domain_name: 'example.com', in_select_email_flow: false, user_id: user.uuid, @@ -109,7 +109,7 @@ it 'includes extra analytics in result for flow value' do expect(submit.to_h).to eq( success: true, - errors: {}, + errors: nil, domain_name: 'example.com', in_select_email_flow: true, user_id: user.uuid, diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb index e87d699675b..a909e5a0b52 100644 --- a/spec/forms/idv/api_image_upload_form_spec.rb +++ b/spec/forms/idv/api_image_upload_form_spec.rb @@ -172,7 +172,6 @@ expect(fake_analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: document_capture_session.user.uuid, @@ -252,7 +251,6 @@ expect(fake_analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: document_capture_session.user.uuid, @@ -393,7 +391,6 @@ expect(fake_analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: true, - errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: document_capture_session.user.uuid, diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index 86a71c0e0f3..75db077c746 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -42,7 +42,7 @@ it 'is successful' do expect(result.to_h).to eq( success: true, - errors: {}, + errors: nil, client_id: client_id, prompt: 'select_account', allow_prompt_login: true, diff --git a/spec/forms/openid_connect_token_form_spec.rb b/spec/forms/openid_connect_token_form_spec.rb index b79c08aedbf..9283dbc7218 100644 --- a/spec/forms/openid_connect_token_form_spec.rb +++ b/spec/forms/openid_connect_token_form_spec.rb @@ -375,7 +375,7 @@ expect(submission.to_h).to eq( success: true, - errors: {}, + errors: nil, client_id: client_id, user_id: user.uuid, code_digest: Digest::SHA256.hexdigest(code), diff --git a/spec/forms/otp_delivery_selection_form_spec.rb b/spec/forms/otp_delivery_selection_form_spec.rb index ddc8a3da6d6..c589d34ce5e 100644 --- a/spec/forms/otp_delivery_selection_form_spec.rb +++ b/spec/forms/otp_delivery_selection_form_spec.rb @@ -33,7 +33,7 @@ expect(subject.submit(otp_delivery_preference: 'sms', resend: 'true').to_h).to eq( success: true, - errors: {}, + errors: nil, **extra, ) end diff --git a/spec/forms/password_form_spec.rb b/spec/forms/password_form_spec.rb index c4ea0483c4e..2d92fd3ed30 100644 --- a/spec/forms/password_form_spec.rb +++ b/spec/forms/password_form_spec.rb @@ -113,7 +113,7 @@ let(:expected_response) do { success: true, - errors: {}, + errors: nil, user_id: user.uuid, request_id_present: true, } @@ -134,7 +134,7 @@ let(:expected_response) do { success: true, - errors: {}, + errors: nil, user_id: user.uuid, request_id_present: true, } diff --git a/spec/forms/password_reset_email_form_spec.rb b/spec/forms/password_reset_email_form_spec.rb index a2fe5f10379..6b1f24e89aa 100644 --- a/spec/forms/password_reset_email_form_spec.rb +++ b/spec/forms/password_reset_email_form_spec.rb @@ -14,7 +14,7 @@ expect(subject.submit.to_h).to eq( success: true, - errors: {}, + errors: nil, user_id: user.uuid, confirmed: true, active_profile: false, @@ -27,7 +27,7 @@ it 'returns hash with properties about the event and the nonexistent user' do expect(subject.submit.to_h).to eq( success: true, - errors: {}, + errors: nil, user_id: 'nonexistent-uuid', confirmed: false, active_profile: false, diff --git a/spec/forms/personal_key_form_spec.rb b/spec/forms/personal_key_form_spec.rb index 3a7e97fb135..5a49b7dbc07 100644 --- a/spec/forms/personal_key_form_spec.rb +++ b/spec/forms/personal_key_form_spec.rb @@ -12,7 +12,7 @@ expect(form.submit.to_h).to eq( success: true, - errors: {}, + errors: nil, ) expect(user.reload.encrypted_recovery_code_digest).to eq old_code end diff --git a/spec/forms/register_user_email_form_spec.rb b/spec/forms/register_user_email_form_spec.rb index d1aead02e72..5d8ec17bac6 100644 --- a/spec/forms/register_user_email_form_spec.rb +++ b/spec/forms/register_user_email_form_spec.rb @@ -115,7 +115,7 @@ end it 'sets success to true to prevent revealing account existence' do - expect(subject.submit(params).to_h).to eq(success: true, errors: {}, **extra_params) + expect(subject.submit(params).to_h).to eq(success: true, errors: nil, **extra_params) expect(subject.email).to eq registered_email_address expect_delivered_email_count(1) expect_delivered_email( @@ -168,7 +168,7 @@ expect(result).to eq( success: true, - errors: {}, + errors: nil, **extra_params, ) end @@ -246,7 +246,7 @@ expect(submit_form.to_h).to eq( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -370,7 +370,7 @@ expect(result.to_h).to eq( success: true, - errors: {}, + errors: nil, email_already_exists: false, rate_limited: false, user_id: User.find_with_email(email).uuid, @@ -442,7 +442,7 @@ expect(submit_form.to_h).to eq( success: true, - errors: {}, + errors: nil, **extra, ) @@ -470,7 +470,7 @@ expect(submit_form.to_h).to eq( success: true, - errors: {}, + errors: nil, **extra, ) end diff --git a/spec/forms/reset_password_form_spec.rb b/spec/forms/reset_password_form_spec.rb index a9c588cd2e6..f79924ccc1e 100644 --- a/spec/forms/reset_password_form_spec.rb +++ b/spec/forms/reset_password_form_spec.rb @@ -76,7 +76,7 @@ expect(result.to_h).to eq( success: true, - errors: {}, + errors: nil, user_id: '123', profile_deactivated: false, pending_profile_invalidated: false, diff --git a/spec/forms/totp_setup_form_spec.rb b/spec/forms/totp_setup_form_spec.rb index 4f8670ebef7..aae42abd1f1 100644 --- a/spec/forms/totp_setup_form_spec.rb +++ b/spec/forms/totp_setup_form_spec.rb @@ -19,7 +19,7 @@ expect(form.submit.to_h).to eq( success: true, - errors: {}, + errors: nil, **extra, ) expect(user.auth_app_configurations.any?).to eq true @@ -45,7 +45,7 @@ expect(form.submit.to_h).to include( success: false, - errors: {}, + errors: nil, **extra, ) expect(user.auth_app_configurations.any?).to eq false @@ -65,7 +65,7 @@ expect(form.submit.to_h).to include( success: false, - errors: {}, + errors: nil, **extra, ) expect(user.auth_app_configurations.any?).to eq false diff --git a/spec/forms/totp_verification_form_spec.rb b/spec/forms/totp_verification_form_spec.rb index 74500108efa..7f087cdd976 100644 --- a/spec/forms/totp_verification_form_spec.rb +++ b/spec/forms/totp_verification_form_spec.rb @@ -13,7 +13,7 @@ expect(form.submit.to_h).to eq( success: true, - errors: {}, + errors: nil, auth_app_configuration_id: cfg.id, multi_factor_auth_method_created_at: cfg.created_at.strftime('%s%L'), ) @@ -30,7 +30,7 @@ expect(form.submit.to_h).to eq( success: false, - errors: {}, + errors: nil, auth_app_configuration_id: nil, multi_factor_auth_method_created_at: nil, ) @@ -48,7 +48,7 @@ expect(form.submit.to_h).to eq( success: false, - errors: {}, + errors: nil, auth_app_configuration_id: nil, multi_factor_auth_method_created_at: nil, ) diff --git a/spec/forms/two_factor_login_options_form_spec.rb b/spec/forms/two_factor_login_options_form_spec.rb index a25efbf90ef..6c2edbfae1e 100644 --- a/spec/forms/two_factor_login_options_form_spec.rb +++ b/spec/forms/two_factor_login_options_form_spec.rb @@ -19,7 +19,7 @@ expect(subject.submit(selection: 'sms').to_h).to eq( success: true, - errors: {}, + errors: nil, **extra, ) end diff --git a/spec/forms/update_user_password_form_spec.rb b/spec/forms/update_user_password_form_spec.rb index 8a44793b200..1955390f085 100644 --- a/spec/forms/update_user_password_form_spec.rb +++ b/spec/forms/update_user_password_form_spec.rb @@ -53,7 +53,7 @@ it 'returns FormResponse with success: true' do expect(subject.submit(params).to_h).to eq( success: true, - errors: {}, + errors: nil, active_profile_present: false, pending_profile_present: false, user_id: user.uuid, diff --git a/spec/forms/user_piv_cac_verification_form_spec.rb b/spec/forms/user_piv_cac_verification_form_spec.rb index cd522af01ed..5b03cb510b9 100644 --- a/spec/forms/user_piv_cac_verification_form_spec.rb +++ b/spec/forms/user_piv_cac_verification_form_spec.rb @@ -69,7 +69,7 @@ result = form.submit expect(result.to_h).to eq( success: true, - errors: {}, + errors: nil, piv_cac_configuration_id: piv_cac_configuration.id, multi_factor_auth_method_created_at: piv_cac_configuration.created_at.strftime('%s%L'), key_id: 'foo', @@ -130,7 +130,7 @@ expect(result.to_h).to eq( success: false, - errors: {}, + errors: nil, multi_factor_auth_method_created_at: nil, piv_cac_configuration_id: nil, piv_cac_configuration_dn_uuid: nil, diff --git a/spec/forms/webauthn_setup_form_spec.rb b/spec/forms/webauthn_setup_form_spec.rb index f9d9227b5f1..21b02f6a56c 100644 --- a/spec/forms/webauthn_setup_form_spec.rb +++ b/spec/forms/webauthn_setup_form_spec.rb @@ -48,7 +48,7 @@ expect(subject.submit(params).to_h).to eq( success: true, - errors: {}, + errors: nil, **extra_attributes, ) @@ -149,7 +149,7 @@ expect(result.to_h).to eq( success: true, - errors: {}, + errors: nil, enabled_mfa_methods_count: 1, mfa_method_counts: { webauthn: 1 }, multi_factor_auth_method: 'webauthn', diff --git a/spec/forms/webauthn_visit_form_spec.rb b/spec/forms/webauthn_visit_form_spec.rb index 45b4771358d..c1e1d9c11f7 100644 --- a/spec/forms/webauthn_visit_form_spec.rb +++ b/spec/forms/webauthn_visit_form_spec.rb @@ -22,7 +22,7 @@ expect(subject.submit(params).to_h).to eq( success: true, - errors: {}, + errors: nil, platform_authenticator: false, enabled_mfa_methods_count: 0, ) @@ -34,7 +34,7 @@ expect(subject.submit(params).to_h).to eq( success: true, - errors: {}, + errors: nil, platform_authenticator: true, enabled_mfa_methods_count: 0, ) diff --git a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb index 95d0b473dac..1d7adca4747 100644 --- a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb +++ b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb @@ -199,7 +199,7 @@ user_id: user.uuid, resolution_result: { success: true, - errors: {}, + errors: nil, context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', diff --git a/spec/services/form_response_spec.rb b/spec/services/form_response_spec.rb index dbd1880aa64..c18654a5da4 100644 --- a/spec/services/form_response_spec.rb +++ b/spec/services/form_response_spec.rb @@ -214,7 +214,7 @@ response = FormResponse.new(success: true, errors: errors) response_hash = { success: true, - errors: {}, + errors: nil, } expect(response.to_h).to eq response_hash @@ -227,7 +227,7 @@ combined_response = response1.merge(response2) response_hash = { success: true, - errors: {}, + errors: nil, } expect(combined_response.to_h).to eq response_hash diff --git a/spec/services/saml_request_validator_spec.rb b/spec/services/saml_request_validator_spec.rb index 5d16379edbd..4a5bbf833d6 100644 --- a/spec/services/saml_request_validator_spec.rb +++ b/spec/services/saml_request_validator_spec.rb @@ -43,7 +43,7 @@ it 'returns FormResponse with success: true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -79,7 +79,7 @@ it 'is valid' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -96,7 +96,7 @@ it 'returns FormResponse with success: true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -108,7 +108,7 @@ it 'returns FormResponse with success: true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -157,7 +157,7 @@ it 'returns FormResponse with success: true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -170,7 +170,7 @@ it 'returns FormResponse with success: true for ial2 on ial:2 sp' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -191,7 +191,7 @@ expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -210,7 +210,7 @@ expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -258,7 +258,7 @@ it 'returns FormResponse with success: true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -318,7 +318,7 @@ it 'returns a successful response' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -370,7 +370,7 @@ it 'returns a successful response' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -427,7 +427,7 @@ it 'returns FormResponse with success true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end @@ -441,7 +441,7 @@ it 'returns FormResponse with success true' do expect(response.to_h).to include( success: true, - errors: {}, + errors: nil, **extra, ) end From 919b8566f96f7a5abba1d61891382815d40285d2 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Mon, 27 Jan 2025 08:54:24 -0500 Subject: [PATCH 14/14] Avoid empty hash default values in analytics methods (#11800) changelog: Internal, Analytics, Avoid logging empty hash default values --- app/services/analytics_events.rb | 6 +++--- spec/controllers/users/reset_passwords_controller_spec.rb | 7 ------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 89b6ffd2c57..8b84d05100a 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -2077,7 +2077,7 @@ def idv_doc_auth_submitted_pii_validation( front_image_fingerprint: nil, back_image_fingerprint: nil, selfie_image_fingerprint: nil, - classification_info: {}, + classification_info: nil, **extra ) track_event( @@ -6282,7 +6282,7 @@ def password_reset_email( errors:, confirmed:, active_profile:, - error_details: {}, + error_details: nil, **extra ) track_event( @@ -6312,7 +6312,7 @@ def password_reset_password( profile_deactivated:, pending_profile_invalidated:, pending_profile_pending_reasons:, - error_details: {}, + error_details: nil, **extra ) track_event( diff --git a/spec/controllers/users/reset_passwords_controller_spec.rb b/spec/controllers/users/reset_passwords_controller_spec.rb index 65ce5a3e402..2258371d997 100644 --- a/spec/controllers/users/reset_passwords_controller_spec.rb +++ b/spec/controllers/users/reset_passwords_controller_spec.rb @@ -344,7 +344,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - error_details: {}, user_id: user.uuid, profile_deactivated: false, pending_profile_invalidated: false, @@ -391,7 +390,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - error_details: {}, user_id: user.uuid, profile_deactivated: true, pending_profile_invalidated: false, @@ -435,7 +433,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - error_details: {}, user_id: user.uuid, profile_deactivated: false, pending_profile_invalidated: false, @@ -465,7 +462,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - error_details: {}, user_id: 'nonexistent-uuid', confirmed: false, active_profile: false, @@ -491,7 +487,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - error_details: {}, user_id: user.uuid, confirmed: true, active_profile: false, @@ -521,7 +516,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - error_details: {}, user_id: user.uuid, confirmed: false, active_profile: false, @@ -545,7 +539,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - error_details: {}, user_id: user.uuid, confirmed: true, active_profile: true,