diff --git a/app/assets/fonts/glyphs.txt b/app/assets/fonts/glyphs.txt index e8fe58d5e2c..ed2170c7ce2 100644 --- a/app/assets/fonts/glyphs.txt +++ b/app/assets/fonts/glyphs.txt @@ -1 +1 @@ - !"#$%&'()+,-./0123456789:;>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ «»¿ÀÁÈÉÊÎÓÚàáâãçèéêëíîïñóôùúû‑—‘’“”…‹中体文简() + !"#$%&'()+,-./0123456789:;>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ «»¿ÀÁÉÊÎÚàáâãçèéêëíîïñóôùúû‑—‘’“”…‹中体文简() diff --git a/app/assets/fonts/public-sans/PublicSans-Black.woff2 b/app/assets/fonts/public-sans/PublicSans-Black.woff2 index c979fce12f1..e54e44c4a45 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Black.woff2 and b/app/assets/fonts/public-sans/PublicSans-Black.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-BlackItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-BlackItalic.woff2 index c94e87ffac3..ca8c718d8a2 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-BlackItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-BlackItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-Bold.woff2 b/app/assets/fonts/public-sans/PublicSans-Bold.woff2 index 196295dcd53..922492a24c8 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Bold.woff2 and b/app/assets/fonts/public-sans/PublicSans-Bold.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-BoldItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-BoldItalic.woff2 index d4c9e303597..3a7836af5dc 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-BoldItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-BoldItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-ExtraBold.woff2 b/app/assets/fonts/public-sans/PublicSans-ExtraBold.woff2 index c530ec8a2e4..f2b5d18fdf0 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-ExtraBold.woff2 and b/app/assets/fonts/public-sans/PublicSans-ExtraBold.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-ExtraBoldItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-ExtraBoldItalic.woff2 index fac7eba8f92..db4dbfcea5a 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-ExtraBoldItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-ExtraBoldItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-ExtraLight.woff2 b/app/assets/fonts/public-sans/PublicSans-ExtraLight.woff2 index fa75130b250..60cc8485623 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-ExtraLight.woff2 and b/app/assets/fonts/public-sans/PublicSans-ExtraLight.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-ExtraLightItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-ExtraLightItalic.woff2 index 3ec81ee675e..8292a699295 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-ExtraLightItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-ExtraLightItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-Italic.woff2 b/app/assets/fonts/public-sans/PublicSans-Italic.woff2 index ccec04ce01f..1214cf29867 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Italic.woff2 and b/app/assets/fonts/public-sans/PublicSans-Italic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-Light.woff2 b/app/assets/fonts/public-sans/PublicSans-Light.woff2 index d7f9c368e2c..5a82b737054 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Light.woff2 and b/app/assets/fonts/public-sans/PublicSans-Light.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-LightItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-LightItalic.woff2 index 8349b5e0d6c..c77b2b6f3ac 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-LightItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-LightItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-Medium.woff2 b/app/assets/fonts/public-sans/PublicSans-Medium.woff2 index ca145b0684a..748f22691e7 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Medium.woff2 and b/app/assets/fonts/public-sans/PublicSans-Medium.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-MediumItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-MediumItalic.woff2 index ce5ac0b138f..7cc0520c585 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-MediumItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-MediumItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-Regular.woff2 b/app/assets/fonts/public-sans/PublicSans-Regular.woff2 index a64cd2680a5..671e520c390 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Regular.woff2 and b/app/assets/fonts/public-sans/PublicSans-Regular.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-SemiBold.woff2 b/app/assets/fonts/public-sans/PublicSans-SemiBold.woff2 index 58e47850f40..632cd82cfe3 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-SemiBold.woff2 and b/app/assets/fonts/public-sans/PublicSans-SemiBold.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-SemiBoldItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-SemiBoldItalic.woff2 index bda89fd272b..3b05b408887 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-SemiBoldItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-SemiBoldItalic.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-Thin.woff2 b/app/assets/fonts/public-sans/PublicSans-Thin.woff2 index 8791cc384c0..6349cbaaddc 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-Thin.woff2 and b/app/assets/fonts/public-sans/PublicSans-Thin.woff2 differ diff --git a/app/assets/fonts/public-sans/PublicSans-ThinItalic.woff2 b/app/assets/fonts/public-sans/PublicSans-ThinItalic.woff2 index 8dd022ad5b9..3f866a55b77 100644 Binary files a/app/assets/fonts/public-sans/PublicSans-ThinItalic.woff2 and b/app/assets/fonts/public-sans/PublicSans-ThinItalic.woff2 differ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index de107c2bb95..1833c0db8e4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -541,6 +541,7 @@ def find_device_profiling_result(type) def user_duplicate_profiles_detected? return false unless sp_eligible_for_one_account? profile = current_user&.active_profile + return false unless profile DuplicateProfileConfirmation.where( profile_id: profile.id, confirmed_all: nil, diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index 53e006eb9bc..5e3f8a1e3c6 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -134,6 +134,15 @@ def track_document_request_event(document_request:, document_response:, timer:) analytics.idv_socure_document_request_submitted(**analytics_hash) end + def choose_id_type_path + idv_choose_id_type_path + end + + def doc_auth_upload_enabled? + # false for now until we consolidate this method with desktop_selfie_test_mode_enabled + false + end + private def track_document_issuing_state(user, state) diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index ae461f9d582..df7f8018141 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -84,11 +84,6 @@ def self.step_info private - def doc_auth_upload_enabled? - !(resolved_authn_context_result.facial_match? || - ab_test_bucket(:DOC_AUTH_MANUAL_UPLOAD_DISABLED) == :manual_upload_disabled) - end - def extra_view_variables { id_type:, @@ -101,6 +96,7 @@ def extra_view_variables skip_doc_auth_from_handoff: idv_session.skip_doc_auth_from_handoff, skip_doc_auth_from_socure: idv_session.skip_doc_auth_from_socure, opted_in_to_in_person_proofing: idv_session.opted_in_to_in_person_proofing, + choose_id_type_path: choose_id_type_path, doc_auth_selfie_capture: resolved_authn_context_result.facial_match?, doc_auth_upload_enabled: doc_auth_upload_enabled?, socure_errors_timeout_url: idv_socure_document_capture_errors_url(error_code: :timeout), diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index 13c362fbfb4..145ce1a618f 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -13,7 +13,7 @@ class HybridHandoffController < ApplicationController def show abandon_any_ipp_progress - @upload_disabled = upload_disabled? + @upload_enabled = upload_enabled? @direct_ipp_with_selfie_enabled = IdentityConfig.store.in_person_doc_auth_button_enabled && Idv::InPersonConfig.enabled_for_issuer?( @@ -69,12 +69,12 @@ def self.step_info key: :hybrid_handoff, controller: self, next_steps: [:choose_id_type, :link_sent, :document_capture, :socure_document_capture], - preconditions: ->(idv_session:, user:) { - idv_session.idv_consent_given? && - (self.selected_remote(idv_session: idv_session) || # from opt-in screen - # back from ipp doc capture screen - idv_session.skip_doc_auth_from_handoff) - }, + preconditions: ->(idv_session:, user:) do + idv_session.idv_consent_given? && + (self.selected_remote(idv_session: idv_session) || # from opt-in screen + # back from ipp doc capture screen + idv_session.skip_doc_auth_from_handoff) + end, undo_step: ->(idv_session:, user:) do idv_session.flow_path = nil idv_session.phone_for_mobile_flow = nil @@ -147,15 +147,8 @@ def sp_or_app_name current_sp&.friendly_name.presence || APP_NAME end - def upload_disabled? - return true if document_capture_session.doc_auth_vendor == Idp::Constants::Vendors::SOCURE - - (idv_session.selfie_check_required || doc_auth_upload_disabled?) && - !idv_session.desktop_selfie_test_mode_enabled? - end - - def doc_auth_upload_disabled? - ab_test_bucket(:DOC_AUTH_MANUAL_UPLOAD_DISABLED) == :manual_upload_disabled + def upload_enabled? + idv_session.desktop_selfie_test_mode_enabled? end def build_telephony_form_response(telephony_result) diff --git a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb index 4c349dc1368..4bae33d0115 100644 --- a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb @@ -64,6 +64,7 @@ def extra_view_variables document_capture_session_uuid: document_capture_session_uuid, failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'), doc_auth_selfie_capture: resolved_authn_context_result.facial_match?, + choose_id_type_path: choose_id_type_path, doc_auth_upload_enabled: doc_auth_upload_enabled?, skip_doc_auth_from_socure: @skip_doc_auth_from_socure, socure_errors_timeout_url: idv_hybrid_mobile_socure_document_capture_errors_url( @@ -76,12 +77,6 @@ def extra_view_variables private - def doc_auth_upload_enabled? - !(resolved_authn_context_result.facial_match? || - ab_test_bucket(:DOC_AUTH_MANUAL_UPLOAD_DISABLED, user: document_capture_user) == - :manual_upload_disabled) - end - def analytics_arguments { flow_path: 'hybrid', diff --git a/app/controllers/idv/in_person/address_controller.rb b/app/controllers/idv/in_person/address_controller.rb index 65228f802a6..a734c9aa05a 100644 --- a/app/controllers/idv/in_person/address_controller.rb +++ b/app/controllers/idv/in_person/address_controller.rb @@ -49,11 +49,12 @@ def self.step_info key: :ipp_address, controller: self, next_steps: [:ipp_ssn], - preconditions: ->(idv_session:, user:) { + preconditions: ->(idv_session:, user:) do # Handling passport navigation with checking in_person_passports_allowed? since passport # form is not setup yet. This should be updated during LG-15985 implmentation. - idv_session.ipp_state_id_complete? || idv_session.in_person_passports_allowed? - }, + (idv_session.ipp_state_id_complete? || idv_session.in_person_passports_allowed?) && + user.has_establishing_in_person_enrollment? + end, undo_step: ->(idv_session:, user:) do idv_session.invalidate_in_person_address_step! end, diff --git a/app/controllers/idv/in_person/ssn_controller.rb b/app/controllers/idv/in_person/ssn_controller.rb index daa27e3be31..95de33d782a 100644 --- a/app/controllers/idv/in_person/ssn_controller.rb +++ b/app/controllers/idv/in_person/ssn_controller.rb @@ -71,9 +71,10 @@ def self.step_info key: :ipp_ssn, controller: self, next_steps: [:ipp_verify_info], - preconditions: ->(idv_session:, user:) { - idv_session.ipp_document_capture_complete? - }, + preconditions: ->(idv_session:, user:) do + idv_session.ipp_document_capture_complete? && + user.has_establishing_in_person_enrollment? + end, undo_step: ->(idv_session:, user:) { idv_session.invalidate_ssn_step! }, diff --git a/app/controllers/idv/in_person/state_id_controller.rb b/app/controllers/idv/in_person/state_id_controller.rb index f37a8dd42f1..1a67a0c0a20 100644 --- a/app/controllers/idv/in_person/state_id_controller.rb +++ b/app/controllers/idv/in_person/state_id_controller.rb @@ -79,7 +79,10 @@ def self.step_info key: :ipp_state_id, controller: self, next_steps: [:ipp_address, :ipp_ssn], - preconditions: ->(idv_session:, user:) { user.has_establishing_in_person_enrollment? }, + preconditions: ->(idv_session:, user:) do + user.has_establishing_in_person_enrollment? && + !idv_session.opted_in_to_in_person_proofing.nil? + end, undo_step: ->(idv_session:, user:) do idv_session.invalidate_in_person_pii_from_user! end, diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 7aa0df576b3..9f000d7916d 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -40,7 +40,8 @@ def self.step_info next_steps: [:phone], preconditions: ->(idv_session:, user:) do idv_session.ssn && idv_session.ipp_document_capture_complete? && - threatmetrix_session_id_present_or_not_required?(idv_session:) + threatmetrix_session_id_present_or_not_required?(idv_session:) && + user.has_establishing_in_person_enrollment? end, undo_step: ->(idv_session:, user:) do idv_session.residential_resolution_vendor = nil diff --git a/app/javascript/packages/components/link.tsx b/app/javascript/packages/components/link.tsx index 28e8a300710..e4f3f4736e6 100644 --- a/app/javascript/packages/components/link.tsx +++ b/app/javascript/packages/components/link.tsx @@ -50,7 +50,7 @@ function Link({ const handleClick = () => { // Clear the onbeforeunload event to prevent the "Leave site?" prompt - if (href === 'choose_id_type') { + if (href === '/verify/choose_id_type') { window.onbeforeunload = null; } }; diff --git a/app/javascript/packages/document-capture/components/document-capture-troubleshooting-options.tsx b/app/javascript/packages/document-capture/components/document-capture-troubleshooting-options.tsx index 65fea4f07c5..b784573136e 100644 --- a/app/javascript/packages/document-capture/components/document-capture-troubleshooting-options.tsx +++ b/app/javascript/packages/document-capture/components/document-capture-troubleshooting-options.tsx @@ -29,7 +29,7 @@ function DocumentCaptureTroubleshootingOptions({ showDocumentTips = true, }: DocumentCaptureTroubleshootingOptionsProps) { const { t } = useI18n(); - const { inPersonURL, passportEnabled } = useContext(InPersonContext); + const { chooseIdTypePath, inPersonURL, passportEnabled } = useContext(InPersonContext); const { getHelpCenterURL } = useContext(MarketingSiteContext); return ( @@ -40,7 +40,7 @@ function DocumentCaptureTroubleshootingOptions({ options={ [ passportEnabled && { - url: 'choose_id_type', + url: chooseIdTypePath, text: t('idv.troubleshooting.options.use_another_id_type'), isExternal: false, }, diff --git a/app/javascript/packages/document-capture/components/general-error.tsx b/app/javascript/packages/document-capture/components/general-error.tsx index 30e1d55755b..c5b00bbf9d7 100644 --- a/app/javascript/packages/document-capture/components/general-error.tsx +++ b/app/javascript/packages/document-capture/components/general-error.tsx @@ -5,6 +5,7 @@ import { FormStepError } from '@18f/identity-form-steps'; import { Link } from '@18f/identity-components'; import formatHTML from '@18f/identity-react-i18n/format-html'; import MarketingSiteContext from '../context/marketing-site'; +import { InPersonContext } from '../context'; interface GeneralErrorProps extends ComponentProps<'p'> { unknownFieldErrors: FormStepError<{ front: string; back: string }>[]; @@ -41,6 +42,10 @@ function getError({ unknownFieldErrors }: GetErrorArguments) { return err; } +function isNetworkError(unknownFieldErrors) { + return unknownFieldErrors.some((error) => error.field === 'network'); +} + function GeneralError({ unknownFieldErrors = [], isFailedDocType = false, @@ -53,6 +58,7 @@ function GeneralError({ }: GeneralErrorProps) { const { t } = useI18n(); const { getHelpCenterURL } = useContext(MarketingSiteContext); + const { chooseIdTypePath } = useContext(InPersonContext); const helpCenterLink = getHelpCenterURL({ category: 'verify-your-identity', article: 'how-to-add-images-of-your-state-issued-id', @@ -66,6 +72,7 @@ function GeneralError({ }); const err = getError({ unknownFieldErrors }); + const isNetwork = isNetworkError(unknownFieldErrors); if (isFailedDocType && !!altFailedDocTypeMsg) { return ( @@ -94,7 +101,18 @@ function GeneralError({ ); } if (isPassportError) { - return

