diff --git a/.eslintrc b/.eslintrc index 015b41f4bdf..0c776ec027a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,8 +18,8 @@ "no-restricted-syntax": [ "error", { - "selector": "AssignmentExpression[left.property.name='href'][right.type=/(Template)?Literal/],NewExpression[callee.name=URL][arguments.0.type=/(Template)?Literal/]", - "message": "Avoid hard-coded string URLs, since they will not include the current locale" + "selector": "AssignmentExpression[left.property.name='href'][right.type=/(Template)?Literal/]", + "message": "Do not assign window.location.href to a string or string template to avoid losing i18n parameters" } ], "react-hooks/exhaustive-deps": "error" @@ -42,7 +42,6 @@ "packageDir": "." } ], - "no-restricted-syntax": "off", "testing-library/await-async-events": "error", "testing-library/await-async-queries": "error", "testing-library/await-async-utils": "error", diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ed0580e2514..0e36154b270 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,7 +67,9 @@ stages: workflow: rules: - - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "external_pull_request_event" || $CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "external_pull_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "external_pull_request_event" || $CI_PIPELINE_SOURCE == "web"' - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "stages/prod"' - if: '$CI_MERGE_REQUEST_IID || $CI_EXTERNAL_PULL_REQUEST_IID' @@ -339,6 +341,30 @@ js_tests: - *yarn_install - yarn test +pinpoint-check: + needs: + - job: install + stage: test + cache: + - <<: *ruby_cache + - <<: *yarn_cache + script: + - *bundle_install + - *yarn_install + - make lint_country_dialing_codes + +audit_packages: + needs: + - job: install + stage: test + cache: + - <<: *ruby_cache + - <<: *yarn_cache + script: + - *bundle_install + - *yarn_install + - make audit + prepare_deploy: # Runs in parallel with tests so we can deploy more quickly after passing stage: test @@ -812,51 +838,3 @@ ecr-scan-ci: stage: scan variables: ecr_repo: idp/ci - -pinpoint_check_scheduled: - needs: - - job: install - cache: - - <<: *ruby_cache - - <<: *yarn_cache - script: - - *bundle_install - - *yarn_install - - make lint_country_dialing_codes - after_script: - - |- - if [ "$CI_JOB_STATUS" != "success" ]; then - ./scripts/notify-slack \ - --icon ":gitlab:" \ - --username "gitlab-notify" \ - --channel "#login-appdev" \ - --webhook "${SLACK_WEBHOOK}" \ - --raise \ - --text "Pinpoint supported countries check in GitLab failed.\nBuild Results: ${CI_JOB_URL}.\nCheck results locally with 'make lint_country_dialing_codes'" - fi - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" - -audit_packages_scheduled: - needs: - - job: install - cache: - - <<: *ruby_cache - - <<: *yarn_cache - script: - - *bundle_install - - *yarn_install - - make audit - after_script: - - |- - if [ "$CI_JOB_STATUS" != "success" ]; then - ./scripts/notify-slack \ - --icon ":gitlab:" \ - --username "gitlab-notify" \ - --channel "#login-appdev" \ - --webhook "${SLACK_WEBHOOK}" \ - --raise \ - --text "Dependencies audit in GitLab failed.\nBuild Results: ${CI_JOB_URL}\nCheck results locally with 'make audit'" - fi - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" diff --git a/app/controllers/concerns/idv/verify_by_mail_concern.rb b/app/controllers/concerns/idv/verify_by_mail_concern.rb deleted file mode 100644 index 6907db67cc9..00000000000 --- a/app/controllers/concerns/idv/verify_by_mail_concern.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module Idv - module VerifyByMailConcern - def gpo_verify_by_mail_policy - @gpo_verify_by_mail_policy ||= Idv::GpoVerifyByMailPolicy.new( - current_user, - resolved_authn_context_result, - ) - end - - def log_letter_requested_analytics(resend:) - analytics.idv_gpo_address_letter_requested( - resend: resend, - first_letter_requested_at: first_letter_requested_at, - hours_since_first_letter: hours_since_first_letter, - phone_step_attempts: phone_step_attempt_count, - **ab_test_analytics_buckets, - ) - end - - def log_letter_enqueued_analytics(resend:) - analytics.idv_gpo_address_letter_enqueued( - enqueued_at: Time.zone.now, - resend: resend, - phone_step_attempts: phone_step_attempt_count, - first_letter_requested_at: first_letter_requested_at, - hours_since_first_letter: hours_since_first_letter, - **ab_test_analytics_buckets, - ) - end - - def phone_step_attempt_count - @phone_step_attempt_count ||= RateLimiter.new( - user: current_user, - rate_limit_type: :proof_address, - ).attempts - end - - def first_letter_requested_at - current_user.gpo_verification_pending_profile&.gpo_verification_pending_at - end - - def hours_since_first_letter - if first_letter_requested_at.present? - (Time.zone.now - first_letter_requested_at).to_i.seconds.in_hours.to_i - else - 0 - end - end - end -end diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb index fccc07a6f34..82a08bace1d 100644 --- a/app/controllers/concerns/idv_step_concern.rb +++ b/app/controllers/concerns/idv_step_concern.rb @@ -7,7 +7,6 @@ module IdvStepConcern include RateLimitConcern include FraudReviewConcern include Idv::AbTestAnalyticsConcern - include Idv::VerifyByMailConcern included do before_action :confirm_two_factor_authenticated @@ -51,7 +50,8 @@ def check_for_mail_only_outage end def redirect_for_mail_only - if gpo_verify_by_mail_policy.send_letter_available? + policy = Idv::GpoVerifyByMailPolicy.new(current_user) + if policy.send_letter_available? redirect_to idv_mail_only_warning_url else redirect_to vendor_outage_url diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb index e49ab0e6bc8..db5ecd55aa4 100644 --- a/app/controllers/concerns/rate_limit_concern.rb +++ b/app/controllers/concerns/rate_limit_concern.rb @@ -29,7 +29,8 @@ def confirm_not_rate_limited_for_phone_address_verification private def confirm_not_rate_limited_for_phone_and_letter_address_verification - if idv_attempter_rate_limited?(:proof_address) && gpo_verify_by_mail_policy.rate_limited? + gpo_policy = Idv::GpoVerifyByMailPolicy.new(current_user) + if idv_attempter_rate_limited?(:proof_address) && gpo_policy.rate_limited? rate_limit_redirect!(:proof_address) return true end diff --git a/app/controllers/idv/by_mail/enter_code_controller.rb b/app/controllers/idv/by_mail/enter_code_controller.rb index 53a2821182f..c4c34d1ba2c 100644 --- a/app/controllers/idv/by_mail/enter_code_controller.rb +++ b/app/controllers/idv/by_mail/enter_code_controller.rb @@ -7,8 +7,6 @@ class EnterCodeController < ApplicationController include IdvSessionConcern include Idv::StepIndicatorConcern include FraudReviewConcern - include AbTestAnalyticsConcern - include VerifyByMailConcern prepend_before_action :note_if_user_did_not_receive_letter before_action :confirm_two_factor_authenticated @@ -18,7 +16,7 @@ def index analytics.idv_verify_by_mail_enter_code_visited( source: user_did_not_receive_letter? ? 'gpo_reminder_email' : nil, otp_rate_limited: rate_limiter.limited?, - user_can_request_another_letter: gpo_verify_by_mail_policy.resend_letter_available?, + user_can_request_another_letter: user_can_request_another_letter?, ) if rate_limiter.limited? @@ -28,12 +26,7 @@ def index end prefilled_code = session[:last_gpo_confirmation_code] if FeatureManagement.reveal_gpo_code? - @gpo_verify_form = GpoVerifyForm.new( - user: current_user, - pii: pii, - resolved_authn_context_result: resolved_authn_context_result, - otp: prefilled_code, - ) + @gpo_verify_form = GpoVerifyForm.new(user: current_user, pii: pii, otp: prefilled_code) render_enter_code_form end @@ -70,7 +63,7 @@ def create private def render_enter_code_form - @can_request_another_letter = gpo_verify_by_mail_policy.resend_letter_available? + @can_request_another_letter = user_can_request_another_letter? @user_did_not_receive_letter = user_did_not_receive_letter? @last_date_letter_was_sent = last_date_letter_was_sent render :index @@ -126,7 +119,6 @@ def build_gpo_verify_form GpoVerifyForm.new( user: current_user, pii: pii, - resolved_authn_context_result: resolved_authn_context_result, otp: params_otp, ) end @@ -154,6 +146,12 @@ def user_did_not_receive_letter? params[:did_not_receive_letter].present? end + def user_can_request_another_letter? + return @user_can_request_another_letter if defined?(@user_can_request_another_letter) + policy = Idv::GpoVerifyByMailPolicy.new(current_user) + @user_can_request_another_letter = policy.resend_letter_available? + end + def last_date_letter_was_sent return @last_date_letter_was_sent if defined?(@last_date_letter_was_sent) diff --git a/app/controllers/idv/by_mail/request_letter_controller.rb b/app/controllers/idv/by_mail/request_letter_controller.rb index b071093cf9c..eae902c80d4 100644 --- a/app/controllers/idv/by_mail/request_letter_controller.rb +++ b/app/controllers/idv/by_mail/request_letter_controller.rb @@ -5,25 +5,42 @@ module ByMail class RequestLetterController < ApplicationController include Idv::AvailabilityConcern include IdvStepConcern + skip_before_action :confirm_no_pending_gpo_profile include Idv::StepIndicatorConcern - include VerifyByMailConcern before_action :confirm_mail_not_rate_limited before_action :confirm_step_allowed + before_action :confirm_profile_not_too_old def index @applicant = idv_session.applicant + @presenter = RequestLetterPresenter.new(current_user, url_options) Funnel::DocAuth::RegisterStep.new(current_user.id, current_sp&.issuer). call(:usps_address, :view, true) - analytics.idv_request_letter_visited + analytics.idv_request_letter_visited( + letter_already_sent: @presenter.resend_requested?, + ) end def create clear_future_steps! update_tracking idv_session.address_verification_mechanism = :gpo - redirect_to idv_enter_password_url + + if resend_requested? && pii_locked? + redirect_to capture_password_url + elsif resend_requested? + resend_letter + flash[:success] = t('idv.messages.gpo.another_letter_on_the_way') + redirect_to idv_letter_enqueued_url + else + redirect_to idv_enter_password_url + end + end + + def gpo_mail_policy + @gpo_mail_policy ||= Idv::GpoVerifyByMailPolicy.new(current_user) end def self.step_info @@ -41,18 +58,87 @@ def self.step_info private + def confirm_profile_not_too_old + redirect_to idv_path if gpo_mail_policy.profile_too_old? + end + def update_tracking Funnel::DocAuth::RegisterStep.new(current_user.id, current_sp&.issuer). call(:usps_letter_sent, :update, true) - log_letter_requested_analytics(resend: false) + analytics.idv_gpo_address_letter_requested( + resend: resend_requested?, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: + hours_since_first_letter(first_letter_requested_at), + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + **ab_test_analytics_buckets, + ) create_user_event(:gpo_mail_sent, current_user) ProofingComponent.find_or_create_by(user: current_user).update(address_check: 'gpo_letter') end + def resend_requested? + current_user.gpo_verification_pending_profile? + end + + def first_letter_requested_at + current_user.gpo_verification_pending_profile&.gpo_verification_pending_at + end + + def hours_since_first_letter(first_letter_requested_at) + first_letter_requested_at ? + (Time.zone.now - first_letter_requested_at).to_i.seconds.in_hours.to_i : 0 + end + def confirm_mail_not_rate_limited - redirect_to idv_enter_password_url if gpo_verify_by_mail_policy.rate_limited? + redirect_to idv_enter_password_url if gpo_mail_policy.rate_limited? + end + + def resend_letter + analytics.idv_gpo_address_letter_enqueued( + enqueued_at: Time.zone.now, + resend: true, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: + hours_since_first_letter(first_letter_requested_at), + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + **ab_test_analytics_buckets, + ) + confirmation_maker = confirmation_maker_perform + send_reminder + return unless FeatureManagement.reveal_gpo_code? + session[:last_gpo_confirmation_code] = confirmation_maker.otp + end + + def confirmation_maker_perform + confirmation_maker = GpoConfirmationMaker.new( + pii: pii, + service_provider: current_sp, + profile: current_user.pending_profile, + ) + confirmation_maker.perform + confirmation_maker + end + + def pii + Pii::Cacher.new(current_user, user_session). + fetch(current_user.gpo_verification_pending_profile.id) + end + + def send_reminder + current_user.send_email_to_all_addresses(:verify_by_mail_letter_requested) + end + + def pii_locked? + !Pii::Cacher.new(current_user, user_session).exists_in_session? end def step_indicator_steps diff --git a/app/controllers/idv/by_mail/resend_letter_controller.rb b/app/controllers/idv/by_mail/resend_letter_controller.rb index 10f81cb71b1..86efadac256 100644 --- a/app/controllers/idv/by_mail/resend_letter_controller.rb +++ b/app/controllers/idv/by_mail/resend_letter_controller.rb @@ -3,11 +3,9 @@ module Idv module ByMail class ResendLetterController < ApplicationController - include AvailabilityConcern + include Idv::AvailabilityConcern include IdvSessionConcern - include StepIndicatorConcern - include VerifyByMailConcern - include AbTestAnalyticsConcern + include Idv::StepIndicatorConcern before_action :confirm_two_factor_authenticated before_action :confirm_verification_needed @@ -29,6 +27,10 @@ def create end end + def gpo_mail_policy + @gpo_mail_policy ||= Idv::GpoVerifyByMailPolicy.new(current_user) + end + private def confirm_verification_needed @@ -37,13 +39,22 @@ def confirm_verification_needed end def confirm_resend_letter_available - unless gpo_verify_by_mail_policy.resend_letter_available? + unless gpo_mail_policy.resend_letter_available? redirect_to idv_verify_by_mail_enter_code_path end end def update_tracking - log_letter_requested_analytics(resend: true) + analytics.idv_gpo_address_letter_requested( + resend: true, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: + hours_since_first_letter(first_letter_requested_at), + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + ) create_user_event(:gpo_mail_sent, current_user) end @@ -51,8 +62,27 @@ def resend_requested? current_user.gpo_verification_pending_profile? end + def first_letter_requested_at + current_user.gpo_verification_pending_profile&.gpo_verification_pending_at + end + + def hours_since_first_letter(first_letter_requested_at) + first_letter_requested_at ? + (Time.zone.now - first_letter_requested_at).to_i.seconds.in_hours.to_i : 0 + end + def resend_letter - log_letter_enqueued_analytics(resend: true) + analytics.idv_gpo_address_letter_enqueued( + enqueued_at: Time.zone.now, + resend: true, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: + hours_since_first_letter(first_letter_requested_at), + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + ) confirmation_maker = confirmation_maker_perform send_reminder return unless FeatureManagement.reveal_gpo_code? diff --git a/app/controllers/idv/enter_password_controller.rb b/app/controllers/idv/enter_password_controller.rb index 2ca053235a1..6b22e0fedcd 100644 --- a/app/controllers/idv/enter_password_controller.rb +++ b/app/controllers/idv/enter_password_controller.rb @@ -5,7 +5,6 @@ class EnterPasswordController < ApplicationController include Idv::AvailabilityConcern include IdvStepConcern include StepIndicatorConcern - include VerifyByMailConcern before_action :confirm_step_allowed before_action :confirm_no_profile_yet @@ -127,7 +126,17 @@ def init_profile ) if idv_session.verify_by_mail? current_user.send_email_to_all_addresses(:verify_by_mail_letter_requested) - log_letter_enqueued_analytics(resend: false) + analytics.idv_gpo_address_letter_enqueued( + enqueued_at: Time.zone.now, + resend: false, + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: hours_since_first_letter(first_letter_requested_at), + **ab_test_analytics_buckets, + ) end if idv_session.profile.active? @@ -144,6 +153,11 @@ def first_letter_requested_at idv_session.profile.gpo_verification_pending_at end + def hours_since_first_letter(first_letter_requested_at) + first_letter_requested_at ? + (Time.zone.now - first_letter_requested_at).to_i.seconds.in_hours.to_i : 0 + end + def valid_password? current_user.valid_password?(password) end diff --git a/app/controllers/idv/in_person/public/usps_locations_controller.rb b/app/controllers/idv/in_person/public/usps_locations_controller.rb index eafddf1924a..5ecb9c28ae1 100644 --- a/app/controllers/idv/in_person/public/usps_locations_controller.rb +++ b/app/controllers/idv/in_person/public/usps_locations_controller.rb @@ -18,26 +18,19 @@ def index ) locations = proofer.request_facilities(candidate, false) - render json: localized_locations(locations).to_json + render json: locations.to_json end def options head :ok end - private + protected def proofer @proofer ||= UspsInPersonProofing::EnrollmentHelper.usps_proofer end - def localized_locations(locations) - return nil if locations.nil? - locations.map do |location| - UspsInPersonProofing::EnrollmentHelper.localized_location(location) - end - end - def enabled? IdentityConfig.store.in_person_public_address_search_enabled end diff --git a/app/controllers/idv/in_person/ready_to_verify_controller.rb b/app/controllers/idv/in_person/ready_to_verify_controller.rb index fbd0aeeee2c..ab70eeb3f32 100644 --- a/app/controllers/idv/in_person/ready_to_verify_controller.rb +++ b/app/controllers/idv/in_person/ready_to_verify_controller.rb @@ -12,8 +12,8 @@ class ReadyToVerifyController < ApplicationController check_or_render_not_found -> { IdentityConfig.store.in_person_proofing_enabled } - before_action :confirm_two_factor_authenticated before_action :handle_fraud + before_action :confirm_two_factor_authenticated before_action :confirm_in_person_session def show diff --git a/app/controllers/idv/in_person/usps_locations_controller.rb b/app/controllers/idv/in_person/usps_locations_controller.rb index 45cac7ac484..4367b795ba3 100644 --- a/app/controllers/idv/in_person/usps_locations_controller.rb +++ b/app/controllers/idv/in_person/usps_locations_controller.rb @@ -27,18 +27,18 @@ def index zip_code: search_params['zip_code'] ) is_enhanced_ipp = resolved_authn_context_result.enhanced_ipp? - locations = proofer.request_facilities(candidate, is_enhanced_ipp) - if locations.length > 0 + response = proofer.request_facilities(candidate, is_enhanced_ipp) + if response.length > 0 analytics.idv_in_person_locations_searched( success: true, - result_total: locations.length, + result_total: response.length, ) else analytics.idv_in_person_locations_searched( success: false, errors: 'No USPS locations found', ) end - render json: localized_locations(locations).to_json + render json: response.to_json end # save the Post Office location the user selected to an enrollment @@ -64,13 +64,6 @@ def add_proofing_component update(document_check: Idp::Constants::Vendors::USPS) end - def localized_locations(locations) - return nil if locations.nil? - locations.map do |location| - EnrollmentHelper.localized_location(location) - end - end - def handle_error(err) remapped_error = case err when ActionController::InvalidAuthenticityToken, diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index ed676b6ad94..97db1d19f08 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -7,7 +7,6 @@ class PhoneController < ApplicationController include StepIndicatorConcern include PhoneOtpRateLimitable include PhoneOtpSendable - include Idv::VerifyByMailConcern attr_reader :idv_form @@ -36,15 +35,11 @@ def new analytics.idv_phone_of_record_visited( **ab_test_analytics_buckets, ) - render( - :new, locals: { gpo_letter_available: gpo_verify_by_mail_policy.send_letter_available? } - ) + render :new, locals: { gpo_letter_available: gpo_letter_available } elsif async_state.missing? analytics.proofing_address_result_missing flash.now[:error] = I18n.t('idv.failure.timeout') - render( - :new, locals: { gpo_letter_available: gpo_verify_by_mail_policy.send_letter_available? } - ) + render :new, locals: { gpo_letter_available: gpo_letter_available } end end @@ -61,9 +56,7 @@ def create redirect_to idv_phone_path else flash.now[:error] = result.first_error_message - render( - :new, locals: { gpo_letter_available: gpo_verify_by_mail_policy.send_letter_available? } - ) + render :new, locals: { gpo_letter_available: gpo_letter_available } end end @@ -230,6 +223,12 @@ def formatted_previous_phone_step_params_phone ) end + def gpo_letter_available + return @gpo_letter_available if defined?(@gpo_letter_available) + policy = Idv::GpoVerifyByMailPolicy.new(current_user) + @gpo_letter_available = policy.send_letter_available? + end + # Migrated from otp_delivery_method_controller def otp_sent_tracker_error(result) if send_phone_confirmation_otp_rate_limited? diff --git a/app/controllers/idv/phone_errors_controller.rb b/app/controllers/idv/phone_errors_controller.rb index 770be4cbe47..57670946238 100644 --- a/app/controllers/idv/phone_errors_controller.rb +++ b/app/controllers/idv/phone_errors_controller.rb @@ -6,7 +6,6 @@ class PhoneErrorsController < ApplicationController include IdvStepConcern include StepIndicatorConcern include Idv::AbTestAnalyticsConcern - include Idv::VerifyByMailConcern before_action :confirm_step_allowed, except: [:failure] before_action :set_gpo_letter_available @@ -72,8 +71,12 @@ def track_event(type:) analytics.idv_phone_error_visited(**attributes) end + # rubocop:disable Naming/MemoizedInstanceVariableName def set_gpo_letter_available - @gpo_letter_available = gpo_verify_by_mail_policy.send_letter_available? + return @gpo_letter_available if defined?(@gpo_letter_available) + policy = Idv::GpoVerifyByMailPolicy.new(current_user) + @gpo_letter_available = policy.send_letter_available? end + # rubocop:enable Naming/MemoizedInstanceVariableName end end diff --git a/app/controllers/idv/sessions_controller.rb b/app/controllers/idv/sessions_controller.rb index 50bdc27ac86..37d98075582 100644 --- a/app/controllers/idv/sessions_controller.rb +++ b/app/controllers/idv/sessions_controller.rb @@ -9,8 +9,8 @@ class SessionsController < ApplicationController def destroy cancel_processing - log_analytics clear_session + log_analytics redirect_to idv_url end diff --git a/app/controllers/idv_controller.rb b/app/controllers/idv_controller.rb index 7d58f23a72a..bef3b246b29 100644 --- a/app/controllers/idv_controller.rb +++ b/app/controllers/idv_controller.rb @@ -5,7 +5,6 @@ class IdvController < ApplicationController include AccountReactivationConcern include VerifyProfileConcern include RateLimitConcern - include Idv::VerifyByMailConcern before_action :confirm_two_factor_authenticated before_action :profile_needs_reactivation?, only: [:index] diff --git a/app/controllers/openid_connect/logout_controller.rb b/app/controllers/openid_connect/logout_controller.rb index ada39ed341e..16f042142d2 100644 --- a/app/controllers/openid_connect/logout_controller.rb +++ b/app/controllers/openid_connect/logout_controller.rb @@ -133,9 +133,9 @@ def handle_successful_logout_request(result, redirect_uri) def handle_logout(result, redirect_uri) analytics.logout_initiated(**to_event(result)) - redirect_user(redirect_uri, @logout_form.service_provider&.issuer, current_user&.uuid) - sign_out + + redirect_user(redirect_uri, @logout_form.service_provider&.issuer, current_user&.uuid) end # Convert FormResponse into loggable analytics event diff --git a/app/controllers/users/delete_controller.rb b/app/controllers/users/delete_controller.rb index c76d65b904a..a58e6cbb72f 100644 --- a/app/controllers/users/delete_controller.rb +++ b/app/controllers/users/delete_controller.rb @@ -16,10 +16,10 @@ def delete send_push_notifications notify_user_via_email_of_deletion notify_user_via_sms_of_deletion - analytics.account_delete_submitted(success: true) delete_user sign_out flash[:success] = t('devise.registrations.destroyed') + analytics.account_delete_submitted(success: true) redirect_to root_url end diff --git a/app/controllers/vendor_outage_controller.rb b/app/controllers/vendor_outage_controller.rb index 411d735f899..76b22c2447f 100644 --- a/app/controllers/vendor_outage_controller.rb +++ b/app/controllers/vendor_outage_controller.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true class VendorOutageController < ApplicationController - include Idv::VerifyByMailConcern - def show outage_status = OutageStatus.new @specific_message = outage_status.outage_message - @show_gpo_option = from_idv_phone? && - user_signed_in? && - gpo_verify_by_mail_policy.send_letter_available? + @show_gpo_option = from_idv_phone? && gpo_letter_available? outage_status.track_event(analytics) end @@ -18,4 +14,10 @@ def show def from_idv_phone? params[:from] == 'idv_phone' end + + def gpo_letter_available? + return false unless current_user + policy = Idv::GpoVerifyByMailPolicy.new(current_user) + policy.send_letter_available? + end end diff --git a/app/forms/gpo_verify_form.rb b/app/forms/gpo_verify_form.rb index 497bb79678c..e9ca623efc9 100644 --- a/app/forms/gpo_verify_form.rb +++ b/app/forms/gpo_verify_form.rb @@ -9,12 +9,11 @@ class GpoVerifyForm validate :validate_pending_profile attr_accessor :otp, :pii, :pii_attributes - attr_reader :user, :resolved_authn_context_result + attr_reader :user - def initialize(user:, pii:, resolved_authn_context_result:, otp: nil) + def initialize(user:, pii:, otp: nil) @user = user @pii = pii - @resolved_authn_context_result = resolved_authn_context_result @otp = otp end @@ -128,7 +127,7 @@ def activate_profile end def user_can_request_another_letter? - policy = Idv::GpoVerifyByMailPolicy.new(user, resolved_authn_context_result) + policy = Idv::GpoVerifyByMailPolicy.new(user) policy.resend_letter_available? end end diff --git a/app/forms/recaptcha_form.rb b/app/forms/recaptcha_form.rb index 1ddd6fa9ee9..4d49437d65b 100644 --- a/app/forms/recaptcha_form.rb +++ b/app/forms/recaptcha_form.rb @@ -6,7 +6,6 @@ class RecaptchaForm VERIFICATION_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify' RESULT_ERRORS = ['missing-input-secret', 'invalid-input-secret'].freeze - EXEMPT_RESULT_REASONS = ['LOW_CONFIDENCE_SCORE'].freeze attr_reader :recaptcha_action, :recaptcha_token, @@ -96,7 +95,6 @@ def faraday def recaptcha_result_valid?(result) return true if result.blank? - return true if result_reason_exempt?(result) if result.success? result.score >= score_threshold @@ -109,10 +107,6 @@ def is_result_error?(error_code) RESULT_ERRORS.include?(error_code) end - def result_reason_exempt?(result) - (EXEMPT_RESULT_REASONS & result.reasons).any? - end - def log_analytics(result: nil, error: nil) analytics&.recaptcha_verify_result_received( recaptcha_result: result.to_h.presence, diff --git a/app/javascript/packages/session/requests.spec.ts b/app/javascript/packages/session/requests.spec.ts index 24607acb2c3..2793584b50a 100644 --- a/app/javascript/packages/session/requests.spec.ts +++ b/app/javascript/packages/session/requests.spec.ts @@ -1,11 +1,9 @@ import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import type { SetupServer } from 'msw/node'; -import { requestSessionStatus, extendSession } from './requests'; +import { SESSIONS_URL, requestSessionStatus, extendSession } from './requests'; import type { SessionLiveStatusResponse, SessionTimedOutStatusResponse } from './requests'; -const SESSIONS_URL = '/api/internal/sessions'; - describe('requestSessionStatus', () => { let server: SetupServer; @@ -24,7 +22,7 @@ describe('requestSessionStatus', () => { }); it('resolves to the status', async () => { - const result = await requestSessionStatus(SESSIONS_URL); + const result = await requestSessionStatus(); expect(result).to.deep.equal({ isLive: false }); }); @@ -48,7 +46,7 @@ describe('requestSessionStatus', () => { }); it('resolves to the status', async () => { - const result = await requestSessionStatus(SESSIONS_URL); + const result = await requestSessionStatus(); expect(result).to.deep.equal({ isLive: true, timeout: new Date(timeout) }); }); @@ -67,7 +65,7 @@ describe('requestSessionStatus', () => { }); it('resolves to the status', async () => { - const result = await requestSessionStatus(SESSIONS_URL); + const result = await requestSessionStatus(); expect(result).to.deep.equal({ isLive: false }); }); @@ -86,7 +84,7 @@ describe('requestSessionStatus', () => { }); it('throws an error', async () => { - await expect(requestSessionStatus(SESSIONS_URL)).to.be.rejected(); + await expect(requestSessionStatus()).to.be.rejected(); }); }); }); @@ -111,7 +109,7 @@ describe('extendSession', () => { }); it('resolves to the status', async () => { - const result = await extendSession(SESSIONS_URL); + const result = await extendSession(); expect(result).to.deep.equal({ isLive: true, timeout: new Date(timeout) }); }); @@ -130,7 +128,7 @@ describe('extendSession', () => { }); it('resolves to the status', async () => { - const result = await extendSession(SESSIONS_URL); + const result = await extendSession(); expect(result).to.deep.equal({ isLive: false }); }); @@ -149,7 +147,7 @@ describe('extendSession', () => { }); it('throws an error', async () => { - await expect(extendSession(SESSIONS_URL)).to.be.rejected(); + await expect(extendSession()).to.be.rejected(); }); }); }); diff --git a/app/javascript/packages/session/requests.ts b/app/javascript/packages/session/requests.ts index a04e2cc213f..048834b6ca5 100644 --- a/app/javascript/packages/session/requests.ts +++ b/app/javascript/packages/session/requests.ts @@ -52,6 +52,8 @@ interface SessionTimedOutStatus { export type SessionStatus = SessionLiveStatus | SessionTimedOutStatus; +export const SESSIONS_URL = new URL('/api/internal/sessions', window.location.href).toString(); + function mapSessionStatusResponse( response: R, ): SessionLiveStatus; @@ -81,11 +83,10 @@ function handleUnauthorizedStatusResponse(error: ResponseError) { /** * Request the current session status. Returns a promise resolving to the current session status. * - * @param sessionsURL The URL for the session API * @return A promise resolving to the current session status */ -export const requestSessionStatus = (sessionsURL: string): Promise => - request(sessionsURL) +export const requestSessionStatus = (): Promise => + request(SESSIONS_URL) .catch(handleUnauthorizedStatusResponse) .then(mapSessionStatusResponse); @@ -93,10 +94,9 @@ export const requestSessionStatus = (sessionsURL: string): Promise => - request(sessionsURL, { method: 'PUT' }) +export const extendSession = (): Promise => + request(SESSIONS_URL, { method: 'PUT' }) .catch(handleUnauthorizedStatusResponse) .then(mapSessionStatusResponse); diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx index 2cf4b4bafeb..be9c15bd19b 100644 --- a/app/javascript/packs/document-capture.tsx +++ b/app/javascript/packs/document-capture.tsx @@ -39,8 +39,6 @@ interface AppRootData { howToVerifyURL: string; previousStepUrl: string; docAuthSelfieDesktopTestMode: string; - locationsUrl: string; - addressSearchUrl: string; } const appRoot = document.getElementById('document-capture-form')!; @@ -109,8 +107,6 @@ const { howToVerifyUrl, previousStepUrl, docAuthSelfieDesktopTestMode, - locationsUrl: locationsURL, - addressSearchUrl: addressSearchURL, } = appRoot.dataset as DOMStringMap & AppRootData; let parsedUsStatesTerritories = []; @@ -126,8 +122,8 @@ const App = composeComponents( { value: { inPersonURL, - locationsURL, - addressSearchURL, + locationsURL: new URL('/verify/in_person/usps_locations', window.location.href).toString(), + addressSearchURL: new URL('/api/addresses', window.location.href).toString(), inPersonOutageMessageEnabled: inPersonOutageMessageEnabled === 'true', inPersonOutageExpectedUpdateDate, inPersonFullAddressEntryEnabled: inPersonFullAddressEntryEnabled === 'true', diff --git a/app/javascript/packs/session-timeout-ping.ts b/app/javascript/packs/session-timeout-ping.ts index a86f60254b5..339ce097c86 100644 --- a/app/javascript/packs/session-timeout-ping.ts +++ b/app/javascript/packs/session-timeout-ping.ts @@ -11,8 +11,7 @@ const defaultTime = '60'; const frequency = parseInt(warningEl?.dataset.frequency || defaultTime, 10) * 1000; const warning = parseInt(warningEl?.dataset.warning || defaultTime, 10) * 1000; const start = parseInt(warningEl?.dataset.start || defaultTime, 10) * 1000; -const timeoutURL = warningEl?.dataset.timeoutUrl!; -const sessionsURL = warningEl?.dataset.sessionsUrl!; +const timeoutUrl = warningEl?.dataset.timeoutUrl!; const modal = document.querySelector('lg-modal.session-timeout-modal')!; const keepaliveEl = document.getElementById('session-keepalive-btn'); @@ -20,8 +19,8 @@ const countdownEls: NodeListOf = modal.querySelectorAll('lg-co function success({ isLive, timeout }: SessionStatus) { if (!isLive) { - if (timeoutURL) { - forceRedirect(timeoutURL); + if (timeoutUrl) { + forceRedirect(timeoutUrl); } return; } @@ -44,12 +43,12 @@ function success({ isLive, timeout }: SessionStatus) { setTimeout(ping, nextPingTimeout); } -const ping = () => requestSessionStatus(sessionsURL).then(success); +const ping = () => requestSessionStatus().then(success); function keepalive() { modal.hide(); countdownEls.forEach((countdownEl) => countdownEl.stop()); - extendSession(sessionsURL); + extendSession(); } keepaliveEl?.addEventListener('click', keepalive, false); diff --git a/app/models/anonymous_user.rb b/app/models/anonymous_user.rb index f3b22485419..b871d1a74be 100644 --- a/app/models/anonymous_user.rb +++ b/app/models/anonymous_user.rb @@ -5,10 +5,6 @@ def uuid 'anonymous-uuid' end - def establishing_in_person_enrollment; end - - def pending_in_person_enrollment; end - def second_factor_locked_at nil end diff --git a/app/policies/idv/gpo_verify_by_mail_policy.rb b/app/policies/idv/gpo_verify_by_mail_policy.rb index e0a0f9247d6..6f2fd464dd9 100644 --- a/app/policies/idv/gpo_verify_by_mail_policy.rb +++ b/app/policies/idv/gpo_verify_by_mail_policy.rb @@ -2,23 +2,21 @@ module Idv class GpoVerifyByMailPolicy - attr_reader :user, :resolved_authn_context_result + attr_reader :user - def initialize(user, resolved_authn_context_result) + def initialize(user) @user = user - @resolved_authn_context_result = resolved_authn_context_result end def resend_letter_available? - @resend_letter_available ||= FeatureManagement.gpo_verification_enabled? && - !rate_limited? && - !profile_too_old? + FeatureManagement.gpo_verification_enabled? && + !rate_limited? && + !profile_too_old? end def send_letter_available? - @send_letter_available ||= FeatureManagement.gpo_verification_enabled? && - !disabled_for_biometric_comparison? && - !rate_limited? + FeatureManagement.gpo_verification_enabled? && + !rate_limited? end def rate_limited? @@ -36,12 +34,6 @@ def profile_too_old? private - def disabled_for_biometric_comparison? - return false unless IdentityConfig.store.no_verify_by_mail_for_biometric_comparison_enabled - - resolved_authn_context_result.two_pieces_of_fair_evidence? - end - def window_limit_enabled? IdentityConfig.store.max_mail_events != 0 && IdentityConfig.store.max_mail_events_window_in_days != 0 diff --git a/app/presenters/idv/by_mail/request_letter_presenter.rb b/app/presenters/idv/by_mail/request_letter_presenter.rb new file mode 100644 index 00000000000..349e7554619 --- /dev/null +++ b/app/presenters/idv/by_mail/request_letter_presenter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Idv + module ByMail + class RequestLetterPresenter + include Rails.application.routes.url_helpers + + attr_reader :current_user, :url_options + + def initialize(current_user, url_options) + @current_user = current_user + @url_options = url_options + end + + def title + resend_requested? ? + I18n.t('idv.gpo.request_another_letter.title') : + I18n.t('idv.titles.mail.verify') + end + + def button + resend_requested? ? + I18n.t('idv.gpo.request_another_letter.button') : + I18n.t('idv.buttons.mail.send') + end + + def fallback_back_path + return idv_verify_info_path if OutageStatus.new.any_phone_vendor_outage? + user_needs_address_otp_verification? ? idv_verify_by_mail_enter_code_path : idv_phone_path + end + + def resend_requested? + current_user.gpo_verification_pending_profile? + end + + def back_or_cancel_partial + if FeatureManagement.idv_by_mail_only? + 'idv/doc_auth/cancel' + else + 'idv/shared/back' + end + end + + def back_or_cancel_parameters + if FeatureManagement.idv_by_mail_only? + { step: 'gpo' } + else + { fallback_path: fallback_back_path } + end + end + + private + + def user_needs_address_otp_verification? + current_user.pending_profile? + end + end + end +end diff --git a/app/presenters/idv/in_person/ready_to_verify_presenter.rb b/app/presenters/idv/in_person/ready_to_verify_presenter.rb index 61caeca8b5a..a5f8462f742 100644 --- a/app/presenters/idv/in_person/ready_to_verify_presenter.rb +++ b/app/presenters/idv/in_person/ready_to_verify_presenter.rb @@ -31,7 +31,7 @@ def formatted_due_date def selected_location_hours(prefix) return unless selected_location_details hours = selected_location_details["#{prefix}_hours"] - UspsInPersonProofing::EnrollmentHelper.localized_hours(hours) if hours + return localized_hours(hours) if hours end def service_provider @@ -107,6 +107,18 @@ def format_outage_date(date) I18n.l(date.to_date, format: :short) end + def localized_hours(hours) + case hours + when 'Closed' + I18n.t('in_person_proofing.body.barcode.retail_hours_closed') + else + hours. + split(' - '). # Hyphen + map { |time| Time.zone.parse(time).strftime(I18n.t('time.formats.event_time')) }. + join(' – ') # Endash + end + end + def sp_return_url_resolver SpReturnUrlResolver.new(service_provider: service_provider) end diff --git a/app/services/access_token_verifier.rb b/app/services/access_token_verifier.rb index d203a25b22a..02f55ed401a 100644 --- a/app/services/access_token_verifier.rb +++ b/app/services/access_token_verifier.rb @@ -59,7 +59,7 @@ def extract_access_token(header) end bearer, access_token = header.split(' ', 2) - if bearer != 'Bearer' || access_token.blank? + if bearer != 'Bearer' errors.add( :access_token, t('openid_connect.user_info.errors.malformed_authorization'), type: :malformed_authorization diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 3177bb11df2..699e56d39c0 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -887,14 +887,7 @@ def idv_camera_info_logged(flow_path:, camera_info:, **_extra) end # @param [String] step the step that the user was on when they clicked cancel - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user confirmed their choice to cancel going through IDV @@ -916,14 +909,7 @@ def idv_cancellation_confirmed( end # @param [String] step the step that the user was on when they clicked cancel - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [boolean,nil] cancelled_enrollment Whether the user's IPP enrollment has been canceled # @param [String,nil] enrollment_code IPP enrollment code # @param [Integer,nil] enrollment_id ID of the associated IPP enrollment record @@ -956,14 +942,7 @@ def idv_cancellation_go_back( # @param [String] step the step that the user was on when they clicked cancel # @param [String] request_came_from the controller and action from the # source such as "users/sessions#new" - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user clicked cancel during IDV (presented with an option to go back or confirm) @@ -1474,14 +1453,7 @@ def idv_doc_auth_welcome_visited(**extra) # @param [Boolean] fraud_rejection # @param [Boolean] gpo_verification_pending # @param [Boolean] in_person_verification_pending - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String, nil] deactivation_reason Reason user's profile was deactivated, if any. # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. @@ -1513,14 +1485,8 @@ def idv_enter_password_submitted( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's + # current proofing components # @param [String] address_verification_method The method (phone or gpo) being # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. @@ -1550,14 +1516,7 @@ def idv_enter_password_visited( # @param [Boolean] fraud_rejection Profile is rejected due to fraud # @param [Boolean] gpo_verification_pending Profile is awaiting gpo verification # @param [Boolean] in_person_verification_pending Profile is awaiting in person verification - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # @param [Array,nil] profile_history Array of user's profiles (oldest to newest). @@ -1593,14 +1552,7 @@ def idv_final( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # User visited forgot password page @@ -1619,14 +1571,7 @@ def idv_forgot_password( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # User confirmed forgot password @@ -1772,14 +1717,7 @@ def idv_front_image_clicked( # @param [Integer] hours_since_first_letter Difference between first_letter_requested_at # and now in hours # @param [Integer] phone_step_attempts Number of attempts at phone step before requesting letter - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # GPO letter was enqueued and the time at which it was enqueued @@ -1813,14 +1751,7 @@ def idv_gpo_address_letter_enqueued( # @param [Integer] hours_since_first_letter Difference between first_letter_requested_at # and now in hours # @param [Integer] phone_step_attempts Number of attempts at phone step before requesting letter - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # GPO letter was requested @@ -2281,14 +2212,7 @@ def idv_in_person_ready_to_verify_sp_link_clicked(**extra) ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user visited the "ready to verify" page for the in person proofing flow @@ -2770,14 +2694,7 @@ def idv_ipp_deactivated_for_never_visiting_post_office( end # The user visited the "letter enqueued" page shown during the verify by mail flow - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # @identity.idp.previous_event_name IdV: come back later visited @@ -2860,14 +2777,7 @@ def idv_not_verified_visited(**extra) # Tracks if a user clicks the 'acknowledge' checkbox during personal # key creation - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [boolean] checked whether the user checked or un-checked # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. @@ -2891,14 +2801,7 @@ def idv_personal_key_acknowledgment_toggled( # A user has downloaded their personal key. This event is no longer emitted. # @identity.idp.previous_event_name IdV: download personal key - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. def idv_personal_key_downloaded( @@ -2916,14 +2819,7 @@ def idv_personal_key_downloaded( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String, nil] deactivation_reason Reason profile was deactivated. # @param [Boolean] fraud_review_pending Profile is under review for fraud # @param [Boolean] fraud_rejection Profile is rejected due to fraud @@ -2957,14 +2853,7 @@ def idv_personal_key_submitted( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String] address_verification_method "phone" or "gpo" # @param [Boolean,nil] in_person_verification_pending # @param [Boolean] encrypted_profiles_missing True if user's session had no encrypted pii @@ -2996,14 +2885,7 @@ def idv_personal_key_visited( # @param [Hash] errors Errors resulting from form validation # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param ["sms", "voice"] otp_delivery_preference - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user submitted their phone on the phone confirmation page @@ -3030,14 +2912,7 @@ def idv_phone_confirmation_form_submitted( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user was rate limited for submitting too many OTPs during the IDV phone step @@ -3056,14 +2931,7 @@ def idv_phone_confirmation_otp_rate_limit_attempts( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user was locked out for hitting the phone OTP rate limit during IDV @@ -3082,14 +2950,7 @@ def idv_phone_confirmation_otp_rate_limit_locked_out( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The user was rate limited for requesting too many OTPs during the IDV phone step @@ -3117,14 +2978,7 @@ def idv_phone_confirmation_otp_rate_limit_sends( # @param [Boolean] rate_limit_exceeded whether or not the rate limit was exceeded by this attempt # @param [Hash] telephony_response response from Telephony gem # @param [String] phone_fingerprint Fingerprint string identifying phone number - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # @param [Hash, nil] ab_tests data for ongoing A/B tests @@ -3173,14 +3027,7 @@ def idv_phone_confirmation_otp_resent( # @param [Boolean] rate_limit_exceeded whether or not the rate limit was exceeded by this attempt # @param [String] phone_fingerprint the hmac fingerprint of the phone number formatted as e164 # @param [Hash] telephony_response response from Telephony gem - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [:test, :pinpoint] adapter which adapter the OTP was delivered with # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. @@ -3228,14 +3075,7 @@ def idv_phone_confirmation_otp_sent( # @param [:sms,:voice] otp_delivery_preference # @param [Integer] second_factor_attempts_count number of attempts to confirm this phone # @param [Time, nil] second_factor_locked_at timestamp when the phone was locked out - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # When a user attempts to confirm posession of a new phone number during the IDV process @@ -3270,14 +3110,7 @@ def idv_phone_confirmation_otp_submitted( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # When a user visits the page to confirm posession of a new phone number during the IDV process @@ -3299,14 +3132,7 @@ def idv_phone_confirmation_otp_visit( # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation # @param [Hash] error_details Details for errors that occurred in unsuccessful submission - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The vendor finished the process of confirming the users phone @@ -3335,14 +3161,7 @@ def idv_phone_confirmation_vendor_submitted( # @param [Time] limiter_expires_at when the rate limit expires # @param [Integer] remaining_submit_attempts number of submit attempts remaining # (previously called "remaining_attempts") - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # When a user gets an error during the phone finder flow of IDV @@ -3369,14 +3188,7 @@ def idv_phone_error_visited( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # User visited idv phone of record @@ -3395,14 +3207,7 @@ def idv_phone_of_record_visited( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String] step the step the user was on when they clicked use a different phone number # User decided to use a different phone number in idv def idv_phone_use_different(step:, proofing_components: nil, **extra) @@ -3415,14 +3220,7 @@ def idv_phone_use_different(step:, proofing_components: nil, **extra) end # @identity.idp.previous_event_name IdV: Verify setup errors visited - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # @param [Array,nil] profile_history Array of user's profiles (oldest to newest). @@ -3444,14 +3242,7 @@ def idv_please_call_visited( ) end - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # The system encountered an error and the proofing results are missing @@ -3470,13 +3261,16 @@ def idv_proofing_resolution_result_missing( ) end + # @param [Boolean] letter_already_sent # GPO "request letter" page visited # @identity.idp.previous_event_name IdV: USPS address visited def idv_request_letter_visited( + letter_already_sent:, **extra ) track_event( 'IdV: request letter visited', + letter_already_sent: letter_already_sent, **extra, ) end @@ -3758,14 +3552,7 @@ def idv_session_error_visited( # @param [String] step # @param [String] location - # @param [Hash,nil] proofing_components User's current proofing components - # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID - # @option proofing_components [String,nil] 'document_type' Type of ID used to verify - # @option proofing_components [String,nil] 'source_check' Source used to verify user's PII - # @option proofing_components [String,nil] 'resolution_check' Vendor for identity resolution check - # @option proofing_components [String,nil] 'address_check' Method used to verify user's address - # @option proofing_components [Boolean,nil] 'threatmetrix' Whether ThreatMetrix check was done - # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user + # @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components # @param [boolean,nil] cancelled_enrollment Whether the user's IPP enrollment has been canceled # @param [String,nil] enrollment_code IPP enrollment code # @param [Integer,nil] enrollment_id ID of the associated IPP enrollment record diff --git a/app/services/gpo_confirmation_uploader.rb b/app/services/gpo_confirmation_uploader.rb index 54422aba58c..315c649a244 100644 --- a/app/services/gpo_confirmation_uploader.rb +++ b/app/services/gpo_confirmation_uploader.rb @@ -43,14 +43,8 @@ def generate_export(confirmations) def upload_export(export) return unless FeatureManagement.gpo_upload_enabled? io = StringIO.new(export) - - with_retries( - max_tries: 5, - rescue: [Net::SFTP::Exception, Net::SSH::Exception], - ) do - Net::SFTP.start(*sftp_config) do |sftp| - sftp.upload!(io, remote_path) - end + Net::SFTP.start(*sftp_config) do |sftp| + sftp.upload!(io, remote_path) end end diff --git a/app/services/idv/analytics_events_enhancer.rb b/app/services/idv/analytics_events_enhancer.rb index f75fd52f412..5645c1f3320 100644 --- a/app/services/idv/analytics_events_enhancer.rb +++ b/app/services/idv/analytics_events_enhancer.rb @@ -183,20 +183,8 @@ def profile_history end def proofing_components - return if !user - - idv_session = Idv::Session.new( - user_session: session&.dig('warden.user.user.session') || {}, - current_user: user, - service_provider: sp, - ) - - proofing_components_hash = ProofingComponents.new( - user:, - idv_session:, - ).to_h - - proofing_components_hash.empty? ? nil : proofing_components_hash + return if !user&.respond_to?(:proofing_component) || !user.proofing_component + ProofingComponentsLogging.new(user.proofing_component) end end end diff --git a/app/services/idv/proofing_components.rb b/app/services/idv/proofing_components.rb deleted file mode 100644 index 6300bfd8ff3..00000000000 --- a/app/services/idv/proofing_components.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module Idv - class ProofingComponents - def initialize( - user:, - idv_session: - ) - @user = user - @idv_session = idv_session - end - - def document_check - if user.establishing_in_person_enrollment || user.pending_in_person_enrollment - Idp::Constants::Vendors::USPS - elsif idv_session.remote_document_capture_complete? - DocAuthRouter.doc_auth_vendor( - discriminator: idv_session.document_capture_session_uuid, - ) - end - end - - def document_type - return 'state_id' if idv_session.remote_document_capture_complete? - end - - def source_check - Idp::Constants::Vendors::AAMVA if idv_session.verify_info_step_complete? - end - - def resolution_check - Idp::Constants::Vendors::LEXIS_NEXIS if idv_session.verify_info_step_complete? - end - - def address_check - if idv_session.verify_by_mail? - 'gpo_letter' - elsif idv_session.address_verification_mechanism == 'phone' - 'lexis_nexis_address' - end - end - - def threatmetrix - if idv_session.threatmetrix_review_status.present? - FeatureManagement.proofing_device_profiling_collecting_enabled? - end - end - - def threatmetrix_review_status - idv_session.threatmetrix_review_status - end - - def to_h - { - document_check:, - document_type:, - source_check:, - resolution_check:, - address_check:, - threatmetrix:, - threatmetrix_review_status:, - - }.compact - end - - private - - attr_reader :user, :idv_session - end -end diff --git a/app/services/idv/proofing_components_logging.rb b/app/services/idv/proofing_components_logging.rb new file mode 100644 index 00000000000..d6f31ced71c --- /dev/null +++ b/app/services/idv/proofing_components_logging.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Idv + ProofingComponentsLogging = Struct.new(:proofing_components) do + def as_json(*) + proofing_components.slice( + :document_check, + :document_type, + :source_check, + :resolution_check, + :address_check, + :liveness_check, + :device_fingerprinting_vendor, + :threatmetrix, + :threatmetrix_review_status, + :threatmetrix_risk_rating, + :threatmetrix_policy_score, + ).compact + end + end +end diff --git a/app/services/usps_in_person_proofing/enrollment_helper.rb b/app/services/usps_in_person_proofing/enrollment_helper.rb index cb8cc789c9f..1a500465f57 100644 --- a/app/services/usps_in_person_proofing/enrollment_helper.rb +++ b/app/services/usps_in_person_proofing/enrollment_helper.rb @@ -20,12 +20,6 @@ def schedule_in_person_enrollment(user:, pii:, is_enhanced_ipp:, opt_in: nil) enrollment_code = create_usps_enrollment(enrollment, pii, is_enhanced_ipp) return unless enrollment_code - if is_enhanced_ipp - enrollment.sponsor_id = IdentityConfig.store.usps_eipp_sponsor_id - else - enrollment.sponsor_id = IdentityConfig.store.usps_ipp_sponsor_id - end - # update the enrollment to status pending enrollment.enrollment_code = enrollment_code enrollment.status = :pending @@ -101,40 +95,6 @@ def usps_proofer end end - def localized_location(location) - { - address: location.address, - city: location.city, - distance: location.distance, - name: location.name, - saturday_hours: EnrollmentHelper.localized_hours(location.saturday_hours), - state: location.state, - sunday_hours: EnrollmentHelper.localized_hours(location.sunday_hours), - weekday_hours: EnrollmentHelper.localized_hours(location.weekday_hours), - zip_code_4: location.zip_code_4, - zip_code_5: location.zip_code_5, - is_pilot: location.is_pilot, - } - end - - def localized_hours(hours) - if hours == 'Closed' - I18n.t('in_person_proofing.body.barcode.retail_hours_closed') - elsif hours.include?(' - ') # Hyphen - hours. - split(' - '). # Hyphen - map { |time| Time.zone.parse(time).strftime(I18n.t('time.formats.event_time')) }. - join(' – ') # Endash - elsif hours.include?(' – ') # Endash - hours. - split(' – '). # Endash - map { |time| Time.zone.parse(time).strftime(I18n.t('time.formats.event_time')) }. - join(' – ') # Endash - else - hours - end - end - private SECONDARY_ID_ADDRESS_MAP = { diff --git a/app/views/idv/by_mail/request_letter/index.html.erb b/app/views/idv/by_mail/request_letter/index.html.erb index 730c829806c..99a2f06e42b 100644 --- a/app/views/idv/by_mail/request_letter/index.html.erb +++ b/app/views/idv/by_mail/request_letter/index.html.erb @@ -9,58 +9,77 @@ ) %> <% end %> -<%= render AlertComponent.new( - type: :info, - message: t('idv.messages.gpo.info_alert'), - class: 'margin-bottom-4', - ) %> - -<%= render PageHeadingComponent.new.with_content(t('idv.titles.mail.verify')) %> - -

