diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb new file mode 100644 index 00000000000..5a75c89d5d9 --- /dev/null +++ b/app/controllers/idv/document_capture_controller.rb @@ -0,0 +1,101 @@ +module Idv + class DocumentCaptureController < ApplicationController + include IdvSession + include StepIndicatorConcern + include StepUtilitiesConcern + + before_action :render_404_if_document_capture_controller_disabled + before_action :confirm_two_factor_authenticated + + def show + increment_step_counts + + analytics.idv_doc_auth_document_capture_visited(**analytics_arguments) + + render :show, locals: extra_view_variables + end + + def extra_view_variables + url_builder = ImageUploadPresignedUrlGenerator.new + + { + flow_session: flow_session, + flow_path: 'standard', + sp_name: decorated_session.sp_name, + failure_to_proof_url: idv_doc_auth_return_to_sp_url, + + front_image_upload_url: url_builder.presigned_image_upload_url( + image_type: 'front', + transaction_id: flow_session[:document_capture_session_uuid], + ), + back_image_upload_url: url_builder.presigned_image_upload_url( + image_type: 'back', + transaction_id: flow_session[:document_capture_session_uuid], + ), + }.merge( + native_camera_ab_testing_variables, + acuant_sdk_upgrade_a_b_testing_variables, + in_person_cta_variant_testing_variables, + ) + end + + private + + def render_404_if_document_capture_controller_disabled + render_not_found unless IdentityConfig.store.doc_auth_document_capture_controller_enabled + end + + def analytics_arguments + { + flow_path: flow_path, + step: 'document capture', + step_count: current_flow_step_counts['Idv::Steps::DocumentCaptureStep'], + analytics_id: 'Doc Auth', + irs_reproofing: irs_reproofing?, + }.merge(**acuant_sdk_ab_test_analytics_args) + end + + def current_flow_step_counts + user_session['idv/doc_auth_flow_step_counts'] ||= {} + user_session['idv/doc_auth_flow_step_counts'].default = 0 + user_session['idv/doc_auth_flow_step_counts'] + end + + def increment_step_counts + current_flow_step_counts['Idv::Steps::DocumentCaptureStep'] += 1 + end + + def native_camera_ab_testing_variables + { + acuant_sdk_upgrade_ab_test_bucket: + AbTests::ACUANT_SDK.bucket(flow_session[:document_capture_session_uuid]), + } + end + + def acuant_sdk_upgrade_a_b_testing_variables + bucket = AbTests::ACUANT_SDK.bucket(flow_session[:document_capture_session_uuid]) + testing_enabled = IdentityConfig.store.idv_acuant_sdk_upgrade_a_b_testing_enabled + use_alternate_sdk = (bucket == :use_alternate_sdk) + if use_alternate_sdk + acuant_version = IdentityConfig.store.idv_acuant_sdk_version_alternate + else + acuant_version = IdentityConfig.store.idv_acuant_sdk_version_default + end + { + acuant_sdk_upgrade_a_b_testing_enabled: + testing_enabled, + use_alternate_sdk: use_alternate_sdk, + acuant_version: acuant_version, + } + end + + def in_person_cta_variant_testing_variables + bucket = AbTests::IN_PERSON_CTA.bucket(flow_session[:document_capture_session_uuid]) + { + in_person_cta_variant_testing_enabled: + IdentityConfig.store.in_person_cta_variant_testing_enabled, + in_person_cta_variant_active: bucket, + } + end + end +end diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb new file mode 100644 index 00000000000..79002a05d0b --- /dev/null +++ b/app/views/idv/document_capture/show.html.erb @@ -0,0 +1,14 @@ +<%= render( + 'idv/shared/document_capture', + flow_session: flow_session, + flow_path: 'standard', + sp_name: decorated_session.sp_name, + failure_to_proof_url: idv_doc_auth_return_to_sp_url, + front_image_upload_url: front_image_upload_url, + back_image_upload_url: back_image_upload_url, + acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled, + use_alternate_sdk: use_alternate_sdk, + acuant_version: acuant_version, + in_person_cta_variant_testing_enabled: in_person_cta_variant_testing_enabled, + in_person_cta_variant_active: in_person_cta_variant_active, + ) %> \ No newline at end of file diff --git a/config/application.yml.default b/config/application.yml.default index c538066563d..46e6e9959a5 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -87,6 +87,7 @@ doc_auth_extend_timeout_by_minutes: 40 doc_capture_polling_enabled: true doc_auth_client_glare_threshold: 50 doc_auth_client_sharpness_threshold: 50 +doc_auth_document_capture_controller_enabled: false doc_auth_enable_presigned_s3_urls: false doc_auth_s3_request_timeout: 5 doc_auth_error_dpi_threshold: 290 diff --git a/config/routes.rb b/config/routes.rb index 7a20bb652f8..c79649eaadd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -307,6 +307,7 @@ post '/personal_key' => 'personal_key#update' get '/forgot_password' => 'forgot_password#new' post '/forgot_password' => 'forgot_password#update' + get '/document_capture' => 'document_capture#show' get '/ssn' => 'ssn#show' put '/ssn' => 'ssn#update' get '/verify_info' => 'verify_info#show' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 20aaa7f6260..dc981f71c28 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -157,6 +157,7 @@ def self.build_store(config_map) config.add(:doc_auth_client_glare_threshold, type: :integer) config.add(:doc_auth_client_sharpness_threshold, type: :integer) config.add(:doc_auth_combined_hybrid_handoff_enabled, type: :boolean) + config.add(:doc_auth_document_capture_controller_enabled, type: :boolean) config.add(:doc_auth_enable_presigned_s3_urls, type: :boolean) config.add(:doc_auth_error_dpi_threshold, type: :integer) config.add(:doc_auth_error_glare_threshold, type: :integer) diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb new file mode 100644 index 00000000000..71f3d831ee0 --- /dev/null +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -0,0 +1,103 @@ +require 'rails_helper' + +describe Idv::DocumentCaptureController do + include IdvHelper + + 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' } + end + + let(:user) { build(:user) } + let(:service_provider) do + create( + :service_provider, + issuer: 'http://sp.example.com', + app_id: '123', + ) + end + + let(:default_sdk_version) { IdentityConfig.store.idv_acuant_sdk_version_default } + let(:alternate_sdk_version) { IdentityConfig.store.idv_acuant_sdk_version_alternate } + + before do + allow(subject).to receive(:flow_session).and_return(flow_session) + stub_sign_in(user) + end + + describe 'before_actions' do + it 'checks that feature flag is enabled' do + expect(subject).to have_actions( + :before, + :render_404_if_document_capture_controller_disabled, + ) + end + + it 'includes authentication before_action' do + expect(subject).to have_actions( + :before, + :confirm_two_factor_authenticated, + ) + end + end + + context 'when doc_auth_document_capture_controller_enabled' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(true) + stub_analytics + stub_attempts_tracker + allow(@analytics).to receive(:track_event) + end + + describe '#show' do + let(:analytics_name) { 'IdV: doc auth document_capture visited' } + let(:analytics_args) do + { + analytics_id: 'Doc Auth', + flow_path: 'standard', + irs_reproofing: false, + step: 'document capture', + step_count: 1, + } + end + + context '#show' do + it 'renders the show template' do + get :show + + expect(response).to render_template :show + end + + it 'sends analytics_visited event' do + get :show + + expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + end + + it 'sends correct step count to analytics' do + get :show + get :show + analytics_args[:step_count] = 2 + + expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + end + end + end + end + + context 'when doc_auth_document_capture_controller_enabled is false' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(false) + end + + it 'returns 404' do + get :show + + expect(response.status).to eq(404) + 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 new file mode 100644 index 00000000000..40b20635272 --- /dev/null +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +feature 'doc auth document capture step', :js do + include IdvStepHelper + include DocAuthHelper + include ActionView::Helpers::DateHelper + + let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } + let(:user) { user_with_2fa } + let(:doc_auth_enable_presigned_s3_urls) { false } + let(:fake_analytics) { FakeAnalytics.new } + let(:sp_name) { 'Test SP' } + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(true) + allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls). + and_return(doc_auth_enable_presigned_s3_urls) + allow(Identity::Hostdata::EC2).to receive(:load). + and_return(OpenStruct.new(region: 'us-west-2', account_id: '123456789')) + allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ServiceProviderSessionDecorator).to receive(:sp_name).and_return(sp_name) + + visit_idp_from_oidc_sp_with_ial2 + + sign_in_and_2fa_user(user) + complete_doc_auth_steps_before_document_capture_step + end + + it 'shows the new DocumentCapture page for desktop standard flow' do + visit(idv_document_capture_url) + 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, + ) + end +end