diff --git a/app/controllers/idv/gpo_verify_controller.rb b/app/controllers/idv/gpo_verify_controller.rb index 2da6832d51b..6501f53b1fe 100644 --- a/app/controllers/idv/gpo_verify_controller.rb +++ b/app/controllers/idv/gpo_verify_controller.rb @@ -2,6 +2,7 @@ module Idv class GpoVerifyController < ApplicationController include IdvSession include StepIndicatorConcern + include ThreatmetrixReviewConcern before_action :confirm_two_factor_authenticated before_action :confirm_verification_needed @@ -43,14 +44,19 @@ def create redirect_to idv_in_person_ready_to_verify_url else event, disavowal_token = create_user_event_with_disavowal(:account_verified) - UserAlerts::AlertUserAboutAccountVerified.call( - user: current_user, - date_time: event.created_at, - sp_name: decorated_session.sp_name, - disavowal_token: disavowal_token, - ) - flash[:success] = t('account.index.verification.success') - redirect_to sign_up_completed_url + + if result.extra[:threatmetrix_check_failed] && threatmetrix_enabled? + redirect_to_threatmetrix_review + else + UserAlerts::AlertUserAboutAccountVerified.call( + user: current_user, + date_time: event.created_at, + sp_name: decorated_session.sp_name, + disavowal_token: disavowal_token, + ) + flash[:success] = t('account.index.verification.success') + redirect_to sign_up_completed_url + end end else flash[:error] = @gpo_verify_form.errors.first.message @@ -93,5 +99,9 @@ def confirm_verification_needed return if current_user.decorate.pending_profile_requires_verification? redirect_to account_url end + + def threatmetrix_enabled? + IdentityConfig.store.proofing_device_profiling_decisioning_enabled + end end end diff --git a/app/forms/gpo_verify_form.rb b/app/forms/gpo_verify_form.rb index 44a48b0569e..70174a73e47 100644 --- a/app/forms/gpo_verify_form.rb +++ b/app/forms/gpo_verify_form.rb @@ -21,6 +21,8 @@ def submit if pending_in_person_enrollment? UspsInPersonProofing::EnrollmentHelper.schedule_in_person_enrollment(user, pii) pending_profile&.deactivate(:in_person_verification_pending) + elsif threatmetrix_check_failed? + pending_profile&.deactivate(:threatmetrix_review_pending) else activate_profile end @@ -34,6 +36,7 @@ def submit enqueued_at: gpo_confirmation_code&.code_sent_at, pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], pending_in_person_enrollment: pending_in_person_enrollment?, + threatmetrix_check_failed: threatmetrix_check_failed?, }, ) end @@ -77,6 +80,11 @@ def pending_in_person_enrollment? pending_profile&.proofing_components&.[]('document_check') == Idp::Constants::Vendors::USPS end + def threatmetrix_check_failed? + status = pending_profile&.proofing_components&.[]('threatmetrix_review_status') + !status.nil? && status != 'pass' + end + def activate_profile user.pending_profile&.activate end diff --git a/spec/controllers/idv/gpo_verify_controller_spec.rb b/spec/controllers/idv/gpo_verify_controller_spec.rb index 14a9e3b1390..9708a97b116 100644 --- a/spec/controllers/idv/gpo_verify_controller_spec.rb +++ b/spec/controllers/idv/gpo_verify_controller_spec.rb @@ -15,6 +15,7 @@ end let(:proofing_components) { nil } let(:user) { create(:user) } + let(:threatmetrix_enabled) { false } before do stub_analytics @@ -28,6 +29,13 @@ ) allow(decorated_user).to receive(:pending_profile_requires_verification?). and_return(has_pending_profile) + + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled). + and_return(threatmetrix_enabled) + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_required_to_verify). + and_return(threatmetrix_enabled) + allow(IdentityConfig.store).to receive(:proofing_device_profiling_decisioning_enabled). + and_return(threatmetrix_enabled) end describe '#index' do @@ -109,6 +117,7 @@ success: true, errors: {}, pending_in_person_enrollment: false, + threatmetrix_check_failed: false, enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], ) @@ -146,6 +155,7 @@ success: true, errors: {}, pending_in_person_enrollment: true, + threatmetrix_check_failed: false, enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], ) @@ -163,6 +173,101 @@ action end end + + context 'threatmetrix disabled' do + context 'with threatmetrix status of "reject"' do + let(:proofing_components) do + ProofingComponent.create( + user: user, threatmetrix: true, + threatmetrix_review_status: 'reject' + ) + end + + it 'redirects to the sign_up/completions page' do + expect(@analytics).to receive(:track_event).with( + 'IdV: GPO verification submitted', + success: true, + errors: {}, + pending_in_person_enrollment: false, + threatmetrix_check_failed: true, + enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, + pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], + ) + expect(@irs_attempts_api_tracker).to receive(:idv_gpo_verification_submitted). + with(success_properties) + + action + + disavowal_event_count = user.events.where(event_type: :account_verified, ip: '0.0.0.0'). + where.not(disavowal_token_fingerprint: nil).count + expect(disavowal_event_count).to eq 1 + expect(response).to redirect_to(sign_up_completed_url) + end + end + end + + context 'threatmetrix enabled' do + let(:threatmetrix_enabled) { true } + + context 'with threatmetrix status of "reject"' do + let(:proofing_components) do + ProofingComponent.create( + user: user, threatmetrix: true, + threatmetrix_review_status: 'reject' + ) + end + + it 'redirects to the sad face screen' do + expect(@analytics).to receive(:track_event).with( + 'IdV: GPO verification submitted', + success: true, + errors: {}, + pending_in_person_enrollment: false, + threatmetrix_check_failed: true, + enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, + pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], + ) + + action + + expect(response).to redirect_to(idv_setup_errors_url) + end + + it 'does not show a flash message' do + expect(flash[:success]).to be_nil + action + end + + it 'does not dispatch account verified alert' do + expect(UserAlerts::AlertUserAboutAccountVerified).not_to receive(:call) + action + end + end + + context 'with threatmetrix status of "review"' do + let(:proofing_components) do + ProofingComponent.create( + user: user, threatmetrix: true, + threatmetrix_review_status: 'review' + ) + end + it 'redirects to the sad face screen' do + expect(@analytics).to receive(:track_event).with( + 'IdV: GPO verification submitted', + success: true, + errors: {}, + pending_in_person_enrollment: false, + threatmetrix_check_failed: true, + enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at, + pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], + ) + + action + + expect(response).to redirect_to(idv_setup_errors_url) + end + end + end end context 'with an invalid form' do @@ -174,6 +279,7 @@ success: false, errors: otp_code_error_message, pending_in_person_enrollment: false, + threatmetrix_check_failed: false, enqueued_at: nil, error_details: otp_code_incorrect, pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], @@ -204,6 +310,7 @@ success: false, errors: otp_code_error_message, pending_in_person_enrollment: false, + threatmetrix_check_failed: false, enqueued_at: nil, error_details: otp_code_incorrect, pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]], diff --git a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb index c9f754b46dd..68b612a6ef8 100644 --- a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb +++ b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb @@ -9,6 +9,10 @@ :profile, deactivation_reason: :gpo_verification_pending, pii: { ssn: '123-45-6789', dob: '1970-01-01' }, + proofing_components: { + threatmetrix: threatmetrix_enabled, + threatmetrix_review_status: threatmetrix_review_status, + }, ) end let(:gpo_confirmation_code) do @@ -19,59 +23,51 @@ ) end let(:user) { profile.user } - - it 'prompts for one-time code at sign in' do - sign_in_live_with_2fa(user) - - expect(current_path).to eq idv_gpo_verify_path - expect(page).to have_content t('idv.messages.gpo.resend') - - gpo_confirmation_code - fill_in t('forms.verify_profile.name'), with: otp - click_button t('forms.verify_profile.submit') - - expect(user.events.account_verified.size).to eq 1 - expect(page).to_not have_content(t('account.index.verification.reactivate_button')) - end - - it 'renders an error for an expired GPO OTP' do - sign_in_live_with_2fa(user) - - gpo_confirmation_code.update(code_sent_at: 11.days.ago) - fill_in t('forms.verify_profile.name'), with: otp - click_button t('forms.verify_profile.submit') - - expect(current_path).to eq idv_gpo_verify_path - expect(page).to have_content t('errors.messages.gpo_otp_expired') - - user.reload - - expect(user.events.account_verified.size).to eq 0 - expect(user.active_profile).to be_nil + let(:threatmetrix_enabled) { false } + let(:threatmetrix_review_status) { nil } + let(:redirect_after_verification) { nil } + let(:profile_should_be_active) { true } + let(:expected_deactivation_reason) { nil } + + before do + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled). + and_return(threatmetrix_enabled) + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_required_to_verify). + and_return(threatmetrix_enabled) + allow(IdentityConfig.store).to receive(:proofing_device_profiling_decisioning_enabled). + and_return(threatmetrix_enabled) end - it 'allows a user to resend a letter' do - allow(Base32::Crockford).to receive(:encode).and_return(otp) - - sign_in_live_with_2fa(user) - - expect(GpoConfirmation.count).to eq(0) - expect(GpoConfirmationCode.count).to eq(0) - click_on t('idv.messages.gpo.resend') + it_behaves_like 'gpo otp verification' - expect_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter')) + context 'ThreatMetrix enabled' do + let(:threatmetrix_enabled) { true } - click_on t('idv.buttons.mail.resend') + context 'ThreatMetrix says "pass"' do + let(:threatmetrix_review_status) { 'pass' } + it_behaves_like 'gpo otp verification' + end - expect(GpoConfirmation.count).to eq(1) - expect(GpoConfirmationCode.count).to eq(1) - expect(current_path).to eq idv_come_back_later_path + context 'ThreatMetrix says "review"' do + let(:threatmetrix_review_status) { 'review' } + let(:redirect_after_verification) { idv_setup_errors_path } + let(:profile_should_be_active) { false } + let(:expected_deactivation_reason) { 'threatmetrix_review_pending' } + it_behaves_like 'gpo otp verification' + end - confirmation_code = GpoConfirmationCode.first - otp_fingerprint = Pii::Fingerprinter.fingerprint(otp) + context 'ThreatMetrix says "reject"' do + let(:threatmetrix_review_status) { 'reject' } + let(:redirect_after_verification) { idv_setup_errors_path } + let(:profile_should_be_active) { false } + let(:expected_deactivation_reason) { 'threatmetrix_review_pending' } + it_behaves_like 'gpo otp verification' + end - expect(confirmation_code.otp_fingerprint).to eq(otp_fingerprint) - expect(confirmation_code.profile).to eq(profile) + context 'No ThreatMetrix result on proofing component' do + let(:threatmetrix_review_status) { nil } + it_behaves_like 'gpo otp verification' + end end context 'with gpo feature disabled' do diff --git a/spec/forms/gpo_verify_form_spec.rb b/spec/forms/gpo_verify_form_spec.rb index 3a0835e5794..e8402e1aa8b 100644 --- a/spec/forms/gpo_verify_form_spec.rb +++ b/spec/forms/gpo_verify_form_spec.rb @@ -147,6 +147,36 @@ expect(enrollment.enrollment_code).to be_a(String) end end + + context 'ThreatMetrix rejection' do + let(:proofing_components) do + ProofingComponent.create( + user: user, threatmetrix: true, + threatmetrix_review_status: threatmetrix_review_status + ) + end + + let(:threatmetrix_review_status) { 'reject' } + + before do + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled). + and_return(true) + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_required_to_verify). + and_return(true) + allow(IdentityConfig.store).to receive(:proofing_device_profiling_decisioning_enabled). + and_return(true) + end + + it 'returns true' do + result = subject.submit + expect(result.success?).to eq true + end + + it 'notes that threatmetrix failed' do + result = subject.submit + expect(result.extra).to include(threatmetrix_check_failed: true) + end + end end context 'incorrect OTP' do diff --git a/spec/support/idv_examples/gpo_otp_verification.rb b/spec/support/idv_examples/gpo_otp_verification.rb new file mode 100644 index 00000000000..ed41f67b242 --- /dev/null +++ b/spec/support/idv_examples/gpo_otp_verification.rb @@ -0,0 +1,71 @@ +shared_examples 'gpo otp verification' do + include IdvStepHelper + + it 'prompts for one-time code at sign in' do + sign_in_live_with_2fa(user) + + expect(current_path).to eq idv_gpo_verify_path + expect(page).to have_content t('idv.messages.gpo.resend') + + gpo_confirmation_code + fill_in t('forms.verify_profile.name'), with: otp + click_button t('forms.verify_profile.submit') + + expect(page).to have_current_path(redirect_after_verification) if redirect_after_verification + + profile.reload + + if profile_should_be_active + expect(profile.active).to be(true) + expect(profile.deactivation_reason).to be(nil) + else + expect(profile.active).to be(false) + if expected_deactivation_reason + expect(profile.deactivation_reason).to eq(expected_deactivation_reason) + end + end + + expect(user.events.account_verified.size).to eq 1 + expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + end + + it 'renders an error for an expired GPO OTP' do + sign_in_live_with_2fa(user) + + gpo_confirmation_code.update(code_sent_at: 11.days.ago) + fill_in t('forms.verify_profile.name'), with: otp + click_button t('forms.verify_profile.submit') + + expect(current_path).to eq idv_gpo_verify_path + expect(page).to have_content t('errors.messages.gpo_otp_expired') + + user.reload + + expect(user.events.account_verified.size).to eq 0 + expect(user.active_profile).to be_nil + end + + it 'allows a user to resend a letter' do + allow(Base32::Crockford).to receive(:encode).and_return(otp) + + sign_in_live_with_2fa(user) + + expect(GpoConfirmation.count).to eq(0) + expect(GpoConfirmationCode.count).to eq(0) + click_on t('idv.messages.gpo.resend') + + expect_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter')) + + click_on t('idv.buttons.mail.resend') + + expect(GpoConfirmation.count).to eq(1) + expect(GpoConfirmationCode.count).to eq(1) + expect(current_path).to eq idv_come_back_later_path + + confirmation_code = GpoConfirmationCode.first + otp_fingerprint = Pii::Fingerprinter.fingerprint(otp) + + expect(confirmation_code.otp_fingerprint).to eq(otp_fingerprint) + expect(confirmation_code.profile).to eq(profile) + end +end