{t('doc_auth.info.review_passport')}

; + if (!isNetwork) { + return

{t('doc_auth.info.review_passport')}

; + } + return ( +

+ {t('doc_auth.errors.general.network_error_passport')}{' '} + + {t('doc_auth.errors.general.network_error_passport_link_text')} + {' '} + {t('doc_auth.errors.general.network_error_passport_ending')} +

+ ); } if (err && !hasDismissed) { return

{err.message}

; diff --git a/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.spec.tsx b/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.spec.tsx index 6f2c2ac76ce..5b3dec0f412 100644 --- a/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.spec.tsx +++ b/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.spec.tsx @@ -42,20 +42,6 @@ describe('HybridDocCaptureWarning', () => { t('doc_auth.hybrid_flow_warning.explanation_non_sp_html'), ); }); - - it('does not render a third list item pertaining to SP services', () => { - const { getByRole } = render( - - - , - ); - const alertElement = getByRole('status'); - const notExpectedString = t('doc_auth.hybrid_flow_warning.only_add_sp_services_html', { - service_provider_name: SP_NAME, - }); - - expect(alertElement.textContent).to.not.have.string(notExpectedString); - }); }); describe('with SP', () => { @@ -71,19 +57,6 @@ describe('HybridDocCaptureWarning', () => { service_provider_name: SP_NAME, }); - expect(alertElement.textContent).to.have.string(expectedString); - }); - it('renders a third list item pertaining to SP services', () => { - const { getByRole } = render( - - - , - ); - const alertElement = getByRole('status'); - const expectedString = t('doc_auth.hybrid_flow_warning.only_add_sp_services_html', { - service_provider_name: SP_NAME, - }); - expect(alertElement.textContent).to.have.string(expectedString); }); }); diff --git a/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.tsx b/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.tsx index f944aa59e11..ac1bb6ef6c3 100644 --- a/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.tsx +++ b/app/javascript/packages/document-capture/components/hybrid-doc-capture-warning.tsx @@ -27,14 +27,6 @@ function HybridDocCaptureWarning({ className = '' }: HybridDocCaptureWarningProp const serviceProviderName = spContext.name; const appName = getConfigValue('appName'); - const listHeadingText = t('doc_auth.hybrid_flow_warning.only_add_if_text'); - const ownAccountItemText = t('doc_auth.hybrid_flow_warning.only_add_own_account', { - app_name: appName, - }); - const phoneVerifyItemText = t('doc_auth.hybrid_flow_warning.only_add_phone_verify', { - app_name: appName, - }); - let spServicesItemText; let warningText = t('doc_auth.hybrid_flow_warning.explanation_non_sp_html', { app_name: appName, }); @@ -44,22 +36,11 @@ function HybridDocCaptureWarning({ className = '' }: HybridDocCaptureWarningProp app_name: appName, service_provider_name: serviceProviderName, }); - spServicesItemText = t('doc_auth.hybrid_flow_warning.only_add_sp_services_html', { - service_provider_name: serviceProviderName, - }); } return (

{formatWithStrong(warningText)}

-

- {listHeadingText} -

-
); } diff --git a/app/javascript/packages/document-capture/context/in-person.ts b/app/javascript/packages/document-capture/context/in-person.ts index 5639c0c4c6d..54c042eaa48 100644 --- a/app/javascript/packages/document-capture/context/in-person.ts +++ b/app/javascript/packages/document-capture/context/in-person.ts @@ -50,6 +50,12 @@ export interface InPersonContextProps { */ skipDocAuthFromSocure?: boolean; + /** + * path to choose ID type page, used when passports is enabled + */ + + chooseIdTypePath?: string; + /** * URL for Opt-in IPP, used when in_person_proofing_opt_in_enabled is enabled */ diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx index 9ce2c72e38d..b4001330c2e 100644 --- a/app/javascript/packs/document-capture.tsx +++ b/app/javascript/packs/document-capture.tsx @@ -41,6 +41,7 @@ interface AppRootData { howToVerifyURL: string; socureErrorsTimeoutURL: string; previousStepUrl: string; + chooseIdTypePath: string; docAuthPassportsEnabled: string; docAuthSelfieDesktopTestMode: string; docAuthUploadEnabled: string; @@ -123,6 +124,7 @@ const { howToVerifyUrl, socureErrorsTimeoutUrl, previousStepUrl, + chooseIdTypePath, docAuthPassportsEnabled, docAuthSelfieDesktopTestMode, locationsUrl: locationsURL, @@ -152,6 +154,7 @@ render( skipDocAuthFromHandoff: skipDocAuthFromHandoff === 'true', skipDocAuthFromSocure: skipDocAuthFromSocure === 'true', howToVerifyURL: howToVerifyUrl, + chooseIdTypePath, socureErrorsTimeoutURL: socureErrorsTimeoutUrl, passportEnabled: String(docAuthPassportsEnabled) === 'true', previousStepURL: previousStepUrl, diff --git a/app/jobs/data_warehouse/table_summary_stats_export_job.rb b/app/jobs/data_warehouse/table_summary_stats_export_job.rb index d54437dca0d..19fee242246 100644 --- a/app/jobs/data_warehouse/table_summary_stats_export_job.rb +++ b/app/jobs/data_warehouse/table_summary_stats_export_job.rb @@ -37,41 +37,81 @@ def fetch_log_group_counts(timestamp) to: timestamp.end_of_day, )[0] # set row count to 0 if nil or else convert to int - result[table_name].nil? ? result[table_name] = - { row_count: 0 } : result[table_name]['row_count'] = - result[table_name]['row_count'].to_i + offset = get_offset_count(log_group_name, timestamp) + if result[table_name].nil? + result[table_name] = { row_count: 0 } + else + result[table_name]['row_count'] = result[table_name]['row_count'].to_i - offset + end end end def log_groups - env = Identity::Hostdata.env [ "#{env}_/srv/idp/shared/log/events.log", "#{env}_/srv/idp/shared/log/production.log", ] end - def cloudwatch_client(log_group_name: nil) + def env + Identity::Hostdata.env + end + + def cloudwatch_client(log_group_name: nil, slice_interval: 1.day) Reporting::CloudwatchClient.new( log_group_name: log_group_name, ensure_complete_logs: false, + num_threads: 6, + slice_interval: slice_interval, ) end - def cloudwatch_query(log_group_name) - base_log_group = "#{Identity::Hostdata.env}_/srv/idp/shared/log" - log_stream_filter_map = { + def log_stream_filter_map + base_log_group = "#{env}_/srv/idp/shared/log" + { "#{base_log_group}/events.log" => "@logStream like 'worker-i-' or @logStream like 'idp-i-'", "#{base_log_group}/production.log" => "@logStream like 'idp-i-'", } + end + + def cloudwatch_query(log_group_name) <<~QUERY - fields @timestamp + fields jsonParse(@message) as @messageJson | filter #{log_stream_filter_map[log_group_name]} - | filter @message like /\\{.*/ + | filter isPresent(@messageJson) | stats count() as row_count QUERY end + def production_offset_query + log_group_name = "#{env}_/srv/idp/shared/log/production.log" + <<~QUERY + fields jsonParse(@message) as @messageJson, concat(@timestamp, @message) as ts_plus_message + | filter #{log_stream_filter_map[log_group_name]} + | filter isPresent(@messageJson) + | stats count() as cnt by ts_plus_message + | stats sum(cnt - 1) as offset_count + QUERY + end + + def get_offset_count(log_group_name, timestamp) + if log_group_name == "#{env}_/srv/idp/shared/log/production.log" + # For production logs, exclude count of duplicates with same message & timestamp + cw_client = cloudwatch_client( + log_group_name: log_group_name, + slice_interval: 30.minutes, + ) + result = cw_client.fetch( + query: production_offset_query, + from: timestamp.beginning_of_day, + to: timestamp.end_of_day, + ) + result.sum { |h| h['offset_count'].to_i } + else + 0 + end + end + def fetch_table_max_ids_and_counts(timestamp) Reports::BaseReport.transaction_with_timeout do max_ids_and_counts(timestamp) diff --git a/app/jobs/reports/irs_verification_demographics_report.rb b/app/jobs/reports/irs_verification_demographics_report.rb new file mode 100644 index 00000000000..1e20f6cf6b8 --- /dev/null +++ b/app/jobs/reports/irs_verification_demographics_report.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'csv' +require 'reporting/irs_verification_demographics_report' + +module Reports + class IrsVerificationDemographicsReport < BaseReport + REPORT_NAME = 'irs-verification-demographics-report' + + attr_reader :report_date + + def initialize(report_date = nil, *args, **rest) + @report_date = report_date + super(*args, **rest) + end + + def perform(date = Time.zone.yesterday.end_of_day) + @report_date = date + + email_addresses = emails.select(&:present?) + if email_addresses.empty? + Rails.logger.warn 'No email addresses - IRS Verification Demographics Report NOT SENT' + return false + end + + reports.each do |report| + upload_to_s3(report.table, report_name: report.filename) + end + + ReportMailer.tables_report( + email: email_addresses, + subject: "IRS Verification Demographics Metrics Report - #{report_date.to_date}", + reports: reports, + message: preamble, + attachment_format: :csv, + ).deliver_now + end + + # Explanatory text to go before the report in the email + # @return [String] + def preamble(env: Identity::Hostdata.env || 'local') + ERB.new(<<~ERB).result(binding).html_safe # rubocop:disable Rails/OutputSafety + <% if env != 'prod' %> +
+
+ <%# + NOTE: our AlertComponent doesn't support heading content like this uses, + so for a one-off outside the Rails pipeline it was easier to inline the HTML here. + %> +

+ Non-Production Report +

+

+ This was generated in the <%= env %> environment. +

+
+
+ <% end %> + ERB + end + + def reports + @reports ||= irs_verification_demographics_report.as_emailable_reports + end + + def irs_verification_demographics_report + @irs_verification_demographics_report ||= Reporting::IrsVerificationDemographicsReport.new( + issuers: issuers, + time_range: report_date.all_quarter, + ) + end + + def issuers + [*IdentityConfig.store.irs_verification_report_issuers] + end + + def emails + [*IdentityConfig.store.irs_verification_report_config] + end + + def upload_to_s3(report_body, report_name: nil) + _latest, path = generate_s3_paths(REPORT_NAME, 'csv', subname: report_name, now: report_date) + + if bucket_name.present? + upload_file_to_s3_bucket( + path: path, + body: csv_file(report_body), + content_type: 'text/csv', + bucket: bucket_name, + ) + end + end + + def csv_file(report_array) + CSV.generate do |csv| + report_array.each do |row| + csv << row + end + end + end + end +end diff --git a/app/services/agency_identity_linker.rb b/app/services/agency_identity_linker.rb index 5c25e7078ae..81ec64f25aa 100644 --- a/app/services/agency_identity_linker.rb +++ b/app/services/agency_identity_linker.rb @@ -51,7 +51,7 @@ def find_or_create_agency_identity def create_agency_identity_for_sp return unless agency_id - AgencyIdentity.create( + AgencyIdentity.create_or_find_by( agency_id: agency_id, user_id: @sp_identity.user_id, uuid: @sp_identity.uuid, diff --git a/app/services/doc_auth/classification_concern.rb b/app/services/doc_auth/classification_concern.rb index 61adf0f76b6..0630f329abd 100644 --- a/app/services/doc_auth/classification_concern.rb +++ b/app/services/doc_auth/classification_concern.rb @@ -20,14 +20,18 @@ def id_type_supported? alias_method :doc_type_supported?, :id_type_supported? - private + private # @param [Object] classification_info assureid classification info # @param [String] doc_side value of ['Front', 'Back'] def doc_side_class_ok?(classification_info, doc_side) side_type = classification_info&.with_indifferent_access&.dig(doc_side, :ClassName) !side_type.present? || - DocAuth::Response::ID_TYPE_SLUGS.key?(side_type) || + ( + IdentityConfig.store.doc_auth_passports_enabled ? + DocAuth::Response::ID_TYPE_SLUGS.key?(side_type) : + DocAuth::Response::STATE_ID_TYPE_SLUGS.key?(side_type) + ) || side_type == 'Unknown' end @@ -50,5 +54,5 @@ def doc_issuer_type_ok?(classification_info, doc_side) def supported_country_codes IdentityConfig.store.doc_auth_supported_country_codes end -end + end end diff --git a/app/services/doc_auth/dos/request.rb b/app/services/doc_auth/dos/request.rb index c05ea26f0a2..a37fdc8a7f1 100644 --- a/app/services/doc_auth/dos/request.rb +++ b/app/services/doc_auth/dos/request.rb @@ -86,7 +86,7 @@ def handle_connection_error( ) DocAuth::Response.new( success: false, - errors: { network: true }, + errors: { network: true, passport: true }, exception: exception, extra: { vendor: 'DoS', diff --git a/app/services/doc_auth/lexis_nexis/doc_pii_reader.rb b/app/services/doc_auth/lexis_nexis/doc_pii_reader.rb index f37f104bfa8..4251d0aa572 100644 --- a/app/services/doc_auth/lexis_nexis/doc_pii_reader.rb +++ b/app/services/doc_auth/lexis_nexis/doc_pii_reader.rb @@ -26,7 +26,9 @@ def read_pii(true_id_product) return nil unless id_auth_field_data.present? id_doc_type_slug = id_auth_field_data['Fields_DocumentClassName'] - @id_doc_type = DocAuth::Response::ID_TYPE_SLUGS[id_doc_type_slug] + @id_doc_type = IdentityConfig.store.doc_auth_passports_enabled ? + DocAuth::Response::ID_TYPE_SLUGS[id_doc_type_slug] : + DocAuth::Response::STATE_ID_TYPE_SLUGS[id_doc_type_slug] if id_doc_type == 'drivers_license' || id_doc_type == 'state_id_card' generate_state_id_pii diff --git a/app/services/doc_auth/response.rb b/app/services/doc_auth/response.rb index 925ca251a5c..e5c50d5e769 100644 --- a/app/services/doc_auth/response.rb +++ b/app/services/doc_auth/response.rb @@ -11,9 +11,10 @@ class Response ID_TYPE_SLUGS = { 'Identification Card' => 'state_id_card', 'Drivers License' => 'drivers_license', + 'Passport' => 'passport', }.freeze - SOCURE_ID_TYPE_SLUGS = { + STATE_ID_TYPE_SLUGS = { 'Identification Card' => 'state_id_card', 'Drivers License' => 'drivers_license', }.freeze diff --git a/app/services/doc_auth/socure/responses/docv_result_response.rb b/app/services/doc_auth/socure/responses/docv_result_response.rb index 20abbac5e8d..b23346b5672 100644 --- a/app/services/doc_auth/socure/responses/docv_result_response.rb +++ b/app/services/doc_auth/socure/responses/docv_result_response.rb @@ -201,7 +201,7 @@ def parse_date(date_string) end def id_type_supported? - DocAuth::Response::SOCURE_ID_TYPE_SLUGS.key?(document_id_type) + DocAuth::Response::STATE_ID_TYPE_SLUGS.key?(document_id_type) end def reason_codes_selfie_pass diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb index 848acd0ea54..ffa08e464c3 100644 --- a/app/views/idv/document_capture/show.html.erb +++ b/app/views/idv/document_capture/show.html.erb @@ -13,6 +13,7 @@ skip_doc_auth_from_handoff: skip_doc_auth_from_handoff, skip_doc_auth_from_socure: skip_doc_auth_from_socure, socure_errors_timeout_url: socure_errors_timeout_url, + choose_id_type_path: choose_id_type_path, doc_auth_selfie_capture: doc_auth_selfie_capture, doc_auth_upload_enabled: doc_auth_upload_enabled, mock_client: mock_client, diff --git a/app/views/idv/hybrid_handoff/show.html.erb b/app/views/idv/hybrid_handoff/show.html.erb index 7edaeb74b96..f3bd8e9eb84 100644 --- a/app/views/idv/hybrid_handoff/show.html.erb +++ b/app/views/idv/hybrid_handoff/show.html.erb @@ -115,7 +115,7 @@ <% end %> -<% unless @upload_disabled %> +<% if @upload_enabled %>
diff --git a/app/views/idv/hybrid_mobile/document_capture/show.html.erb b/app/views/idv/hybrid_mobile/document_capture/show.html.erb index dff2ad06368..402de56796a 100644 --- a/app/views/idv/hybrid_mobile/document_capture/show.html.erb +++ b/app/views/idv/hybrid_mobile/document_capture/show.html.erb @@ -13,6 +13,7 @@ skip_doc_auth_from_handoff: nil, skip_doc_auth_from_socure: skip_doc_auth_from_socure, socure_errors_timeout_url: socure_errors_timeout_url, + choose_id_type_path: choose_id_type_path, doc_auth_selfie_capture: doc_auth_selfie_capture, doc_auth_upload_enabled: doc_auth_upload_enabled, mock_client: mock_client, diff --git a/app/views/idv/link_sent/show.html.erb b/app/views/idv/link_sent/show.html.erb index b3bb506b4ef..4bef8552cde 100644 --- a/app/views/idv/link_sent/show.html.erb +++ b/app/views/idv/link_sent/show.html.erb @@ -10,7 +10,7 @@ <% self.title = t('titles.doc_auth.link_sent') %> <%= render AlertComponent.new(type: :warning, class: 'margin-bottom-4') do %> - <%= t('doc_auth.info.keep_window_open') %> + <%= t('doc_auth.info.keep_window_open') %> <% if FeatureManagement.doc_capture_polling_enabled? %> <%= t('doc_auth.info.link_sent_complete_polling') %> <% else %> diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb index b843553c8df..48257ef5b66 100644 --- a/app/views/idv/shared/_document_capture.html.erb +++ b/app/views/idv/shared/_document_capture.html.erb @@ -35,6 +35,7 @@ in_person_outage_message_enabled: IdentityConfig.store.in_person_outage_message_enabled, in_person_outage_expected_update_date: IdentityConfig.store.in_person_outage_expected_update_date, us_states_territories: @presenter.usps_states_territories, + choose_id_type_path: choose_id_type_path, doc_auth_passports_enabled: IdentityConfig.store.doc_auth_passports_enabled, doc_auth_selfie_capture: doc_auth_selfie_capture, doc_auth_selfie_desktop_test_mode: IdentityConfig.store.doc_auth_selfie_desktop_test_mode, diff --git a/app/views/idv/shared/choose_id_type.html.erb b/app/views/idv/shared/choose_id_type.html.erb index e146c5e7122..853f35b1c40 100644 --- a/app/views/idv/shared/choose_id_type.html.erb +++ b/app/views/idv/shared/choose_id_type.html.erb @@ -11,8 +11,11 @@ <% if presenter.hybrid_flow? %> <%= render AlertComponent.new(type: :warning, class: 'margin-bottom-4') do %> - <%= t('doc_auth.info.login_access_sp', app_name: APP_NAME, sp_name: decorated_sp_session.sp_name) %> - <%= t('doc_auth.info.add_id_consent_with_phone', app_name: APP_NAME) %> + <%= t( + 'doc_auth.hybrid_flow_warning.explanation_html', + app_name: APP_NAME, + service_provider_name: decorated_sp_session.sp_name, + ) %> <% end %> <% end %> diff --git a/config/application.yml.default b/config/application.yml.default index 4321f7bdc85..26e2f01bd38 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -107,8 +107,6 @@ doc_auth_client_sharpness_threshold: 50 doc_auth_error_dpi_threshold: 290 doc_auth_error_glare_threshold: 40 doc_auth_error_sharpness_threshold: 40 -doc_auth_manual_upload_disabled_a_b_testing_enabled: false -doc_auth_manual_upload_disabled_a_b_testing_percent: 0 doc_auth_max_attempts: 5 doc_auth_max_capture_attempts_before_native_camera: 3 doc_auth_max_submission_attempts_before_native_camera: 3 diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb index dc88ef9bd62..3a3d52302b4 100644 --- a/config/initializers/ab_tests.rb +++ b/config/initializers/ab_tests.rb @@ -168,18 +168,6 @@ def self.all user&.uuid end.freeze - DOC_AUTH_MANUAL_UPLOAD_DISABLED = AbTest.new( - experiment_name: 'Doc Auth Manual Upload Disabled', - should_log: /^idv/i, - buckets: { - manual_upload_disabled: - IdentityConfig.store.doc_auth_manual_upload_disabled_a_b_testing_enabled ? - IdentityConfig.store.doc_auth_manual_upload_disabled_a_b_testing_percent : 0, - }, - ) do |service_provider:, session:, user:, user_session:, **| - user&.uuid - end.freeze - PROOFING_VENDOR = AbTest.new( experiment_name: 'Proofing Vendor', should_log: /^idv/i, diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 47d5e03ff7b..866a04f7850 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -10,6 +10,7 @@ cron_every_monday_1am = 'every Monday at 1:00 UTC' # equivalent to '0 1 * * 1' cron_every_monday_2am = 'every Monday at 2:00 UTC' # equivalent to '0 2 * * 1' cron_monthly = '30 0 1 * *' # monthly, 0:30 UTC to not overlap with jobs running at 0000 +cron_quarterly = '0 0 1 1,4,7,10 *' # quarterly s3_cron_24h = '0 6 * * *' # 6am UTC is 1am EST/2am EDT if defined?(Rails::Console) @@ -291,6 +292,12 @@ cron: cron_monthly, args: -> { [Time.zone.yesterday.end_of_day] }, }, + # Send irs quarterly metrics to Team Data + irs_verification_demographics_report: { + class: 'Reports::IrsVerificationDemographicsReport', + cron: cron_quarterly, + args: -> { [Time.zone.yesterday.end_of_day] }, + }, # Download and store Socure reason codes socure_reason_code_download: { class: 'SocureReasonCodeDownloadJob', diff --git a/config/locales/en.yml b/config/locales/en.yml index 845a6b05e06..94c0acd5432 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -551,7 +551,10 @@ doc_auth.errors.file_type.invalid: This file type is not accepted, please choose doc_auth.errors.general.fallback_field_level: Please add a new image doc_auth.errors.general.multiple_back_id_failures: We couldn’t verify the back of your ID. Try taking a new picture. doc_auth.errors.general.multiple_front_id_failures: We couldn’t verify the front of your ID. Try taking a new picture. -doc_auth.errors.general.network_error: We are having technical difficulties on our end. Please try to submit your images again later. +doc_auth.errors.general.network_error: We are having technical difficulties verifying your ID. Try again or come back later if you continue to see this error. +doc_auth.errors.general.network_error_passport: We are having technical difficulties verifying your passport. Try again or +doc_auth.errors.general.network_error_passport_ending: if you have one. +doc_auth.errors.general.network_error_passport_link_text: use another type of ID doc_auth.errors.general.new_network_error: Try again later. doc_auth.errors.general.no_liveness: Try taking new pictures. doc_auth.errors.general.selfie_failure: We couldn’t verify the photo of yourself. Try taking a new picture. @@ -634,13 +637,8 @@ doc_auth.headings.verify_at_post_office: Go to a participating Post Office doc_auth.headings.verify_identity: Verify your identity doc_auth.headings.verify_online: Verify your identity online doc_auth.headings.welcome: Let’s verify your identity for %{sp_name} -doc_auth.hybrid_flow_warning.explanation_html: You’re using %{app_name} to verify your identity for access to %{service_provider_name} and its services. +doc_auth.hybrid_flow_warning.explanation_html: You’re using %{app_name} to access %{service_provider_name}. Only add your ID if you asked %{app_name} to verify your ID using your phone. doc_auth.hybrid_flow_warning.explanation_non_sp_html: You’re using %{app_name} to verify your identity. -doc_auth.hybrid_flow_warning.only_add_if_text: 'ONLY ADD YOUR ID IF:' -doc_auth.hybrid_flow_warning.only_add_own_account: You are using your own %{app_name} account -doc_auth.hybrid_flow_warning.only_add_phone_verify: You asked %{app_name} to verify your ID using your phone -doc_auth.hybrid_flow_warning.only_add_sp_services_html: You are trying to access %{service_provider_name} services -doc_auth.info.add_id_consent_with_phone: Only add your ID if you asked %{app_name} to verify your ID using your phone. doc_auth.info.address: If your residential address and mailing address are different, you can also try to verify your mailing address. doc_auth.info.address_guidance_puerto_rico_html: Puerto Rico residents:

Edit your address to list your urbanization or condominium on address line 2. doc_auth.info.capture_status_big_document: Too Close @@ -667,8 +665,7 @@ doc_auth.info.learn_more: Learn more about how we protect your sensitive informa doc_auth.info.lets_go: 'Identity verification happens in two parts:' doc_auth.info.link_sent: Please check your phone and follow instructions to take a photo of your ID. doc_auth.info.link_sent_complete_no_polling: When you are done, click Continue here to finish verifying your identity. -doc_auth.info.link_sent_complete_polling: The next step will load automatically. -doc_auth.info.login_access_sp: You’re using %{app_name} to access %{sp_name}. +doc_auth.info.link_sent_complete_polling: The next step will load automatically after you verify your ID on your phone. doc_auth.info.mailing_address: We’ll mail the letter with a verification code to this address. doc_auth.info.no_ssn: You must have a Social Security number to finish verifying your identity. doc_auth.info.passport_capture: Take a photo of the data page of your passport book. This page has your passport photo and personal information. diff --git a/config/locales/es.yml b/config/locales/es.yml index 3c1cf5b528b..9ce32083a18 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -562,7 +562,10 @@ doc_auth.errors.file_type.invalid: No se acepta este tipo de archivo; elija un a doc_auth.errors.general.fallback_field_level: Agregue una imagen nueva doc_auth.errors.general.multiple_back_id_failures: No pudimos verificar el reverso de su identificación. Intente tomar una nueva foto. doc_auth.errors.general.multiple_front_id_failures: No pudimos verificar el frente de su identificación. Intente tomar una nueva foto. -doc_auth.errors.general.network_error: Estamos teniendo problemas técnicos. Intente enviar sus imágenes de nuevo más tarde. +doc_auth.errors.general.network_error: Estamos teniendo problemas técnicos para verificar su identidad. Vuelva a intentarlo o regrese más tarde si sigue recibiendo este error. +doc_auth.errors.general.network_error_passport: Estamos teniendo problemas técnicos para verificar su pasaporte. Vuelva a intentarlo o +doc_auth.errors.general.network_error_passport_ending: si la tiene. +doc_auth.errors.general.network_error_passport_link_text: use otro tipo de identificación. doc_auth.errors.general.new_network_error: Vuelva a intentarlo más tarde. doc_auth.errors.general.no_liveness: Intente tomar nuevas fotos. doc_auth.errors.general.selfie_failure: No pudimos verificar su foto. Intente tomar una nueva foto. @@ -645,13 +648,8 @@ doc_auth.headings.verify_at_post_office: Vaya a una oficina de correos participa doc_auth.headings.verify_identity: Verifique su identidad doc_auth.headings.verify_online: Verifique su identidad en línea doc_auth.headings.welcome: Verifiquemos su identidad para %{sp_name} -doc_auth.hybrid_flow_warning.explanation_html: Está utilizando %{app_name} para verificar su identidad y acceder a %{service_provider_name} y sus servicios. +doc_auth.hybrid_flow_warning.explanation_html: Está usando %{app_name} para acceder a %{service_provider_name}. Solo agregue su identificación si solicitó a %{app_name} verificar su identidad usando su teléfono. doc_auth.hybrid_flow_warning.explanation_non_sp_html: Está utilizando %{app_name} para verificar su identidad. -doc_auth.hybrid_flow_warning.only_add_if_text: 'SOLO AGREGUE SU IDENTIFICACIÓN SI:' -doc_auth.hybrid_flow_warning.only_add_own_account: Está usando su propia cuenta de %{app_name} -doc_auth.hybrid_flow_warning.only_add_phone_verify: Usted solicitó a %{app_name} que verificara su identificación con su teléfono -doc_auth.hybrid_flow_warning.only_add_sp_services_html: Está tratando de acceder a los servicios de %{service_provider_name} -doc_auth.info.add_id_consent_with_phone: Solo agregue su identificación si solicitó a %{app_name} verificar su identidad usando su teléfono. doc_auth.info.address: Si su domicilio y su dirección postal no son iguales, también puede intentar verificar su dirección postal. doc_auth.info.address_guidance_puerto_rico_html: Residentes en Puerto Rico:

Edite su dirección para que el edificio de su vivienda o condominio figure en la línea de dirección 2. doc_auth.info.capture_status_big_document: Demasiado cerca @@ -678,8 +676,7 @@ doc_auth.info.learn_more: Obtenga más información sobre cómo protegemos su in doc_auth.info.lets_go: 'La verificación de la identidad se efectúa en dos partes:' doc_auth.info.link_sent: Revise su teléfono y siga las instrucciones para tomar una foto de su identificación. doc_auth.info.link_sent_complete_no_polling: Cuando termine, haga clic en “Continuar” para terminar la verificación de su identidad. -doc_auth.info.link_sent_complete_polling: El siguiente paso se cargará automáticamente. -doc_auth.info.login_access_sp: Está usando %{app_name} para acceder a %{sp_name}. +doc_auth.info.link_sent_complete_polling: El siguiente paso se cargará automáticamente después de que verifique su identidad en su teléfono. doc_auth.info.mailing_address: Enviaremos la carta con un código de verificación a esta dirección. doc_auth.info.no_ssn: Debe tener un número de Seguro Social para finalizar la verificación de su identidad. doc_auth.info.passport_capture: Tome una fotografía de la página de datos de su pasaporte, es decir, de la página que muestra su foto y su información personal. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b08705ee6af..932b869ff2d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -551,7 +551,10 @@ doc_auth.errors.file_type.invalid: Ce type de fichier n’est pas accepté ; ve doc_auth.errors.general.fallback_field_level: Veuillez ajouter une nouvelle image doc_auth.errors.general.multiple_back_id_failures: Nous n’avons pas pu vérifier le verso de votre pièce d’identité. Essayez de prendre une nouvelle photo. doc_auth.errors.general.multiple_front_id_failures: Nous n’avons pas pu vérifier le recto de votre pièce d’identité. Essayez de prendre une nouvelle photo. -doc_auth.errors.general.network_error: Nous rencontrons actuellement des difficultés techniques de notre côté. Veuillez réessayer d’envoyer vos images ultérieurement. +doc_auth.errors.general.network_error: Nous rencontrons actuellement des difficultés techniques pour confirmer votre pièce d’identité. Veuillez réessayer ou revenir plus tard si le problème persiste. +doc_auth.errors.general.network_error_passport: Nous rencontrons actuellement des difficultés techniques pour vérifier votre passeport. Veuillez réessayer ou +doc_auth.errors.general.network_error_passport_ending: si vous en avez une. +doc_auth.errors.general.network_error_passport_link_text: utiliser un autre type de pièce d’identité doc_auth.errors.general.new_network_error: Veuillez réessayer ultérieurement. doc_auth.errors.general.no_liveness: Essayez de prendre de nouvelles photos. doc_auth.errors.general.selfie_failure: Nous n’avons pas pu vérifier votre photo. Essayez de prendre une nouvelle photo. @@ -634,13 +637,8 @@ doc_auth.headings.verify_at_post_office: Vous rendre à un bureau de poste parti doc_auth.headings.verify_identity: Confirmer votre identité doc_auth.headings.verify_online: Confirmer votre identité en ligne doc_auth.headings.welcome: Vérifions votre identité auprès de %{sp_name} -doc_auth.hybrid_flow_warning.explanation_html: Vous utilisez %{app_name} pour confirmer votre identité et accéder à %{service_provider_name} et à ses services. +doc_auth.hybrid_flow_warning.explanation_html: Vous utilisez %{app_name} pour accéder à %{service_provider_name}. Ajoutez uniquement votre pièce d’identité si vous avez demandé à %{app_name} de vérifier votre identité à l’aide de votre téléphone. doc_auth.hybrid_flow_warning.explanation_non_sp_html: Vous utilisez %{app_name} pour confirmer votre identité. -doc_auth.hybrid_flow_warning.only_add_if_text: 'N’AJOUTEZ VOTRE PIÈCE D’IDENTITÉ QUE SI :' -doc_auth.hybrid_flow_warning.only_add_own_account: Vous utilisez votre propre compte %{app_name} -doc_auth.hybrid_flow_warning.only_add_phone_verify: Vous avez demandé à %{app_name} de confirmer votre identité à l’aide de votre téléphone -doc_auth.hybrid_flow_warning.only_add_sp_services_html: Vous essayez d’accéder aux services de %{service_provider_name} -doc_auth.info.add_id_consent_with_phone: Ajoutez uniquement votre pièce d’identité si vous avez demandé à %{app_name} de vérifier votre identité à l’aide de votre téléphone. doc_auth.info.address: Si votre adresse personnelle et votre adresse postale de contact sont différentes, vous pouvez également essayer de confirmer votre adresse postale. doc_auth.info.address_guidance_puerto_rico_html: Résidents de Porto Rico :

modifiez votre adresse pour indiquer votre lotissement ou votre condominium à la ligne 2. doc_auth.info.capture_status_big_document: Trop près @@ -667,8 +665,7 @@ doc_auth.info.learn_more: En savoir plus sur la façon dont nous protégeons vos doc_auth.info.lets_go: 'La vérification de l’identité se fait en deux temps :' doc_auth.info.link_sent: Veuillez consulter votre téléphone et suivre les instructions pour prendre une photo de votre pièce d’identité. doc_auth.info.link_sent_complete_no_polling: Quand vous aurez fini, cliquez sur « Suite » ici pour terminer la vérification de votre identité. -doc_auth.info.link_sent_complete_polling: L’étape suivante se chargera automatiquement. -doc_auth.info.login_access_sp: Vous utilisez %{app_name} pour accéder à %{sp_name}. +doc_auth.info.link_sent_complete_polling: L’étape suivante se chargera automatiquement une fois que vous aurez confirmé votre identité sur votre téléphone. doc_auth.info.mailing_address: Nous vous enverrons le courrier contenant un code de vérification à cette adresse. doc_auth.info.no_ssn: Vous devez avoir un numéro de sécurité sociale pour terminer la vérification de votre identité. doc_auth.info.passport_capture: Prenez une photo de la page d’informations de votre passeport. Cette page comporte votre photo d’identité et vos informations personnelles. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index ba97423fadf..fa40f7d16be 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -562,7 +562,10 @@ doc_auth.errors.file_type.invalid: 这一文件类型我们不接受。请选择 doc_auth.errors.general.fallback_field_level: 请添加一个新图像 doc_auth.errors.general.multiple_back_id_failures: 我们无法验证你身份证件的背面。尝试重拍一张。 doc_auth.errors.general.multiple_front_id_failures: 我们无法验证你身份证件的正面。尝试重拍一张。 -doc_auth.errors.general.network_error: 我们这边有技术困难。请稍后再提交你的图像。 +doc_auth.errors.general.network_error: 我们在验证你的ID时遇到技术困难。如果你继续看到这个出错信息,请再试一下或稍后再试。 +doc_auth.errors.general.network_error_passport: 我们在验证你的护照时遇到技术困难。请再试一下,或 +doc_auth.errors.general.network_error_passport_ending: (如果有的话)。 +doc_auth.errors.general.network_error_passport_link_text: 使用其他类型的身份证件 doc_auth.errors.general.new_network_error: 请稍后再试。 doc_auth.errors.general.no_liveness: 尝试重拍。 doc_auth.errors.general.selfie_failure: 我们无法验证你自己的照片。请重拍一张。 @@ -645,13 +648,8 @@ doc_auth.headings.verify_at_post_office: 到一个参与本项目的邮局去 doc_auth.headings.verify_identity: 验证你的身份 doc_auth.headings.verify_online: 在网上验证身份 doc_auth.headings.welcome: 现在我们来为%{sp_name}验证你的身份 -doc_auth.hybrid_flow_warning.explanation_html: 你在使用 %{app_name} 验证身份以访问 %{service_provider_name} 及其服务。 +doc_auth.hybrid_flow_warning.explanation_html: 你在使用%{app_name}访问%{service_provider_name}。仅在你已请求%{app_name}使用你的电话来验证你的身份的情况下才添加你的身份证件。 doc_auth.hybrid_flow_warning.explanation_non_sp_html: 你在使用 %{app_name} 验证身份。 -doc_auth.hybrid_flow_warning.only_add_if_text: 只有在以下情况下才添加你的身份证件: -doc_auth.hybrid_flow_warning.only_add_own_account: 你在使用自己的 %{app_name} 账户 -doc_auth.hybrid_flow_warning.only_add_phone_verify: 你要求 %{app_name} 使用你的电话来验证你的身份证件。 -doc_auth.hybrid_flow_warning.only_add_sp_services_html: 你在试图访问 %{service_provider_name} 服务。 -doc_auth.info.add_id_consent_with_phone: 仅在你已请求%{app_name}使用你的电话来验证你的身份的情况下才添加你的身份证件。 doc_auth.info.address: 如果你的居住地址和邮寄地址不同,也可以尝试验证你的邮寄地址。 doc_auth.info.address_guidance_puerto_rico_html: 波多黎各居民:

编辑你的地址,在地址第 2 行列出你的 urbanization 或 condominium。 doc_auth.info.capture_status_big_document: 太近了 @@ -678,8 +676,7 @@ doc_auth.info.learn_more: 对于我们如何保护你的敏感信息获得更多 doc_auth.info.lets_go: 身份验证包括两部分: doc_auth.info.link_sent: 请查看你的手机并按照说明拍一张你身份证件的照片。 doc_auth.info.link_sent_complete_no_polling: 照好以后,在这里点击“继续”,完成验证身份。 -doc_auth.info.link_sent_complete_polling: 下一步会自动加载。 -doc_auth.info.login_access_sp: 你在使用%{app_name}访问 %{sp_name}。 +doc_auth.info.link_sent_complete_polling: 你在手机上验证身份后,下一步将会自动加载。 doc_auth.info.mailing_address: 我们会将包含验证码的信件邮寄到这个地址。 doc_auth.info.no_ssn: 你必须有社会保障号码才能完成身份验证。 doc_auth.info.passport_capture: 拍摄一张你护照本的数据页的照片。数据页上有你的护照照片和个人信息。 diff --git a/docs/attempts-api/schemas/events/IdentityProofingEvents.yml b/docs/attempts-api/schemas/events/IdentityProofingEvents.yml index eb7a035212a..b27d98593c8 100644 --- a/docs/attempts-api/schemas/events/IdentityProofingEvents.yml +++ b/docs/attempts-api/schemas/events/IdentityProofingEvents.yml @@ -1,31 +1,31 @@ properties: idv-address-submitted: - $ref: "./identity-proofing/IdvAddressSubmitted.yml" + $ref: './identity-proofing/IdvAddressSubmitted.yml' idv-document-uploaded: - $ref: "./identity-proofing/IdvDocumentUploaded.yml" + $ref: './identity-proofing/IdvDocumentUploaded.yml' idv-document-upload-submitted: - $ref: "./identity-proofing/IdvDocumentUploadSubmitted.yml" + $ref: './identity-proofing/IdvDocumentUploadSubmitted.yml' idv-enrollment-complete: - $ref: "./identity-proofing/IdvEnrollmentComplete.yml" + $ref: './identity-proofing/IdvEnrollmentComplete.yml' idv-ipp-ready-to-verify-visit: - $ref: "./identity-proofing/IdvIppReadyToVerifyVisit.yml" + $ref: './identity-proofing/IdvIppReadyToVerifyVisit.yml' idv-phone-otp-sent: - $ref: "./identity-proofing/IdvPhoneOtpSent.yml" + $ref: './identity-proofing/IdvPhoneOtpSent.yml' idv-phone-otp-submitted: - $ref: "./identity-proofing/IdvPhoneOtpSubmitted.yml" + $ref: './identity-proofing/IdvPhoneOtpSubmitted.yml' idv-phone-submitted: - $ref: "./identity-proofing/IdvPhoneSubmitted.yml" + $ref: './identity-proofing/IdvPhoneSubmitted.yml' idv-rate-limited: - $ref: "./identity-proofing/IdvRateLimited.yml" + $ref: './identity-proofing/IdvRateLimited.yml' idv-reproof: - $ref: "./identity-proofing/IdvReproof.yml" + $ref: './identity-proofing/IdvReproof.yml' idv-ssn-submitted: - $ref: "./identity-proofing/IdvSsnSubmitted.yml" + $ref: './identity-proofing/IdvSsnSubmitted.yml' idv-device-risk-assessment: - $ref: "./identity-proofing/IdvDeviceRiskAssessment.yml" + $ref: './identity-proofing/IdvDeviceRiskAssessment.yml' idv-verification-submitted: - $ref: "./identity-proofing/IdvVerificationSubmitted.yml" + $ref: './identity-proofing/IdvVerificationSubmitted.yml' idv-verify-by-mail-letter-requested: - $ref: "./identity-proofing/IdvVerifyByMailLetterRequested.yml" + $ref: './identity-proofing/IdvVerifyByMailLetterRequested.yml' idv-verify-by-mail-enter-code-submitted: - $ref: "./identity-proofing/IdvVerifyByMailEnterCodeSubmitted.yml" + $ref: './identity-proofing/IdvVerifyByMailEnterCodeSubmitted.yml' diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvDeviceRiskAssessment.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvDeviceRiskAssessment.yml index 0f47b501cd7..34b3373c9b1 100644 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvDeviceRiskAssessment.yml +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvDeviceRiskAssessment.yml @@ -1,7 +1,7 @@ description: | This event captures the result of the Device Risk Assessment during Identity Verification. allOf: - - $ref: "../shared/EventProperties.yml" + - $ref: '../shared/EventProperties.yml' - type: object properties: failure_reason: diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvImageRetrievalFailed.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvImageRetrievalFailed.yml index 7c7754e9414..40d412eded3 100644 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvImageRetrievalFailed.yml +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvImageRetrievalFailed.yml @@ -3,18 +3,18 @@ description: | Therefore, document upload failed. These image file names and encryption keys are invalid, and are not saved in the document escrow. allOf: - - $ref: "../shared/EventProperties.yml" + - $ref: '../shared/EventProperties.yml' - type: object properties: document_front_image_file_id: type: string - description: If this image existed, the ID generated for storage + description: If this image existed, the ID generated for storage document_back_image_file_id: type: string - description: If this image existed, the ID generated for storage + description: If this image existed, the ID generated for storage document_selfie_image_file_id: type: string - description: If this image existed, the ID generated for storage + description: If this image existed, the ID generated for storage document_front_image_encryption_key: type: string description: Randomly generated Base64-encoded key generated to encrypt the front image file if it exists. diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml index 3e72120bff1..f5fa2787cd9 100644 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml @@ -1,7 +1,7 @@ description: | When the user verifies their information when identity proofing. allOf: - - $ref: "../shared/EventProperties.yml" + - $ref: '../shared/EventProperties.yml' - type: object properties: document_state: diff --git a/docs/attempts-api/schemas/events/shared/EventProperties.yml b/docs/attempts-api/schemas/events/shared/EventProperties.yml index f7421a7f8ba..80d61a0b346 100644 --- a/docs/attempts-api/schemas/events/shared/EventProperties.yml +++ b/docs/attempts-api/schemas/events/shared/EventProperties.yml @@ -25,7 +25,7 @@ properties: format: float64 description: The time when the event occurred. subject: - $ref: "./Subject.yml" + $ref: './Subject.yml' useragent_string: type: string description: The end user's browser's useragent string, which identifies the type of browser, device and operating system being used as well as the version of the browser. diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 86ce3f187c5..f08e2c92807 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -127,8 +127,6 @@ def self.store config.add(:doc_auth_error_glare_threshold, type: :integer) config.add(:doc_auth_error_sharpness_threshold, type: :integer) config.add(:doc_auth_max_attempts, type: :integer) - config.add(:doc_auth_manual_upload_disabled_a_b_testing_enabled, type: :boolean) - config.add(:doc_auth_manual_upload_disabled_a_b_testing_percent, type: :integer) config.add(:doc_auth_max_capture_attempts_before_native_camera, type: :integer) config.add(:doc_auth_max_submission_attempts_before_native_camera, type: :integer) config.add(:doc_auth_passports_enabled, type: :boolean) diff --git a/lib/reporting/api_transaction_count_report.rb b/lib/reporting/api_transaction_count_report.rb index 97ce97f5491..d0eae77b872 100644 --- a/lib/reporting/api_transaction_count_report.rb +++ b/lib/reporting/api_transaction_count_report.rb @@ -21,8 +21,8 @@ def initialize( time_range:, verbose: false, progress: false, - slice: 6.hours, - threads: 1 + slice: 1.day, + threads: 5 ) @time_range = time_range @verbose = verbose @@ -76,7 +76,8 @@ def api_transaction_count 'Socure (KYC) - Shadow', 'Socure (KYC) - Non-Shadow', 'Fraud Score and Attribute', - 'Threat Metrix', + 'Threat Metrix (IDV)', + 'Threat Metrix (Auth Only)', ], [ "#{ time_range.begin.to_date} - #{time_range.end.to_date}", @@ -84,10 +85,11 @@ def api_transaction_count instant_verify_table.first, phone_finder_table.first, socure_table.first, - socure_kyc_non_shadow_table.first, socure_kyc_shadow_table.first, + socure_kyc_non_shadow_table.first, fraud_score_and_attribute_table.first, - threat_metrix_table.first, + threat_metrix_idv_table.first, + threat_metrix_auth_only_table.first, ], ] end @@ -138,8 +140,14 @@ def instant_verify_table [instant_verify_table_count, result] end - def threat_metrix_table - result = fetch_results(query: threat_metrix_query) + def threat_metrix_idv_table + result = fetch_results(query: threat_metrix_idv_query) + threat_metrix_table_count = result.count + [threat_metrix_table_count, result] + end + + def threat_metrix_auth_only_table + result = fetch_results(query: threat_metrix_auth_only_query) threat_metrix_table_count = result.count [threat_metrix_table_count, result] end @@ -200,6 +208,7 @@ def true_id_query properties.event_properties.vendor as vendor | display uuid, id, timestamp, sp, dol_state, success, billed, vendor, product_status, transaction_status, conversation_id, request_id, referenceID, decision_status, submit_attempts, remaining_submit_attempts + | limit 10000 QUERY end @@ -221,6 +230,7 @@ def phone_finder_query | display uuid, id, timestamp, sp, dol_state, success, phoneFinder_referenceID, phoneFinder_transactionID, phoneFinder_pass, coalesce(temp_checks,"passed_all","") as phoneFinder_checks + | limit 10000 QUERY end @@ -236,6 +246,7 @@ def socure_query properties.event_properties.reference_id as reference_id, properties.event_properties.submit_attempts as submit_attempts, replace(replace(strcontains(name, "front"),"1","front"),"0","back") as side | display uuid, id, timestamp, sp, dol_state, success, decision_result, side, docv_transaction_token, reference_id, submit_attempts + | limit 10000 QUERY end @@ -271,19 +282,29 @@ def instant_verify_query resolution_transactionID, resolution_success, resolution_timed_out_flag + | limit 10000 QUERY end - def threat_metrix_query + def threat_metrix_idv_query <<~QUERY filter name = "IdV: doc auth verify proofing results" | fields properties.user_id as uuid, @timestamp as timestamp, properties.event_properties.proofing_results.context.stages.threatmetrix.success as tmx_success - | stats max(tmx_success) as max_tmx_success by uuid - + | limit 10000 + QUERY + end + + def threat_metrix_auth_only_query + <<~QUERY + filter name = "account_creation_tmx_result" + | fields + properties.user_id as uuid, + @timestamp as timestamp, + | limit 10000 QUERY end @@ -301,6 +322,7 @@ def fraud_score_and_attribute_query properties.event_properties.response_body.fraudpoint.vulnerable_victim_index as vulnerable_victim_index, properties.event_properties.response_body.fraudpoint.risk_indicators_codes as risk_indicators_codes, properties.event_properties.response_body.fraudpoint.risk_indicators_descriptions as risk_indicators_descriptions + | limit 10000 QUERY end @@ -324,7 +346,7 @@ def socure_kyc_shadow_query properties.event_properties.socure_result.errors.I919 as I919, properties.event_properties.socure_result.errors.R354 as R354 | filter name = "idv_socure_shadow_mode_proofing_result" - | stats count(*) as c + | limit 10000 QUERY end @@ -334,7 +356,7 @@ def socure_kyc_non_shadow_query | filter name='IdV: doc auth verify proofing results' and properties.event_properties.proofing_results.context.stages.resolution.vendor_name='socure_kyc' | sort @timestamp desc - | stats count(*) as c + | limit 10000 QUERY end end diff --git a/lib/reporting/irs_verification_demographics_report.rb b/lib/reporting/irs_verification_demographics_report.rb new file mode 100644 index 00000000000..656a836ed14 --- /dev/null +++ b/lib/reporting/irs_verification_demographics_report.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +require 'csv' +begin + require 'reporting/cloudwatch_client' + require 'reporting/cloudwatch_query_quoting' + require 'reporting/command_line_options' +rescue LoadError => e + warn 'could not load paths, try running with "bundle exec rails runner"' + raise e +end + +module Reporting + class IrsVerificationDemographicsReport + include Reporting::CloudwatchQueryQuoting + + attr_reader :issuers, :time_range + + module Events + IDV_DOC_AUTH_PROOFING_RESULTS = 'IdV: doc auth verify proofing results' + SP_REDIRECT_INITIATED = 'SP redirect initiated' + + def self.all_events + constants.map { |c| const_get(c) } + end + end + + # @param [Array] issuers + # @param [Range