- <%= t('idv.messages.gpo.timeframe_html') %> -
- <%= new_tab_link_to( - t('idv.messages.gpo.learn_more_verify_by_mail'), - help_center_redirect_url( - category: 'verify-your-identity', - article: 'verify-your-address-by-mail', - flow: :idv, - step: :gpo_send_letter, - ), - ) - %> -

-

- <%= t('idv.messages.gpo.address_on_file') %> -

+<% if @presenter.resend_requested? %> + <%= render PageHeadingComponent.new.with_content(@presenter.title) %> +

+ <%= t('idv.gpo.request_another_letter.instructions_html') %> +

+

+ <%= new_tab_link_to( + t('idv.gpo.request_another_letter.learn_more_link'), + help_center_redirect_url( + category: 'verify-your-identity', + article: 'verify-your-address-by-mail', + flow: :idv, + step: :gpo_send_letter, + ), + ) %> +

+<% else %> + <%= render AlertComponent.new( + type: :info, + message: t('idv.messages.gpo.info_alert'), + class: 'margin-bottom-4', + ) %> + <%= render PageHeadingComponent.new.with_content(@presenter.title) %> +

+ <%= t('idv.messages.gpo.timeframe_html') %> +
+ <%= new_tab_link_to( + t('idv.messages.gpo.learn_more_verify_by_mail'), + help_center_redirect_url( + category: 'verify-your-identity', + article: 'verify-your-address-by-mail', + flow: :idv, + step: :gpo_send_letter, + ), + ) + %> +

