Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/services/idv/flows/recovery_flow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
53 changes: 10 additions & 43 deletions app/services/idv/steps/recover_verify_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions app/services/idv/steps/recover_verify_wait_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Idv
module Steps
class RecoverVerifyWaitStep < VerifyBaseStep
def call; end
end
end
end
116 changes: 116 additions & 0 deletions app/services/idv/steps/recover_verify_wait_step_show.rb
Original file line number Diff line number Diff line change
@@ -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
10 changes: 0 additions & 10 deletions app/services/idv/steps/verify_base_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions app/views/idv/doc_auth/recover_verify_wait.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<% title t('doc_auth.titles.doc_auth') %>

<h1 class='h3 my0'><%= t('doc_auth.headings.interstitial') %></h1>
2 changes: 1 addition & 1 deletion spec/features/idv/recovery/recover_fail_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

before do
sign_in_before_2fa(user)
allow_any_instance_of(Idv::Steps::RecoverVerifyStep).to receive(:saved_pii).
allow_any_instance_of(Idv::Steps::RecoverVerifyWaitStepShow).to receive(:saved_pii).
and_return(bad_pii.to_json)
complete_recovery_steps_before_doc_success_step
end
Expand Down
2 changes: 1 addition & 1 deletion spec/features/idv/recovery/verify_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
end

it 'proceeds to the next page upon confirmation' do
allow_any_instance_of(Idv::Steps::RecoverVerifyStep).to receive(:saved_pii).
allow_any_instance_of(Idv::Steps::RecoverVerifyWaitStepShow).to receive(:saved_pii).
and_return(saved_pii.to_json)
complete_recovery_steps_before_verify_step
click_idv_continue
Expand Down