Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class SelectedEmailController < ApplicationController
def edit
@identity = identity
@select_email_form = build_select_email_form
@can_add_email = EmailPolicy.new(current_user).can_add_email?
analytics.sp_select_email_visited
end

Expand Down
11 changes: 0 additions & 11 deletions app/controllers/concerns/idv/document_capture_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ module DocumentCaptureConcern

def handle_stored_result(user: current_user, store_in_session: true)
if stored_result&.success? && selfie_requirement_met?
save_proofing_components(user)
extract_pii_from_doc(user, store_in_session: store_in_session)
flash[:success] = t('doc_auth.headings.capture_complete')
successful_response
Expand All @@ -18,16 +17,6 @@ def handle_stored_result(user: current_user, store_in_session: true)
end
end

def save_proofing_components(user)
return unless user

component_attributes = {
document_check: doc_auth_vendor,
document_type: 'state_id',
}
ProofingComponent.create_or_find_by(user: user).update(component_attributes)
end

def successful_response
FormResponse.new(success: true)
end
Expand Down
17 changes: 1 addition & 16 deletions app/controllers/concerns/idv/verify_info_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -267,26 +267,11 @@ def summarize_result_and_rate_limit(summary_result)
proofing_results_exception = summary_result.extra.dig(:proofing_results, :exception)
resolution_rate_limiter.increment! if proofing_results_exception.blank?

if summary_result.success?
add_proofing_components(summary_result)
else
if !summary_result.success?
idv_failure(summary_result)
end
end

def add_proofing_components(summary_result)
ProofingComponent.create_or_find_by(user: current_user).update(
resolution_check: Idp::Constants::Vendors::LEXIS_NEXIS,
source_check: summary_result.extra.dig(
:proofing_results,
:context,
:stages,
:state_id,
:vendor_name,
),
)
end

def load_async_state
dcs_uuid = idv_session.verify_info_step_document_capture_session_uuid
dcs = DocumentCaptureSession.find_by(uuid: dcs_uuid)
Expand Down
2 changes: 0 additions & 2 deletions app/controllers/idv/by_mail/request_letter_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ def update_tracking

log_letter_requested_analytics(resend: false)
create_user_event(:gpo_mail_sent, current_user)

ProofingComponent.find_or_create_by(user: current_user).update(address_check: 'gpo_letter')
end

def confirm_mail_not_rate_limited
Expand Down
12 changes: 2 additions & 10 deletions app/controllers/idv/in_person/usps_locations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,9 @@ def update
sponsor_id: enrollment_sponsor_id,
)

add_proofing_component

render json: { success: true }, status: :ok
end

private

def idv_session
if user_session && current_user
@idv_session ||= Idv::Session.new(
Expand All @@ -80,6 +76,8 @@ def idv_session
end
end

private

def document_capture_session
if idv_session&.document_capture_session_uuid # standard flow
DocumentCaptureSession.find_by(uuid: idv_session.document_capture_session_uuid)
Expand All @@ -92,12 +90,6 @@ def proofer
@proofer ||= EnrollmentHelper.usps_proofer
end

def add_proofing_component
ProofingComponent.
create_or_find_by(user: current_or_hybrid_user).
update(document_check: Idp::Constants::Vendors::USPS)
end

def localized_locations(locations)
return nil if locations.nil?
locations.map do |location|
Expand Down
1 change: 0 additions & 1 deletion app/controllers/idv/link_sent_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def analytics_arguments
end

def handle_document_verification_success
save_proofing_components(current_user)
extract_pii_from_doc(current_user, store_in_session: true)
idv_session.flow_path = 'hybrid'
end
Expand Down
5 changes: 0 additions & 5 deletions app/controllers/idv/personal_key_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ def show
if pii_is_missing?
redirect_to_retrieve_pii
else
add_proofing_component
finish_idv_session
end
end
Expand Down Expand Up @@ -78,10 +77,6 @@ def next_step
end
end

def add_proofing_component
ProofingComponent.find_or_create_by(user: current_user).update(verified_at: Time.zone.now)
end

def finish_idv_session
@code = personal_key
@personal_key_generated_at = current_user.personal_key_generated_at
Expand Down
10 changes: 2 additions & 8 deletions app/controllers/redirect/redirect_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@ def partner_params
}.compact
end