+

+ <%= t('idv.messages.gpo.address_on_file') %> +

-

- <%= render 'shared/address', - address: @applicant.slice(:address1, :address2, :city, :state, :zipcode) - %> -

+

+ <%= @applicant[:address1] %> +
+ <% if @applicant[:address2] %> + <%= @applicant[:address2] %> +
+ <% end %> + <%= @applicant[:city] %>, <%= @applicant[:state] %> <%= @applicant[:zipcode] %> +
+

-

- <%= t( - 'idv.messages.gpo.start_over_html', - start_over_link_html: link_to( +

+ <%= start_over_link_html = link_to( t('idv.messages.gpo.start_over_link_text'), idv_confirm_start_over_before_letter_path, - ), - ) - %> -

+ ) + t( + 'idv.messages.gpo.start_over_html', + start_over_link_html: start_over_link_html, + ) + %> +

+ +<% end %>
- <%= button_to t('idv.buttons.mail.send'), + <%= button_to @presenter.button, idv_request_letter_path, method: 'put', class: 'usa-button usa-button--big usa-button--wide' %>
-<% if FeatureManagement.idv_by_mail_only? %> - <%= render 'idv/doc_auth/cancel', step: 'gpo' %> -<% else %> - <%= render 'idv/shared/back', fallback_path: idv_phone_path %> -<% end %> +<%= render @presenter.back_or_cancel_partial, @presenter.back_or_cancel_parameters %> diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb index 424ffac95ff..72000e65e8b 100644 --- a/app/views/idv/shared/_document_capture.html.erb +++ b/app/views/idv/shared/_document_capture.html.erb @@ -42,8 +42,6 @@ skip_doc_auth_from_handoff: skip_doc_auth_from_handoff, how_to_verify_url: idv_how_to_verify_url, previous_step_url: @previous_step_url, - locations_url: idv_in_person_usps_locations_url, - address_search_url: '', } %> <%= simple_form_for( :doc_auth, diff --git a/app/views/session_timeout/_ping.html.erb b/app/views/session_timeout/_ping.html.erb index 4a8aaa1b8fb..9c600645c3d 100644 --- a/app/views/session_timeout/_ping.html.erb +++ b/app/views/session_timeout/_ping.html.erb @@ -1,7 +1,6 @@ <%= tag.div id: 'session-timeout-cntnr', data: { timeout_url: new_user_session_url(timeout: :session, request_id: sp_session[:request_id]), - sessions_url: api_internal_sessions_path, warning: warning, start: start, frequency: frequency, diff --git a/config/application.yml.default b/config/application.yml.default index 0ab878f88cc..24e0c0ecfb4 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -199,7 +199,6 @@ min_password_score: 3 minimum_wait_before_another_usps_letter_in_hours: 24 mx_timeout: 3 new_device_alert_delay_in_minutes: 5 -no_verify_by_mail_for_biometric_comparison_enabled: true openid_connect_redirect: client_side_js openid_connect_content_security_form_action_enabled: false openid_connect_redirect_uuid_override_map: '{}' @@ -282,7 +281,7 @@ risc_notifications_rate_limit_max_requests: 1_000 risc_notifications_rate_limit_overrides: '{"https://example.com/push":{"interval":120,"max_requests":10000}}' risc_notifications_request_timeout: 3 ruby_workers_idv_enabled: true -rules_of_use_horizon_years: 5 +rules_of_use_horizon_years: 6 rules_of_use_updated_at: '2022-01-19T00:00:00Z' # Production has a newer timestamp than this, update directly in S3 s3_public_reports_enabled: false s3_reports_enabled: false @@ -468,7 +467,6 @@ production: logins_per_ip_period: 20 logins_per_ip_track_only_mode: true newrelic_license_key: '' - no_verify_by_mail_for_biometric_comparison_enabled: false openid_connect_redirect: server_side openid_connect_content_security_form_action_enabled: true otp_delivery_blocklist_findtime: 5 diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 5584cdc6549..773018ff91a 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -239,7 +239,6 @@ def self.store config.add(:mx_timeout, type: :integer) config.add(:new_device_alert_delay_in_minutes, type: :integer) config.add(:newrelic_license_key, type: :string) - config.add(:no_verify_by_mail_for_biometric_comparison_enabled, type: :boolean) config.add( :openid_connect_redirect, type: :string, diff --git a/lib/tasks/data_requests.rake b/lib/tasks/data_requests.rake index 79fd6bbaaff..8727246f4b2 100644 --- a/lib/tasks/data_requests.rake +++ b/lib/tasks/data_requests.rake @@ -33,11 +33,6 @@ namespace :data_requests do # export USERS_REPORT=/tmp/query-2020-11-17/user_report.json # export OUTPUT_DIR=/tmp/query-2020-11-17/results/ - # - # Optionally filter by start and/or end date - # export START_DATE=2024-01-01 - # export END_DATE=2025-01-01 - # # rake data_requests:process_users_report desc 'Take a JSON user report, download logs from cloud watch, and write user data' task process_users_report: :environment do @@ -45,8 +40,6 @@ namespace :data_requests do users_report = JSON.parse(File.read(ENV['USERS_REPORT']), symbolize_names: true) output_dir = ENV['OUTPUT_DIR'] - start_date = Time.zone.parse(ENV['START_DATE']).to_date if ENV['START_DATE'] - end_date = Time.zone.parse(ENV['END_DATE']).to_date if ENV['END_DATE'] users_csv = CSV.open(File.join(output_dir, 'users.csv'), 'w') user_events_csv = CSV.open(File.join(output_dir, 'user_events.csv'), 'w') @@ -70,25 +63,12 @@ namespace :data_requests do cloudwatch_dates = user_report[:user_events].pluck(:date_time).map do |date_time| Time.zone.parse(date_time).to_date - end.uniq.filter do |date| - if start_date && date < start_date - false - elsif end_date && date > end_date - false - else - true - end - end - - cloudwatch_results = - if cloudwatch_dates.empty? - [] - else - DataRequests::Local::FetchCloudwatchLogs.new( - user_report[:login_uuid], - cloudwatch_dates, - ).call - end + end.uniq + + cloudwatch_results = DataRequests::Local::FetchCloudwatchLogs.new( + user_report[:login_uuid], + cloudwatch_dates, + ).call DataRequests::Local::WriteCloudwatchLogs.new( cloudwatch_results:, diff --git a/scripts/notify-slack b/scripts/notify-slack deleted file mode 100755 index 52066f45598..00000000000 --- a/scripts/notify-slack +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'json' -require 'net/http' -require 'optparse' - -# Posts a message to Slack via webhook -class NotifySlack - def run(argv:, stdin:, stdout:) - channel = nil - username = nil - text = nil - webhook = nil - icon = ':login-gov:' - raise_on_failure = false - - program_name = File.basename($PROGRAM_NAME) - - parser = OptionParser.new do |opts| - opts.banner = <<~STR - #{program_name} [options] - - Usage: - - * Using arguments - - #{program_name} --text "my *message*" \\ - --channel "#some-channel" \\ - --webhook https://example.com/webhook - - * Passing text over STDIN - - echo "my *message*" | #{program_name} --text - \\ - --channel "#some-channel" \\ - --webhook https://example.com/webhook - - Options: - STR - - opts.on('--channel CHANNEL', 'which channel to notify') do |channel_v| - channel = channel_v - end - - opts.on('--webhook WEBHOOK', 'Slack webhook URL') do |webhook_v| - webhook = webhook_v - end - - opts.on('--username USERNAME', 'which username to notify as') do |username_v| - username = username_v - end - - opts.on('--text TEXT', 'text of notification, pass - to read from STDIN') do |text_v| - if text_v == '-' - if stdin.tty? - stdout.print 'please enter text of message: ' - text = stdin.gets - else - text = stdin.read - end - else - text = text_v - end - end - - opts.on('--icon ICON', 'slack emoji to use as icon (optional)') do |icon_v| - icon = icon_v - end - - opts.on('--[no-]raise', <<~EOS) do |raise_v| - raise errors/exit uncleanly if the webhook fails. defaults to not raising - EOS - raise_on_failure = raise_v - end - - opts.on('--help') do - puts opts - exit 0 - end - end - - parser.parse!(argv) - - if !channel || !username || !text || !webhook - stdout.puts parser - exit 1 - end - - notify( - webhook: webhook, - channel: channel, - username: username, - text: text, - icon: format_icon(icon), - ) - stdout.puts 'OK' - rescue Net::HTTPClientException => err - stdout.puts "#{program_name} HTTP ERROR: #{err.response.code}" - raise err if raise_on_failure - rescue => err - stdout.puts "#{program_name} ERROR: #{err.message}" - raise err if raise_on_failure - end - - # @raise [Net::HTTPClientException] throws an error for non-successful response - # @return [Net::HTTPResponse] - def notify(webhook:, channel:, username:, text:, icon:) - url = URI(webhook) - - req = Net::HTTP::Post.new(url) - req.form_data = { - 'payload' => { - channel: channel, - username: username, - text: text, - icon_emoji: icon, - }.to_json, - } - - Net::HTTP.start( - url.hostname, - url.port, - use_ssl: url.scheme == 'https', - open_timeout: 1, - read_timeout: 1, - write_timeout: 1, - ssl_timeout: 1, - ) do |http| - http.request(req) - end.value - end - - def format_icon(icon) - if icon.start_with?(':') && icon.end_with?(':') - icon - else - ":#{icon}:" - end - end -end - -if $PROGRAM_NAME == __FILE__ - NotifySlack.new.run(argv: ARGV, stdin: STDIN, stdout: STDOUT) -end diff --git a/spec/controllers/concerns/rate_limit_concern_spec.rb b/spec/controllers/concerns/rate_limit_concern_spec.rb index 09ab0b02f16..9c324bb8f00 100644 --- a/spec/controllers/concerns/rate_limit_concern_spec.rb +++ b/spec/controllers/concerns/rate_limit_concern_spec.rb @@ -10,7 +10,6 @@ def self.name include RateLimitConcern include IdvSessionConcern - include Idv::VerifyByMailConcern def show render plain: 'Hello' 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 d62f9818e8d..199513694a8 100644 --- a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb @@ -453,14 +453,7 @@ end let(:is_enhanced_ipp) { true } let(:user) { create(:user, :with_pending_gpo_profile, created_at: 2.days.ago) } - let(:gpo_verify_form) do - GpoVerifyForm.new( - user: user, - pii: {}, - resolved_authn_context_result: Vot::Parser::Result.no_sp_result, - otp: good_otp, - ) - end + let(:gpo_verify_form) { GpoVerifyForm.new(user: user, pii: {}, otp: good_otp) } before do authn_context_result = Vot::Parser.new(vector_of_trust: 'Pe').parse allow(controller).to( diff --git a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb index bcee2c46ed0..8a5b0170dc8 100644 --- a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb +++ b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb @@ -26,6 +26,7 @@ :confirm_two_factor_authenticated, :confirm_idv_needed, :confirm_mail_not_rate_limited, + :confirm_profile_not_too_old, ) end @@ -43,7 +44,10 @@ get :index expect(response).to have_http_status(200) - expect(@analytics).to have_logged_event('IdV: request letter visited') + expect(@analytics).to have_logged_event( + 'IdV: request letter visited', + letter_already_sent: false, + ) end it 'updates the doc auth log for the user for the usps_address_view event' do @@ -56,58 +60,197 @@ end it 'redirects if the user has sent too much mail' do - allow(controller.gpo_verify_by_mail_policy).to receive(:rate_limited?).and_return(true) + allow(controller.gpo_mail_policy).to receive(:rate_limited?).and_return(true) allow(subject.idv_session).to receive(:address_mechanism_chosen?). and_return(true) get :index expect(response).to redirect_to idv_enter_password_path end - end - describe '#create' do - before do - stub_verify_steps_one_and_two(user) + it 'allows a user to request another letter' do + allow(controller.gpo_mail_policy).to receive(:rate_limited?).and_return(false) + get :index + + expect(response).to be_ok end - it 'invalidates future steps' do - expect(subject).to receive(:clear_future_steps!) + context 'with letter already sent' do + before do + allow_any_instance_of(Idv::ByMail::RequestLetterPresenter). + to receive(:resend_requested?).and_return(true) + end - put :create + it 'logs visited event' do + get :index + + expect(@analytics).to have_logged_event( + 'IdV: request letter visited', + letter_already_sent: true, + ) + end end - it 'sets session to :gpo and redirects' do - expect(subject.idv_session.address_verification_mechanism).to be_nil + context 'user has a pending profile' do + let(:profile_created_at) { Time.zone.now } + let(:pending_profile) do + create( + :profile, + :with_pii, + user: user, + created_at: profile_created_at, + ) + end + before do + allow(user).to receive(:pending_profile).and_return(pending_profile) + end - put :create + it 'renders ok' do + get :index + expect(response).to be_ok + end - expect(response).to redirect_to idv_enter_password_path - expect(subject.idv_session.address_verification_mechanism).to eq :gpo + context 'but pending profile is too old to send another letter' do + let(:profile_created_at) { Time.zone.now - 31.days } + it 'redirects back to /verify' do + get :index + expect(response).to redirect_to(idv_path) + end + end end + end - it 'logs USPS address letter requested analytics event with phone step attempts' do - RateLimiter.new(user: user, rate_limit_type: :proof_address).increment! - put :create + describe '#create' do + context 'first time through the idv process' do + before do + stub_verify_steps_one_and_two(user) + end - expect(@analytics).to have_logged_event( - 'IdV: USPS address letter requested', - hash_including( - resend: false, - phone_step_attempts: 1, - first_letter_requested_at: nil, - hours_since_first_letter: 0, - **ab_test_args, - ), - ) + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :create + end + + it 'sets session to :gpo and redirects' do + expect(subject.idv_session.address_verification_mechanism).to be_nil + + put :create + + expect(response).to redirect_to idv_enter_password_path + expect(subject.idv_session.address_verification_mechanism).to eq :gpo + end + + it 'logs USPS address letter requested analytics event with phone step attempts' do + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment! + put :create + + expect(@analytics).to have_logged_event( + 'IdV: USPS address letter requested', + hash_including( + resend: false, + phone_step_attempts: 1, + first_letter_requested_at: nil, + hours_since_first_letter: 0, + **ab_test_args, + ), + ) + end + + it 'updates the doc auth log for the user for the usps_letter_sent event' do + unstub_analytics + doc_auth_log = DocAuthLog.create(user_id: user.id) + + expect { put :create }.to( + change { doc_auth_log.reload.usps_letter_sent_submit_count }.from(0).to(1), + ) + end end - it 'updates the doc auth log for the user for the usps_letter_sent event' do - unstub_analytics - doc_auth_log = DocAuthLog.create(user_id: user.id) + context 'resending a letter' do + let(:has_pending_profile) { true } + let(:pending_profile) { create(:profile, :with_pii, :verify_by_mail_pending) } - expect { put :create }.to( - change { doc_auth_log.reload.usps_letter_sent_submit_count }.from(0).to(1), - ) + before do + stub_sign_in(user) + stub_user_with_pending_profile(user) + allow(user).to receive(:gpo_verification_pending_profile?).and_return(true) + subject.idv_session.welcome_visited = true + subject.idv_session.idv_consent_given = true + subject.idv_session.flow_path = 'standard' + subject.idv_session.resolution_successful = true + subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN + end + + it 'calls the GpoConfirmationMaker to send another letter and redirects' do + expect_resend_letter_to_send_letter_and_redirect(otp: false) + end + + it 'calls GpoConfirmationMaker to send another letter with reveal_gpo_code on' do + allow(FeatureManagement).to receive(:reveal_gpo_code?).and_return(true) + expect_resend_letter_to_send_letter_and_redirect(otp: true) + end + + it 'logs USPS address letter analytics events with phone step attempts', :freeze_time do + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment! + expect_resend_letter_to_send_letter_and_redirect(otp: false) + + expect(@analytics).to have_logged_event( + 'IdV: USPS address letter requested', + hash_including( + resend: true, + phone_step_attempts: 1, + first_letter_requested_at: pending_profile.gpo_verification_pending_at, + hours_since_first_letter: 24, + **ab_test_args, + ), + ) + + expect(@analytics).to have_logged_event( + 'IdV: USPS address letter enqueued', + hash_including( + resend: true, + first_letter_requested_at: pending_profile.gpo_verification_pending_at, + hours_since_first_letter: 24, + enqueued_at: Time.zone.now, + phone_step_attempts: 1, + proofing_components: nil, + **ab_test_args, + ), + ) + end + + it 'redirects to capture password if pii is locked' do + pii_cacher = instance_double(Pii::Cacher) + allow(pii_cacher).to receive(:fetch).and_return(nil) + allow(pii_cacher).to receive(:exists_in_session?).and_return(false) + allow(Pii::Cacher).to receive(:new).and_return(pii_cacher) + + put :create + + expect(response).to redirect_to capture_password_path + end end end + + def expect_resend_letter_to_send_letter_and_redirect(otp:) + pii = pending_profile.decrypt_pii(user.password).to_h + pii_cacher = instance_double(Pii::Cacher) + allow(pii_cacher).to receive(:fetch).with(pending_profile.id).and_return(pii) + allow(pii_cacher).to receive(:exists_in_session?).and_return(true) + allow(Pii::Cacher).to receive(:new).and_return(pii_cacher) + + service_provider = create(:service_provider, issuer: '123abc') + session[:sp] = { issuer: service_provider.issuer, vtr: ['C1'] } + + gpo_confirmation_maker = instance_double(GpoConfirmationMaker) + allow(GpoConfirmationMaker).to receive(:new). + with(pii: pii, service_provider: service_provider, profile: pending_profile). + and_return(gpo_confirmation_maker) + + expect(gpo_confirmation_maker).to receive(:perform) + expect(gpo_confirmation_maker).to receive(:otp) if otp + expect { put :create }.to change { ActionMailer::Base.deliveries.count }.by(1) + expect(response).to redirect_to idv_letter_enqueued_path + end end diff --git a/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb b/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb index 88d5bad68c8..15670d3f2f6 100644 --- a/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb +++ b/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb @@ -1,17 +1,11 @@ require 'rails_helper' -RSpec.describe Idv::ByMail::ResendLetterController, - allowed_extra_analytics: [:sample_bucket1, :sample_bucket2] do +RSpec.describe Idv::ByMail::ResendLetterController do let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#new' do diff --git a/spec/controllers/idv/in_person/ready_to_verify_controller_spec.rb b/spec/controllers/idv/in_person/ready_to_verify_controller_spec.rb index 88f77fd2e31..17f4be87a7c 100644 --- a/spec/controllers/idv/in_person/ready_to_verify_controller_spec.rb +++ b/spec/controllers/idv/in_person/ready_to_verify_controller_spec.rb @@ -7,6 +7,7 @@ before do stub_analytics + stub_sign_in(user) allow(IdentityConfig.store).to receive(:in_person_proofing_enabled). and_return(in_person_proofing_enabled) allow(IdentityConfig.store).to receive(:in_person_proofing_enforce_tmx). @@ -26,117 +27,97 @@ subject(:response) { get :show } it 'renders not found' do - stub_sign_in(user) expect(response.status).to eq 404 end context 'with in person proofing enabled' do let(:in_person_proofing_enabled) { true } - context 'authenticated' do - before do - stub_sign_in(user) - end + it 'redirects to account page' do + expect(response).to redirect_to account_url + end + + context 'with enrollment' do + let(:user) { create(:user, :with_pending_in_person_enrollment) } + let(:profile) { create(:profile, :with_pii, user: user) } - it 'redirects to account page' do - expect(response).to redirect_to account_url + it 'renders show template' do + expect(response).to render_template :show end - context 'with enrollment' do - let(:user) { create(:user, :with_pending_in_person_enrollment) } - let(:profile) { create(:profile, :with_pii, user: user) } + it 'logs analytics' do + response - it 'renders show template' do - expect(response).to render_template :show - end + expect(@analytics).to have_logged_event('IdV: in person ready to verify visited') + end - it 'logs analytics' do + context 'with in_person_proofing_enforce_tmx disabled and pending fraud review' do + let!(:profile) { create(:profile, fraud_review_pending_at: 1.day.ago, user: user) } + let!(:enrollment) { create(:in_person_enrollment, :passed, user: user, profile: profile) } + it 'redirects to please call page' do response - expect(@analytics).to have_logged_event('IdV: in person ready to verify visited') - end - - context 'with in_person_proofing_enforce_tmx disabled and pending fraud review' do - let!(:profile) { create(:profile, fraud_review_pending_at: 1.day.ago, user: user) } - let!(:enrollment) do - create(:in_person_enrollment, :passed, user: user, profile: profile) - end - it 'redirects to please call page' do - response - - expect(response).not_to render_template :show - expect(response).to redirect_to idv_please_call_url - end + expect(response).not_to render_template :show + expect(response).to redirect_to idv_please_call_url end + end - context 'in_person_proofing_enforce_tmx enabled, pending fraud review, enrollment pass' do - let(:in_person_proofing_enforce_tmx) { true } - let!(:profile) { create(:profile, fraud_review_pending_at: 1.day.ago, user: user) } - let!(:enrollment) do - create(:in_person_enrollment, :passed, user: user, profile: profile) - end + context 'in_person_proofing_enforce_tmx enabled, pending fraud review, enrollment passed' do + let(:in_person_proofing_enforce_tmx) { true } + let!(:profile) { create(:profile, fraud_review_pending_at: 1.day.ago, user: user) } + let!(:enrollment) { create(:in_person_enrollment, :passed, user: user, profile: profile) } - it 'redirects to please call' do - response + it 'redirects to please call' do + response - expect(response).to redirect_to idv_please_call_url - end + expect(response).to redirect_to idv_please_call_url end + end - context 'in_person_proofing_enforce_tmx enabled, pending fraud review, + context 'in_person_proofing_enforce_tmx enabled, pending fraud review, enrollment not passed' do - let(:in_person_proofing_enforce_tmx) { true } - let!(:profile) { create(:profile, fraud_review_pending_at: 1.day.ago, user: user) } - let!(:enrollment) do - create(:in_person_enrollment, :establishing, user: user, profile: profile) - end - - it 'does not redirect to please call' do - response - - expect(response).to render_template :show - expect(response).not_to redirect_to idv_please_call_url - end + let(:in_person_proofing_enforce_tmx) { true } + let!(:profile) { create(:profile, fraud_review_pending_at: 1.day.ago, user: user) } + let!(:enrollment) do + create(:in_person_enrollment, :establishing, user: user, profile: profile) end - context 'when vtr (vector of trust) does not include Enhanced Proofing (Pe)' do - before do - resolved_authn_context_result = Vot::Parser.new(vector_of_trust: 'Pb').parse + it 'does not redirect to please call' do + response - allow(controller).to receive(:resolved_authn_context_result). - and_return(resolved_authn_context_result) - end + expect(response).to render_template :show + expect(response).not_to redirect_to idv_please_call_url + end + end - it 'evaluates to In Person Proofing' do - response + context 'when vtr (vector of trust) does not include Enhanced Proofing (Pe)' do + before do + resolved_authn_context_result = Vot::Parser.new(vector_of_trust: 'Pb').parse - expect(assigns(:is_enhanced_ipp)).to be false - end + allow(controller).to receive(:resolved_authn_context_result). + and_return(resolved_authn_context_result) end - context 'when vtr (vector of trust) includes Enhanced Proofing (Pe)' do - before do - resolved_authn_context_result = Vot::Parser.new(vector_of_trust: 'Pe').parse + it 'evaluates to In Person Proofing' do + response - allow(controller).to receive(:resolved_authn_context_result). - and_return(resolved_authn_context_result) - end + expect(assigns(:is_enhanced_ipp)).to be false + end + end - it 'evaluates to Enhanced IPP' do - response + context 'when vtr (vector of trust) includes Enhanced Proofing (Pe)' do + before do + resolved_authn_context_result = Vot::Parser.new(vector_of_trust: 'Pe').parse - expect(assigns(:is_enhanced_ipp)).to be true - end + allow(controller).to receive(:resolved_authn_context_result). + and_return(resolved_authn_context_result) end - end - end - context 'with hybrid session' do - let(:in_person_proofing_enforce_tmx) { true } - it 'redirects to root' do - controller.session[:doc_capture_user_id] = user.id + it 'evaluates to Enhanced IPP' do + response - expect(response).to redirect_to(new_user_session_url) + expect(assigns(:is_enhanced_ipp)).to be true + end end end end diff --git a/spec/controllers/idv/in_person/usps_locations_controller_spec.rb b/spec/controllers/idv/in_person/usps_locations_controller_spec.rb index aed47ea128e..8d0a7228a43 100644 --- a/spec/controllers/idv/in_person/usps_locations_controller_spec.rb +++ b/spec/controllers/idv/in_person/usps_locations_controller_spec.rb @@ -4,6 +4,7 @@ let(:user) { create(:user) } let(:sp) { nil } let(:in_person_proofing_enabled) { true } + let(:empty_locations) { [] } let(:address) do UspsInPersonProofing::Applicant.new( address: '1600 Pennsylvania Ave', @@ -38,12 +39,10 @@ end describe '#index' do - let(:locale) { nil } let(:proofer) { double('Proofer') } let(:locations) do [ - UspsInPersonProofing::PostOffice.new( - address: '3118 WASHINGTON BLVD', + { address: '3118 WASHINGTON BLVD', city: 'ARLINGTON', distance: '6.02 mi', name: 'ARLINGTON', @@ -52,10 +51,8 @@ sunday_hours: 'Closed', weekday_hours: '9:00 AM - 5:00 PM', zip_code_4: '9998', - zip_code_5: '22201', - ), - UspsInPersonProofing::PostOffice.new( - address: '4005 WISCONSIN AVE NW', + zip_code_5: '22201' }, + { address: '4005 WISCONSIN AVE NW', city: 'WASHINGTON', distance: '6.59 mi', name: 'FRIENDSHIP', @@ -64,10 +61,8 @@ sunday_hours: '10:00 AM - 4:00 PM', weekday_hours: '8:00 AM - 6:00 PM', zip_code_4: '9997', - zip_code_5: '20016', - ), - UspsInPersonProofing::PostOffice.new( - address: '6900 WISCONSIN AVE STE 100', + zip_code_5: '20016' }, + { address: '6900 WISCONSIN AVE STE 100', city: 'CHEVY CHASE', distance: '8.99 mi', name: 'BETHESDA', @@ -76,13 +71,11 @@ sunday_hours: 'Closed', weekday_hours: '9:00 AM - 5:00 PM', zip_code_4: '9996', - zip_code_5: '20815', - ), + zip_code_5: '20815' }, ] end subject(:response) do - post :index, params: { locale: locale, - address: { street_address: '1600 Pennsylvania Ave', + post :index, params: { address: { street_address: '1600 Pennsylvania Ave', city: 'Washington', state: 'DC', zip_code: '20500' } } @@ -136,7 +129,7 @@ context 'no addresses found by usps' do before do allow(proofer).to receive(:request_facilities).with(address, false). - and_return([]) + and_return(empty_locations) end it 'logs analytics with error when successful response is empty' do @@ -172,32 +165,6 @@ response_status_code: nil, ) end - - context 'with non-English locale' do - let(:locale) { 'fr' } - - it 'returns content in selected locale' do - json = response.body - - expect(json).to include( - I18n.t('in_person_proofing.body.barcode.retail_hours_closed', locale: locale), - ) - I18n.locale = locale - facilities = JSON.parse(json) - - facilities.zip(locations).each do |facility, location| - expect(facility['weekday_hours']).to eq( - UspsInPersonProofing::EnrollmentHelper.localized_hours(location[:weekday_hours]), - ) - expect(facility['saturday_hours']).to eq( - UspsInPersonProofing::EnrollmentHelper.localized_hours(location[:saturday_hours]), - ) - expect(facility['sunday_hours']).to eq( - UspsInPersonProofing::EnrollmentHelper.localized_hours(location[:sunday_hours]), - ) - end - end - end end context 'with a timeout from Faraday' do diff --git a/spec/controllers/users/delete_controller_spec.rb b/spec/controllers/users/delete_controller_spec.rb index e5afa1f93d9..c5c4d113907 100644 --- a/spec/controllers/users/delete_controller_spec.rb +++ b/spec/controllers/users/delete_controller_spec.rb @@ -44,12 +44,13 @@ end it 'logs a failed submit' do - user = stub_signed_in_user - stub_analytics(user:) + stub_analytics + stub_signed_in_user - delete + expect(@analytics).to receive(:track_event). + with('Account Delete submitted', success: false) - expect(@analytics).to have_logged_event('Account Delete submitted', success: false) + delete end end @@ -81,12 +82,13 @@ end it 'logs a succesful submit' do - user = stub_signed_in_user - stub_analytics(user:) + stub_analytics + stub_signed_in_user - delete + expect(@analytics).to receive(:track_event). + with('Account Delete submitted', success: true) - expect(@analytics).to have_logged_event('Account Delete submitted', success: true) + delete end it 'does not delete identities to prevent uuid reuse' do diff --git a/spec/controllers/users/piv_cac_login_controller_spec.rb b/spec/controllers/users/piv_cac_login_controller_spec.rb index 3853a61b023..75c99da9d05 100644 --- a/spec/controllers/users/piv_cac_login_controller_spec.rb +++ b/spec/controllers/users/piv_cac_login_controller_spec.rb @@ -2,10 +2,8 @@ RSpec.describe Users::PivCacLoginController do describe 'GET new' do - let(:user) {} - before do - stub_analytics(user:) + stub_analytics end context 'without a token' do @@ -49,6 +47,7 @@ end context 'with a valid token' do + let(:user) {} let(:service_provider) { create(:service_provider) } let(:sp_session) { { ial: 1, issuer: service_provider.issuer, vtr: vtr } } let(:nonce) { SecureRandom.base64(20) } @@ -69,6 +68,7 @@ controller.session[:sp] = sp_session allow(PivCacService).to receive(:decode_token).with(token) { data } + stub_analytics(user:) end context 'without a valid user' do diff --git a/spec/controllers/vendor_outage_controller_spec.rb b/spec/controllers/vendor_outage_controller_spec.rb index 3ceb6600733..07d879161ac 100644 --- a/spec/controllers/vendor_outage_controller_spec.rb +++ b/spec/controllers/vendor_outage_controller_spec.rb @@ -21,11 +21,7 @@ before { allow(controller).to receive(:from_idv_phone?).and_return(true) } context 'gpo letter available' do - before do - stub_sign_in - allow(controller.gpo_verify_by_mail_policy).to receive(:send_letter_available?). - and_return(true) - end + before { allow(controller).to receive(:gpo_letter_available?).and_return(true) } it 'sets show_gpo_option as true' do get :show diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index f3a70efe348..2da550bb912 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -460,7 +460,9 @@ active_profile_idv_level: nil, pending_profile_idv_level: nil, proofing_components: base_proofing_components }, - 'IdV: request letter visited' => {}, + 'IdV: request letter visited' => { + letter_already_sent: false, + }, :idv_enter_password_visited => { address_verification_method: 'gpo', acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil, active_profile_idv_level: nil, pending_profile_idv_level: nil, @@ -673,7 +675,7 @@ 'IdV: doc auth image upload form submitted' => { success: true, errors: {}, error_details: nil, 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 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: {}, error_details: nil, 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' }, @@ -774,7 +776,6 @@ and_return(proofing_device_profiling) allow_any_instance_of(ApplicationController).to receive(:analytics) do |controller| fake_analytics.user = controller.analytics_user - fake_analytics.session = controller.session fake_analytics end allow(IdentityConfig.store).to receive(:idv_acuant_sdk_upgrade_a_b_testing_enabled). diff --git a/spec/features/idv/cancel_spec.rb b/spec/features/idv/cancel_spec.rb index 0eddb6e18d0..9f2592914be 100644 --- a/spec/features/idv/cancel_spec.rb +++ b/spec/features/idv/cancel_spec.rb @@ -13,11 +13,7 @@ start_idv_from_sp(sp) sign_in_and_2fa_user(user) complete_doc_auth_steps_before_agreement_step - - allow_any_instance_of(ApplicationController).to receive(:analytics) do |controller| - fake_analytics.session = controller.session - fake_analytics - end + allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) end it 'shows the user a cancellation message with the option to go back to the step' do diff --git a/spec/features/idv/steps/request_letter_step_spec.rb b/spec/features/idv/steps/request_letter_step_spec.rb index 1c144b7473e..6c32f38a1de 100644 --- a/spec/features/idv/steps/request_letter_step_spec.rb +++ b/spec/features/idv/steps/request_letter_step_spec.rb @@ -4,6 +4,19 @@ include IdvStepHelper include OidcAuthHelper + let(:minimum_wait_for_letter) { 24 } + let(:days_passed) { max_days_before_resend_disabled + 1 } + let(:max_days_before_resend_disabled) { 30 } + + before do + allow(IdentityConfig.store).to receive(:minimum_wait_before_another_usps_letter_in_hours). + and_return(minimum_wait_for_letter) + allow(IdentityConfig.store).to receive(:gpo_max_profile_age_to_send_letter_in_days). + and_return(max_days_before_resend_disabled) + allow(IdentityConfig.store).to receive(:second_mfa_reminder_account_age_in_days). + and_return(days_passed + 1) + end + it 'visits and completes the enter password step when the user chooses verify by letter', :js do start_idv_from_sp complete_idv_steps_before_gpo_step @@ -16,6 +29,142 @@ expect(page).to have_content(t('idv.messages.gpo.letter_on_the_way')) end + context 'the user has sent a letter but not verified an OTP' do + let(:user) { user_with_2fa } + + before do + # Without this, the check for GPO expiration leaves an expired + # OTP rate limiter laying around. + allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(3) + end + + it 'if not rate limited, allow user to resend letter & redirect to letter enqueued step', :js do + complete_idv_by_mail_and_sign_out + + # rate-limited because too little time has passed + sign_in_live_with_2fa(user) + confirm_rate_limited + sign_out + + # still rate-limited because too little time has passed + travel_to((minimum_wait_for_letter - 1).hours.from_now) do + sign_in_live_with_2fa(user) + confirm_rate_limited + sign_out + end + + # will be rate-limted after expiration + travel_to(days_passed.days.from_now) do + sign_in_live_with_2fa(user) + confirm_rate_limited + sign_out + # Clear MFA SMS message from the future to allow re-logging in with test helper + Telephony::Test::Message.clear_messages + end + + # can re-request after the waiting period + travel_to((minimum_wait_for_letter + 1).hours.from_now) do + sign_in_live_with_2fa(user) + click_on t('idv.messages.gpo.resend') + + # Confirm that we show the correct content on + # the GPO page for users requesting re-send + expect(page).to have_content(t('idv.gpo.request_another_letter.title')) + expect(page).to have_content( + strip_tags(t('idv.gpo.request_another_letter.instructions_html')), + ) + expect(page).to have_content(t('idv.gpo.request_another_letter.button')) + expect(page).to_not have_content(t('idv.messages.gpo.info_alert')) + + # Ensure user can go back from this page + click_doc_auth_back_link + expect(page).to have_content(t('idv.gpo.title')) + expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) + expect_user_to_be_unverified(user) + click_on t('idv.messages.gpo.resend') + + # And then actually ask for a resend + expect { click_on t('idv.gpo.request_another_letter.button') }. + to change { GpoConfirmation.count }.from(1).to(2) + expect_user_to_be_unverified(user) + expect(page).to have_content(t('idv.messages.gpo.another_letter_on_the_way')) + expect(page).to have_content(t('idv.titles.come_back_later')) + expect(page).to have_current_path(idv_letter_enqueued_path) + + # Confirm that user cannot visit other IdV pages while unverified + visit idv_agreement_path + expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) + visit idv_ssn_url + expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) + visit idv_verify_info_url + expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) + + # complete verification: end to end gpo test + sign_out + sign_in_live_with_2fa(user) + + complete_gpo_verification(user) + expect(user.identity_verified?).to be(true) + expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + end + end + + context 'logged in with PIV/CAC and no password' do + it 'does not 500' do + create(:profile, :with_pii, user: user, gpo_verification_pending_at: 1.day.ago) + create(:gpo_confirmation_code, profile: user.pending_profile) + create(:piv_cac_configuration, user: user, x509_dn_uuid: 'helloworld', name: 'My PIV Card') + + signin_with_piv(user) + fill_in t('account.index.password'), with: user.password + click_button t('forms.buttons.submit.default') + + complete_gpo_verification(user) + + expect(user.identity_verified?).to be(true) + + expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + end + end + + def complete_idv_by_mail_and_sign_out + start_idv_from_sp + complete_idv_steps_before_gpo_step(user) + click_on t('idv.buttons.mail.send') + fill_in 'Password', with: user_password + click_continue + sign_out + end + + def expect_user_to_be_unverified(user) + expect(user.events.account_verified.size).to be(0) + expect(user.profiles.count).to eq 1 + + profile = user.profiles.first + + expect(profile.active?).to eq false + expect(profile.gpo_verification_pending?).to eq true + end + + def sign_out + visit sign_out_url + end + + def confirm_rate_limited + expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) + expect(page).not_to have_link( + t('idv.messages.gpo.resend'), + ) + # does not allow the user to go to the resend page manually + visit idv_request_letter_path + + expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) + expect(page).not_to have_link( + t('idv.messages.gpo.resend'), + ) + end + end + context 'GPO verified user has reset their password and needs to re-verify with GPO again', :js do let(:user) { user_verified_with_gpo } diff --git a/spec/features/idv/steps/resend_letter_step_spec.rb b/spec/features/idv/steps/resend_letter_step_spec.rb deleted file mode 100644 index ab10aae5d6c..00000000000 --- a/spec/features/idv/steps/resend_letter_step_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'rails_helper' - -RSpec.feature 'idv resend letter step', allowed_extra_analytics: [:*] do - include IdvStepHelper - include OidcAuthHelper - - let(:minimum_wait_for_letter) { 24 } - let(:days_passed) { max_days_before_resend_disabled + 1 } - let(:max_days_before_resend_disabled) { 30 } - - before do - allow(IdentityConfig.store).to receive(:minimum_wait_before_another_usps_letter_in_hours). - and_return(minimum_wait_for_letter) - allow(IdentityConfig.store).to receive(:gpo_max_profile_age_to_send_letter_in_days). - and_return(max_days_before_resend_disabled) - allow(IdentityConfig.store).to receive(:second_mfa_reminder_account_age_in_days). - and_return(days_passed + 1) - end - - let(:user) { user_with_2fa } - - before do - # Without this, the check for GPO expiration leaves an expired - # OTP rate limiter laying around. - allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(3) - end - - it 'if not rate limited, allow user to resend letter & redirect to letter enqueued step', :js do - complete_idv_by_mail_and_sign_out - - # rate-limited because too little time has passed - sign_in_live_with_2fa(user) - confirm_rate_limited - sign_out - - # still rate-limited because too little time has passed - travel_to((minimum_wait_for_letter - 1).hours.from_now) do - sign_in_live_with_2fa(user) - confirm_rate_limited - sign_out - end - - # will be rate-limted after expiration - travel_to(days_passed.days.from_now) do - sign_in_live_with_2fa(user) - confirm_rate_limited - sign_out - # Clear MFA SMS message from the future to allow re-logging in with test helper - Telephony::Test::Message.clear_messages - end - - # can re-request after the waiting period - travel_to((minimum_wait_for_letter + 1).hours.from_now) do - sign_in_live_with_2fa(user) - click_on t('idv.messages.gpo.resend') - - # Confirm that we show the correct content on - # the GPO page for users requesting re-send - expect(page).to have_content(t('idv.gpo.request_another_letter.title')) - expect(page).to have_content( - strip_tags(t('idv.gpo.request_another_letter.instructions_html')), - ) - expect(page).to have_content(t('idv.gpo.request_another_letter.button')) - expect(page).to_not have_content(t('idv.messages.gpo.info_alert')) - - # Ensure user can go back from this page - click_doc_auth_back_link - expect(page).to have_content(t('idv.gpo.title')) - expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) - expect_user_to_be_unverified(user) - click_on t('idv.messages.gpo.resend') - - # And then actually ask for a resend - expect { click_on t('idv.gpo.request_another_letter.button') }. - to change { GpoConfirmation.count }.from(1).to(2) - expect_user_to_be_unverified(user) - expect(page).to have_content(t('idv.messages.gpo.another_letter_on_the_way')) - expect(page).to have_content(t('idv.titles.come_back_later')) - expect(page).to have_current_path(idv_letter_enqueued_path) - - # Confirm that user cannot visit other IdV pages while unverified - visit idv_agreement_path - expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) - visit idv_ssn_url - expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) - visit idv_verify_info_url - expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) - - # complete verification: end to end gpo test - sign_out - sign_in_live_with_2fa(user) - - complete_gpo_verification(user) - expect(user.identity_verified?).to be(true) - expect(page).to_not have_content(t('account.index.verification.reactivate_button')) - end - end - - context 'logged in with PIV/CAC and no password' do - it 'does not 500' do - create(:profile, :with_pii, user: user, gpo_verification_pending_at: 1.day.ago) - create(:gpo_confirmation_code, profile: user.pending_profile) - create(:piv_cac_configuration, user: user, x509_dn_uuid: 'helloworld', name: 'My PIV Card') - - signin_with_piv(user) - fill_in t('account.index.password'), with: user.password - click_button t('forms.buttons.submit.default') - - complete_gpo_verification(user) - - expect(user.identity_verified?).to be(true) - - expect(page).to_not have_content(t('account.index.verification.reactivate_button')) - end - end - - def complete_idv_by_mail_and_sign_out - start_idv_from_sp - complete_idv_steps_before_gpo_step(user) - click_on t('idv.buttons.mail.send') - fill_in 'Password', with: user_password - click_continue - sign_out - end - - def expect_user_to_be_unverified(user) - expect(user.events.account_verified.size).to be(0) - expect(user.profiles.count).to eq 1 - - profile = user.profiles.first - - expect(profile.active?).to eq false - expect(profile.gpo_verification_pending?).to eq true - end - - def sign_out - visit sign_out_url - end - - def confirm_rate_limited - expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) - expect(page).not_to have_link( - t('idv.messages.gpo.resend'), - ) - # does not allow the user to go to the resend page manually - visit idv_request_letter_path - - expect(page).to have_current_path(idv_verify_by_mail_enter_code_path) - expect(page).not_to have_link( - t('idv.messages.gpo.resend'), - ) - end -end diff --git a/spec/forms/gpo_verify_form_spec.rb b/spec/forms/gpo_verify_form_spec.rb index 5960098c39b..774213f7805 100644 --- a/spec/forms/gpo_verify_form_spec.rb +++ b/spec/forms/gpo_verify_form_spec.rb @@ -2,12 +2,7 @@ RSpec.describe GpoVerifyForm, allowed_extra_analytics: [:*] do subject(:form) do - GpoVerifyForm.new( - user: user, - pii: applicant, - resolved_authn_context_result: Vot::Parser::Result.no_sp_result, - otp: entered_otp, - ) + GpoVerifyForm.new(user: user, pii: applicant, otp: entered_otp) end let(:user) { create(:user, :fully_registered) } diff --git a/spec/forms/recaptcha_enterprise_form_spec.rb b/spec/forms/recaptcha_enterprise_form_spec.rb index 1d9858d8b2e..2e32736c455 100644 --- a/spec/forms/recaptcha_enterprise_form_spec.rb +++ b/spec/forms/recaptcha_enterprise_form_spec.rb @@ -270,46 +270,6 @@ form_class: 'RecaptchaEnterpriseForm', ) end - - context 'with low confidence score as one of the reasons for failure' do - before do - stub_recaptcha_response( - body: { - tokenProperties: { valid: true, action: }, - riskAnalysis: { score:, reasons: ['LOW_CONFIDENCE_SCORE'] }, - event: {}, - name:, - }, - action:, - token:, - ) - end - - it 'is successful with assessment id' do - response, assessment_id = result - - expect(response.to_h).to eq(success: true) - expect(assessment_id).to eq(name) - end - - it 'logs analytics of the body' do - result - - expect(analytics).to have_logged_event( - 'reCAPTCHA verify result received', - recaptcha_result: { - success: true, - score:, - reasons: ['LOW_CONFIDENCE_SCORE'], - errors: [], - assessment_id: name, - }, - evaluated_as_valid: true, - score_threshold: score_threshold, - form_class: 'RecaptchaEnterpriseForm', - ) - end - end end context 'with successful score from validation service' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a84e0a83bad..fa8143236da 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -567,7 +567,7 @@ end describe '#accepted_rules_of_use_still_valid?' do - let(:rules_of_use_horizon_years) { 5 } + let(:rules_of_use_horizon_years) { 6 } let(:rules_of_use_updated_at) { 1.day.ago } let(:accepted_terms_at) { nil } let(:user) { create(:user, :fully_registered, accepted_terms_at: accepted_terms_at) } @@ -601,9 +601,9 @@ end context 'with a user who accepted the rules of use more than 6 years ago' do - let(:rules_of_use_horizon_years) { 5 } - let(:rules_of_use_updated_at) { 6.years.ago } - let(:accepted_terms_at) { 5.years.ago - 1.day } + let(:rules_of_use_horizon_years) { 6 } + let(:rules_of_use_updated_at) { 7.years.ago } + let(:accepted_terms_at) { 6.years.ago - 1.day } it 'should return a falsey value' do expect(user.accepted_rules_of_use_still_valid?).to be_falsey diff --git a/spec/policies/idv/gpo_verify_by_mail_policy_spec.rb b/spec/policies/idv/gpo_verify_by_mail_policy_spec.rb index 02c2062e361..179c321b6a8 100644 --- a/spec/policies/idv/gpo_verify_by_mail_policy_spec.rb +++ b/spec/policies/idv/gpo_verify_by_mail_policy_spec.rb @@ -3,14 +3,8 @@ require 'rails_helper' RSpec.describe Idv::GpoVerifyByMailPolicy do - let(:subject) { described_class.new(user, resolved_authn_context_result) } + let(:subject) { described_class.new(user) } let(:user) { create(:user) } - let(:two_pieces_of_fair_evidence) { false } - let(:resolved_authn_context_result) do - Vot::Parser::Result.no_sp_result.with( - two_pieces_of_fair_evidence?: two_pieces_of_fair_evidence, - ) - end describe '#resend_letter_available?' do context 'when the feature flag is off' do @@ -70,10 +64,6 @@ and_return true end - it 'returns true when the user is not rate-limited' do - expect(subject.send_letter_available?).to eq true - end - it 'returns false when the user is rate-limited' do enqueue_gpo_letter_for(user, at_time: 4.days.ago) enqueue_gpo_letter_for(user, at_time: 3.days.ago) @@ -86,26 +76,6 @@ create(:profile, :verify_by_mail_pending, user: user, created_at: 90.days.ago) expect(subject.send_letter_available?).to eq true end - - context 'the 2 pieces of fair evidence requirement is present' do - let(:two_pieces_of_fair_evidence) { true } - - it 'returns false when the feature flag is enabled' do - allow(IdentityConfig.store).to receive( - :no_verify_by_mail_for_biometric_comparison_enabled, - ).and_return(true) - - expect(subject.send_letter_available?).to eq(false) - end - - it 'returns true when the feature flag is disabled' do - allow(IdentityConfig.store).to receive( - :no_verify_by_mail_for_biometric_comparison_enabled, - ).and_return(false) - - expect(subject.send_letter_available?).to eq(true) - end - end end end diff --git a/spec/presenters/idv/by_mail/request_letter_presenter_spec.rb b/spec/presenters/idv/by_mail/request_letter_presenter_spec.rb new file mode 100644 index 00000000000..629b02c76d0 --- /dev/null +++ b/spec/presenters/idv/by_mail/request_letter_presenter_spec.rb @@ -0,0 +1,91 @@ +require 'rails_helper' + +RSpec.describe Idv::ByMail::RequestLetterPresenter do + include Rails.application.routes.url_helpers + + let(:user) { create(:user) } + + subject(:decorator) do + described_class.new(user, {}) + end + + describe '#title' do + context 'a letter has not been sent' do + it 'provides text to send' do + expect(subject.title).to eq( + I18n.t('idv.titles.mail.verify'), + ) + end + end + + context 'a letter has been sent' do + before do + allow(user).to receive(:gpo_verification_pending_profile).and_return(true) + end + it 'provides text to resend' do + create_letter_send_event + + expect(subject.title).to eq( + I18n.t('idv.gpo.request_another_letter.title'), + ) + end + end + + context 'the user has verified with GPO before, but is re-proofing' do + let(:user) { user_verified_with_gpo } + it 'provides text to send' do + create_letter_send_event + expect(subject.title).to eq( + I18n.t('idv.titles.mail.verify'), + ) + end + end + end + + describe '#button' do + let(:user) { create(:user) } + context 'a letter has not been sent' do + it 'provides text to send' do + expect(subject.button).to eq( + I18n.t('idv.buttons.mail.send'), + ) + end + end + + context 'a letter has been sent' do + before do + allow(user).to receive(:gpo_verification_pending_profile).and_return(true) + end + it 'provides text to resend' do + create_letter_send_event + expect(subject.button).to eq( + I18n.t('idv.gpo.request_another_letter.button'), + ) + end + end + end + + describe '#fallback_back_path' do + context 'when the user has a pending profile' do + it 'returns the verify account path' do + create(:profile, user: user, gpo_verification_pending_at: 1.day.ago) + expect(subject.fallback_back_path).to eq(idv_verify_by_mail_enter_code_path) + end + end + + context 'when the user does not have a pending profile' do + it 'returns the idv phone path' do + expect(subject.fallback_back_path).to eq(idv_phone_path) + end + end + end + + def create_letter_send_event + device = create(:device, user: user) + create(:event, user: user, device: device, event_type: :gpo_mail_sent) + end + + def user_verified_with_gpo + create(:user, :proofed_with_gpo) + end +end diff --git a/spec/requests/openid_connect_userinfo_spec.rb b/spec/requests/openid_connect_userinfo_spec.rb index b09f4e61294..8d28b4bbbd3 100644 --- a/spec/requests/openid_connect_userinfo_spec.rb +++ b/spec/requests/openid_connect_userinfo_spec.rb @@ -16,19 +16,5 @@ headers: { 'HTTP_AUTHORIZATION' => authorization_header } expect(response.headers['Set-Cookie']).to_not include(APPLICATION_SESSION_COOKIE_KEY) end - - it 'returns error with blank Bearer Token' do - identity = create( - :service_provider_identity, - rails_session_id: SecureRandom.hex, - access_token: nil, - user: create(:user), - ) - authorization_header = 'Bearer' - OutOfBandSessionAccessor.new(identity.rails_session_id).put_empty_user_session(50) - get api_openid_connect_userinfo_path, - headers: { 'HTTP_AUTHORIZATION' => authorization_header } - expect(response).to be_unauthorized - end end end diff --git a/spec/scripts/notify-slack_spec.rb b/spec/scripts/notify-slack_spec.rb deleted file mode 100644 index 6ddf1903e4a..00000000000 --- a/spec/scripts/notify-slack_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'spec_helper' -require 'rack/utils' -load File.expand_path('../../scripts/notify-slack', __dir__) - -RSpec.describe NotifySlack do - subject(:notifier) { described_class.new } - - let(:webhook) { 'https://slack.example.com/abcdef/ghijkl' } - let(:channel) { '#fun-channel' } - let(:username) { 'notifier-bot' } - let(:text) { 'my message' } - let(:icon) { ':red_circle:' } - - describe '#run' do - let(:argv) do - [ - '--webhook', - webhook, - '--channel', - channel, - '--username', - username, - '--text', - text, - '--icon', icon - ] - end - let(:stdin) { StringIO.new } - let(:stdout) { StringIO.new } - - subject(:run) do - notifier.run(argv:, stdin:, stdout:) - end - - before do - allow(notifier).to receive(:exit) - end - - context 'missing required argument' do - before do - argv.delete('--webhook') - argv.delete(webhook) - end - - it 'prints help and exits uncleanly' do - expect(notifier).to receive(:exit).with(1) - - run - - expect(stdout.string).to include('Usage') - end - end - - it 'notifies' do - post_request = stub_request(:post, webhook) - - run - - expect(post_request).to have_been_made - end - - context 'network error' do - before do - stub_request(:post, webhook).to_return(status: 500) - end - - it 'prints an error and exits cleanly' do - expect(notifier).to_not receive(:exit) - - run - - expect(stdout.string).to include('ERROR: 500') - end - - context 'with --raise' do - before { argv << '--raise' } - - it 'raises an error' do - expect { run }.to raise_error(Net::HTTPExceptions) - end - end - end - end - - describe '#notify' do - subject(:notify) do - notifier.notify(webhook:, channel:, username:, text:, icon:) - end - - it 'POSTs JSON inside of form encoding to the webhook' do - post_request = stub_request(:post, webhook).with( - headers: { - content_type: 'application/x-www-form-urlencoded', - }, - ) do |req| - form = Rack::Utils.parse_query(req.body) - expect(JSON.parse(form['payload'], symbolize_names: true)).to eq( - channel:, - username:, - text:, - icon_emoji: icon, - ) - end - - notify - - expect(post_request).to have_been_made - end - end - - describe '#format_icon' do - it 'adds colons around icon names if missing' do - expect(notifier.format_icon('joy')).to eq(':joy:') - end - - it 'leaves colons around icon names if present' do - expect(notifier.format_icon(':sob:')).to eq(':sob:') - end - end -end diff --git a/spec/services/gpo_confirmation_uploader_spec.rb b/spec/services/gpo_confirmation_uploader_spec.rb index 8b95c27fb22..4612d1882fc 100644 --- a/spec/services/gpo_confirmation_uploader_spec.rb +++ b/spec/services/gpo_confirmation_uploader_spec.rb @@ -71,28 +71,6 @@ subject end - - context 'when an SSH error occurs' do - it 'retries the upload' do - expect(Net::SFTP).to receive(:start).twice.with(*sftp_options).and_yield(sftp_connection) - expect(sftp_connection).to receive(:upload!).once.and_raise(Net::SSH::ConnectionTimeout) - expect(sftp_connection).to receive(:upload!).once - - subject - end - - it 'raises after 5 unsuccessful retries' do - expect(Net::SFTP).to receive(:start). - exactly(5).times. - with(*sftp_options). - and_yield(sftp_connection) - expect(sftp_connection).to receive(:upload!). - exactly(5).times. - and_raise(Net::SSH::ConnectionTimeout) - - expect { subject }.to raise_error(Net::SSH::ConnectionTimeout) - end - end end describe '#run' do diff --git a/spec/services/gpo_reminder_sender_spec.rb b/spec/services/gpo_reminder_sender_spec.rb index a8ce6708be2..797937626b7 100644 --- a/spec/services/gpo_reminder_sender_spec.rb +++ b/spec/services/gpo_reminder_sender_spec.rb @@ -175,9 +175,6 @@ def set_reminder_sent_at(to_time) GpoVerifyForm.new( user: user, pii: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE, - resolved_authn_context_result: Vot::Parser::Result.no_sp_result.with( - enhanced_ipp?: is_enhanced_ipp, - ), otp: otp, ).submit(is_enhanced_ipp) end diff --git a/spec/services/idv/analytics_events_enhancer_spec.rb b/spec/services/idv/analytics_events_enhancer_spec.rb index a553f623b4e..874e23f4069 100644 --- a/spec/services/idv/analytics_events_enhancer_spec.rb +++ b/spec/services/idv/analytics_events_enhancer_spec.rb @@ -3,15 +3,7 @@ RSpec.describe Idv::AnalyticsEventsEnhancer do let(:user) { build(:user) } let(:sp) { nil } - let(:user_session) { nil } - let(:session) do - if user_session.present? - { - 'warden.user.user.session' => user_session, - } - end - end - + let(:session) { nil } let(:analytics_class) do Class.new(FakeAnalytics) do include AnalyticsEvents @@ -71,7 +63,11 @@ def track_event(_event, **kwargs) end describe 'proofing_components' do - let(:user_session) { {} } + let(:proofing_components) { nil } + + before do + user.proofing_component = proofing_components + end context 'without proofing component' do it 'calls analytics method with original attributes' do @@ -83,26 +79,17 @@ def track_event(_event, **kwargs) end end - context 'with proofing components' do - before do - # Set up the user_session so it looks like the user's been through doc auth - idv_session = Idv::Session.new( - user_session:, - current_user: user, - service_provider: sp, - ) - idv_session.pii_from_doc = Idp::Constants::MOCK_IDV_APPLICANT + context 'with proofing component' do + let(:proofing_components) do + ProofingComponent.new(source_check: Idp::Constants::Vendors::AAMVA) end it 'calls analytics method with original attributes and proofing_components' do analytics.idv_test_method(extra: true) - expect(analytics.called_kwargs).to eql( + expect(analytics.called_kwargs).to match( extra: true, - proofing_components: { - document_check: 'mock', - document_type: 'state_id', - }, + proofing_components: kind_of(Idv::ProofingComponentsLogging), ) end end diff --git a/spec/services/idv/proofing_components_logging_spec.rb b/spec/services/idv/proofing_components_logging_spec.rb new file mode 100644 index 00000000000..b270a722c8d --- /dev/null +++ b/spec/services/idv/proofing_components_logging_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe Idv::ProofingComponentsLogging do + describe '#as_json' do + it 'returns hash with nil values omitted' do + proofing_components = ProofingComponent.new(document_check: Idp::Constants::Vendors::AAMVA) + logging = described_class.new(proofing_components) + + expect(logging.as_json).to eq('document_check' => Idp::Constants::Vendors::AAMVA) + end + end +end diff --git a/spec/services/idv/proofing_components_spec.rb b/spec/services/idv/proofing_components_spec.rb deleted file mode 100644 index d6bd1b81b73..00000000000 --- a/spec/services/idv/proofing_components_spec.rb +++ /dev/null @@ -1,243 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::ProofingComponents do - let(:user) { create(:user) } - - let(:user_session) { {} } - - let(:idv_session) do - Idv::Session.new( - current_user: user, - user_session:, - service_provider: nil, - ).tap do |idv_session| - idv_session.pii_from_doc = pii_from_doc - end - end - - let(:pii_from_doc) { nil } - - subject do - described_class.new( - user:, - idv_session:, - ) - end - - describe '#to_h' do - let(:pii_from_doc) { Idp::Constants::MOCK_IDV_APPLICANT } - - before do - allow(IdentityConfig.store).to receive(:doc_auth_vendor).and_return('test_vendor') - idv_session.mark_verify_info_step_complete! - idv_session.address_verification_mechanism = 'gpo' - allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). - and_return(true) - idv_session.threatmetrix_review_status = 'pass' - end - - it 'returns expected result' do - expect(subject.to_h).to eql( - { - document_check: 'test_vendor', - document_type: 'state_id', - source_check: 'aamva', - resolution_check: 'lexis_nexis', - address_check: 'gpo_letter', - threatmetrix: true, - threatmetrix_review_status: 'pass', - }, - ) - end - end - - describe '#document_check' do - it 'returns nil by default' do - expect(subject.document_check).to be_nil - end - - context 'in-person proofing' do - context 'establishing' do - let!(:enrollment) { create(:in_person_enrollment, :establishing, user:) } - it 'returns USPS' do - expect(subject.document_check).to eql(Idp::Constants::Vendors::USPS) - end - end - - context 'pending' do - let!(:enrollment) { create(:in_person_enrollment, :pending, user:) } - it 'returns USPS' do - expect(subject.document_check).to eql(Idp::Constants::Vendors::USPS) - end - end - end - - context 'doc auth' do - before do - allow(IdentityConfig.store).to receive(:doc_auth_vendor).and_return('test_vendor') - end - context 'before doc auth complete' do - it 'returns nil' do - expect(subject.document_check).to be_nil - end - end - context 'after doc auth completed successfully' do - let(:pii_from_doc) { Idp::Constants::MOCK_IDV_APPLICANT } - it 'returns doc auth vendor' do - expect(subject.document_check).to eql('test_vendor') - end - end - end - end - - describe '#document_type' do - context 'in-person proofing' do - context 'establishing' do - let!(:enrollment) { create(:in_person_enrollment, :establishing, user:) } - it 'returns nil' do - expect(subject.document_type).to be_nil - end - end - - context 'pending' do - let!(:enrollment) { create(:in_person_enrollment, :pending, user:) } - it 'returns nil' do - expect(subject.document_type).to be_nil - end - end - end - - context 'doc auth' do - context 'before doc auth complete' do - it 'returns nil' do - expect(subject.document_type).to be_nil - end - end - context 'after doc auth completed successfully' do - let(:pii_from_doc) { Idp::Constants::MOCK_IDV_APPLICANT } - it 'returns doc auth vendor' do - expect(subject.document_type).to eql('state_id') - end - end - end - end - - describe '#source_check' do - it 'returns nil by default' do - expect(subject.source_check).to be_nil - end - - context 'after verification' do - before do - idv_session.mark_verify_info_step_complete! - end - - it 'returns aamva' do - expect(subject.source_check).to eql(Idp::Constants::Vendors::AAMVA) - end - end - end - - describe '#resolution_check' do - it 'returns nil by default' do - expect(subject.resolution_check).to be_nil - end - - context 'after verification' do - before do - idv_session.mark_verify_info_step_complete! - end - - it 'returns LexisNexis' do - expect(subject.resolution_check).to eql(Idp::Constants::Vendors::LEXIS_NEXIS) - end - end - end - - describe '#address_check' do - it 'returns nil by default' do - expect(subject.address_check).to be_nil - end - - context 'in GPO flow' do - before do - idv_session.address_verification_mechanism = 'gpo' - end - - it 'returns gpo_letter' do - expect(subject.address_check).to eql('gpo_letter') - end - end - - context 'using phone verification' do - before do - idv_session.mark_phone_step_started! - end - - it 'returns lexis_nexis_address' do - expect(subject.address_check).to eql('lexis_nexis_address') - end - end - end - - describe '#threatmetrix' do - context 'device profiling collecting enabled' do - before do - allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). - and_return(true) - end - - context 'threatmetrix_review_status present' do - before do - idv_session.threatmetrix_review_status = 'pass' - end - it 'returns true' do - expect(subject.threatmetrix).to be_truthy - end - end - context 'threatmetrix_review_status not present' do - it 'returns nil' do - expect(subject.threatmetrix).to be_nil - end - end - end - - context 'device profiling collecting disabled' do - before do - allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). - and_return(false) - end - - context 'threatmetrix_review_status present' do - before do - idv_session.threatmetrix_review_status = 'pass' - end - it 'returns false' do - expect(subject.threatmetrix).to eql(false) - end - end - - context 'threatmetrix_review_status not present' do - it 'returns nil' do - expect(subject.threatmetrix).to be_nil - end - end - end - end - - describe '#threatmetrix_review_status' do - context 'threatmetrix_review_status present in idv_session' do - before do - idv_session.threatmetrix_review_status = 'pass' - end - it 'returns value' do - expect(subject.threatmetrix_review_status).to eql('pass') - end - end - context 'threatmetrix_review_status not present in idv_session' do - it 'returns nil' do - expect(subject.threatmetrix_review_status).to be_nil - end - end - end -end diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb index 03575d66993..91c905eaf1d 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -23,7 +23,6 @@ end let(:proofer) { UspsInPersonProofing::Mock::Proofer.new } let(:is_enhanced_ipp) { false } - let(:usps_ipp_sponsor_id) { '2718281828' } before(:each) do stub_request_token @@ -39,7 +38,6 @@ allow(subject).to receive(:analytics).and_return(subject_analytics) allow(IdentityConfig.store).to receive(:usps_ipp_transliteration_enabled). and_return(usps_ipp_transliteration_enabled) - allow(IdentityConfig.store).to receive(:usps_ipp_sponsor_id).and_return(usps_ipp_sponsor_id) end describe '#schedule_in_person_enrollment' do @@ -181,14 +179,10 @@ end end - it <<~STR.squish do - sets enrollment status to pending, sponsor_id to usps_ipp_sponsor_id, - and sets established at date and unique id - STR + it 'sets enrollment status to pending and sets established at date and unique id' do subject.schedule_in_person_enrollment(user:, pii:, is_enhanced_ipp:) expect(user.in_person_enrollments.first.status).to eq(InPersonEnrollment::STATUS_PENDING) - expect(user.in_person_enrollments.first.sponsor_id).to eq(usps_ipp_sponsor_id) expect(user.in_person_enrollments.first.enrollment_established_at).to_not be_nil expect(user.in_person_enrollments.first.unique_id).to_not be_nil end @@ -337,26 +331,10 @@ allow(proofer).to receive(:request_enroll).and_call_original end context 'when the user is going through enhanced ipp' do - let!(:enrollment) do - create( - :in_person_enrollment, - user: user, - service_provider: service_provider, - status: :establishing, - profile: nil, - ) - end - it 'creates an enhanced ipp enrollment' do expect(proofer).to receive(:request_enroll).with(applicant, is_enhanced_ipp) subject.create_usps_enrollment(enrollment, pii, is_enhanced_ipp) end - - it 'saves sponsor_id on the enrollment to the usps_eipp_sponsor_id' do - subject.schedule_in_person_enrollment(user:, pii:, is_enhanced_ipp:) - - expect(user.in_person_enrollments.first.sponsor_id).to eq(usps_eipp_sponsor_id) - end end end diff --git a/spec/support/analytics_helper.rb b/spec/support/analytics_helper.rb index 1c2ae7fe23a..0f7e125a0c3 100644 --- a/spec/support/analytics_helper.rb +++ b/spec/support/analytics_helper.rb @@ -3,8 +3,8 @@ def stub_analytics(user: nil) analytics = FakeAnalytics.new if user - allow(controller).to receive(:analytics).and_wrap_original do |original| - expect(original.call.user).to eq(user) + allow(controller).to receive(:analytics) do + expect(controller.analytics_user).to eq(user) analytics end else diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index 7e8b1f15d10..d8c33b1c171 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -19,9 +19,6 @@ def stub_sign_in(user = build(:user, password: VALID_PASSWORD)) allow(controller).to receive(:user_session).and_return({}.with_indifferent_access) controller.auth_methods_session.authenticate!(TwoFactorAuthenticatable::AuthMethod::SMS) allow(controller).to receive(:current_user).and_return(user) - allow(controller).to receive(:sign_out) do - allow(controller).to receive(:current_user).and_return(nil) - end allow(controller).to receive(:confirm_two_factor_authenticated).and_return(true) allow(controller).to receive(:user_fully_authenticated?).and_return(true) allow(controller).to receive(:remember_device_expired_for_sp?).and_return(false) diff --git a/spec/support/fake_analytics.rb b/spec/support/fake_analytics.rb index a8dbe00ca24..e456e629275 100644 --- a/spec/support/fake_analytics.rb +++ b/spec/support/fake_analytics.rb @@ -150,7 +150,6 @@ def option_param_names(instance_method) attr_reader :events attr_accessor :user - attr_accessor :session def initialize(user: AnonymousUser.new, sp: nil, session: nil) @events = Hash.new @@ -160,7 +159,7 @@ def initialize(user: AnonymousUser.new, sp: nil, session: nil) end def track_event(event, attributes = {}) - if attributes[:proofing_components].instance_of?(Idv::ProofingComponents) + if attributes[:proofing_components].instance_of?(Idv::ProofingComponentsLogging) attributes[:proofing_components] = attributes[:proofing_components].as_json.symbolize_keys end events[event] ||= [] diff --git a/spec/views/accounts/show.html.erb_spec.rb b/spec/views/accounts/show.html.erb_spec.rb index 7e0cf6fbd3e..0734dc6c0f5 100644 --- a/spec/views/accounts/show.html.erb_spec.rb +++ b/spec/views/accounts/show.html.erb_spec.rb @@ -5,7 +5,6 @@ before do allow(view).to receive(:current_user).and_return(user) - allow(view).to receive(:user_session).and_return({}) assign( :presenter, AccountShowPresenter.new( @@ -90,8 +89,6 @@ end it 'does not render phone' do - render - expect(view).to_not render_template(partial: '_phone') end end @@ -119,8 +116,6 @@ let(:user) { create(:user, :fully_registered, :with_authentication_app) } it 'does not render piv/cac' do - render - expect(view).to_not render_template(partial: '_piv_cac') end end @@ -128,6 +123,10 @@ context 'user has a piv/cac' do let(:user) { create(:user, :fully_registered, :with_piv_or_cac) } + before do + allow(view).to receive(:user_session).and_return({}) + end + it 'renders the piv/cac section' do render diff --git a/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb b/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb index a855c021384..7ec1c4d16f8 100644 --- a/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb +++ b/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb @@ -21,7 +21,6 @@ @gpo_verify_form = GpoVerifyForm.new( user: user, pii: pii, - resolved_authn_context_result: Vot::Parser::Result.no_sp_result, otp: '1234', ) diff --git a/spec/views/idv/by_mail/request_letter/index.html.erb_spec.rb b/spec/views/idv/by_mail/request_letter/index.html.erb_spec.rb index b01459d71a9..6c581ad775c 100644 --- a/spec/views/idv/by_mail/request_letter/index.html.erb_spec.rb +++ b/spec/views/idv/by_mail/request_letter/index.html.erb_spec.rb @@ -1,10 +1,14 @@ require 'rails_helper' RSpec.describe 'idv/by_mail/request_letter/index.html.erb' do - let(:user) { build(:user) } + let(:resend_requested) { false } + let(:user_needs_address_otp_verification) { false } let(:go_back_path) { nil } let(:step_indicator_steps) { Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS } - let(:idv_by_mail_only) { false } + let(:presenter) do + user = build_stubbed(:user, :fully_registered) + Idv::ByMail::RequestLetterPresenter.new(user, {}) + end let(:address1) { 'applicant address 1' } let(:address2) { nil } @@ -13,11 +17,14 @@ let(:zipcode) { 'applicant zipcode' } before do - allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:go_back_path).and_return(go_back_path) allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps) - allow(FeatureManagement).to receive(:idv_by_mail_only?).and_return(idv_by_mail_only) + allow(presenter).to receive(:resend_requested?).and_return(resend_requested) + allow(presenter).to receive(:user_needs_address_otp_verification?). + and_return(user_needs_address_otp_verification) + + @presenter = presenter @applicant = { address1: 'applicant address 1', city: 'applicant city', @@ -60,11 +67,44 @@ end end - context 'idv_by_mail_only is enabled' do - let(:idv_by_mail_only) { true } + context 'letter already sent' do + let(:resend_requested) { true } + + it 'has the right title' do + expect(rendered).to have_css('h1', text: t('idv.gpo.request_another_letter.title')) + end + + it 'has the right body' do + expect(rendered).to have_text( + strip_tags(t('idv.gpo.request_another_letter.instructions_html')), + ) + end + + it 'includes link to help' do + expect(rendered).to have_link( + t('idv.gpo.request_another_letter.learn_more_link'), + href: help_center_redirect_url( + category: 'verify-your-identity', + article: 'verify-your-address-by-mail', + flow: :idv, + step: :gpo_send_letter, + ), + ) + end + + it 'does not include troubleshooting options' do + expect(rendered).not_to have_css('.troubleshooting-options') + end + end + + context 'user needs address otp verification' do + let(:user_needs_address_otp_verification) { true } - it 'returns a cancel link' do - expect(rendered).to have_link(t('links.cancel'), href: idv_cancel_path(step: 'gpo')) + it 'renders fallback link to return to verify path' do + expect(rendered).to have_link( + '‹ ' + t('forms.buttons.back'), + href: idv_verify_by_mail_enter_code_path, + ) end end end