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
1 change: 1 addition & 0 deletions app/controllers/concerns/idv/verify_info_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def add_proofing_costs(results)
elsif stage == :threatmetrix
# transaction_id comes from request_id
tmx_id = hash[:transaction_id]
log_irs_tmx_fraud_check_event(hash) if tmx_id
add_cost(:threatmetrix, transaction_id: tmx_id) if tmx_id
end
end
Expand Down
1 change: 1 addition & 0 deletions app/controllers/idv/verify_info_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class VerifyInfoController < ApplicationController
include StepUtilitiesConcern
include StepIndicatorConcern
include VerifyInfoConcern
include Steps::ThreatMetrixStepHelper

before_action :confirm_two_factor_authenticated
before_action :confirm_ssn_step_complete
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/packs/mock-device-profiling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const CHAOS_OPTIONS: ChaosOption[] = [
];

function MockDeviceProfilingOptions() {
const [selectedValue, setSelectedValue] = useState('');
const [selectedValue, setSelectedValue] = useState('pass');
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the default selected value as a pass because it was missed in #7892 PR for LG-8985 “Make Pass the default option for a mock”.


useEffect(() => {
if (selectedValue === 'chaotic') {
Expand Down
20 changes: 20 additions & 0 deletions app/services/idv/steps/threat_metrix_step_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ def threatmetrix_iframe_url(session_id)
session_id: session_id,
)
end

def log_irs_tmx_fraud_check_event(result)
return unless IdentityConfig.store.irs_attempt_api_track_tmx_fraud_check_event
return unless FeatureManagement.proofing_device_profiling_collecting_enabled?
success = result[:review_status] == 'pass'

if !success && (tmx_summary_reason_code = result.dig(
:response_body,
:tmx_summary_reason_code,
))
failure_reason = {
tmx_summary_reason_code: tmx_summary_reason_code,
}
end

irs_attempts_api_tracker.idv_tmx_fraud_check(
success: success,
failure_reason: failure_reason,
)
end
end
end
end
12 changes: 12 additions & 0 deletions app/services/irs_attempts_api/tracker_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,18 @@ def idv_ssn_submitted(ssn:)
)
end

# @param [Boolean] success
# @param [Hash<Symbol,Array<Symbol>>] failure_reason
# This event will capture the result of the TMX fraud check
# during Identity Verification
def idv_tmx_fraud_check(success:, failure_reason: nil)
track_event(
:idv_tmx_fraud_check,
success: success,
failure_reason: failure_reason,
)
end

# @param [String] throttle_context - Either single-session or multi-session
# Track when idv verification is rate limited during idv flow
def idv_verification_rate_limited(throttle_context:)
Expand Down
1 change: 1 addition & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ irs_attempt_api_event_ttl_seconds: 86400
irs_attempt_api_event_count_default: 1000
irs_attempt_api_event_count_max: 10000
irs_attempt_api_payload_size_logging_enabled: true
irs_attempt_api_track_tmx_fraud_check_event: false
key_pair_generation_percent: 0
logins_per_ip_track_only_mode: false
# LexisNexis #####################################################
Expand Down
1 change: 1 addition & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def self.build_store(config_map)
config.add(:irs_attempt_api_event_count_default, type: :integer)
config.add(:irs_attempt_api_event_count_max, type: :integer)
config.add(:irs_attempt_api_payload_size_logging_enabled, type: :boolean)
config.add(:irs_attempt_api_track_tmx_fraud_check_event, type: :boolean)
config.add(:irs_attempt_api_public_key)
config.add(:irs_attempt_api_public_key_id)
config.add(:lexisnexis_base_url, type: :string)
Expand Down
87 changes: 87 additions & 0 deletions spec/controllers/idv/verify_info_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
stub_analytics
stub_attempts_tracker
allow(@analytics).to receive(:track_event)
allow(@irs_attempts_api_tracker).to receive(:track_event)
end

it 'renders the show template' do
Expand Down Expand Up @@ -172,6 +173,92 @@
get :show
end
end

context 'when proofing_device_profiling is enabled' do
let(:idv_result) do
{
context: {
stages: {
threatmetrix: {
transaction_id: 1,
review_status: review_status,
response_body: {
tmx_summary_reason_code: ['Identity_Negative_History'],
},
},
},
},
errors: {},
exception: nil,
success: true,
}
end

let(:document_capture_session) do
document_capture_session = DocumentCaptureSession.create!(user: user)
document_capture_session.create_proofing_session
document_capture_session.store_proofing_result(idv_result)
document_capture_session
end

let(:expected_failure_reason) { DocAuthHelper::SAMPLE_TMX_SUMMARY_REASON_CODE }

before do
controller.
idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid
allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled)
allow(IdentityConfig.store).to receive(:irs_attempt_api_track_tmx_fraud_check_event).
and_return(true)
end

context 'when threatmetrix response is Pass' do
let(:review_status) { 'pass' }

it 'it logs IRS idv_tmx_fraud_check event' do
expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with(
success: true,
failure_reason: nil,
)
get :show
end
end

context 'when threatmetrix response is No Result' do
let(:review_status) { 'no_result' }

it 'it logs IRS idv_tmx_fraud_check event' do
expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with(
success: false,
failure_reason: expected_failure_reason,
)
Comment on lines +227 to +233
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmhooper is this consistent with what we want for Threatmetrix no result? It's treating it as a failure.

get :show
end
end

context 'when threatmetrix response is Reject' do
let(:review_status) { 'reject' }

it 'it logs IRS idv_tmx_fraud_check event' do
expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with(
success: false,
failure_reason: expected_failure_reason,
)
get :show
end
end

context 'when threatmetrix response is Review' do
let(:review_status) { 'review' }

it 'it logs IRS idv_tmx_fraud_check event' do
expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with(
success: false,
failure_reason: expected_failure_reason,
)
get :show
end
end
end
end

describe '#update' do
Expand Down
66 changes: 66 additions & 0 deletions spec/features/idv/threatmetrix_pending_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@

RSpec.feature 'Users pending threatmetrix review', :js do
include IdvStepHelper
include OidcAuthHelper
include IrsAttemptsApiTrackingHelper
include DocAuthHelper

before do
allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled)
allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_org_id).and_return('test_org')
allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true)
allow(IdentityConfig.store).to receive(:irs_attempt_api_track_tmx_fraud_check_event).
and_return(true)
mock_irs_attempts_api_encryption_key
end