def redirect_to_and_log(url, event: nil, tracker_method: analytics.method(:external_redirect))
if event
# Once all events have been moved to tracker methods, we can remove the event: param
analytics.track_event(event, redirect_url: url, **location_params)
else
tracker_method.call(redirect_url: url, **location_params)
end

def redirect_to_and_log(url, tracker_method: analytics.method(:external_redirect))
tracker_method.call(redirect_url: url, **location_params)
redirect_url = UriService.add_params(url, partner_params)
redirect_to(redirect_url, allow_other_host: true)
end
Expand Down
1 change: 1 addition & 0 deletions app/controllers/sign_up/select_email_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def show
@user_emails = user_emails
@last_sign_in_email_address = last_email
@select_email_form = build_select_email_form
@can_add_email = EmailPolicy.new(current_user).can_add_email?
analytics.sp_select_email_visited(needs_completion_screen_reason:)
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/socure_webhook_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def fetch_results
if IdentityConfig.store.ruby_workers_idv_enabled
SocureDocvResultsJob.perform_later(document_capture_session_uuid: dcs.uuid)
else
SocureDocvResultsJob.perform_now(document_capture_session_uuid: dcs.uuid)
SocureDocvResultsJob.perform_now(document_capture_session_uuid: dcs.uuid, async: false)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def mark_profile_inactive

user.active_profile&.deactivate(:password_reset)
Funnel::DocAuth::ResetSteps.call(@user.id)
user.proofing_component&.destroy
end

def extra_analytics_attributes
Expand Down
1 change: 0 additions & 1 deletion app/forms/reset_password_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def mark_profile_inactive

active_profile.deactivate(:password_reset)
Funnel::DocAuth::ResetSteps.call(user.id)
user.proofing_component&.destroy
end

# It is possible for an account that is resetting their password to be "invalid".
Expand Down
30 changes: 30 additions & 0 deletions app/javascript/packages/webauthn/is-expected-error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,34 @@ describe('isExpectedWebauthnError', () => {

expect(result).to.be.true();
});

it('returns true for a NotReadableError Android credential manager incompatibility', () => {
const error = new DOMException(
'An unknown error occurred while talking to the credential manager.',
'NotReadableError',
);
const result = isExpectedWebauthnError(error);

expect(result).to.be.true();
});

it('returns false for NotSupportedError when not during verification', () => {
const error = new DOMException(
'The user agent does not support public key credentials.',
'NotSupportedError',
);
const result = isExpectedWebauthnError(error);

expect(result).to.be.false();
});

it('returns true for NotSupportedError during verification', () => {
const error = new DOMException(
'The user agent does not support public key credentials.',
'NotSupportedError',
);
const result = isExpectedWebauthnError(error, { isVerifying: true });

expect(result).to.be.true();
});
});
50 changes: 30 additions & 20 deletions app/javascript/packages/webauthn/is-expected-error.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import isUserVerificationScreenLockError from './is-user-verification-screen-lock-error';

