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
20 changes: 0 additions & 20 deletions app/controllers/analytics_events_controller.rb

This file was deleted.

5 changes: 0 additions & 5 deletions app/controllers/concerns/idv/verify_info_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,6 @@ def async_state_done(current_async_state)
},
)

form_response.extra[:ssn_is_unique] = DuplicateSsnFinder.new(
ssn: idv_session.ssn,
user: current_user,
).ssn_is_unique?

summarize_result_and_rate_limit(form_response)
delete_async

Expand Down
127 changes: 127 additions & 0 deletions app/controllers/idv/by_mail/resend_letter_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# frozen_string_literal: true

module Idv
module ByMail
class ResendLetterController < ApplicationController
include Idv::AvailabilityConcern
include IdvSessionConcern
include Idv::StepIndicatorConcern

before_action :confirm_two_factor_authenticated
before_action :confirm_verification_needed
before_action :confirm_mail_not_rate_limited
before_action :confirm_profile_not_too_old

def new
analytics.idv_resend_letter_visited
end

def create
update_tracking

if pii_locked?
redirect_to capture_password_url
elsif resend_requested?
resend_letter
flash[:success] = t('idv.messages.gpo.another_letter_on_the_way')
redirect_to idv_letter_enqueued_url
end
end

def gpo_mail_service
@gpo_mail_service ||= Idv::GpoMail.new(current_user)
end

private

def confirm_verification_needed
return if current_user.gpo_verification_pending_profile?
redirect_to account_url
end

def confirm_profile_not_too_old
redirect_to idv_verify_by_mail_enter_code_path if gpo_mail_service.profile_too_old?
end

def confirm_mail_not_rate_limited
redirect_to idv_verify_by_mail_enter_code_path if gpo_mail_service.rate_limited?
end

def update_tracking
analytics.idv_gpo_address_letter_requested(
resend: true,
first_letter_requested_at: first_letter_requested_at,
hours_since_first_letter:
hours_since_first_letter(first_letter_requested_at),
phone_step_attempts: RateLimiter.new(
user: current_user,
rate_limit_type: :proof_address,
).attempts,
)
create_user_event(:gpo_mail_sent, current_user)
end

def resend_requested?
current_user.gpo_verification_pending_profile?
end

def first_letter_requested_at
current_user.gpo_verification_pending_profile&.gpo_verification_pending_at
end

def hours_since_first_letter(first_letter_requested_at)
first_letter_requested_at ?
(Time.zone.now - first_letter_requested_at).to_i.seconds.in_hours.to_i : 0
end

def resend_letter
analytics.idv_gpo_address_letter_enqueued(
enqueued_at: Time.zone.now,
resend: true,
first_letter_requested_at: first_letter_requested_at,
hours_since_first_letter:
hours_since_first_letter(first_letter_requested_at),
phone_step_attempts: RateLimiter.new(
user: current_user,
rate_limit_type: :proof_address,
).attempts,
)
confirmation_maker = confirmation_maker_perform
send_reminder
return unless FeatureManagement.reveal_gpo_code?
session[:last_gpo_confirmation_code] = confirmation_maker.otp
end

def confirmation_maker_perform
confirmation_maker = GpoConfirmationMaker.new(
pii: pii,
service_provider: current_sp,
profile: current_user.pending_profile,
)
confirmation_maker.perform
confirmation_maker
end

def pii
Pii::Cacher.new(current_user, user_session).
fetch(current_user.gpo_verification_pending_profile.id)
end

def send_reminder
current_user.send_email_to_all_addresses(:verify_by_mail_letter_requested)
end

def pii_locked?
!Pii::Cacher.new(current_user, user_session).exists_in_session?
end

