diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb index dc5f3742356..9f10dfdf291 100644 --- a/app/controllers/concerns/idv_step_concern.rb +++ b/app/controllers/concerns/idv_step_concern.rb @@ -12,8 +12,15 @@ def confirm_document_capture_complete @pii = flow_session&.[]('pii_from_doc') # hash with indifferent access return if @pii.present? - flow_session&.delete('Idv::Steps::DocumentCaptureStep') - redirect_to idv_doc_auth_url + flow_path = flow_session&.[](:flow_path) + + if IdentityConfig.store.doc_auth_document_capture_controller_enabled && + flow_path == 'standard' + redirect_to idv_document_capture_url + else + flow_session&.delete('Idv::Steps::DocumentCaptureStep') + redirect_to idv_doc_auth_url + end end def confirm_verify_info_step_complete diff --git a/app/controllers/idv/address_controller.rb b/app/controllers/idv/address_controller.rb index 9492de49ae9..9c07d69ed6c 100644 --- a/app/controllers/idv/address_controller.rb +++ b/app/controllers/idv/address_controller.rb @@ -27,7 +27,15 @@ def update def confirm_document_capture_complete @pii = user_session.dig('idv/doc_auth', 'pii_from_doc') return if @pii.present? - redirect_to idv_doc_auth_url + + flow_path = user_session.dig('idv/doc_auth', :flow_path) + + if IdentityConfig.store.doc_auth_document_capture_controller_enabled && + flow_path == 'standard' + redirect_to idv_document_capture_url + else + redirect_to idv_doc_auth_url + end end def idv_form diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index 9505554cba6..36aa13bfd55 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -7,7 +7,8 @@ class DocumentCaptureController < ApplicationController before_action :render_404_if_document_capture_controller_disabled before_action :confirm_two_factor_authenticated - before_action :confirm_agreement_step_complete + before_action :confirm_upload_step_complete + before_action :confirm_document_capture_needed before_action :override_document_capture_step_csp def show @@ -61,16 +62,23 @@ def render_404_if_document_capture_controller_disabled render_not_found unless IdentityConfig.store.doc_auth_document_capture_controller_enabled end - def confirm_agreement_step_complete - return if flow_session['Idv::Steps::AgreementStep'] + def confirm_upload_step_complete + return if flow_session['Idv::Steps::UploadStep'] redirect_to idv_doc_auth_url end + def confirm_document_capture_needed + pii = flow_session&.[]('pii_from_doc') # hash with indifferent access + return if pii.blank? && !idv_session.verify_info_step_complete? + + redirect_to idv_ssn_url + end + def analytics_arguments { flow_path: flow_path, - step: 'document capture', + step: 'document_capture', step_count: current_flow_step_counts['Idv::Steps::DocumentCaptureStep'], analytics_id: 'Doc Auth', irs_reproofing: irs_reproofing?, diff --git a/app/services/idv/steps/upload_step.rb b/app/services/idv/steps/upload_step.rb index b5edaec3bd9..441eb068ecb 100644 --- a/app/services/idv/steps/upload_step.rb +++ b/app/services/idv/steps/upload_step.rb @@ -100,6 +100,10 @@ def send_user_to_email_sent_step def bypass_send_link_steps mark_step_complete(:link_sent) mark_step_complete(:email_sent) + if IdentityConfig.store.doc_auth_document_capture_controller_enabled + flow_session[:flow_path] = @flow.flow_path + redirect_to idv_document_capture_url + end form_response(destination: :document_capture) end diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index 1539520f7d3..b757586cfa1 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -5,10 +5,9 @@ let(:flow_session) do { 'document_capture_session_uuid' => 'fd14e181-6fb1-4cdc-92e0-ef66dad0df4e', - 'pii_from_doc' => Idp::Constants::MOCK_IDV_APPLICANT.dup, :threatmetrix_session_id => 'c90ae7a5-6629-4e77-b97c-f1987c2df7d0', :flow_path => 'standard', - 'Idv::Steps::AgreementStep' => true } + 'Idv::Steps::UploadStep' => true } end let(:user) { create(:user) } @@ -43,10 +42,10 @@ ) end - it 'checks that agreement step is complete' do + it 'checks that upload step is complete' do expect(subject).to have_actions( :before, - :confirm_agreement_step_complete, + :confirm_upload_step_complete, ) end end @@ -67,7 +66,7 @@ analytics_id: 'Doc Auth', flow_path: 'standard', irs_reproofing: false, - step: 'document capture', + step: 'document_capture', step_count: 1, } end @@ -100,15 +99,24 @@ ) end - context 'agreement step is not complete' do + context 'upload step is not complete' do it 'redirects to idv_doc_auth_url' do - flow_session['Idv::Steps::AgreementStep'] = nil + flow_session['Idv::Steps::UploadStep'] = nil get :show expect(response).to redirect_to(idv_doc_auth_url) end end + + context 'with pii in session' do + it 'redirects to ssn step' do + flow_session['pii_from_doc'] = Idp::Constants::MOCK_IDV_APPLICANT + get :show + + expect(response).to redirect_to(idv_ssn_url) + end + end end describe '#update' do @@ -118,7 +126,7 @@ analytics_id: 'Doc Auth', flow_path: 'standard', irs_reproofing: false, - step: 'document capture', + step: 'document_capture', step_count: 1, } end diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index af91a94147f..113330ea835 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -194,7 +194,32 @@ put :update expect(flow_session['Idv::Steps::DocumentCaptureStep']).to eq nil expect(response.status).to eq 302 + expect(response).to redirect_to idv_doc_auth_url end end end + + describe 'doc_auth_document_capture_controller_enabled flag is true' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(true) + end + + it 'redirects to document_capture_controller when pii_from_doc is not present' do + flow_session.delete('pii_from_doc') + flow_session['Idv::Steps::DocumentCaptureStep'] = true + put :update + expect(response.status).to eq 302 + expect(response).to redirect_to idv_document_capture_url + end + + it 'in hybrid flow it does not redirect to document_capture_controller' do + flow_session.delete('pii_from_doc') + flow_session['Idv::Steps::DocumentCaptureStep'] = true + flow_session[:flow_path] = 'hybrid' + put :update + expect(response.status).to eq 302 + expect(response).to redirect_to idv_doc_auth_url + end + end end diff --git a/spec/features/idv/doc_auth/address_step_spec.rb b/spec/features/idv/doc_auth/address_step_spec.rb index b55352e51b2..71a519bbaa7 100644 --- a/spec/features/idv/doc_auth/address_step_spec.rb +++ b/spec/features/idv/doc_auth/address_step_spec.rb @@ -60,4 +60,24 @@ expect(page).to have_current_path(idv_verify_info_path) end end + + context 'with document capture controller flag set, no PII in session' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(true) + sign_in_and_2fa_user + end + + it 'goes to new document capture page on standard flow' do + complete_doc_auth_steps_before_document_capture_step + visit(idv_address_url) + expect(page).to have_current_path(idv_document_capture_url) + end + + it 'stays in FSM on hybrid flow' do + complete_doc_auth_steps_before_link_sent_step + visit(idv_address_url) + expect(page).to have_current_path(idv_doc_auth_link_sent_step) + end + end end diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index fa354541058..26d4b1281b7 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -23,121 +23,180 @@ visit_idp_from_oidc_sp_with_ial2 sign_in_and_2fa_user(user) - complete_doc_auth_steps_before_document_capture_step - visit(idv_document_capture_url) end - it 'shows the new DocumentCapture page for desktop standard flow' do - expect(page).to have_current_path(idv_document_capture_url) - - expect(page).to have_content(t('doc_auth.headings.document_capture')) - expect(page).to have_content(t('step_indicator.flows.idv.verify_id')) - - expect(fake_analytics).to have_logged_event( - 'IdV: doc auth document_capture visited', - flow_path: 'standard', - step: 'document_capture', - step_count: 1, - analytics_id: 'Doc Auth', - irs_reproofing: false, - acuant_sdk_upgrade_ab_test_bucket: :default, - ) + context 'standard desktop flow does not skip ahead' do + before do + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_doc_auth_welcome_step) + complete_welcome_step + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_doc_auth_agreement_step) + complete_agreement_step + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_doc_auth_upload_step) + end end - it 'logs return to sp link click' do - new_window = window_opened_by do - click_on t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name) + context 'standard desktop flow' do + before do + complete_doc_auth_steps_before_document_capture_step end - within_window new_window do + it 'shows the new DocumentCapture page for desktop standard flow' do + expect(page).to have_current_path(idv_document_capture_url) + + expect(page).to have_content(t('doc_auth.headings.document_capture')) + expect(page).to have_content(t('step_indicator.flows.idv.verify_id')) + expect(fake_analytics).to have_logged_event( - 'Return to SP: Failed to proof', - flow: nil, - location: 'document_capture_troubleshooting_options', - redirect_url: instance_of(String), + 'IdV: doc auth document_capture visited', + flow_path: 'standard', step: 'document_capture', + step_count: 1, + analytics_id: 'Doc Auth', + irs_reproofing: false, + acuant_sdk_upgrade_ab_test_bucket: :default, ) + + visit(idv_ssn_url) + expect(page).to have_current_path(idv_document_capture_url) + visit(idv_address_url) + expect(page).to have_current_path(idv_document_capture_url) end - end - context 'throttles calls to acuant', allow_browser_log: true do - let(:fake_attempts_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } - before do - allow_any_instance_of(ApplicationController).to receive( - :irs_attempts_api_tracker, - ).and_return(fake_attempts_tracker) - allow(fake_attempts_tracker).to receive(:idv_document_upload_rate_limited) - allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(max_attempts) - DocAuth::Mock::DocAuthMockClient.mock_response!( - method: :post_front_image, - response: DocAuth::Response.new( - success: false, - errors: { network: I18n.t('doc_auth.errors.general.network_error') }, - ), - ) + it 'logs return to sp link click' do + new_window = window_opened_by do + click_on t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name) + end - (max_attempts - 1).times do - attach_and_submit_images - click_on t('idv.failure.button.warning') + within_window new_window do + expect(fake_analytics).to have_logged_event( + 'Return to SP: Failed to proof', + flow: nil, + location: 'document_capture_troubleshooting_options', + redirect_url: instance_of(String), + step: 'document_capture', + ) end end - it 'redirects to the throttled error page' do - freeze_time do + context 'throttles calls to acuant', allow_browser_log: true do + let(:fake_attempts_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } + before do + allow_any_instance_of(ApplicationController).to receive( + :irs_attempts_api_tracker, + ).and_return(fake_attempts_tracker) + allow(fake_attempts_tracker).to receive(:idv_document_upload_rate_limited) + allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(max_attempts) + DocAuth::Mock::DocAuthMockClient.mock_response!( + method: :post_front_image, + response: DocAuth::Response.new( + success: false, + errors: { network: I18n.t('doc_auth.errors.general.network_error') }, + ), + ) + + (max_attempts - 1).times do + attach_and_submit_images + click_on t('idv.failure.button.warning') + end + end + + it 'redirects to the throttled error page' do + freeze_time do + attach_and_submit_images + timeout = distance_of_time_in_words( + Throttle.attempt_window_in_minutes(:idv_doc_auth).minutes, + ) + message = strip_tags(t('errors.doc_auth.throttled_text_html', timeout: timeout)) + expect(page).to have_content(message) + expect(page).to have_current_path(idv_session_errors_throttled_path) + end + end + + it 'logs the throttled analytics event for doc_auth' do attach_and_submit_images - timeout = distance_of_time_in_words( - Throttle.attempt_window_in_minutes(:idv_doc_auth).minutes, + expect(fake_analytics).to have_logged_event( + 'Throttler Rate Limit Triggered', + throttle_type: :idv_doc_auth, ) - message = strip_tags(t('errors.doc_auth.throttled_text_html', timeout: timeout)) - expect(page).to have_content(message) - expect(page).to have_current_path(idv_session_errors_throttled_path) + end + + it 'logs irs attempts event for rate limiting' do + attach_and_submit_images + expect(fake_attempts_tracker).to have_received(:idv_document_upload_rate_limited) end end - it 'logs the throttled analytics event for doc_auth' do + it 'proceeds to the next page with valid info' do + expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id')) + attach_and_submit_images - expect(fake_analytics).to have_logged_event( - 'Throttler Rate Limit Triggered', - throttle_type: :idv_doc_auth, - ) + + expect(page).to have_current_path(idv_ssn_url) + expect_costing_for_document + expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT') + + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_ssn_url) + fill_out_ssn_form_ok + click_idv_continue + complete_verify_step + expect(page).to have_current_path(idv_phone_url) + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_phone_url) end - it 'logs irs attempts event for rate limiting' do + it 'catches network connection errors on post_front_image', allow_browser_log: true do + DocAuth::Mock::DocAuthMockClient.mock_response!( + method: :post_front_image, + response: DocAuth::Response.new( + success: false, + errors: { network: I18n.t('doc_auth.errors.general.network_error') }, + ), + ) + attach_and_submit_images - expect(fake_attempts_tracker).to have_received(:idv_document_upload_rate_limited) - end - end - it 'proceeds to the next page with valid info' do - expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id')) + expect(page).to have_current_path(idv_document_capture_url) + expect(page).to have_content(I18n.t('doc_auth.errors.general.network_error')) + end - attach_and_submit_images + it 'does not track state if state tracking is disabled' do + allow(IdentityConfig.store).to receive(:state_tracking_enabled).and_return(false) + attach_and_submit_images - expect(page).to have_current_path(idv_ssn_url) - expect_costing_for_document - expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT') + expect(DocAuthLog.find_by(user_id: user.id).state).to be_nil + end end - it 'catches network connection errors on post_front_image', allow_browser_log: true do - DocAuth::Mock::DocAuthMockClient.mock_response!( - method: :post_front_image, - response: DocAuth::Response.new( - success: false, - errors: { network: I18n.t('doc_auth.errors.general.network_error') }, - ), - ) - - attach_and_submit_images + context 'standard mobile flow' do + it 'proceeds to the next page with valid info' do + perform_in_browser(:mobile) do + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) + complete_doc_auth_steps_before_document_capture_step - expect(page).to have_current_path(idv_document_capture_url) - expect(page).to have_content(I18n.t('doc_auth.errors.general.network_error')) - end + expect(page).to have_current_path(idv_document_capture_url) + expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id')) - it 'does not track state if state tracking is disabled' do - allow(IdentityConfig.store).to receive(:state_tracking_enabled).and_return(false) - attach_and_submit_images + attach_and_submit_images - expect(DocAuthLog.find_by(user_id: user.id).state).to be_nil + expect(page).to have_current_path(idv_ssn_url) + expect_costing_for_document + expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT') + + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_ssn_url) + fill_out_ssn_form_ok + click_idv_continue + complete_verify_step + expect(page).to have_current_path(idv_phone_url) + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_phone_url) + end + end end def expect_costing_for_document