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
11 changes: 1 addition & 10 deletions app/controllers/idv/unavailable_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,7 @@ class UnavailableController < ApplicationController
before_action :redirect_if_idv_available_and_from_create_account

def show
analytics.vendor_outage(
vendor_status: {
acuant: IdentityConfig.store.vendor_status_acuant,
lexisnexis_instant_verify: IdentityConfig.store.vendor_status_lexisnexis_instant_verify,
lexisnexis_trueid: IdentityConfig.store.vendor_status_lexisnexis_trueid,
sms: IdentityConfig.store.vendor_status_sms,
voice: IdentityConfig.store.vendor_status_voice,
},
redirect_from: from,
)
OutageStatus.new.track_event(analytics, redirect_from: from)
end

private
Expand Down
20 changes: 15 additions & 5 deletions app/controllers/users/webauthn_setup_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ def new
platform_authenticator: @platform_authenticator,
url_options:,
)
properties = result.to_h.merge(analytics_properties)
analytics.webauthn_setup_visit(**properties)
analytics.webauthn_setup_visit(
platform_authenticator: result.extra[:platform_authenticator],
in_account_creation_flow: user_session[:in_account_creation_flow] || false,
enabled_mfa_methods_count: result.extra[:enabled_mfa_methods_count],
)
save_challenge_in_session
@exclude_credentials = exclude_credentials
@need_to_set_up_additional_mfa = need_to_set_up_additional_mfa?
Expand All @@ -42,6 +45,14 @@ def new
end
end

if result.errors.present?
analytics.webauthn_setup_submitted(
platform_authenticator: form.platform_authenticator?,
errors: result.errors,
success: false,
)
end

flash_error(result.errors) unless result.success?
end

Expand Down Expand Up @@ -151,10 +162,9 @@ def save_challenge_in_session

def process_valid_webauthn(form)
create_user_event(:webauthn_key_added)
mfa_user = MfaContext.new(current_user)
analytics.multi_factor_auth_added_webauthn(
analytics.webauthn_setup_submitted(
platform_authenticator: form.platform_authenticator?,
enabled_mfa_methods_count: mfa_user.enabled_mfa_methods_count,
success: true,
)
handle_remember_device_preference(params[:remember_device])
if form.platform_authenticator?
Expand Down
11 changes: 11 additions & 0 deletions app/forms/idv/api_image_upload_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def extra_attributes
user_id: user_uuid,
pii_like_keypaths: DocPiiForm.pii_like_keypaths,
flow_path: params[:flow_path],
phone_with_camera: phone_with_camera,
}

@extra_attributes[:front_image_fingerprint] = front_image_fingerprint
Expand Down Expand Up @@ -300,6 +301,7 @@ def update_analytics(client_response:, vendor_request_time_in_ms:)
client_image_metrics: image_metadata,
async: false,
flow_path: params[:flow_path],
phone_with_camera: phone_with_camera,
vendor_request_time_in_ms: vendor_request_time_in_ms,
).except(:classification_info).
merge(acuant_sdk_upgrade_ab_test_data).
Expand Down Expand Up @@ -473,5 +475,14 @@ def store_failed_images(client_response, doc_pii_response)
def image_resubmission_check?
IdentityConfig.store.doc_auth_check_failed_image_resubmission_enabled
end

def phone_with_camera
case params[:phone_with_camera]
when 'true'
true
when 'false'
false
end
Comment on lines +480 to +485
Copy link
Copy Markdown
Contributor

@zachmargolis zachmargolis Nov 8, 2023

Choose a reason for hiding this comment

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

(not blocking) I missed it in the original PR, but my favorite trick to coerce strings to bools is:

Suggested change
case params[:phone_with_camera]
when 'true'
true
when 'false'
false
end
params[:phone_with_camera].to_s == 'true'

end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Button } from '@18f/identity-components';
import { useI18n } from '@18f/identity-react-i18n';
import { useContext } from 'react';
import FlowContext from '@18f/identity-verify-flow/context/flow-context';
import { addSearchParams, forceRedirect, Navigate } from '@18f/identity-url';
import { getConfigValue } from '@18f/identity-config';
import AnalyticsContext from '../context/analytics';
import { ServiceProviderContext } from '../context';

export interface DocumentCaptureNotReadyProps {
navigate?: Navigate;
}

function DocumentCaptureNotReady({ navigate }: DocumentCaptureNotReadyProps) {
const { t } = useI18n();
const { trackEvent } = useContext(AnalyticsContext);
const { currentStep } = useContext(FlowContext);
const { name: spName, failureToProofURL } = useContext(ServiceProviderContext);
const appName = getConfigValue('appName');
const handleExit = () => {
trackEvent('IdV: docauth not ready link clicked');
forceRedirect(
addSearchParams(spName ? failureToProofURL : '/account', {
step: currentStep,
location: 'not_ready',
}),
navigate,
);
};

return (
<>
<h2 className="h3">{t('doc_auth.not_ready.header')}</h2>
<p>
{spName
? t('doc_auth.not_ready.content_sp', {
sp_name: spName,
app_name: appName,
})
: t('doc_auth.not_ready.content_nosp', {
app_name: appName,
})}
</p>
<Button isUnstyled className="margin-top-1" onClick={handleExit}>
{spName
? t('doc_auth.not_ready.button_sp', { app_name: appName, sp_name: spName })
: t('doc_auth.not_ready.button_nosp')}
</Button>
</>
);
}

