diff --git a/app/services/idv/flows/recovery_flow.rb b/app/services/idv/flows/recovery_flow.rb index c5be9e028de..f5d28038a51 100644 --- a/app/services/idv/flows/recovery_flow.rb +++ b/app/services/idv/flows/recovery_flow.rb @@ -14,9 +14,14 @@ class RecoveryFlow < Flow::BaseFlow mobile_back_image: Idv::Steps::MobileBackImageStep, ssn: Idv::Steps::SsnStep, verify: Idv::Steps::RecoverVerifyStep, + verify_wait: Idv::Steps::RecoverVerifyWaitStep, doc_success: Idv::Steps::DocSuccessStep, }.freeze + OPTIONAL_SHOW_STEPS = { + verify_wait: Idv::Steps::RecoverVerifyWaitStepShow, + }.freeze + ACTIONS = { reset: Idv::Actions::ResetAction, redo_ssn: Idv::Actions::RedoSsnAction, diff --git a/app/services/idv/steps/recover_verify_step.rb b/app/services/idv/steps/recover_verify_step.rb index b94cb42f876..2e9233ce86e 100644 --- a/app/services/idv/steps/recover_verify_step.rb +++ b/app/services/idv/steps/recover_verify_step.rb @@ -2,56 +2,23 @@ module Idv module Steps class RecoverVerifyStep < VerifyBaseStep def call - perform_resolution_and_check_ssn + enqueue_job end private - def idv_failure(result) - attempter_increment if result.extra.dig(:proofing_results, :exception).blank? - if attempter_throttled? - redirect_to idv_session_errors_recovery_failure_url - elsif result.extra.dig(:proofing_results, :exception).present? - redirect_to idv_session_errors_recovery_exception_url - else - redirect_to idv_session_errors_recovery_warning_url - end - result - end - - def summarize_result_and_throttle_failures(summary_result) - if summary_result.success? && doc_auth_pii_matches_decrypted_pii - add_proofing_components - summary_result - else - idv_failure(summary_result) - end - end - - def doc_auth_pii_matches_decrypted_pii - pii_from_doc = session['idv/recovery']['pii_from_doc'] - decrypted_pii = JSON.parse(saved_pii) - return unless pii_matches_data_on_file?(pii_from_doc, decrypted_pii) + def enqueue_job + pii_from_doc = flow_session[:pii_from_doc] - recovery_success - end - - def recovery_success - flash[:success] = I18n.t('recover.reverify.success') - redirect_to account_url - session['need_two_factor_authentication'] = false - true - end + document_capture_session = DocumentCaptureSession.create(user_id: user_id, + requested_at: Time.zone.now) + document_capture_session.store_proofing_pii_from_doc(pii_from_doc) - def saved_pii - session['decrypted_pii'] - end + flow_session[:idv_recover_verify_step_document_capture_session_uuid] = + document_capture_session.uuid - def pii_matches_data_on_file?(pii_from_doc, decrypted_pii) - %w[first_name last_name dob ssn].each do |key| - return false unless pii_from_doc[key] == decrypted_pii[key] - end - true + VendorProofJob.perform_resolution_proof(document_capture_session.uuid, + should_use_aamva?(pii_from_doc)) end end end diff --git a/app/services/idv/steps/recover_verify_wait_step.rb b/app/services/idv/steps/recover_verify_wait_step.rb new file mode 100644 index 00000000000..583d41d321f --- /dev/null +++ b/app/services/idv/steps/recover_verify_wait_step.rb @@ -0,0 +1,7 @@ +module Idv + module Steps + class RecoverVerifyWaitStep < VerifyBaseStep + def call; end + end + end +end diff --git a/app/services/idv/steps/recover_verify_wait_step_show.rb b/app/services/idv/steps/recover_verify_wait_step_show.rb new file mode 100644 index 00000000000..87e57065629 --- /dev/null +++ b/app/services/idv/steps/recover_verify_wait_step_show.rb @@ -0,0 +1,116 @@ +module Idv + module Steps + class RecoverVerifyWaitStepShow < VerifyBaseStep + def call + poll_with_meta_refresh(Figaro.env.poll_rate_for_verify_in_seconds.to_i) + + process_async_state(async_state) + end + + private + + def process_async_state(current_async_state) + case current_async_state.status + when :none + mark_step_incomplete(:verify) + when :in_progress + nil + when :timed_out + mark_step_incomplete(:verify) + when :done + async_state_done(current_async_state) + end + end + + def async_state_done(current_async_state) + add_proofing_costs(current_async_state.result) + response = idv_result_to_form_response(current_async_state.result) + response = check_ssn(current_async_state.pii) if response.success? + summarize_result_and_throttle_failures(response) + + if response.success? + delete_async + mark_step_complete(:verify_wait) + else + mark_step_incomplete(:verify) + end + end + + def async_state + dcs_uuid = flow_session[:idv_recover_verify_step_document_capture_session_uuid] + dcs = DocumentCaptureSession.find_by(uuid: dcs_uuid) + return ProofingDocumentCaptureSessionResult.none if dcs_uuid.nil? + return ProofingDocumentCaptureSessionResult.timed_out if dcs.nil? + + proofing_job_result = dcs.load_proofing_result + return ProofingDocumentCaptureSessionResult.timed_out if proofing_job_result.nil? + + if proofing_job_result.result + proofing_job_result.done + elsif proofing_job_result.pii + ProofingDocumentCaptureSessionResult.in_progress + end + end + + def delete_async + flow_session.delete(:idv_recover_verify_step_document_capture_session_uuid) + end + + def idv_failure(result) + attempter_increment if result.extra.dig(:proofing_results, :exception).blank? + if attempter_throttled? + redirect_to idv_session_errors_recovery_failure_url + elsif result.extra.dig(:proofing_results, :exception).present? + redirect_to idv_session_errors_recovery_exception_url + else + redirect_to idv_session_errors_recovery_warning_url + end + result + end + + def summarize_result_and_throttle_failures(summary_result) + if summary_result.success? && doc_auth_pii_matches_decrypted_pii + add_proofing_components + summary_result + else + idv_failure(summary_result) + end + end + + def doc_auth_pii_matches_decrypted_pii + pii_from_doc = session['idv/recovery']['pii_from_doc'] + decrypted_pii = JSON.parse(saved_pii) + return unless pii_matches_data_on_file?(pii_from_doc, decrypted_pii) + + recovery_success + end + + def recovery_success + flash[:success] = I18n.t('recover.reverify.success') + redirect_to account_url + session['need_two_factor_authentication'] = false + true + end + + def saved_pii + session['decrypted_pii'] + end + + # This method securely compares all fields to mitigate + # timing attacks from normal comparisons and early exits. + def pii_matches_data_on_file?(pii_from_doc, decrypted_pii) + all_match = true + %w[first_name last_name dob ssn].each do |key| + match = ActiveSupport::SecurityUtils.secure_compare( + pii_from_doc[key], + decrypted_pii[key], + ) + + all_match &&= match + end + + all_match + end + end + end +end diff --git a/app/services/idv/steps/verify_base_step.rb b/app/services/idv/steps/verify_base_step.rb index 2e44a1dfd99..e2b4362d824 100644 --- a/app/services/idv/steps/verify_base_step.rb +++ b/app/services/idv/steps/verify_base_step.rb @@ -8,16 +8,6 @@ class VerifyBaseStep < DocAuthBaseStep private - def perform_resolution_and_check_ssn - pii_from_doc = flow_session[:pii_from_doc] - # do resolution first to prevent ssn time/discovery. resolution time order > than db call - idv_result = perform_resolution(pii_from_doc) - add_proofing_costs(idv_result) - response = idv_result_to_form_response(idv_result) - response = check_ssn(pii_from_doc) if response.success? - summarize_result_and_throttle_failures(response) - end - def summarize_result_and_throttle_failures(summary_result) if summary_result.success? add_proofing_components diff --git a/app/views/idv/doc_auth/recover_verify_wait.html.erb b/app/views/idv/doc_auth/recover_verify_wait.html.erb new file mode 100644 index 00000000000..636eeee580b --- /dev/null +++ b/app/views/idv/doc_auth/recover_verify_wait.html.erb @@ -0,0 +1,3 @@ +<% title t('doc_auth.titles.doc_auth') %> + +