def step_indicator_steps
if in_person_proofing?
Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS_GPO
else
StepIndicatorConcern::STEP_INDICATOR_STEPS_GPO
end
end
end
end
end
16 changes: 8 additions & 8 deletions app/jobs/reports/combined_invoice_supplement_report_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def combine_by_iaa_month(

'iaa_ial1_unique_users',
'iaa_ial2_unique_users',
'iaa_ial1_plus_2_unique_users',
'iaa_unique_users',
'partner_ial2_unique_user_events_year1',
'partner_ial2_unique_user_events_year2',
'partner_ial2_unique_user_events_year3',
Expand Down Expand Up @@ -133,7 +133,7 @@ def combine_by_iaa_month(

'issuer_ial1_unique_users',
'issuer_ial2_unique_users',
'issuer_ial1_plus_2_unique_users',
'issuer_unique_users',
]
by_issuer_iaa_issuer_year_months.each do |iaa_key, issuer_year_months|
issuer_year_months.each do |issuer, year_months_data|
Expand Down Expand Up @@ -167,9 +167,9 @@ def combine_by_iaa_month(
year_month,
year_month_start.strftime('%B %Y'),

(iaa_ial1_unique_users = extract(iaa_results, :unique_users, ial: 1)),
(iaa_ial2_unique_users = extract(iaa_results, :unique_users, ial: 2)),
iaa_ial1_unique_users + iaa_ial2_unique_users,
extract(iaa_results, :unique_users, ial: 1),
extract(iaa_results, :unique_users, ial: 2),
extract(iaa_results, :unique_users, ial: :all),
partner_results[:partner_ial2_unique_user_events_year1] || 0,
partner_results[:partner_ial2_unique_user_events_year2] || 0,
partner_results[:partner_ial2_unique_user_events_year3] || 0,
Expand Down Expand Up @@ -204,9 +204,9 @@ def combine_by_iaa_month(
(ial2_total_auth_count = extract(issuer_results, :total_auth_count, ial: 2)),
ial1_total_auth_count + ial2_total_auth_count,

(issuer_ial1_unique_users = extract(issuer_results, :unique_users, ial: 1)),
(issuer_ial2_unique_users = extract(issuer_results, :unique_users, ial: 2)),
issuer_ial1_unique_users + issuer_ial2_unique_users,
extract(issuer_results, :unique_users, ial: 1),
extract(issuer_results, :unique_users, ial: 2),
extract(issuer_results, :unique_users, ial: :all),
]
end
end
Expand Down
6 changes: 5 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class User < ApplicationRecord
has_many :sign_in_restrictions, dependent: :destroy
has_many :in_person_enrollments, dependent: :destroy
has_many :fraud_review_requests, dependent: :destroy
has_many :gpo_confirmation_codes, through: :profiles

has_one :pending_in_person_enrollment,
-> { where(status: :pending).order(created_at: :desc) },
Expand Down Expand Up @@ -424,7 +425,10 @@ def has_devices?

def authenticated_device?(cookie_uuid:)
return false if cookie_uuid.blank?
devices.joins(:events).exists?(cookie_uuid:, events: { event_type: :sign_in_after_2fa })
devices.joins(:events).exists?(
cookie_uuid:,
events: { event_type: [:account_created, :sign_in_after_2fa] },
)
end

# Returns the number of times the user has signed in, corresponding to the `sign_in_before_2fa`
Expand Down
119 changes: 117 additions & 2 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1309,9 +1309,113 @@ def idv_doc_auth_submitted_pii_validation(
)
end

def idv_doc_auth_verify_proofing_results(**extra)
track_event('IdV: doc auth verify proofing results', **extra)
# rubocop:disable Layout/LineLength
# @param ab_tests [Hash] Object that holds A/B test data (legacy A/B tests may include attributes outside the scope of this object)
# @param acuant_sdk_upgrade_ab_test_bucket [String] A/B test bucket for Acuant document capture SDK upgrades
# @param address_edited [Boolean] Whether the user edited their address before submitting the "Verify your information" step
# @param address_line2_present [Boolean] Whether the user's address includes a second address line
# @param analytics_id [String] "Doc Auth" for remote unsupervised, "In Person Proofing" for IPP
# @param errors [Hash] Details about vendor-specific errors encountered during the stages of the identity resolution process
# @param flow_path [String] "hybrid" for hybrid handoff, "standard" otherwise
# @param irs_reproofing [Boolean] Whether the user is being forced to reproof to access the IRS
# @param lexisnexis_instant_verify_workflow_ab_test_bucket [String] A/B test bucket for Lexis Nexis InstantVerify workflow testing
# @param opted_in_to_in_person_proofing [Boolean] Whether this user explicitly opted into in-person proofing
# @param proofing_results [Hash]
# @option proofing_results [String,nil] exception If an exception occurred during any phase of proofing its message is provided here
# @option proofing_results [Boolean] timed_out true if any vendor API calls timed out during proofing
# @option proofing_results [String] threatmetrix_review_status Result of Threatmetrix assessment, either "review", "reject", or "pass"
# @option proofing_results [Hash] context Full context of the proofing process
# @option proofing_results [String] context.device_profiling_adjudication_reason Reason code describing how we arrived at the device profiling result
# @option proofing_results [String] context.resolution_adjudication_reason Reason code describing how we arrived at the identity resolution result
# @option proofing_results [Boolean] context.should_proof_state_id Whether we need to verify the user's PII with AAMVA. False if the user is using a document from a non-AAMVA jurisdiction
# @option proofing_results [Hash] context.stages Object holding details about each stage of the proofing process
# @option proofing_results [Hash] context.stages.resolution Object holding details about the call made to the identity resolution vendor
# @option proofing_results [Boolean] context.stages.resolution.success Whether identity resolution proofing was successful
# @option proofing_results [Hash] context.stages.resolution.errors Object describing errors encountered during identity resolution
# @option proofing_results [String,nil] context.stages.resolution.exception If an exception occured during identity resolution its message is provided here
# @option proofing_results [Boolean] context.stages.resolution.timed_out Whether the identity resolution API request timed out
# @option proofing_results [String] context.stages.resolution.transaction_id A unique id for the underlying vendor request
# @option proofing_results [Boolean] context.stages.resolution.can_pass_with_additional_verification Whether the PII could be verified if another vendor verified certain attributes
# @option proofing_results [Array<String>] context.stages.resolution.attributes_requiring_additional_verification Attributes that need to be verified by another vendor
# @option proofing_results [String] context.stages.resolution.vendor_name Vendor used (e.g. lexisnexis:instant_verify)
# @option proofing_results [String] context.stages.resolution.vendor_workflow ID of workflow or configuration the vendor used for this transaction
# @option proofing_results [Boolean] context.stages.residential_address.success Whether the residential address passed proofing
# @option proofing_results [Hash] context.stages.residential_address.errors Object holding error details returned by the residential address proofing vendor.
# @option proofing_results [String,nil] context.stages.residential_address.exception If an exception occured during residential address verification its message is provided here
# @option proofing_results [Boolean] context.stages.residential_address.timed_out True if the request to the residential address proofing vendor timed out
# @option proofing_results [String] context.stages.residential_address.transaction_id Vendor-specific transaction ID for the request made to the residential address proofing vendor
# @option proofing_results [Boolean] context.stages.residential_address.can_pass_with_additional_verification Whether, if residential address proofing failed, it could pass with additional proofing from another vendor
# @option proofing_results [Array<String>,nil] context.stages.residential_address.attributes_requiring_additional_verification List of PII attributes that require additional verification for residential address proofing to pass
# @option proofing_results [String] context.stages.residential_address.vendor_name Vendor used for residential address proofing
# @option proofing_results [String] context.stages.residential_address.vendor_workflow Vendor-specific workflow or configuration ID associated with the request made.
# @option proofing_results [Hash] context.stages.state_id Object holding details about the call made to the state ID proofing vendor
# @option proofing_results [Boolean] context.stages.state_id.success Whether the PII associated with the user's state ID document passed proofing
# @option proofing_results [Hash] context.stages.state_id.errors Object describing errors encountered while proofing the user's state ID PII
# @option proofing_results [String,nil] context.stages.state_id.exception If an exception occured during state ID PII verification its message is provided here
# @option proofing_results [Boolean] context.stages.state_id.mva_exception For AAMVA, whether the exception that occurred was due to an error on the state MVA side
# @option proofing_results [Hash<String,Numeric>] context.stages.state_id.requested_attributes An object whose keys are field names and values are "1" representing PII attributes sent to the state ID proofing vendor for verification.
# @option proofing_results [Boolean] context.stages.state_id.timed_out Whether the request to the state ID verification vendor timed out
# @option proofing_results [String] context.stages.state_id.transaction_id Vendor-specific transaction ID for the request made to the state id proofing vendor
# @option proofing_results [String] context.stages.state_id.vendor_name Name of the vendor used for state ID PII verification. If the ID was not from a supported jurisdiction, it will be "UnsupportedJurisdiction". It MAY also be "UnsupportedJurisdiction" if state ID verification was not needed because other vendor calls did not succeed.
# @option proofing_results [String] context.stages.state_id.state The state that was listed as the user's address on their state ID. Note that this may differ from state_id_jurisdiction.
# @option proofing_results [String] context.stages.state_id.state_id_jurisdiction The state that issued the drivers license or ID card being used for proofing.
# @option proofing_results [String] context.stages.state_id.state_id_number A string describing the _format_ of the state ID number provided.
# @option proofing_results [Hash] context.stages.threatmetrix Object holding details about the call made to the device profiling vendor
# @option proofing_results [String] context.stages.threatmetrix.client Identifier string indicating which client was used.
# @option proofing_results [Boolean] context.stages.threatmetrix.success Whether the request to the vendor succeeded.
# @option proofing_results [Hash] context.stages.threatmetrix.errors Hash describing errors encountered when making the request.
# @option proofing_results [String,nil] context.stages.threatmetrix.exception If an exception was encountered making the request to the vendor, its message is provided here.
# @option proofing_results [Boolean] context.stages.threatmetrix.timed_out Whether the request to the vendor timed out.
# @option proofing_results [String] context.stages.threatmetrix.transaction_id Vendor-specific transaction ID for the request.
# @option proofing_results [Hash] context.stages.threatmetrix.response_body JSON body of the response returned from the vendor. PII has been redacted.
# @option proofing_results [String] context.stages.threatmetrix.response_body.account_lex_id LexID associated with the response.
# @option proofing_results [String] context.stages.threatmetrix.response_body.session_id Session ID associated with the response.
# @option proofing_results [String] context.stages.threatmetrix.review_status One of "pass", "review", "reject".
# @param skip_hybrid_handoff [Boolean] Whether the user should skip hybrid handoff (i.e. because they are already on a mobile device)
# @param ssn_is_unique [Boolean] Whether another Profile existed with the same SSN at the time the profile associated with the current IdV session was minted.
# @param step [String] Always "verify" (leftover from flow state machine days)
# @param success [Boolean] Whether identity resolution succeeded overall
def idv_doc_auth_verify_proofing_results(
ab_tests: nil,
acuant_sdk_upgrade_ab_test_bucket: nil,
address_edited: nil,
address_line2_present: nil,
analytics_id: nil,
errors: nil,
flow_path: nil,
irs_reproofing: nil,
lexisnexis_instant_verify_workflow_ab_test_bucket: nil,
opted_in_to_in_person_proofing: nil,
proofing_results: nil,
skip_hybrid_handoff: nil,
ssn_is_unique: nil,
step: nil,
success: nil,
**extra
)
track_event(
'IdV: doc auth verify proofing results',
{
ab_tests:,
acuant_sdk_upgrade_ab_test_bucket:,
address_edited:,
address_line2_present:,
analytics_id:,
errors:,
flow_path:,
irs_reproofing:,
lexisnexis_instant_verify_workflow_ab_test_bucket:,
opted_in_to_in_person_proofing:,
proofing_results:,
skip_hybrid_handoff:,
ssn_is_unique:,
step:,
success:,
**extra,
}.compact,
)
end
# rubocop:enable Layout/LineLength

# @identity.idp.previous_event_name IdV: in person proofing verify submitted
def idv_doc_auth_verify_submitted(**extra)
Expand Down Expand Up @@ -3171,6 +3275,17 @@ def idv_request_letter_visited(
)
end

# GPO "resend letter" page visited
# @identity.idp.previous_event_name IdV: request letter visited
def idv_resend_letter_visited(
**extra
)
track_event(
:idv_resend_letter_visited,
**extra,
)
end

# Acuant SDK errored after loading but before initialization
# @param [Boolean] success
# @param [String] error_message
Expand Down
Loading