export default DocumentCaptureNotReady;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useContext } from 'react';
import { PageHeading } from '@18f/identity-components';
import {
FormStepError,
Expand All @@ -10,6 +11,8 @@ import { useI18n } from '@18f/identity-react-i18n';
import UnknownError from './unknown-error';
import TipList from './tip-list';
import DocumentSideAcuantCapture from './document-side-acuant-capture';
import DocumentCaptureNotReady from './document-capture-not-ready';
import { FeatureFlagContext } from '../context';

interface DocumentCaptureReviewIssuesProps {
isFailedDocType: boolean;
Expand Down Expand Up @@ -43,6 +46,7 @@ function DocumentCaptureReviewIssues({
hasDismissed,
}: DocumentCaptureReviewIssuesProps) {
const { t } = useI18n();
const { notReadySectionEnabled } = useContext(FeatureFlagContext);
return (
<>
<PageHeading>{t('doc_auth.headings.review_issues')}</PageHeading>
Expand Down Expand Up @@ -78,6 +82,7 @@ function DocumentCaptureReviewIssues({
/>
))}
<FormStepsButton.Submit />
{notReadySectionEnabled && <DocumentCaptureNotReady />}
<Cancel />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import DocumentSideAcuantCapture from './document-side-acuant-capture';
import DeviceContext from '../context/device';
import UploadContext from '../context/upload';
import TipList from './tip-list';
import DocumentCaptureNotReady from './document-capture-not-ready';
import { FeatureFlagContext } from '../context';

/**
* @typedef {'front'|'back'} DocumentSide
Expand Down Expand Up @@ -43,7 +45,7 @@ function DocumentsStep({
const { isMobile } = useContext(DeviceContext);
const { isLastStep } = useContext(FormStepsContext);
const { flowPath } = useContext(UploadContext);

const { notReadySectionEnabled } = useContext(FeatureFlagContext);
return (
<>
{flowPath === 'hybrid' && <HybridDocCaptureWarning className="margin-bottom-4" />}
Expand All @@ -70,7 +72,7 @@ function DocumentsStep({
/>
))}
{isLastStep ? <FormStepsButton.Submit /> : <FormStepsButton.Continue />}

{notReadySectionEnabled && <DocumentCaptureNotReady />}
<Cancel />
</>
);
Expand Down
17 changes: 17 additions & 0 deletions app/javascript/packages/document-capture/context/feature-flag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createContext } from 'react';

export interface FeatureFlagContextProps {
/**
* Specify whether to show the not-ready section on doc capture screen.
* Populated from backend configuration
*/
notReadySectionEnabled: boolean;
}

const FeatureFlagContext = createContext<FeatureFlagContextProps>({
notReadySectionEnabled: false,
});

FeatureFlagContext.displayName = 'FeatureFlagContext';

export default FeatureFlagContext;
1 change: 1 addition & 0 deletions app/javascript/packages/document-capture/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export {
} from './failed-capture-attempts';
export type { DeviceContextValue } from './device';
export { default as InPersonContext } from './in-person';
export { default as FeatureFlagContext } from './feature-flag';
13 changes: 13 additions & 0 deletions app/javascript/packs/document-capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
FailedCaptureAttemptsContextProvider,
MarketingSiteContextProvider,
InPersonContext,
FeatureFlagContext,
} from '@18f/identity-document-capture';
import { isCameraCapableMobile } from '@18f/identity-device';
import { FlowContext } from '@18f/identity-verify-flow';
Expand All @@ -33,6 +34,7 @@ interface AppRootData {
exitUrl: string;
idvInPersonUrl?: string;
securityAndPrivacyHowItWorksUrl: string;
uiNotReadySectionEnabled: string;
}

const appRoot = document.getElementById('document-capture-form')!;
Expand Down Expand Up @@ -98,6 +100,8 @@ const {
inPersonOutageMessageEnabled,
inPersonOutageExpectedUpdateDate,
usStatesTerritories = '',
phoneWithCamera = '',
uiNotReadySectionEnabled = '',
} = appRoot.dataset as DOMStringMap & AppRootData;

let parsedUsStatesTerritories = [];
Expand Down Expand Up @@ -147,6 +151,7 @@ const App = composeComponents(
isMockClient,
formData,
flowPath,
phoneWithCamera,
},
],
[
Expand All @@ -173,6 +178,14 @@ const App = composeComponents(
maxSubmissionAttemptsBeforeNativeCamera: Number(maxSubmissionAttemptsBeforeNativeCamera),
},
],
[
FeatureFlagContext.Provider,
{
value: {
notReadySectionEnabled: String(uiNotReadySectionEnabled) === 'true',
},
},
],
[
DocumentCapture,
{
Expand Down
2 changes: 2 additions & 0 deletions app/models/disposable_domain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class DisposableDomain < ApplicationRecord
end
45 changes: 21 additions & 24 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,7 @@ def idv_doc_auth_ssn_visited(**extra)
# @param [String] back_image_fingerprint Fingerprint of back image data
# @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw
# @param [String] phone_question_ab_test_bucket Prompt user with phone question before doc auth
# @param [String] phone_with_camera the result of the phone question a/b test
Copy link
Copy Markdown
Contributor

@zachmargolis zachmargolis Nov 8, 2023

Choose a reason for hiding this comment

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

(not blocking) also missed in the original PR, but this is a boolean right?

Suggested change
# @param [String] phone_with_camera the result of the phone question a/b test
# @param [Boolean] phone_with_camera the result of the phone question a/b test

# The document capture image uploaded was locally validated during the IDV process
def idv_doc_auth_submitted_image_upload_form(
success:,
Expand All @@ -988,6 +989,7 @@ def idv_doc_auth_submitted_image_upload_form(
back_image_fingerprint: nil,
getting_started_ab_test_bucket: nil,
phone_question_ab_test_bucket: nil,
phone_with_camera: nil,
**extra
)
track_event(
Expand All @@ -1002,6 +1004,7 @@ def idv_doc_auth_submitted_image_upload_form(
back_image_fingerprint: back_image_fingerprint,
getting_started_ab_test_bucket: getting_started_ab_test_bucket,
phone_question_ab_test_bucket: phone_question_ab_test_bucket,
phone_with_camera: phone_with_camera,
**extra,
)
end
Expand All @@ -1023,6 +1026,7 @@ def idv_doc_auth_submitted_image_upload_form(
# @param [String] back_image_fingerprint Fingerprint of back image data
# @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw
# @param [String] phone_question_ab_test_bucket Prompt user with phone question before doc auth
# @param [String] phone_with_camera the result of the phone question a/b test
# The document capture image was uploaded to vendor during the IDV process
def idv_doc_auth_submitted_image_upload_vendor(
success:,
Expand All @@ -1041,6 +1045,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
back_image_fingerprint: nil,
getting_started_ab_test_bucket: nil,
phone_question_ab_test_bucket: nil,
phone_with_camera: nil,
**extra
)
track_event(
Expand All @@ -1062,6 +1067,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
back_image_fingerprint: back_image_fingerprint,
getting_started_ab_test_bucket: getting_started_ab_test_bucket,
phone_question_ab_test_bucket: phone_question_ab_test_bucket,
phone_with_camera: phone_with_camera,
**extra,
)
end
Expand Down Expand Up @@ -3181,24 +3187,6 @@ def multi_factor_auth_added_totp(enabled_mfa_methods_count:, in_account_creation
)
end

# Tracks when the user has added the MFA method webauthn to their account
# @param [Boolean] platform_authenticator indicates if webauthn_platform was used
# @param [Integer] enabled_mfa_methods_count number of registered mfa methods for the user
def multi_factor_auth_added_webauthn(
platform_authenticator:,
enabled_mfa_methods_count:, **extra
)
track_event(
'Multi-Factor Authentication: Added webauthn',
{
method_name: :webauthn,
platform_authenticator: platform_authenticator,
enabled_mfa_methods_count: enabled_mfa_methods_count,
**extra,
}.compact,
)
end

# A user has downloaded their backup codes
def multi_factor_auth_backup_code_download
track_event('Multi-Factor Authentication: download backup code')
Expand Down Expand Up @@ -4804,18 +4792,27 @@ def webauthn_deleted(success:, mfa_method_counts:, pii_like_keypaths:, **extra)
end

# @param [Hash] platform_authenticator
# @param [Hash] errors
# @param [Integer] enabled_mfa_methods_count
# @param [Boolean] success
# @param [Hash, nil] errors
# Tracks whether or not Webauthn setup was successful
def webauthn_setup_submitted(platform_authenticator:, success:, errors: nil, **extra)
track_event(
:webauthn_setup_submitted,
platform_authenticator: platform_authenticator,
success: success,
errors: errors,
**extra,
)
end

# @param [Hash] platform_authenticator
# @param [Integer] enabled_mfa_methods_count
# Tracks when WebAuthn setup is visited
def webauthn_setup_visit(platform_authenticator:, errors:, enabled_mfa_methods_count:, success:,
**extra)
def webauthn_setup_visit(platform_authenticator:, enabled_mfa_methods_count:, **extra)
track_event(
'WebAuthn Setup Visited',
platform_authenticator: platform_authenticator,
errors: errors,
enabled_mfa_methods_count: enabled_mfa_methods_count,
success: success,
**extra,
)
end
Expand Down
Loading