/**
* Set of expected DOM exceptions, which occur based on some user behavior that is not noteworthy:
*
* - Declining permissions
* - Timeout due to inactivity
* - Invalid state such as duplicate key enrollment
*
* @see https://webidl.spec.whatwg.org/#idl-DOMException
* Functions to test whether an error is expected and should not be logged for further analysis.
*/
const EXPECTED_DOM_EXCEPTIONS: Set<string> = new Set([
'NotAllowedError',
'TimeoutError',
'InvalidStateError',
]);
const EXPECTED_ERRORS: Array<(error: Error, options: IsExpectedErrorOptions) => boolean> = [
// A user who is unable to complete due to following DOMException reasons is not noteworthy:
//
// - Declining permissions
// - Timeout due to inactivity
// - Invalid state such as duplicate key enrollment
(error) =>
error.name === 'NotAllowedError' ||
error.name === 'TimeoutError' ||
error.name === 'InvalidStateError',
// Some indication of incompatibilities on specific Android devices, either phone itself or
// through credential manager.
//
// See: https://community.bitwarden.com/t/android-mobile-yubikey-5-nfc-webauth/51732
// See: https://www.reddit.com/r/GooglePixel/comments/17enqf3/pixel_7_pro_unable_to_setup_passkeys/
(error) =>
error.name === 'NotReadableError' &&
error.message === 'An unknown error occurred while talking to the credential manager.',
// A user can choose to authenticate with Face or Touch Unlock from another device from what
// they set up from, which may not necessarily support platform authenticators.
(error, { isVerifying }) => isVerifying && isUserVerificationScreenLockError(error),
(error, { isVerifying }) =>
isVerifying &&
error.name === 'NotSupportedError' &&
error.message === 'The user agent does not support public key credentials.',
];

interface IsExpectedErrorOptions {
/**
Expand All @@ -22,14 +37,9 @@ interface IsExpectedErrorOptions {
isVerifying: boolean;
}

function isExpectedWebauthnError(
const isExpectedWebauthnError = (
error: Error,
{ isVerifying }: Partial<IsExpectedErrorOptions> = {},
): boolean {
return (
(error instanceof DOMException && EXPECTED_DOM_EXCEPTIONS.has(error.name)) ||
(!!isVerifying && isUserVerificationScreenLockError(error))
);
}
{ isVerifying = false }: Partial<IsExpectedErrorOptions> = {},
): boolean => EXPECTED_ERRORS.some((isExpected) => isExpected(error, { isVerifying }));

export default isExpectedWebauthnError;
4 changes: 4 additions & 0 deletions app/jobs/data_warehouse/base_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@ def upload_file_to_s3_bucket(path:, body:, content_type:, bucket: bucket_name)
logger.debug("#{class_name}: upload completed to #{url}")
url
end

def data_warehouse_disabled?
!IdentityConfig.store.data_warehouse_enabled
end
end
end
2 changes: 2 additions & 0 deletions app/jobs/data_warehouse/table_summary_stats_export_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class TableSummaryStatsExportJob < BaseJob
REPORT_NAME = 'table_summary_stats'

def perform(timestamp)
return if data_warehouse_disabled?

data = fetch_table_max_ids_and_counts(timestamp)
upload_to_s3(data, timestamp)
end
Expand Down
21 changes: 12 additions & 9 deletions app/jobs/resolution_proofing_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def perform(
timing: timer.results,
)

if IdentityConfig.store.idv_socure_shadow_mode_enabled
if use_shadow_mode?(user:)
SocureShadowModeProofingJob.perform_later(
document_capture_session_result_id: document_capture_session&.result_id,
encrypted_arguments:,
Expand All @@ -85,6 +85,17 @@ def perform(
end
end

def use_shadow_mode?(user:)
IdentityConfig.store.idv_socure_shadow_mode_enabled &&
AbTests::SOCURE_IDV_SHADOW_MODE.bucket(
request: nil,
service_provider: nil,
session: nil,
user:,
user_session: nil,
) == :shadow_mode_enabled
end

private

# @return [CallbackLogData]
Expand All @@ -108,7 +119,6 @@ def make_vendor_proofing_requests(
)

log_threatmetrix_info(result.device_profiling_result, user)
add_threatmetrix_proofing_component(user.id, result.device_profiling_result) if user.present?

CallbackLogData.new(
device_profiling_success: result.device_profiling_result.success?,
Expand Down Expand Up @@ -139,11 +149,4 @@ def logger_info_hash(hash)
def progressive_proofer
@progressive_proofer ||= Proofing::Resolution::ProgressiveProofer.new
end

def add_threatmetrix_proofing_component(user_id, threatmetrix_result)
ProofingComponent.
create_or_find_by(user_id: user_id).
update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?,
threatmetrix_review_status: threatmetrix_result.review_status)
end
end
Loading