let(:service_provider) do
create(
:service_provider,
active: true,
redirect_uris: ['http://localhost:7654/auth/result'],
ial: 2,
irs_attempts_api_enabled: true,
)
end

scenario 'users pending threatmetrix see sad face screen and cannot perform idv' do
Expand Down Expand Up @@ -49,4 +66,53 @@

expect(current_path).to eq('/auth/result')
end

scenario 'users threatmetrix Pass, it logs idv_tmx_fraud_check event', :js do
freeze_time do
complete_all_idv_steps_with(threatmetrix: 'Pass')
expect_irs_event(expected_success: true, expected_failure_reason: nil)
end
end

scenario 'users pending threatmetrix Reject, it logs idv_tmx_fraud_check event', :js do
freeze_time do
expect_pending_failure_reason(threatmetrix: 'Reject')
end
end

scenario 'users pending threatmetrix Review, it logs idv_tmx_fraud_check event', :js do
freeze_time do
expect_pending_failure_reason(threatmetrix: 'Review')
end
end

scenario 'users pending threatmetrix No Result, it logs idv_tmx_fraud_check event', :js do
freeze_time do
expect_pending_failure_reason(threatmetrix: 'No Result')
end
end

def expect_pending_failure_reason(threatmetrix:)
complete_all_idv_steps_with(threatmetrix: threatmetrix)
expect(page).to have_content(t('idv.failure.setup.heading'))
expect(page).to have_current_path(idv_setup_errors_path)
expect_irs_event(
expected_success: false,
expected_failure_reason: DocAuthHelper::SAMPLE_TMX_SUMMARY_REASON_CODE,
)
end

def expect_irs_event(expected_success:, expected_failure_reason:)
event_name = 'idv-tmx-fraud-check'
events = irs_attempts_api_tracked_events(timestamp: Time.zone.now)
received_event_types = events.map(&:event_type)

idv_tmx_fraud_check_event = events.find { |x| x.event_type == event_name }
failure_reason = idv_tmx_fraud_check_event.event_metadata[:failure_reason]
success = idv_tmx_fraud_check_event.event_metadata[:success]

expect(received_event_types).to include event_name
expect(failure_reason).to eq expected_failure_reason.as_json
expect(success).to eq expected_success
end
end
3 changes: 2 additions & 1 deletion spec/fixtures/proofing/lexis_nexis/ddp/error_response.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"error_detail": "service_type",
"request_id":"1234-abcd",
"request_result":"fail_invalid_parameter",
"review_status":"REVIEW_STATUS"
"review_status":"REVIEW_STATUS",
"tmx_summary_reason_code": ["Identity_Negative_History"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we get this from checking production logs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I checked the production logs and also from LN we have a lot of others as well but I just mock this only.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a constant in DocAuthHelper, SAMPLE_TMX_SUMMARY_REASON_CODE or something like that. Along with GOOD_SSN, etc. Since it's used in 3 tests (so far). You might still have to have the explicit string in the fixtures.

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"summary_risk_score": "-6",
"tmx_risk_rating": "neutral",
"fraudpoint.score": "500",
"first_name": "[redacted]"
"first_name": "[redacted]",
"tmx_summary_reason_code": ["Identity_Negative_History"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"summary_risk_score": "-6",
"tmx_risk_rating": "neutral",
"fraudpoint.score": "500",
"first_name": "WARNING! YOU SHOULD NEVER SEE THIS PII FIELD IN THE LOGS"
"first_name": "WARNING! YOU SHOULD NEVER SEE THIS PII FIELD IN THE LOGS",
"tmx_summary_reason_code": ["Identity_Negative_History"]
}
19 changes: 19 additions & 0 deletions spec/support/features/doc_auth_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module DocAuthHelper

GOOD_SSN = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn]
GOOD_SSN_MASKED = '9**-**-***4'
SAMPLE_TMX_SUMMARY_REASON_CODE = { tmx_summary_reason_code: ['Identity_Negative_History'] }
SSN_THAT_FAILS_RESOLUTION = '123-45-6666'
SSN_THAT_RAISES_EXCEPTION = '000-00-0000'

Expand Down Expand Up @@ -299,4 +300,22 @@ def fill_out_address_form_fail
def fill_out_doc_auth_phone_form_ok(phone = '415-555-0199')
fill_in :doc_auth_phone, with: phone
end

def complete_all_idv_steps_with(threatmetrix:)
allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(300)
user = create(:user, :signed_up)
visit_idp_from_ial1_oidc_sp(
client_id: service_provider.issuer,
irs_attempts_api_session_id: 'test-session-id',
)
visit root_path
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_ssn_step
select threatmetrix, from: :mock_profiling_result
complete_ssn_step
click_idv_continue
complete_phone_step(user)
complete_review_step(user)
acknowledge_and_confirm_personal_key
end
end