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/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/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/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/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/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/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/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..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), @@ -130,6 +136,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/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/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/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/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/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/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/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..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( @@ -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/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/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/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/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/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 @@
<%= @presenter.description %>
+ +<%= t('webauthn_setup_mismatch.description_undo') %> + +
%{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. @@ -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 ef97626b0ef..a16d5822c49 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}。 @@ -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/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/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/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/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/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/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/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/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/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/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/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..2258371d997 100644 --- a/spec/controllers/users/reset_passwords_controller_spec.rb +++ b/spec/controllers/users/reset_passwords_controller_spec.rb @@ -344,8 +344,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - errors: {}, - error_details: {}, user_id: user.uuid, profile_deactivated: false, pending_profile_invalidated: false, @@ -392,8 +390,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - errors: {}, - error_details: {}, user_id: user.uuid, profile_deactivated: true, pending_profile_invalidated: false, @@ -437,8 +433,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: true, - errors: {}, - error_details: {}, user_id: user.uuid, profile_deactivated: false, pending_profile_invalidated: false, @@ -468,8 +462,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, - error_details: {}, user_id: 'nonexistent-uuid', confirmed: false, active_profile: false, @@ -495,8 +487,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, - error_details: {}, user_id: user.uuid, confirmed: true, active_profile: false, @@ -526,8 +516,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, - error_details: {}, user_id: user.uuid, confirmed: false, active_profile: false, @@ -551,8 +539,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: true, - errors: {}, - error_details: {}, user_id: user.uuid, confirmed: true, active_profile: 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/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/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/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..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 @@ -573,5 +574,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/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/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 8c74bbb742c..a909e5a0b52 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, @@ -171,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, @@ -251,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, @@ -392,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, @@ -732,6 +730,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/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index ccdfa5f3b7f..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, @@ -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 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_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/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/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 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 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/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/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/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 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] } 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', ), ), ], 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/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) 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 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/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 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 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/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 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